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'); modal_btn = $('#createvm-modal-done-btn');
if (data.error) { if (data.error) {
// Display error.message in your UI. // 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'); fa_icon.attr('class', 'fa fa-close');
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
$('#createvm-modal-title').text("Error Occurred"); $('#createvm-modal-title').text("Error Occurred");
@ -26,7 +26,7 @@ $( document ).ready(function() {
} else { } else {
// The payment has succeeded // The payment has succeeded
// Display a success message // 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-title').text("Order Succeeded");
$('#createvm-modal-body').html("Order has been added and the instance will be ready soon"); $('#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 () { $('#checkout-btn').click(function () {
var id = this.dataset['id_card']; if($('input[name="payment_card"]:checked').size() == 1) {
$('#id_card').val(id); var id = $('input[name="payment_card"]:checked').val();
submitBillingForm(id); 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"> <section class="section bg-white">
<div class="container"> <div class="container">
<h2 class="text-9 text-center text-uppercase font-weight-400">What you will get?</h2> <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="row">
<div class="col-lg-10 mx-auto"> <div class="col-lg-10 mx-auto">
<div class="row"> <div class="row">
@ -56,28 +56,42 @@
<div class="featured-box style-3"> <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> <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> <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> </div>
<div class="col-sm-6 mb-4"> <div class="col-sm-6 mb-4">
<div class="featured-box style-3"> <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> <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> <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> </div>
<div class="col-sm-6 mb-4 mb-sm-0"> <div class="col-sm-6 mb-4 mb-sm-0">
<div class="featured-box style-3"> <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> <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> <h3 class="font-weight-400">End-to-End Encrypted (E2EE)</h3>
<p>Essent lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p> <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> </div>
<div class="col-sm-6"> <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 style-3">
<div class="featured-box-icon border border-primary text-primary rounded-circle"> <i class="fas fa-book-reader"></i> </div> <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> <h3 class="font-weight-400">100% Open Source</h3>
<p>Quidam lisque persius interesset his et, in quot quidam persequeris vim, ad mea essent possim iriure.</p> <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> </div>
</div> </div>
@ -147,13 +161,12 @@
<section class="section bg-white"> <section class="section bg-white">
<div class="container"> <div class="container">
<h2 class="text-9 text-center text-uppercase font-weight-400">As simple as 1-2-3</h2> <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="row">
<div class="col-sm-4 mb-4"> <div class="col-sm-4 mb-4">
<div class="featured-box style-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> <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> <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> </div>
<div class="col-sm-4 mb-4"> <div class="col-sm-4 mb-4">
@ -171,7 +184,7 @@
</div> </div>
</div> </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> </div>
</section> </section>
<!-- How it works End --> <!-- How it works End -->
@ -201,32 +214,32 @@
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>100% Renewable</h3> <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>
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>100% Open Source</h3> <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>
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Passive Cooling</h3> <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>
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Reuse Hardware</h3> <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>
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>Reuse Old Factories</h3> <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>
<div class="featured-box style-1"> <div class="featured-box style-1">
<div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div> <div class="featured-box-icon text-primary"> <i class="far fa-check-circle"></i> </div>
<h3>24/7 customer service</h3> <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> </div>
</div> </div>
@ -303,50 +316,56 @@
<div class="accordion accordion-alternate arrow-right" id="popularTopics"> <div class="accordion accordion-alternate arrow-right" id="popularTopics">
<div class="card"> <div class="card">
<div class="card-header" id="heading1"> <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>
<div id="collapse1" class="collapse" aria-labelledby="heading1" data-parent="#popularTopics"> <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> </div>
<div class="card"> <div class="card">
<div class="card-header" id="heading2"> <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>
<div id="collapse2" class="collapse" aria-labelledby="heading2" data-parent="#popularTopics"> <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> </div>
<div class="card"> <div class="card">
<div class="card-header" id="heading3"> <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>
<div id="collapse3" class="collapse" aria-labelledby="heading3" data-parent="#popularTopics"> <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> </div>
<div class="card"> <div class="card">
<div class="card-header" id="heading4"> <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>
<div id="collapse4" class="collapse" aria-labelledby="heading4" data-parent="#popularTopics"> <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> </div>
<div class="card"> <div class="card">
<div class="card-header" id="heading5"> <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>
<div id="collapse5" class="collapse" aria-labelledby="heading5" data-parent="#popularTopics"> <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> </div>
<div class="card"> <div class="card">
<div class="card-header" id="heading6"> <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>
<div id="collapse6" class="collapse" aria-labelledby="heading6" data-parent="#popularTopics"> <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> </div>
</div> </div>

View File

@ -40,29 +40,7 @@
</div> </div>
{% endif %} {% endif %}
{% if not error %} {% if not error %}
<div class="order-details"> <div class="order-details container">
<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="row align-items-center flex-row"> <div class="row align-items-center flex-row">
<div class="col col-sm-6"> <div class="col col-sm-6">
<address> <address>
@ -91,109 +69,92 @@
<span class="text-muted text-3 opacity-8">{% trans "Available Balance"%}</span> <span class="text-muted text-3 opacity-8">{% trans "Available Balance"%}</span>
</div> </div>
</div> </div>
<hr> <div class="col-sm-12"><hr class="mt-0"></div>
<div> <div class="row text-4 mb-4 text-center">Matrix Chat Hosting</div>
<h5>{% trans "Order summary" %}</h5> <div class="row">
<p> <div class="table-responsive">
<strong>{% trans "Product" %}:</strong>&nbsp; <table class="table table-striped table-bordered">
Matrix Chat Hosting <tbody>
</p> <tr>
<div class="row"> <td>{% trans "Cores" %}</td>
<div class="col-sm-9"> <td>{% trans "Memory" %}</td>
<p> <td>{% trans "Disk space" %}</td>
<span>{% trans "Cores" %}: </span> </tr>
<strong class="pull-right">{{order.cores}}</strong> <tr>
</p> <td>{{order.cores}}</td>
<p> <td>{{order.memory}} GB</td>
<span>{% trans "Memory" %}: </span> <td>{{order.storage}} GB</td>
<strong class="pull-right">{{order.memory}} GB</strong> </tr>
</p> </tbody>
<p> </table>
<span>{% trans "Disk space" %}: </span> </div>
<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>
</div>
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span>Subtotal</span></p>
</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>
<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>
</div>
{% if pricing.discount.amount > 0 %}
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<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>
</div>
{% endif %}
</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><strong>Total</strong></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<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>
</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>
</div>
</div> </div>
<hr class="thin-hr"> <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>Subtotal</span></strong>
</div>
<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 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-6 col-sm-6 col-xs-6">
<p><span>-{{pricing.discount.amount|floatformat:2}} CHF</span></p>
</div>
</div>
{% endif %}
</div>
</div>
<div class="col-sm-12"><hr class="mt-0"></div>
<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 "" %}</strong></p>
</div>
<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>
<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>
<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>
</div>
<hr class="">
</div> </div>
<form id="virtual_machine_create_form" action="" method="POST"> <form id="virtual_machine_create_form" action="" method="POST">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <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>
<div class="col-sm-12 order-confirm-btn text-right"> <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"> <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 %} {% if stripe_key %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<script type="text/javascript"> <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 error_url = '{{ error_msg.redirect }}';
var success_url = '{{ success_msg.redirect }}'; var success_url = '{{ success_msg.redirect }}';
window.stripeKey = "{{stripe_key}}";
window.isSubscription = ("{{is_subscription}}" === 'true');
</script> </script>
{%endif%} {%endif%}

View File

@ -200,12 +200,14 @@
</div> </div>
</div> </div>
<hr class="mt-2 mx-n3"> <hr class="mt-2 mx-n3">
{% with cards_len=cards|length %} {% if show_cards %}
<div id="cards-section">
{% with cards_len=cards|length %}
<p class="text-muted"> <p class="text-muted">
{% if cards_len > 0 %} {% if cards_len > 0 %}
{% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below.{% endblocktrans %} {% blocktrans %}Please select one of the cards that you used before or fill in your credit card information below.{% endblocktrans %}
{% else %} {% 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 %} {% endif %}
</p> </p>
<div> <div>
@ -226,11 +228,14 @@
</div> </div>
{% endfor %} {% endfor %}
{% if cards_len > 0 %} {% 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"> <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> <label class="custom-control-label" for="new-card"><h6 class="mb-0">{% trans "Add New Card" %}</h6></label>
</div> </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"> <div class="card-details-box p-4 bg-light">
{% include "matrixhosting/includes/_card.html" %} {% include "matrixhosting/includes/_card.html" %}
</div> </div>
@ -240,6 +245,12 @@
{% endif %} {% endif %}
</div> </div>
{% endwith %} {% 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>
</div> </div>
@ -258,6 +269,7 @@
(function () { (function () {
window.stripeKey = "{{stripe_key}}"; window.stripeKey = "{{stripe_key}}";
window.current_lan = "{{LANGUAGE_CODE}}"; window.current_lan = "{{LANGUAGE_CODE}}";
window.hasCreditcard = "{{show_cards}}";
})(); })();
</script> </script>
{%endif%} {%endif%}

View File

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

View File

@ -1,5 +1,6 @@
import logging import logging
import json import json
import decimal
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.contrib import messages from django.contrib import messages
@ -29,16 +30,6 @@ from .serializers import *
logger = logging.getLogger(__name__) 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): class IndexView(FormView):
template_name = "matrixhosting/index.html" template_name = "matrixhosting/index.html"
form_class = InitialRequestForm form_class = InitialRequestForm
@ -53,16 +44,14 @@ class IndexView(FormView):
def form_valid(self, form): def form_valid(self, form):
self.request.session['order'] = form.cleaned_data 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['cores'],
form.cleaned_data['memory'], form.cleaned_data['memory'],
form.cleaned_data['storage'], form.cleaned_data['storage'],
form.cleaned_data['pricing_name'], form.cleaned_data['pricing_name'],
False False
) )
self.request.session['pricing'] = {'name': form.cleaned_data['pricing_name'], self.request.session['pricing'] = pricing
'subtotal': subtotal, 'vat': vat, 'total': total,
'vat_percent': vat_percent, 'discount': discount}
return HttpResponseRedirect(reverse('matrix:payment')) return HttpResponseRedirect(reverse('matrix:payment'))
@ -91,22 +80,24 @@ class OrderPaymentView(FormView):
details_form = RequestHostedVMForm( details_form = RequestHostedVMForm(
initial=self.request.session.get('order', {}) 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) customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
cards = uncloud_stripe.get_customer_cards(customer_id) cards = uncloud_stripe.get_customer_cards(customer_id)
context.update({ context.update({
'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']), 'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']),
'billing_address_form': billing_address_form, 'billing_address_form': billing_address_form,
'details_form': details_form, 'details_form': details_form,
'balance': balance,
'cards': cards, '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 return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): 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): if request.session.get(k):
request.session.pop(k) request.session.pop(k)
if 'order' not in request.session: if 'order' not in request.session:
@ -121,14 +112,11 @@ class OrderPaymentView(FormView):
context.update({'details_form': details_form, context.update({'details_form': details_form,
'billing_address_form': billing_address_form}) 'billing_address_form': billing_address_form})
return self.render_to_response(context) 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 = { this_user = {
'email': self.request.user.email, 'email': self.request.user.email,
'username': self.request.user.username '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) address = get_billing_address_for_user(self.request.user)
if address: if address:
form = BillingAddressForm(self.request.POST, instance=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_id"] = billing_address_ins.id
self.request.session['billing_address_data'] = billing_address_form.cleaned_data 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['billing_address_data']['owner'] = self.request.user.id
self.request.session['user'] = this_user id_payment_method = self.request.POST.get('id_payment_method', False)
self.request.session['customer'] = customer_id 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() vat_number = billing_address_form.cleaned_data.get('vat_number').strip()
if vat_number: if vat_number:
validate_result = validate_vat_number( validate_result = validate_vat_number(
stripe_customer_id=customer_id, stripe_customer_id=customer_id,
billing_address_id=billing_address_ins.id billing_address_id=billing_address_ins.id
) )
if 'error' in validate_result and validate_result['error']: if 'error' in validate_result and validate_result['error']:
messages.add_message( messages.add_message(
self.request, messages.ERROR, validate_result["error"], self.request, messages.ERROR, validate_result["error"],
@ -157,6 +146,21 @@ class OrderPaymentView(FormView):
reverse('matrix:payment') + '#vat_error' reverse('matrix:payment') + '#vat_error'
) )
self.request.session["vat_validation_status"] = validate_result["status"] 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')) return HttpResponseRedirect(reverse('matrix:order_confirmation'))
class OrderDetailsView(DetailView): class OrderDetailsView(DetailView):
@ -164,69 +168,35 @@ class OrderDetailsView(DetailView):
context_object_name = "order" context_object_name = "order"
model = Order model = Order
# @method_decorator(login_required) @method_decorator(login_required)
# def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):
# return super().dispatch(*args, **kwargs) return super().dispatch(*args, **kwargs)
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = {} context = {
# if ('order' not in request.session or 'user' not in request.session): 'order': self.request.session.get('order'),
# return HttpResponseRedirect(reverse('matrix:index')) 'pricing': self.request.session.get('pricing'),
# if 'id_payment_method' in self.request.session: 'balance': get_balance_for_user(self.request.user)
# card = uncloud_stripe.get_card_from_payment(self.request.user, self.request.session['id_payment_method']) }
# if not card: if ('order' not in request.session):
# return HttpResponseRedirect(reverse('matrix:payment')) return HttpResponseRedirect(reverse('matrix:index'))
# context['card'] = card elif 'pricing' not in self.request.session or 'vat_validation_status' not in self.request.session:
# elif 'id_payment_method' not in self.request.session or 'vat_validation_status' not in self.request.session: return HttpResponseRedirect(reverse('matrix:payment'))
# 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,
# })
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
customer = StripeCustomer.objects.get(owner=self.request.user) customer = StripeCustomer.objects.get(owner=self.request.user)
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id')) billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
if 'id_payment_method' in request.session: total = self.request.session['pricing']['total']
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)
order = finalize_order(request, customer, order = finalize_order(request, customer,
billing_address, billing_address,
self.request.session['total_price'], total,
PricingPlan.get_by_name(self.request.session['pricing']['name']), PricingPlan.get_by_name(self.request.session['pricing']['name']),
request.session.get('order')) request.session.get('order'))
if order: if order:
bill = Bill.create_next_bill_for_user_address(billing_address) 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: if payment:
#Close the bill as the payment has been added #Close the bill as the payment has been added
bill.close() bill.close()

View File

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

View File

@ -58,8 +58,8 @@ urlpatterns = [
), name='openapi-schema'), ), name='openapi-schema'),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('accounts/', include('allauth.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('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
path('inbox/notifications/', include(notifications.urls, namespace='notifications')), path('inbox/notifications/', include(notifications.urls, namespace='notifications')),
path('', include('matrixhosting.urls', namespace='matrix')), 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): class Payment(models.Model):
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) 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( amount = models.DecimalField(
max_digits=AMOUNT_MAX_DIGITS, max_digits=AMOUNT_MAX_DIGITS,
@ -90,7 +97,7 @@ class Payment(models.Model):
('stripe', 'Stripe'), ('stripe', 'Stripe'),
('voucher', 'Voucher'), ('voucher', 'Voucher'),
('referral', 'Referral'), ('referral', 'Referral'),
)) ), null=True, blank=True,)
timestamp = models.DateTimeField(default=timezone.now) timestamp = models.DateTimeField(default=timezone.now)
@ -101,17 +108,22 @@ class Payment(models.Model):
def __str__(self): def __str__(self):
return f"{self.amount}{self.currency} from {self.owner} via {self.source} on {self.timestamp}" return f"{self.amount}{self.currency} from {self.owner} via {self.source} on {self.timestamp}"
def save(self, *args, **kwargs): @classmethod
# Try to charge the user via the active card before saving otherwise throw payment Error def deposit(cls, owner, amount, source, currency='CHF', notes=''):
if self.source == 'stripe': if source == 'stripe':
try: try:
result = uncloud_pay.stripe.charge_customer(self.owner, self.amount, self.currency,) payment_intent = uncloud_pay.stripe.charge_customer(owner, amount, currency)
if not result.status or result.status != 'succeeded': if not payment_intent.status or payment_intent.status != 'succeeded':
raise Exception("The payment has been failed, please try to activate another card") 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: except Exception as e:
raise 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 # 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 django.db import transaction
from .models import * from .models import *
def get_payments_for_user(user): def get_deposit_payments_for_user(user):
payments = [ payment.amount for payment in Payment.objects.filter(owner=user) ] payments = [ payment.amount for payment in Payment.objects.filter(owner=user, type='deposit')]
return sum(payments) return sum(payments)
def get_spendings_for_user(user): def get_spendings_for_user(user):
bills = Bill.objects.filter(owner=user) spendings = [payment.amount for payment in Payment.objects.filter(owner=user, type='withdraw')]
return sum(spendings)
amount = 0
for bill in bills:
amount += bill.sum
return amount
@transaction.atomic @transaction.atomic
def get_balance_for_user(user): 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 @transaction.atomic
def has_enough_balance(user, due_amount): 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): 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"]): if pricing_plan.vat_inclusive or (vat_validation_status and vat_validation_status in ["verified", "not_needed"]):
vat_percent = decimal.Decimal(0) vat_percent = decimal.Decimal(0)
vat = 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) subtotal = round(float(subtotal), 2)
vat_percent = round(float(vat_percent), 2) vat_percent = round(float(vat_percent), 2)
discount = { discount = {
'name': pricing_plan.discount_name, 'name': pricing_plan.discount_name or 'Discount',
'amount': discount_amount, 'amount': discount_amount,
'amount_with_vat': round(float(discount_amount_with_vat), 2) '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(memory) * pricing.ram_unit_price) +
(decimal.Decimal(storage) * (pricing.storage_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.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.shortcuts import render
from django.db import transaction from django.db import transaction
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -23,6 +23,7 @@ import logging
from .models import * from .models import *
from .serializers import * from .serializers import *
from .selectors import * from .selectors import *
from .utils import get_order_total_with_vat
from datetime import datetime from datetime import datetime
from vat_validator import sanitize_vat from vat_validator import sanitize_vat
@ -34,8 +35,16 @@ import stripe
logger = logging.getLogger(__name__) 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): class RegisterCard(TemplateView):
template_name = "uncloud_pay/register_stripe.html" template_name = "uncloud_pay/register_stripe.html"