Created
December 17, 2020 06:42
-
-
Save czue/fcda8a328a5d82dc0c582518341be59c to your computer and use it in GitHub Desktop.
subscriptions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% extends "portal_base.html" %} | |
{% load staticfiles %} | |
{% block title %} Payment {% endblock %} | |
{% block content %} | |
<div class="dashboard-interactions"> | |
<div class="interactions-wrap full-width"> | |
<div class="interactions-panel"> | |
<h1 class="interactions-panel-title">Select Plan Type</h1> | |
<div class="interactions-panel-main"> | |
<div class="plan-list"> | |
<div class="row justify-content-center"> | |
<div class="col-xl-4 col-lg-6"> | |
<div class="plan-list-item disabled"> | |
<h2>Business</h2> | |
<div class="price"><strong>$100</strong>/month</div> | |
<p class="info">Built for a small business or team doing up to 1 event per week.</p> | |
<ul> | |
<li><strong>50</strong> Active Events</li> | |
<li>Pulse Community</li> | |
<li>Company Page</li> | |
<li>Dashboards</li> | |
<li>Team & Client Management</li> | |
<li>Event Analytics</li> | |
<li>Event Tools</li> | |
<li><strong>9-5</strong> Support</li> | |
<li>-</li> | |
<li>-</li> | |
</ul> | |
<a href="#0" class="btn">Select Plan</a> | |
</div> | |
</div> | |
<div class="col-xl-4 col-lg-6"> | |
<div class="plan-list-item purple"> | |
<h2>Enterprise</h2> | |
<div class="price"><strong>$265</strong>/month</div> | |
<p class="info">Built for scaling businesses and teams doing MORE than 1 event per week.</p> | |
<ul> | |
<li><strong>500</strong> Active Events</li> | |
<li>Pulse Community</li> | |
<li>Company Page</li> | |
<li>Dashboards</li> | |
<li>Team & Client Management</li> | |
<li>Event Analytics</li> | |
<li>Event Tools</li> | |
<li><strong>24/7</strong> Support</li> | |
<li><strong>1 on 1 Training</strong></li> | |
<li><strong>Custom Features</strong></li> | |
</ul> | |
<a href="#0" class="btn">Select Plan</a> | |
</div> | |
</div> | |
<div class="col-xl-4 col-lg-6 d-block"> | |
<div class="plan-list-item blue disabled"> | |
<h2>Custom</h2> | |
<div class="text-area"> | |
<p>Contact us for greater than 500 active events or license agreements.</p> | |
</div> | |
<a href="#0" class="btn">Contact us</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- dashboard-interactions --> | |
<div class="interactions-panel interactions-panel-form"> | |
<h1 class="interactions-panel-title">Payment</h1> | |
<div class="interactions-panel-main"> | |
<div class="form-group-holder"> | |
<div class="text-holder"> | |
<h2>Credit Card Details</h2> | |
</div> | |
<form> | |
<div class="form-controls-group"> | |
<input type="text" class="form-control" placeholder="Card Number"> | |
<input type="text" class="form-control" placeholder="MM / YY"> | |
<input type="text" class="form-control" placeholder="CVC"> | |
</div> | |
</form> | |
</div> | |
<button type="button" class="btn btn-primary btn-rounded">Subscribe</button> | |
</div> | |
</div> | |
</div> <!-- /interactions-wrap --> | |
</div><!-- /dashboard-interactions --> | |
<!-- from Saas Pegasus --> | |
<section class="section app-card"> | |
<div class="columns columns-reversed"> | |
<div class="column is-three-quarters"> | |
<div class="level"> | |
<div class="level-left"> | |
<h1 class="title is-size-4">My Subscription</h1> | |
</div> | |
</div> | |
<p class="subtitle">You're not currently subscribed to a plan.</p> | |
<p class="my-1"> | |
Choose the plan that best fits your needs below to gain access | |
to additional functionality on our site! | |
</p> | |
</div> | |
</div> | |
<hr> | |
<!-- dynamic monthly vs yearly --> | |
{% if active_plan_intervals|length > 1 %} | |
<div class="buttons has-addons is-centered"> | |
{% for interval in active_plan_intervals %} | |
<button class="button" id="plan-selector-{{interval.interval}}">{{ interval.name }}</button> | |
{% endfor %} | |
</div> | |
<div class="help is-size-6 has-text-centered" id="plan-help" ></div> | |
{% endif %} | |
<!-- active products in stripe --> | |
<div class="columns my-2" id="plan-selector"> | |
{% for product in active_products %} | |
<div class="column"> | |
<div class="plan{% if product.metadata.is_default %} is-selected{% endif %}" | |
data-product-id="{{ product.stripe_id }}" data-plan-id="{{ product.default_plan.id }}" > | |
<div class="plan-summary"> | |
<div class="plan-icon"> | |
<span class="icon is-medium"> | |
<i class="fa"></i> | |
</span> | |
</div> | |
<div class="plan-details"> | |
<p class="title is-size-4">{{ product.metadata.name }}</p> | |
</div> | |
</div> | |
<p class="plan-tagline is-size-6">{{ product.metadata.description }}</p> | |
<div class="plan-price has-text-centered my-2"> | |
<p><span class="price"></span><span class="interval"></span></p> | |
</div> | |
<ul id="upgrade-features"> | |
{% for feature in product.metadata.features %} | |
<li> | |
<span class="icon"><i class="fa fa-check"></i></span> | |
<span class="upgrade-feature">{{ feature }}</span> | |
</li> | |
{% endfor %} | |
</ul> | |
</div> | |
</div> | |
{% endfor %} | |
</div> | |
<div class="columns"> | |
<div class="column is-three-quarters"> | |
<form id="subscription-form"> | |
{% include 'stripe/components/card_element.html' %} | |
<button type="submit" id="subscribe-button" class="button is-primary">Upgrade</button> | |
<div class="my-1" id="payment-details" ></div> | |
</form> | |
</div> | |
</div> | |
</section> | |
{% endblock %} | |
{% block component_js %} | |
{{ active_products_json|json_script:'active-products' }} | |
{{ payment_metadata|json_script:'payment-metadata' }} | |
<script src="https://js.stripe.com/v3/"></script> | |
<script src="{% static 'js/app-bundle.js' %}"></script> | |
<script> | |
// grab our JS Payments module for convenience | |
const App = SiteJS.app; | |
const Payments = App.Payments; | |
const activeProducts = JSON.parse(document.getElementById('active-products').textContent); | |
const paymentMetadata = JSON.parse(document.getElementById('payment-metadata').textContent); | |
const userEmail = '{{ user.email }}'; | |
const createCustomerUrl = "{{ subscription_urls.create_customer }}"; | |
const subscriptionSuccessUrl = "{{ subscription_urls.subscription_success }}"; | |
const defaultAnnual = '{{ default_to_annual }}' === 'True'; | |
const stripe = Stripe('{{ stripe_api_key }}'); | |
const cardElement = Payments.createCardElement(stripe); | |
const form = document.getElementById('subscription-form'); | |
const subscribeButton = document.getElementById('subscribe-button'); | |
// prevents submissions if one is already in progress | |
let submissionPending = false; | |
const getSelectedPlanElement = function () { | |
return document.querySelector('.plan.is-selected'); | |
}; | |
const handleError = function (errorMessage) { | |
Payments.showOrClearError(errorMessage); | |
subscribeButton.classList.remove('is-loading'); | |
}; | |
const handleSubscriptionSuccess = function () { | |
submissionPending = false; | |
location.href = subscriptionSuccessUrl; | |
}; | |
const handlePaymentMethodCreated = function (result) { | |
if (result.error) { | |
handleError(result.error.message); | |
submissionPending = false; | |
} else { | |
let selectedPlanElement = getSelectedPlanElement(); | |
let selectedPlan = selectedPlanElement.dataset.planId; | |
const paymentParams = {...paymentMetadata}; | |
paymentParams.plan_id = selectedPlan; | |
paymentParams.payment_method = result.paymentMethod.id; | |
fetch(createCustomerUrl, { | |
method: 'post', | |
headers: { | |
'Content-Type': 'application/json', | |
'X-CSRFToken': App.Cookies.get('csrftoken'), | |
}, | |
credentials: 'same-origin', | |
body: JSON.stringify(paymentParams), | |
}).then(function(response) { | |
return response.json(); | |
}).then(function(result) { | |
if (result.error) { | |
handleError(result.error.message); | |
submissionPending = false; | |
} else { | |
const subscription = result.subscription; | |
// check/handle error cases https://stripe.com/docs/billing/subscriptions/set-up-subscription#manage-sub-status | |
const { latest_invoice } = subscription; | |
const { payment_intent } = latest_invoice; | |
if (payment_intent) { | |
const { client_secret, status } = payment_intent; | |
if (status === 'requires_action') { | |
// trigger 3D-secure workflow | |
stripe.confirmCardPayment(client_secret).then(function(result) { | |
if (result.error) { | |
// The card was declined (i.e. insufficient funds, card has expired, etc) | |
handleError(result.error.message); | |
submissionPending = false; | |
} else { | |
// Show a success message to your customer | |
handleSubscriptionSuccess(); | |
} | |
}); | |
} else { | |
// No additional information was needed | |
// Show a success message to your customer | |
handleSubscriptionSuccess(); | |
} | |
} | |
} | |
}).catch(function (error) { | |
handleError("Sorry, there was an unexpected error processing your payment. Please contact us for support."); | |
submissionPending = false; | |
}); | |
} | |
}; | |
// from: https://stripe.com/docs/billing/subscriptions/set-up-subscription | |
form.addEventListener('submit', function(event) { | |
// We don't want to let default form submission happen here, | |
// which would refresh the page. | |
event.preventDefault(); | |
if (getSelectedPlanElement() && !submissionPending) { | |
submissionPending = true; | |
subscribeButton.classList.add('is-loading'); | |
stripe.createPaymentMethod({ | |
type: 'card', | |
card: cardElement, | |
billing_details: { | |
email: userEmail, | |
}, | |
}).then(handlePaymentMethodCreated); | |
} | |
}); | |
// hook up "monthly"/"annual" selection events | |
const monthlySelector = document.getElementById('plan-selector-month'); | |
const annualSelector = document.getElementById('plan-selector-year'); | |
const helpLabel = document.getElementById('plan-help'); | |
const planElements = document.getElementsByClassName('plan'); | |
const paymentDetailsElement = document.getElementById('payment-details'); | |
const annualHelpText = "You're getting two months free by choosing an Annual plan!"; | |
const monthlyHelpText = "Upgrade to annual pricing to get two free months."; | |
const updatePlans = function (isAnnual) { | |
for (let i = 0; i < planElements.length; i++) { | |
let planElt = planElements[i]; | |
let productId = planElt.dataset.productId; | |
let planMetadata = ( | |
isAnnual ? activeProducts[productId]['annual_plan'] : activeProducts[productId]['monthly_plan'] | |
); | |
// set data attribute | |
planElt.dataset.planId = planMetadata.stripe_id; | |
planElt.dataset.interval = isAnnual ? 'year' : 'month'; | |
planElt.dataset.paymentAmount = planMetadata.payment_amount; | |
let priceElt = planElt.querySelector('.price'); | |
priceElt.textContent = planMetadata.monthly_amount; | |
let intervalElt = planElt.querySelector('.interval'); | |
intervalElt.textContent = '/ month'; // todo: support annual display pricing | |
} | |
}; | |
const updatePaymentDetails = function () { | |
let selectedPlan = getSelectedPlanElement(); | |
if (selectedPlan) { | |
paymentDetailsElement.innerText = "Your card will be charged " + selectedPlan.dataset.paymentAmount + | |
" for your first " + selectedPlan.dataset.interval + "."; | |
} else { | |
paymentDetailsElement.innerText = "Select a plan to continue."; | |
} | |
}; | |
const selectPeriod = function (isAnnual) { | |
if (isAnnual) { | |
if (annualSelector) { | |
annualSelector.classList.add('is-selected', 'is-primary'); | |
monthlySelector.classList.remove('is-selected', 'is-primary'); | |
helpLabel.innerText = annualHelpText; | |
helpLabel.classList.add('is-primary'); | |
helpLabel.classList.remove('is-danger'); | |
} | |
updatePlans(isAnnual); | |
} else { | |
if (monthlySelector) { | |
annualSelector.classList.remove('is-selected', 'is-primary'); | |
monthlySelector.classList.add('is-selected', 'is-primary'); | |
helpLabel.innerText = monthlyHelpText; | |
helpLabel.classList.add('is-danger'); | |
helpLabel.classList.remove('is-primary'); | |
} | |
updatePlans(isAnnual); | |
} | |
updatePaymentDetails(); | |
}; | |
selectPeriod(defaultAnnual); | |
if (annualSelector) { | |
annualSelector.addEventListener('click', function (event) { | |
selectPeriod(true); | |
}); | |
} | |
if (monthlySelector) { | |
monthlySelector.addEventListener('click', function (event) { | |
selectPeriod(false); | |
}); | |
} | |
// hook up plan selection events | |
const selectPlan = function(plan) { | |
// if already selected there's nothing to do | |
if (!plan.classList.contains('is-selected')) { | |
plan.classList.add('is-selected'); | |
for (let i = 0; i < planElements.length; i++) { | |
if (planElements[i] !== plan) { | |
planElements[i].classList.remove('is-selected'); | |
} | |
} | |
} | |
updatePaymentDetails(); | |
}; | |
for (let i = 0; i < planElements.length; i++) { | |
planElements[i].addEventListener('click', function(event) { | |
let plan = event.target.closest('.plan'); | |
selectPlan(plan); | |
}); | |
} | |
</script> | |
{% endblock component_js %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment