Refactor the Payment Model and Handle Order Confirmation Page

This commit is contained in:
amalelshihaby 2021-07-30 09:04:32 +02:00
parent 94dac8110e
commit 5564400ef8
15 changed files with 312 additions and 267 deletions

View file

@ -18,7 +18,7 @@ $( document ).ready(function() {
modal_btn = $('#createvm-modal-done-btn');
if (data.error) {
// Display error.message in your UI.
modal_btn.attr('href', error_url).removeClass('visually-hidden');
modal_btn.attr('href', error_url).removeClass('sr-only sr-only-focusable');
fa_icon.attr('class', 'fa fa-close');
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
$('#createvm-modal-title').text("Error Occurred");
@ -26,7 +26,7 @@ $( document ).ready(function() {
} else {
// The payment has succeeded
// Display a success message
modal_btn.attr('href', data.redirect).removeClass('visually-hidden');
modal_btn.attr('href', data.redirect).removeClass('sr-only sr-only-focusable');
$('#createvm-modal-title').text("Order Succeeded");
$('#createvm-modal-body').html("Order has been added and the instance will be ready soon");
}

View file

@ -153,10 +153,28 @@ $(document).ready(function () {
}
});
$('.credit-card-info .btn.choice-btn').click(function () {
var id = this.dataset['id_card'];
$('#checkout-btn').click(function () {
if($('input[name="payment_card"]:checked').size() == 1) {
var id = $('input[name="payment_card"]:checked').val();
if (id != 'new') {
$('#id_card').val(id);
submitBillingForm(id);
}
}
});
$('#continue-btn').click(function () {
submitBillingForm();
});
$('input[name="payment_card"]').change(function(e) {
if($('input[name="payment_card"]:checked').val() == 'new') {
$('#checkout-btn').hide();
$('#newcard').show();
} else {
$('#newcard').hide();
$('#checkout-btn').show();
}
});
});

View file

@ -48,7 +48,7 @@
<section class="section bg-white">
<div class="container">
<h2 class="text-9 text-center text-uppercase font-weight-400">What you will get?</h2>
<p class="lead text-center mb-5">Lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p>
<p class="lead text-center mb-5"> A secure chat that does not depend on a single point of failure </p>
<div class="row">
<div class="col-lg-10 mx-auto">
<div class="row">
@ -56,28 +56,42 @@
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-comments"></i> </div>
<h3 class="font-weight-400">Messaging</h3>
<p>Lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p>
<p>Matrix gives you simple HTTP APIs and SDKs (iOS, Android, Web) to create your own messaging plateform</p>
</div>
</div>
<div class="col-sm-6 mb-4">
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-cloud"></i> </div>
<h3 class="font-weight-400">Decentralised</h3>
<p>Persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p>
<p>When you send a message in Matrix, it is replicated over all the servers whose users are participating in a given conversation</p>
</div>
</div>
<div class="col-sm-6 mb-4 mb-sm-0">
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-lock"></i> </div>
<h3 class="font-weight-400">End-to-End Encryption</h3>
<p>Essent lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p>
<h3 class="font-weight-400">End-to-End Encrypted (E2EE)</h3>
<p>Matrix ensures that only the intended recipients can ever decrypt your messages, while warning if any unexpected devices are added to the conversation</p>
</div>
</div>
<div class="col-sm-6">
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-users"></i> </div>
<h3 class="font-weight-400">Bridge with other chats</h3>
<p>An important idea in Matrix is Interoperability. This means that Matrix is open to exchanging data and messages with other platforms</p>
</div>
</div>
<div class="col-sm-6 mb-4 mb-sm-0">
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-book-reader"></i> </div>
<h3 class="font-weight-400">Open Source</h3>
<p>Quidam lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p>
<h3 class="font-weight-400">100% Open Source</h3>
<p>Everything we use, create and encourage is Free and Open Source Software.</p>
</div>
</div>
<div class="col-sm-6">
<div class="featured-box style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-water"></i> </div>
<h3 class="font-weight-400">100% Renewable Energy</h3>
<p>We assure you that our electricity is made of 100% renewable energy. *0.1 % of electricity comes from solar power.</p>
</div>
</div>
</div>
@ -147,13 +161,12 @@
<section class="section bg-white">
<div class="container">
<h2 class="text-9 text-center text-uppercase font-weight-400">As simple as 1-2-3</h2>
<p class="lead text-center mb-5">Lorem Ipsum is simply dummy text of the printing and typesetting industry.</p>
<div class="row">
<div class="col-sm-4 mb-4">
<div class="featured-box style-4">
<div class="featured-box-icon text-dark shadow-none border-bottom"><span class="w-100 text-20 font-weight-500">1</span></div>
<h3 class="mb-3">Sign Up Your Account</h3>
<p class="text-3 font-weight-300">Sign up for your free personal/business Account in fea a minute.</p>
<p class="text-3 font-weight-300">Sign up for your free personal/business Account in a minute.</p>
</div>
</div>
<div class="col-sm-4 mb-4">
@ -171,7 +184,7 @@
</div>
</div>
</div>
<div class="text-center mt-2"><a href="#" class="btn btn-primary">Open a Free Account</a></div>
<div class="text-center mt-2"><a href="{% url 'account_signup' %}" class="btn btn-primary">Open a Free Account</a></div>
</div>
</section>
<!-- How it works End -->
@ -201,32 +214,32 @@
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>100% Renewable</h3>
<p>Essent lisque persius interesset his et, in quot quidam.</p>
<p>*99.9% hydro power, 0.1 % solar power.</p>
</div>
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>100% Open Source</h3>
<p>Lisque persius interesset his et, in quot quidam persequeris.</p>
<p>Everything we use, create and encourage is Open Source.</p>
</div>
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Passive Cooling</h3>
<p>Lisque persius interesset his et, in quot quidam persequeris.</p>
<p>We use passive cooling in our data centers to eliminate the used cooling energy.</p>
</div>
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Reuse Hardware</h3>
<p>Essent lisque persius interesset his et, in quot quidam.</p>
<p>We try always to reuse the hardware if it's still maintained.</p>
</div>
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Reuse Old Factories</h3>
<p>Quidam lisque persius interesset his et, in quot quidam.</p>
<p>We build our datacenters by reusing old factories.</p>
</div>
<div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>24/7 customer service</h3>
<p>Quidam lisque persius interesset his et, in quot quidam.</p>
<p>Customers can get help and find answers to questions as soon as they come up.</p>
</div>
</div>
</div>
@ -303,50 +316,56 @@
<div class="accordion accordion-alternate arrow-right" id="popularTopics">
<div class="card">
<div class="card-header" id="heading1">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse1" aria-expanded="false" aria-controls="collapse1">What is Matrix?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse1" aria-expanded="false" aria-controls="collapse1"> Can I use a custom domain name?</a> </h5>
</div>
<div id="collapse1" class="collapse" aria-labelledby="heading1" data-parent="#popularTopics">
<div class="card-body"> Lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure. Mutat tacimates id sit. Ridens mediocritatem ius an, eu nec magna imperdiet. </div>
<div class="card-body">
<p>Yes! You will have to give us three domain names:</p>
<p>a) the <strong>homeserver</strong>: this is where the actual server is running - this can be on domain "A" - in case of ungleich we use ungleich.matrix.ungleich.cloud and give away YOURNAME.matrix.ungleich.cloud for free</p>
<p>b) the address of the <strong>web client </strong>- this is where people with their webbrowser go to - this should be different from "A". Often this is something like <strong>chat.example.org</strong>or <strong>matrix.example.org</strong>. In case of ungleich this domain is matrix.ungleich.ch</p>
<p>c) the main <strong>matrix domain</strong>: the one you use for users and rooms. This is usually your main domain and is different from A. For ungleich this is ungleich.ch. Most people will choose their "main domain", for instance <strong>example.org</strong> here.</p>
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="heading2">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse2" aria-expanded="false" aria-controls="collapse2">How to request a VM?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse2" aria-expanded="false" aria-controls="collapse2">Can I change the subdomain after the Matrix setup?</a> </h5>
</div>
<div id="collapse2" class="collapse" aria-labelledby="heading2" data-parent="#popularTopics">
<div class="card-body"> Iisque Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. </div>
<div class="card-body">No, since your homeserver will federate with the broader network</div>
</div>
</div>
<div class="card">
<div class="card-header" id="heading3">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse3" aria-expanded="false" aria-controls="collapse3">Is my money safe?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse3" aria-expanded="false" aria-controls="collapse3">Are video/audio calls in Matrix End-to-end-encrypted(E2EE)?</a> </h5>
</div>
<div id="collapse3" class="collapse" aria-labelledby="heading3" data-parent="#popularTopics">
<div class="card-body"> Iisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure. Mutat tacimates id sit. Ridens mediocritatem ius an, eu nec magna imperdiet. </div>
<div class="card-body"> Video & Phone is handled by a jitsi server by default - matrix adds it as an integration, but does not handle video/audio directly. So the answer is: not E2EE for audio/video.
</div>
</div>
</div>
<div class="card">
<div class="card-header" id="heading4">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse4" aria-expanded="false" aria-controls="collapse4">How much fees does Matrixhosting charge?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse4" aria-expanded="false" aria-controls="collapse4">Does ungleich have access to my Matrix admin UI? How does my chat content stay secure?</a> </h5>
</div>
<div id="collapse4" class="collapse" aria-labelledby="heading4" data-parent="#popularTopics">
<div class="card-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. </div>
<div class="card-body"> Once you change the initial password we do not have external access to the software anymore but we have access to the underlying server since we manage it: we can read and change things in the database 'by hand' since we have physical access to it. However end-to-end encrypted rooms stay secure. The content is encrypted with the user's keys and to us it will be shown in ciphertext.</div>
</div>
</div>
<div class="card">
<div class="card-header" id="heading5">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse5" aria-expanded="false" aria-controls="collapse5">How can I cancel my subscription?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse5" aria-expanded="false" aria-controls="collapse5">How many users can I have? What are the resources allocated to my matrix server?</a> </h5>
</div>
<div id="collapse5" class="collapse" aria-labelledby="heading5" data-parent="#popularTopics">
<div class="card-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. </div>
<div class="card-body"> We do not enforce a limit of the number of users: you can do anythign you want as long as you fit the resources allocated to your homeserver. You are provided with 1GB of memory, 1vCPU and 20GB of storage with the base offer, which can be extended on demand (Pricing is the same as ipv6onlyhosting VMs, since that's what we use underneath). </div>
</div>
</div>
<div class="card">
<div class="card-header" id="heading6">
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse6" aria-expanded="false" aria-controls="collapse6">Can I add credit to my wallet?</a> </h5>
<h5 class="mb-0"> <a href="#" class="collapsed" data-toggle="collapse" data-target="#collapse6" aria-expanded="false" aria-controls="collapse6"> What client can I use? Do you recommend one?</a> </h5>
</div>
<div id="collapse6" class="collapse" aria-labelledby="heading6" data-parent="#popularTopics">
<div class="card-body"> Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. </div>
<div class="card-body">We recommend and provide you a web version of the <a href="https://element.io/" target="new">Element client</a> (desktop and mobile) but you can use any matrix client. </div>
</div>
</div>
</div>

View file

@ -40,29 +40,7 @@
</div>
{% endif %}
{% if not error %}
<div class="order-details">
<div>
<address>
<h5>{% trans "Billed To" %}</h5>
<p>
{% with request.session.billing_address_data as billing_address %}
{{billing_address.full_name}}<br>
{{billing_address.street}}, {{billing_address.postal_code}}<br>
{{billing_address.city}}, {{billing_address.country}}
{% if billing_address.vat_number %}
<br/>{% trans "VAT Number" %} {{billing_address.vat_number}}
{% if pricing.vat_country != "ch" and pricing.vat_validation_status != "not_needed" %}
{% if pricing.vat_validation_status == "verified" %}
<span class="fa fa-fw fa-check-circle" aria-hidden="true" title='{% trans "Your VAT number has been verified" %}'></span>
{% else %}
<span class="fa fa-fw fa-info-circle" aria-hidden="true" title='{% trans "Your VAT number is under validation. VAT will be adjusted, once the validation is complete." %}'></span>
{% endif %}
{% endif %}
{% endif %}
{% endwith %}
</p>
</address>
</div>
<div class="order-details container">
<div class="row align-items-center flex-row">
<div class="col col-sm-6">
<address>
@ -91,109 +69,92 @@
<span class="text-muted text-3 opacity-8">{% trans "Available Balance"%}</span>
</div>
</div>
<hr>
<div>
<h5>{% trans "Order summary" %}</h5>
<p>
<strong>{% trans "Product" %}:</strong>&nbsp;
Matrix Chat Hosting
</p>
<div class="col-sm-12"><hr class="mt-0"></div>
<div class="row text-4 mb-4 text-center">Matrix Chat Hosting</div>
<div class="row">
<div class="col-sm-9">
<p>
<span>{% trans "Cores" %}: </span>
<strong class="pull-right">{{order.cores}}</strong>
</p>
<p>
<span>{% trans "Memory" %}: </span>
<strong class="pull-right">{{order.memory}} GB</strong>
</p>
<p>
<span>{% trans "Disk space" %}: </span>
<strong class="pull-right">{{order.storage}} GB</strong>
</p>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<p>
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong>
<strong class="pull-right">{{pricing.subtotal|floatformat:2}} CHF</strong>
</p>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span></span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "Pre VAT" %}</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4 header-no-left-padding">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "With VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)</strong></p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<tbody>
<tr>
<td>{% trans "Cores" %}</td>
<td>{% trans "Memory" %}</td>
<td>{% trans "Disk space" %}</td>
</tr>
<tr>
<td>{{order.cores}}</td>
<td>{{order.memory}} GB</td>
<td>{{order.storage}} GB</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span>Subtotal</span></p>
<div class="col-sm-6">
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><span class="pull-right" >{{pricing.subtotal|floatformat:2}} CHF</span></p>
<div class="col-sm-6">
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong>Subtotal</span></strong>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><span class="pull-right">{{pricing.price_with_vat|floatformat:2}} CHF</span></p>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong class="pull-right" >{{pricing.subtotal|floatformat:2}} CHF</strong></p>
</div>
</div>
{% if pricing.discount.amount > 0 %}
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span>{{pricing.discount.name }}</span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><span class="pull-right">-{{pricing.discount.amount|floatformat:2}} CHF</span></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><span class="pull-right">-{{pricing.discount.amount_with_vat|floatformat:2}} CHF</span></p>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span>-{{pricing.discount.amount|floatformat:2}} CHF</span></p>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="col-sm-12"><hr class="mt-0"></div>
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><strong>Total</strong></p>
<div class="col-sm-6"></div>
<div class="col-sm-6">
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong>{% trans "" %}</strong></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong class="pull-right">{{pricing.subtotal_after_discount|floatformat:2}} CHF</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><strong class="pull-right">{{pricing.price_after_discount_with_vat|floatformat:2}} CHF</strong></p>
</div>
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span>{% trans "VAT for" %} {{pricing.vat_country}} ({{pricing.vat_percent}}%)</span></p>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><span class="pull-right" > CHF</span></p>
</div>
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<strong class="text-uppercase align-center">{% trans "Your Price in Total" %}</strong>
<strong class="total-price pull-right">{{pricing.total_price|floatformat:2}} CHF</strong>
<div class="row">
<div class="col-sm-6"></div>
<div class="col-sm-6">
<div class="row text-right">
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong>{% trans "Total" %}</strong></p>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<p><strong class="pull-right">{{pricing.total|floatformat:2}} CHF</strong></p>
</div>
</div>
</div>
<hr class="thin-hr">
</div>
<hr class="">
</div>
<form id="virtual_machine_create_form" action="" method="POST">
{% csrf_token %}
<div class="row">
<div class="col-sm-12">
<div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2 %}By clicking "Place order" you agree to our <a href="">Terms of Service</a> and this plan will charge your credit card account with {{ vm_total_price }} CHF/month{% endblocktrans %}.</div>
By clicking "Place order" you agree to our <a href="">Terms of Service</a> and this plan will charge your account balance with {{pricing.total|floatformat:2}} CHF
</div>
<div class="col-sm-12 order-confirm-btn text-right">
<button class="btn choice-btn btn-primary" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
@ -238,13 +199,8 @@ aria-hidden="true" data-backdrop="static" data-keyboard="false">
{% if stripe_key %}
{% get_current_language as LANGUAGE_CODE %}
<script type="text/javascript">
window.paymentIntentSecret = "{{payment_intent_secret}}";
var create_vm_error_message = 'Some problem encountered. Please try again later';
var pm_id = '{{id_payment_method}}';
var error_url = '{{ error_msg.redirect }}';
var success_url = '{{ success_msg.redirect }}';
window.stripeKey = "{{stripe_key}}";
window.isSubscription = ("{{is_subscription}}" === 'true');
</script>
{%endif%}

View file

@ -200,12 +200,14 @@
</div>
</div>
<hr class="mt-2 mx-n3">
{% if show_cards %}
<div id="cards-section">
{% with cards_len=cards|length %}
<p class="text-muted">
{% if cards_len > 0 %}
{% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below.{% endblocktrans %}
{% else %}
{% blocktrans %}Please fill in your credit card information below.{% endblocktrans %}
{% blocktrans %}You haven't any active cards, Please fill in your credit card information below.{% endblocktrans %}
{% endif %}
</p>
<div>
@ -226,11 +228,14 @@
</div>
{% endfor %}
{% if cards_len > 0 %}
<div class="form-check py-2 custom-control custom-radio" data-toggle="collapse" data-target="#newcard">
<div class="form-check py-2 custom-control custom-radio">
<input id="new-card" name="payment_card" class="custom-control-input" type="radio" value="new">
<label class="custom-control-label" for="new-card"><h6 class="mb-0">{% trans "Add New Card" %}</h6></label>
</div>
<div id="newcard" class="collapse">
<div class="text-right">
<button id="checkout-btn" class="btn btn-primary btn-wide" type="submit" name="payment-button">{%trans "Checkout" %}</button>
</div>
<div id="newcard">
<div class="card-details-box p-4 bg-light">
{% include "matrixhosting/includes/_card.html" %}
</div>
@ -241,6 +246,12 @@
</div>
{% endwith %}
</div>
{% else %}
<div class="text-right">
<button id="continue-btn" class="btn btn-primary btn-wide" type="submit">{%trans "Checkout" %}</button>
</div>
{% endif %}
</div>
</div>
</div>
</div>
@ -258,6 +269,7 @@
(function () {
window.stripeKey = "{{stripe_key}}";
window.current_lan = "{{LANGUAGE_CODE}}";
window.hasCreditcard = "{{show_cards}}";
})();
</script>
{%endif%}

View file

@ -2,12 +2,11 @@ from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from .views import IndexView, PricingView, OrderPaymentView, OrderDetailsView, Dashboard
from .views import IndexView, OrderPaymentView, OrderDetailsView, Dashboard
app_name = 'matrixhosting'
urlpatterns = [
path('pricing/<slug:name>/calculate/', PricingView.as_view(), name='pricing_calculator'),
path('order/new/', OrderPaymentView.as_view(), name='payment'),
path('order/confirm/', OrderDetailsView.as_view(), name='order_confirmation'),
path('dashboard/', Dashboard.as_view(), name='dashboard'),

View file

@ -1,5 +1,6 @@
import logging
import json
import decimal
from django.shortcuts import redirect, render
from django.contrib import messages
@ -29,16 +30,6 @@ from .serializers import *
logger = logging.getLogger(__name__)
class PricingView(View):
def get(self, request, **args):
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = get_order_total_with_vat(
request.GET.get('cores'),
request.GET.get('memory'),
request.GET.get('storage'),
pricing_name = args['name']
)
return JsonResponse({'subtotal': subtotal, 'total': price_after_discount_with_vat})
class IndexView(FormView):
template_name = "matrixhosting/index.html"
form_class = InitialRequestForm
@ -53,16 +44,14 @@ class IndexView(FormView):
def form_valid(self, form):
self.request.session['order'] = form.cleaned_data
subtotal, subtotal_with_discount, total, vat, vat_percent, discount = get_order_total_with_vat(
pricing = get_order_total_with_vat(
form.cleaned_data['cores'],
form.cleaned_data['memory'],
form.cleaned_data['storage'],
form.cleaned_data['pricing_name'],
False
)
self.request.session['pricing'] = {'name': form.cleaned_data['pricing_name'],
'subtotal': subtotal, 'vat': vat, 'total': total,
'vat_percent': vat_percent, 'discount': discount}
self.request.session['pricing'] = pricing
return HttpResponseRedirect(reverse('matrix:payment'))
@ -91,22 +80,24 @@ class OrderPaymentView(FormView):
details_form = RequestHostedVMForm(
initial=self.request.session.get('order', {})
)
balance = get_balance_for_user(self.request.user)
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
cards = uncloud_stripe.get_customer_cards(customer_id)
context.update({
'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']),
'billing_address_form': billing_address_form,
'details_form': details_form,
'balance': balance,
'cards': cards,
'balance': get_balance_for_user(self.request.user),
'stripe_key': settings.STRIPE_PUBLIC_KEY
'stripe_key': settings.STRIPE_PUBLIC_KEY,
'show_cards': True if balance < self.request.session.get('pricing')['total'] else False,
})
return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
for k in ['vat_validation_status', 'token', 'id_payment_method']:
for k in ['vat_validation_status', 'token', 'id_payment_method', 'total']:
if request.session.get(k):
request.session.pop(k)
if 'order' not in request.session:
@ -121,14 +112,11 @@ class OrderPaymentView(FormView):
context.update({'details_form': details_form,
'billing_address_form': billing_address_form})
return self.render_to_response(context)
id_payment_method = self.request.POST.get('id_payment_method', None)
self.request.session["id_payment_method"] = id_payment_method
this_user = {
'email': self.request.user.email,
'username': self.request.user.username
}
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
uncloud_stripe.attach_payment_method(id_payment_method, customer_id)
address = get_billing_address_for_user(self.request.user)
if address:
form = BillingAddressForm(self.request.POST, instance=address)
@ -139,15 +127,16 @@ class OrderPaymentView(FormView):
self.request.session["billing_address_id"] = billing_address_ins.id
self.request.session['billing_address_data'] = billing_address_form.cleaned_data
self.request.session['billing_address_data']['owner'] = self.request.user.id
self.request.session['user'] = this_user
self.request.session['customer'] = customer_id
id_payment_method = self.request.POST.get('id_payment_method', False)
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
if id_payment_method and id_payment_method != 'undefined':
uncloud_stripe.attach_payment_method(id_payment_method, customer_id)
vat_number = billing_address_form.cleaned_data.get('vat_number').strip()
if vat_number:
validate_result = validate_vat_number(
stripe_customer_id=customer_id,
billing_address_id=billing_address_ins.id
)
if 'error' in validate_result and validate_result['error']:
messages.add_message(
self.request, messages.ERROR, validate_result["error"],
@ -157,6 +146,21 @@ class OrderPaymentView(FormView):
reverse('matrix:payment') + '#vat_error'
)
self.request.session["vat_validation_status"] = validate_result["status"]
specs = details_form.cleaned_data
vat_rate = VATRate.get_vat_rate(billing_address_ins)
vat_validation_status = "verified" if billing_address_ins.vat_number_validated_on and billing_address_ins.vat_number_verified else False
pricing = get_order_total_with_vat(
specs['cores'], specs['memory'], specs['storage'], request.session['pricing']['name'],
vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
)
amount = get_balance_for_user(self.request.user) - decimal.Decimal(pricing["total"])
if (amount < 0):
payment_id = Payment.deposit(request.user, abs(amount), source='stripe')
print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>")
print(payment_id)
self.request.session['pricing'] = pricing
self.request.session['order'] = specs
self.request.session['vat_validation_status'] = vat_validation_status
return HttpResponseRedirect(reverse('matrix:order_confirmation'))
class OrderDetailsView(DetailView):
@ -164,69 +168,35 @@ class OrderDetailsView(DetailView):
context_object_name = "order"
model = Order
# @method_decorator(login_required)
# def dispatch(self, *args, **kwargs):
# return super().dispatch(*args, **kwargs)
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
context = {}
# if ('order' not in request.session or 'user' not in request.session):
# return HttpResponseRedirect(reverse('matrix:index'))
# if 'id_payment_method' in self.request.session:
# card = uncloud_stripe.get_card_from_payment(self.request.user, self.request.session['id_payment_method'])
# if not card:
# return HttpResponseRedirect(reverse('matrix:payment'))
# context['card'] = card
# elif 'id_payment_method' not in self.request.session or 'vat_validation_status' not in self.request.session:
# return HttpResponseRedirect(reverse('matrix:payment'))
# specs = request.session.get('order')
# pricing = request.session.get('pricing')
# billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
# vat_rate = VATRate.get_vat_rate(billing_address)
# vat_validation_status = "verified" if billing_address.vat_number_validated_on and billing_address.vat_number_verified else False
# subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = get_order_total_with_vat(
# specs['cores'], specs['memory'], specs['storage'], request.session['pricing']['name'],
# vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
# )
# pricing = {
# "subtotal": subtotal, "discount": discount, "vat": vat, "vat_percent": vat_percent,
# "vat_country": billing_address.country.lower(),
# "subtotal_after_discount": subtotal_after_discount,
# "price_after_discount_with_vat": price_after_discount_with_vat
# }
# pricing["price_with_vat"] = round(subtotal * (1 + pricing["vat_percent"] * 0.01), 2)
# discount["amount_with_vat"] = round(pricing["price_with_vat"] - pricing["price_after_discount_with_vat"], 2)
# pricing["total_price"] = pricing["price_after_discount_with_vat"]
# self.request.session['total_price'] = pricing["price_after_discount_with_vat"]
# payment_intent_response = uncloud_stripe.get_payment_intent(request.user, pricing["price_after_discount_with_vat"])
# context.update({
# 'payment_intent_secret': payment_intent_response.client_secret,
# 'order': specs,
# 'pricing': pricing,
# 'stripe_key': settings.STRIPE_PUBLIC_KEY,
# })
context = {
'order': self.request.session.get('order'),
'pricing': self.request.session.get('pricing'),
'balance': get_balance_for_user(self.request.user)
}
if ('order' not in request.session):
return HttpResponseRedirect(reverse('matrix:index'))
elif 'pricing' not in self.request.session or 'vat_validation_status' not in self.request.session:
return HttpResponseRedirect(reverse('matrix:payment'))
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
customer = StripeCustomer.objects.get(owner=self.request.user)
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
if 'id_payment_method' in request.session:
card = uncloud_stripe.get_card_from_payment(self.request.user, self.request.session['id_payment_method'])
if not card:
return show_error("There was a payment related error.", self.request)
else:
return show_error("There was a payment related error.", self.request)
total = self.request.session['pricing']['total']
order = finalize_order(request, customer,
billing_address,
self.request.session['total_price'],
total,
PricingPlan.get_by_name(self.request.session['pricing']['name']),
request.session.get('order'))
if order:
bill = Bill.create_next_bill_for_user_address(billing_address)
payment= Payment.objects.create(owner=request.user, amount=self.request.session['total_price'], source='stripe')
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
if payment:
#Close the bill as the payment has been added
bill.close()

View file

@ -1,7 +1,7 @@
ALLOWED_HOSTS=
STRIPE_KEY=
STRIPE_PUBLIC_KEY=
DATABASE_ENGINE=
DATABASE_ENGINE=django.db.backends.sqlite3
DATABASE_NAME=
DATABASE_HOST=
DATABASE_PORT=

View file

@ -58,8 +58,8 @@ urlpatterns = [
), name='openapi-schema'),
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
path('pricing/<slug:name>/calculate/', payviews.PricingView.as_view(), name='pricing_calculator'),
path('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
path('inbox/notifications/', include(notifications.urls, namespace='notifications')),
path('', include('matrixhosting.urls', namespace='matrix')),

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-07-30 13:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0022_remove_order_status'),
]
operations = [
migrations.AddField(
model_name='payment',
name='notes',
field=models.TextField(blank=True, default='', null=True),
),
migrations.AddField(
model_name='payment',
name='type',
field=models.CharField(choices=[('send', 'Send Money'), ('receive', 'Receive Money')], default='send', max_length=256),
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.4 on 2021-07-30 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uncloud_pay', '0023_auto_20210730_1342'),
]
operations = [
migrations.AlterField(
model_name='payment',
name='source',
field=models.CharField(blank=True, choices=[('wire', 'Wire Transfer'), ('stripe', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral')], max_length=256, null=True),
),
migrations.AlterField(
model_name='payment',
name='type',
field=models.CharField(choices=[('withdraw', 'Withdraw Money'), ('deposit', 'Deposit Money')], default='send', max_length=256),
),
]

View file

@ -78,6 +78,13 @@ class StripeCreditCard(models.Model):
class Payment(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
type = models.CharField(max_length=256,
choices = (
('withdraw', 'Withdraw Money'),
('deposit', 'Deposit Money')
), null=False, blank=False, default="send")
notes = models.TextField(default="", null=True, blank=True)
amount = models.DecimalField(
max_digits=AMOUNT_MAX_DIGITS,
@ -90,7 +97,7 @@ class Payment(models.Model):
('stripe', 'Stripe'),
('voucher', 'Voucher'),
('referral', 'Referral'),
))
), null=True, blank=True,)
timestamp = models.DateTimeField(default=timezone.now)
@ -101,17 +108,22 @@ class Payment(models.Model):
def __str__(self):
return f"{self.amount}{self.currency} from {self.owner} via {self.source} on {self.timestamp}"
def save(self, *args, **kwargs):
# Try to charge the user via the active card before saving otherwise throw payment Error
if self.source == 'stripe':
@classmethod
def deposit(cls, owner, amount, source, currency='CHF', notes=''):
if source == 'stripe':
try:
result = uncloud_pay.stripe.charge_customer(self.owner, self.amount, self.currency,)
if not result.status or result.status != 'succeeded':
payment_intent = uncloud_pay.stripe.charge_customer(owner, amount, currency)
if not payment_intent.status or payment_intent.status != 'succeeded':
raise Exception("The payment has been failed, please try to activate another card")
super().save(*args, **kwargs)
return cls.objects.create(owner=owner, type="deposit", amount=amount, external_reference=payment_intent["id"],
currency=currency, source=source, notes=notes)
except Exception as e:
raise e
@classmethod
def withdraw(cls, owner, amount, currency='CHF', notes=''):
cls.objects.create(owner=owner, type="withdraw", amount=amount,
currency=currency, notes=notes)
# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types

View file

@ -2,23 +2,17 @@ from django.utils import timezone
from django.db import transaction
from .models import *
def get_payments_for_user(user):
payments = [ payment.amount for payment in Payment.objects.filter(owner=user) ]
def get_deposit_payments_for_user(user):
payments = [ payment.amount for payment in Payment.objects.filter(owner=user, type='deposit')]
return sum(payments)
def get_spendings_for_user(user):
bills = Bill.objects.filter(owner=user)
amount = 0
for bill in bills:
amount += bill.sum
return amount
spendings = [payment.amount for payment in Payment.objects.filter(owner=user, type='withdraw')]
return sum(spendings)
@transaction.atomic
def get_balance_for_user(user):
return get_payments_for_user(user) - get_spendings_for_user(user)
return get_deposit_payments_for_user(user) - get_spendings_for_user(user)
@transaction.atomic
def has_enough_balance(user, due_amount):

View file

@ -103,7 +103,7 @@ def create_tax_id(stripe_customer_id, billing_address_id, type):
}
def apply_vat_discount(subtotal, pricing_plan, vat_rate=False, vat_validation_status=False):
vat_percent = vat_rate or pricing_plan.vat_percentage
vat_percent = vat_rate
if pricing_plan.vat_inclusive or (vat_validation_status and vat_validation_status in ["verified", "not_needed"]):
vat_percent = decimal.Decimal(0)
vat = decimal.Decimal(0)
@ -119,7 +119,7 @@ def apply_vat_discount(subtotal, pricing_plan, vat_rate=False, vat_validation_st
subtotal = round(float(subtotal), 2)
vat_percent = round(float(vat_percent), 2)
discount = {
'name': pricing_plan.discount_name,
'name': pricing_plan.discount_name or 'Discount',
'amount': discount_amount,
'amount_with_vat': round(float(discount_amount_with_vat), 2)
}
@ -149,7 +149,17 @@ def get_order_total_with_vat(cores, memory, storage,
(decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(storage) * (pricing.storage_unit_price))
)
return apply_vat_discount(subtotal, pricing, vat_rate, vat_validation_status)
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = \
apply_vat_discount(subtotal, pricing, vat_rate, vat_validation_status)
return {
"name": pricing.name,
"subtotal": subtotal,
"discount": discount,
"vat": vat, "vat_percent": vat_percent,
"vat_validation_status": vat_validation_status,
"subtotal_after_discount": subtotal_after_discount,
"total": price_after_discount_with_vat
}

View file

@ -1,5 +1,5 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.base import TemplateView
from django.views.generic.base import TemplateView, View
from django.shortcuts import render
from django.db import transaction
from django.contrib.auth import get_user_model
@ -23,6 +23,7 @@ import logging
from .models import *
from .serializers import *
from .selectors import *
from .utils import get_order_total_with_vat
from datetime import datetime
from vat_validator import sanitize_vat
@ -34,8 +35,16 @@ import stripe
logger = logging.getLogger(__name__)
###
# 2020-12 checked code
class PricingView(View):
def get(self, request, **args):
pricing = get_order_total_with_vat(
request.GET.get('cores'),
request.GET.get('memory'),
request.GET.get('storage'),
pricing_name = args['name']
)
return JsonResponse(pricing)
class RegisterCard(TemplateView):
template_name = "uncloud_pay/register_stripe.html"