Refactor the Payment Model and Handle Order Confirmation Page
This commit is contained in:
parent
94dac8110e
commit
5564400ef8
15 changed files with 312 additions and 267 deletions
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,11 +152,29 @@ $(document).ready(function () {
|
||||||
$(element).closest('.form-group').append(error);
|
$(element).closest('.form-group').append(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#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();
|
||||||
|
});
|
||||||
|
|
||||||
$('.credit-card-info .btn.choice-btn').click(function () {
|
|
||||||
var id = this.dataset['id_card'];
|
$('input[name="payment_card"]').change(function(e) {
|
||||||
$('#id_card').val(id);
|
if($('input[name="payment_card"]:checked').val() == 'new') {
|
||||||
submitBillingForm(id);
|
$('#checkout-btn').hide();
|
||||||
|
$('#newcard').show();
|
||||||
|
} else {
|
||||||
|
$('#newcard').hide();
|
||||||
|
$('#checkout-btn').show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
<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>
|
</div>
|
||||||
</p>
|
<div class="row">
|
||||||
</div>
|
<div class="col-sm-6">
|
||||||
<div class="col-sm-12">
|
</div>
|
||||||
<hr class="thin-hr">
|
<div class="col-sm-6">
|
||||||
</div>
|
<div class="row text-right">
|
||||||
<div class="col-sm-9">
|
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||||
<p>
|
<p><strong>Subtotal</span></strong>
|
||||||
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong>
|
</div>
|
||||||
<strong class="pull-right">{{pricing.subtotal|floatformat:2}} CHF</strong>
|
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||||
</p>
|
<p><strong class="pull-right" >{{pricing.subtotal|floatformat:2}} CHF</strong></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-12">
|
</div>
|
||||||
<hr class="thin-hr">
|
{% if pricing.discount.amount > 0 %}
|
||||||
</div>
|
<div class="row text-right">
|
||||||
<div class="col-sm-9">
|
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||||
<div class="row">
|
<p><span>{{pricing.discount.name }}</span></p>
|
||||||
<div class="col-md-4 col-sm-4 col-xs-4">
|
</div>
|
||||||
<p><span></span></p>
|
<div class="col-md-6 col-sm-6 col-xs-6">
|
||||||
</div>
|
<p><span>-{{pricing.discount.amount|floatformat:2}} CHF</span></p>
|
||||||
<div class="col-md-3 col-sm-3 col-xs-4">
|
</div>
|
||||||
<p class="text-right"><strong class="cmf-ord-heading">{% trans "Pre VAT" %}</strong></p>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="col-md-5 col-sm-5 col-xs-4 header-no-left-padding">
|
</div>
|
||||||
<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="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%}
|
||||||
|
|
||||||
|
|
|
@ -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%}
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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=
|
||||||
|
|
|
@ -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')),
|
||||||
|
|
23
uncloud_pay/migrations/0023_auto_20210730_1342.py
Normal file
23
uncloud_pay/migrations/0023_auto_20210730_1342.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
23
uncloud_pay/migrations/0024_auto_20210730_1441.py
Normal file
23
uncloud_pay/migrations/0024_auto_20210730_1441.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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,19 +108,24 @@ 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
|
||||||
class RecurringPeriodDefaultChoices(models.IntegerChoices):
|
class RecurringPeriodDefaultChoices(models.IntegerChoices):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue