jcloude/press/www/saas/billing.html
2025-12-23 20:48:07 +08:00

289 lines
9.0 KiB
HTML

{%- extends "templates/saas/billing_layout.html" -%} {%- from
"templates/saas/macros.html" import subs_wrapper, plans_wrapper, success_card,
error_card, load_stripe, load_subs, checkout_wrapper, stripe_wrapper,
address_wrapper, email_verify_wrapper -%} {% block content %}
<div class="flex justify-content-center mx-auto my-auto h-100 w-100">
{{ subs_wrapper() }} {{ plans_wrapper() }} {{ address_wrapper() }} {{
checkout_wrapper() }} {{ stripe_wrapper() }} {{ success_card() }} {{
success_card() }} {{ load_stripe() }} {{ load_subs() }} {{ email_verify_wrapper() }}
</div>
{%- endblock -%} {%- block script -%}
<script src="https://js.stripe.com/v3/"></script>
<script>
const url_args = jingrow.utils.get_query_params();
let secret_key = url_args.secret_key;
let address = null;
let elements = null;
let stripe = null;
let response = {};
let gstApplicable = false
if (secret_key.length >= 0 && secret_key != null) {
setup();
}
function setup() {
return call('jcloude.api.developer.marketplace.get_subscriptions', {
secret_key: secret_key,
}).then((r) => {
let data = r.message;
address = data.address;
let subscriptions = data.subscriptions;
response.currency = data.currency;
response.team = data.team
if (subscriptions.length >= 0) {
$('#loading').toggleClass('hidden');
$('#subs-wrapper').toggleClass('hidden');
for (let i in subscriptions) {
$('#subs-wrapper').append(`
<div class="flex flex-col items-center m-2 p-4 w-2/7 rounded cursor-pointer transition-all" onclick='showPlans(${JSON.stringify(
subscriptions[i]
)})'>
<img src="${
subscriptions[i].image
}" alt="Logo" class="rounded mr-2 h-[64px] hover:scale-105 transition-all" />
<span class="text-sm mt-3 font-semibold">${subscriptions[i].title}</span>
</div>
`);
}
}
});
}
function showPlans(sub) {
$('#subs-wrapper').toggleClass('hidden');
$('#plans-wrapper').toggleClass('hidden');
response.sub_name = sub.name;
response.app = sub.app;
response.site = sub.site;
let plans = sub.available_plans;
let style =
'flex flex-col flex-1 justify-content-between m-2 border rounded col-md-auto shadow-sm';
// FC Plans Redirect
$('#plans-wrapper').append(`
<div class="flex flex-col flex-1 justify-content-between m-2 border rounded col-md-auto shadow-sm" style="width: 220px;" >
<div>
<p class="mt-4 font-semibold text-sm">Jingrow Cloud Plans</p>
<span class="my-4 font-bold text-xl">${response.currency === 'INR' ? '₹ 750' : '$ 10'} Onwards</span>
<div class="flex flex-col">
<div class="flex my-1">
<div class="mr-2 grid h-4 w-4 shrink-0 place-items-center rounded-full border border-green-500 bg-green-50">
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.26562 3.86686L3.93229 6.53353L9.26562 1.2002" stroke="#38A160" stroke-miterlimit="10"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<p>Site Hosting Support</p>
</div>
</div>
</div>
<button id="email-verify-btn" class='rounded my-4 w-full bg-gray-100 btn' onclick='sendLoginLink()'>Send Login Link</button>
</div>
`)
for (let i in plans) {
let features = '';
plans[i].features.map((f) => {
features += `<div class="flex my-1">
<div class="mr-2 grid h-4 w-4 shrink-0 place-items-center rounded-full border border-green-500 bg-green-50">
<svg width="10" height="8" viewBox="0 0 10 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.26562 3.86686L3.93229 6.53353L9.26562 1.2002" stroke="#38A160" stroke-miterlimit="10"
stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<p>${f}</p>
</div>`;
});
$('#plans-wrapper').append(`
<div class="${style} ${
sub.marketplace_app_plan === plans[i].name ? 'border-primary' : ''
}" style="width: 220px;" >
<div>
<p class="mt-4 font-semibold text-sm">${plans[i].plan}</p>
<span class="my-4 font-bold text-xl">${response.currency === 'INR' ? '₹' : '$'}${
plans[i][`price_${response.currency.toLowerCase()}`]
}<span class="text-xs text-gray-500"> / mo</span></span>
<div class="flex flex-col">
${features}
</div>
</div>
<button class='btn btn-primary rounded my-4 w-full bg-gray-100 btn ${
sub.marketplace_app_plan === plans[i].name
? 'btn-primary disabled'
: 'active-btn'
}' onclick='selectPlan(${JSON.stringify(plans[i])}, "${sub.name}")'>
${sub.marketplace_app_plan === plans[i].name ? 'Current Plan' : 'Buy Now'}
</button>
</div>
`);
}
}
function setTotal(billing) {
let total = 0;
const plan_price =
response.new_plan[`price_${response.currency.toLowerCase()}`];
total = billing === 'annual' ? plan_price * 12 : plan_price;
$('.new-plan').text(response.new_plan.plan);
$('.new-plan-price').text(
`${response.currency === 'INR' ? '₹' : '$'} ${plan_price}`
);
if (response.new_plan.discounted && billing === 'annual') {
let discount_percent = response.new_plan.discount_percent;
$('.discount').text(`${discount_percent}%`);
total -= total * (discount_percent / 100);
} else {
$('.discount').text('-');
}
if (response.currency === 'INR' && response.new_plan.gst === 1) {
$('.gst').text('18%');
total += total * 0.18;
} else {
$('gst').text('-');
}
response.total = total;
response.billing = billing;
$('.total').text(`${response.currency === 'INR' ? '₹' : '$'} ${total}`);
}
function setupStripe() {
$('#checkout-wrapper').toggleClass('hidden');
$('#loading-stripe').toggleClass('hidden');
call('jcloude.api.developer.marketplace.saas_payment', {
secret_key: secret_key,
data: response,
}).then((r) => {
$('#loading-stripe').toggleClass('hidden');
if (r.message) {
r = r.message;
$('#stripe-wrapper').toggleClass('hidden');
stripe = Stripe(r.publishable_key);
const options = {
clientSecret: r.client_secret,
appearance: {
theme: 'flat',
},
};
elements = stripe.elements(options);
const paymentElement = elements.create('payment');
paymentElement.mount('#card');
$('#card').toggleClass('hidden');
}
});
}
async function handlePayment(e) {
try {
$('#pay-btn').attr('disabled', 'disabled');
$('#pay-btn-spinner').toggleClass('hidden');
$('#pay-btn-text').text('Processing...');
const payload = await stripe.confirmPayment({
elements,
redirect: 'if_required',
});
if (payload.error && payload.error === 'card_error') {
$('#stripe-wrapper').toggleClass('hidden');
$('#success-wrapper').toggleClass('hidden');
} else {
if (payload.paymentIntent.status === 'succeeded') {
$('#stripe-wrapper').toggleClass('hidden');
$('#success-wrapper').toggleClass('hidden');
}
}
} catch (err) {
console.log(err);
$('#pay-btn').removeAttr('disabled');
$('#pay-btn-spinner').toggleClass('hidden');
$('#pay-btn-text').text('Pay Now');
}
}
function selectPlan(new_plan, sub) {
response.new_plan = new_plan;
response.sub = sub;
$('#plans-wrapper').toggleClass('hidden');
if (address) {
$('#checkout-wrapper').toggleClass('hidden');
setTotal('monthly');
} else {
$('#address-card-wrapper').toggleClass('hidden');
}
}
function toggleGSTField(show) {
gstApplicable = show
$('#gstinhide').toggleClass('hidden')
$('#gstinshow').toggleClass('hidden')
$('#gstin').toggleClass('hidden')
}
function updateBillingInfo() {
let billing_info = {
billing_name: $('input[name="billing-name"]').val(),
address: $('input[name="address"]').val(),
country: $('select[name="country"]').val(),
city: $('input[name="city"]').val(),
state: $('input[name="state"]').val(),
postal_code: $('input[name="postal-code"]').val(),
gstin: gstApplicable ? $('input[name="gstin"]').val() : 'Not Applicable',
};
call('jcloude.api.developer.marketplace.update_billing_info', {
secret_key: secret_key,
data: billing_info,
}).then((r) => {
if (r.message == 'success') {
$('#checkout-wrapper').toggleClass('hidden');
$('#address-card-wrapper').toggleClass('hidden');
setTotal('monthly');
}
});
}
function sendLoginLink() {
$('#email-verify-btn').prop('disabled', true)
$("#email-verify-btn").html('<span class="mr-2">Sending</span><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
call('jcloude.api.developer.marketplace.send_login_link', {
secret_key: secret_key,
}).then((r) => {
if (r.message && r.message === "success") {
$('#plans-wrapper').toggleClass('hidden')
$('#email-verify-wrapper').toggleClass('hidden')
$('#verification-email').text(response.team)
}
})
}
function call(method, args) {
return jingrow
.call({
method: method,
args: args,
type: 'POST',
})
.then((r) => {
if (r.exc) {
console.error('An error occurred', r.exc);
return;
}
return r;
});
}
</script>
{% endblock %}