forked from uncloud/uncloud
List all the credit cards and the ability to add more cards
This commit is contained in:
parent
7986b825a7
commit
1c3d3efb3a
22 changed files with 529 additions and 159 deletions
BIN
matrixhosting/static/matrixhosting/images/mastercard.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/mastercard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
BIN
matrixhosting/static/matrixhosting/images/visa.png
Normal file
BIN
matrixhosting/static/matrixhosting/images/visa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
|
@ -1,13 +1,3 @@
|
|||
function setBrandIcon(brand) {
|
||||
var brandIconElement = document.getElementById('brand-icon');
|
||||
var pfClass = 'fa-cc-' + brand;
|
||||
for (var i = brandIconElement.classList.length - 1; i >= 0; i--) {
|
||||
brandIconElement.classList.remove(brandIconElement.classList[i]);
|
||||
}
|
||||
brandIconElement.classList.add('fab');
|
||||
brandIconElement.classList.add(pfClass);
|
||||
}
|
||||
|
||||
function fetch_pricing() {
|
||||
var url = '/pricing/' + $('input[name="pricing_name"]').val() + '/calculate/';
|
||||
var cores = $('#cores').val();
|
||||
|
@ -20,8 +10,18 @@ function fetch_pricing() {
|
|||
dataType: 'json',
|
||||
success: function (data) {
|
||||
if (data && data['total']) {
|
||||
$('#total').text(data['total'] + " CHF");
|
||||
$('#recurring_price').text(data['recurring_price'] + " CHF");
|
||||
$('#vat').text(data['vat_amount'] + " CHF");
|
||||
$('#total').text(data['total'] + " CHF");
|
||||
var balance = parseFloat($('#balance').data('balance'));
|
||||
if(data['total'] > balance) {
|
||||
$('#has-enough-balance').hide();
|
||||
$('#cards-section').show();
|
||||
window.cardNumberElement = loadStripe(stripe);
|
||||
} else {
|
||||
$('#cards-section').hide();
|
||||
$('#has-enough-balance').show();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -43,8 +43,9 @@ function incrementValue(e) {
|
|||
return false;
|
||||
};
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
var stripe = Stripe(window.stripeKey);
|
||||
|
||||
if ($('#pricing_name') != undefined) {
|
||||
fetch_pricing();
|
||||
}
|
||||
|
@ -54,72 +55,8 @@ $(document).ready(function () {
|
|||
$('.fa-minus-circle.left').bind('click', {inc: -1}, incrementValue);
|
||||
|
||||
var hasCreditcard = window.hasCreditcard || false;
|
||||
if (hasCreditcard && window.stripeKey) {
|
||||
var stripe = Stripe(window.stripeKey);
|
||||
if (window.pm_id == undefined) {
|
||||
var element_style = {
|
||||
fonts: [{
|
||||
family: 'lato-light',
|
||||
src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")'
|
||||
}, {
|
||||
family: 'lato-regular',
|
||||
src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")'
|
||||
}
|
||||
],
|
||||
locale: window.current_lan
|
||||
};
|
||||
var elements = stripe.elements(element_style);
|
||||
var credit_card_text_style = {
|
||||
base: {
|
||||
iconColor: '#666EE8',
|
||||
color: '#31325F',
|
||||
lineHeight: '25px',
|
||||
fontWeight: 300,
|
||||
fontFamily: "'lato-light', sans-serif",
|
||||
fontSize: '14px',
|
||||
'::placeholder': {
|
||||
color: '#777'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
iconColor: '#eb4d5c',
|
||||
color: '#eb4d5c',
|
||||
lineHeight: '25px',
|
||||
fontWeight: 300,
|
||||
fontFamily: "'lato-regular', sans-serif",
|
||||
fontSize: '14px',
|
||||
'::placeholder': {
|
||||
color: '#eb4d5c',
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var enter_ccard_text = "Enter your credit card number";
|
||||
if (typeof window.enter_your_card_text !== 'undefined') {
|
||||
enter_ccard_text = window.enter_your_card_text;
|
||||
}
|
||||
var cardNumberElement = elements.create('cardNumber', {
|
||||
style: credit_card_text_style,
|
||||
placeholder: enter_ccard_text
|
||||
});
|
||||
cardNumberElement.mount('#card-number-element');
|
||||
|
||||
var cardExpiryElement = elements.create('cardExpiry', {
|
||||
style: credit_card_text_style
|
||||
});
|
||||
cardExpiryElement.mount('#card-expiry-element');
|
||||
|
||||
var cardCvcElement = elements.create('cardCvc', {
|
||||
style: credit_card_text_style
|
||||
});
|
||||
cardCvcElement.mount('#card-cvc-element');
|
||||
cardNumberElement.on('change', function (event) {
|
||||
if (event.brand) {
|
||||
setBrandIcon(event.brand);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (hasCreditcard) {
|
||||
window.cardNumberElement = loadStripe(stripe);
|
||||
}
|
||||
|
||||
function submitBillingForm(pmId) {
|
||||
|
@ -141,7 +78,7 @@ $(document).ready(function () {
|
|||
}
|
||||
stripe.createPaymentMethod({
|
||||
type: 'card',
|
||||
card: cardNumberElement,
|
||||
card: window.cardNumberElement,
|
||||
})
|
||||
.then(function(result) {
|
||||
// Handle result.error or result.paymentMethod
|
||||
|
@ -154,7 +91,7 @@ $(document).ready(function () {
|
|||
stripePMHandler(result.paymentMethod);
|
||||
}
|
||||
});
|
||||
window.card = cardNumberElement;
|
||||
window.card = window.cardNumberElement;
|
||||
}
|
||||
|
||||
/* Form validation */
|
||||
|
|
79
matrixhosting/static/matrixhosting/js/stripe.js
Normal file
79
matrixhosting/static/matrixhosting/js/stripe.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
function setBrandIcon(brand) {
|
||||
var brandIconElement = document.getElementById('brand-icon');
|
||||
var pfClass = 'fa-cc-' + brand;
|
||||
for (var i = brandIconElement.classList.length - 1; i >= 0; i--) {
|
||||
brandIconElement.classList.remove(brandIconElement.classList[i]);
|
||||
}
|
||||
brandIconElement.classList.add('fab');
|
||||
brandIconElement.classList.add(pfClass);
|
||||
};
|
||||
|
||||
function loadStripe(stripe) {
|
||||
var cardNumberElement;
|
||||
if (stripe) {
|
||||
var element_style = {
|
||||
fonts: [{
|
||||
family: 'lato-light',
|
||||
src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")'
|
||||
}, {
|
||||
family: 'lato-regular',
|
||||
src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")'
|
||||
}
|
||||
],
|
||||
locale: window.current_lan
|
||||
};
|
||||
var elements = stripe.elements(element_style);
|
||||
var credit_card_text_style = {
|
||||
base: {
|
||||
iconColor: '#666EE8',
|
||||
color: '#31325F',
|
||||
lineHeight: '25px',
|
||||
fontWeight: 300,
|
||||
fontFamily: "'lato-light', sans-serif",
|
||||
fontSize: '14px',
|
||||
'::placeholder': {
|
||||
color: '#777'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
iconColor: '#eb4d5c',
|
||||
color: '#eb4d5c',
|
||||
lineHeight: '25px',
|
||||
fontWeight: 300,
|
||||
fontFamily: "'lato-regular', sans-serif",
|
||||
fontSize: '14px',
|
||||
'::placeholder': {
|
||||
color: '#eb4d5c',
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var enter_ccard_text = "Enter your credit card number";
|
||||
if (typeof window.enter_your_card_text !== 'undefined') {
|
||||
enter_ccard_text = window.enter_your_card_text;
|
||||
}
|
||||
cardNumberElement = elements.create('cardNumber', {
|
||||
style: credit_card_text_style,
|
||||
placeholder: enter_ccard_text
|
||||
});
|
||||
cardNumberElement.mount('#card-number-element');
|
||||
|
||||
var cardExpiryElement = elements.create('cardExpiry', {
|
||||
style: credit_card_text_style
|
||||
});
|
||||
cardExpiryElement.mount('#card-expiry-element');
|
||||
|
||||
var cardCvcElement = elements.create('cardCvc', {
|
||||
style: credit_card_text_style
|
||||
});
|
||||
cardCvcElement.mount('#card-cvc-element');
|
||||
cardNumberElement.on('change', function (event) {
|
||||
if (event.brand) {
|
||||
setBrandIcon(event.brand);
|
||||
}
|
||||
});
|
||||
}
|
||||
return cardNumberElement;
|
||||
};
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script src="{% static 'matrixhosting/js/bootstrap.bundle.min.js' %}"></script>
|
||||
<script src="{% static 'matrixhosting/js/stripe.js' %}"></script>
|
||||
<!-- Custom JS -->
|
||||
{% block js_extra %} {% endblock js_extra %}
|
||||
{% compress js %}
|
||||
|
|
210
matrixhosting/templates/matrixhosting/cards.html
Normal file
210
matrixhosting/templates/matrixhosting/cards.html
Normal file
|
@ -0,0 +1,210 @@
|
|||
{% extends "matrixhosting/base.html" %}
|
||||
|
||||
{% load static i18n compress %}
|
||||
|
||||
{% block title %} Payments {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="bg-white shadow-md rounded p-4 m-4">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link" id="first-tab" href="{% url 'matrixhosting:billing' %}">Payment Moves</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">Payment Methods</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div class="tab-content my-3" id="myTabContentVertical">
|
||||
<div class="tab-pane fade show active" id="cards" role="tabpanel" aria-labelledby="cards">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-lg-12">
|
||||
<div class="featured-box style-3 float-right mb-4">
|
||||
<div class="featured-box-icon text-17 text-light"> <i class="fas fa-wallet"></i> </div>
|
||||
<h3 class="text-9 font-weight-400">{{balance}} CHF</h3>
|
||||
<a href="" data-target="#make-deposit" data-toggle="modal">Make a deposit</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-0 mb-4 mx-n8">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h3 class="text-4 font-weight-400 mb-4">{% trans "Credit or Debit Cards"%} <span class="text-muted text-3">({% trans "for payments"%})</span></h3>
|
||||
<hr class="mt-0 mb-2 mx-n8">
|
||||
<div class="row">
|
||||
{% for card in object_list %}
|
||||
<div class="col-12 col-sm-6 col-lg-4 mt-2">
|
||||
<div class="account-card {% if card.active %}account-card-primary{%endif%} text-white rounded p-3 mb-4 mb-lg-0">
|
||||
<p class="text-4">XXXX-XXXX-XXXX-{{card.last4}}</p>
|
||||
<p class="d-flex align-items-center"> <span class="account-card-expire text-uppercase d-inline-block opacity-7 mr-2">Valid<br>
|
||||
thru<br>
|
||||
</span> <span class="text-4 opacity-9">{{card.expiry_date|date:"m"}}/{{card.expiry_date|date:"y"}}</span> {% if card.active %}<span class="badge badge-warning text-0 font-weight-500 rounded-pill px-2 ml-auto">{% trans "Primary"%}</span>{%endif%} </p>
|
||||
<p class="d-flex align-items-center m-0"> <span class="text-uppercase font-weight-500">{{card.card_name}}</span> <img class="ml-auto" src="{% static 'matrixhosting/images/' %}{{card.brand}}.png" alt="visa" title=""> </p>
|
||||
<div class="account-card-overlay rounded" data-card="{{card.card_id}}"> <a href="#" data-href="{% url 'payments:card_activate'%}" class="activate-btn text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-edit"></i></span>{% trans "Set As Primary"%}</a> <a href="#" data-href="{% url 'stripecreditcard-detail' card.id %}" class="delete-card text-light btn-link mx-2"><span class="mr-1"><i class="fas fa-minus-circle"></i></span>{% trans "Delete"%}</a> </div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="col-12 col-sm-6 col-lg-4 mt-2"> <a href="" data-target="#add-new-card-details" data-toggle="modal" class="account-card-new d-flex align-items-center rounded h-100 p-3 mb-4 mb-lg-0">
|
||||
<p class="w-100 text-center line-height-4 m-0"> <span class="text-3"><i class="fas fa-plus-circle"></i></span> <span class="d-block text-body text-3">Add New Card</span> </p>
|
||||
</a> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="make-deposit" class="modal fade" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title font-weight-400">{% trans "Make a deposit"%}</h5>
|
||||
<button type="button" class="close font-weight-400" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form id="make_deposit" method="post" action="{% url 'payment-list' %}">
|
||||
{% csrf_token %}
|
||||
<input id="type" name="type" type="hidden" value="deposit">
|
||||
<input id="currency" name="currency" type="hidden" value="CHF">
|
||||
<div class="form-group">
|
||||
<label for="amount">{% trans "Amount *"%}</label>
|
||||
<input id="amount" name="amount" class="form-control" required type="number" min={{min_amount}} value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">{% trans "Notes"%}</label>
|
||||
<textarea id="notes" name="notes" class="form-control"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary float-right mt-2" id="deposit-button" type="submit">
|
||||
{% trans "Confirm"%}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-new-card-details" class="modal fade" aria-hidden="true" style="display: none;">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title font-weight-400">{% trans "Add a Card"%}</h5>
|
||||
<button type="button" class="close font-weight-400" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button>
|
||||
</div>
|
||||
<div class="modal-body p-4">
|
||||
<form id="addCard" method="post" data-secret="{{ client_secret }}">
|
||||
<div id="card-element"></div>
|
||||
<div id="card-errors"></div>
|
||||
<div class="form-group">
|
||||
<label for="cardNumber">{% trans "Card Number"%}</label>
|
||||
<div id="card-number-element" class="field my-input form-control"></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="expiryDate">{% trans "Expiry Date"%}</label>
|
||||
<div id="card-expiry-element" class="field my-input form-control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<label for="cvvNumber">{% trans "CVV"%} <span class="text-info ml-1" data-toggle="tooltip" data-original-title="For Visa/Mastercard, the three-digit CVV number is printed on the signature panel on the back of the card immediately after the card's account number. For American Express, the four-digit CVV number is printed on the front of the card above the card account number."><i class="fas fa-question-circle"></i></span></label>
|
||||
<div id="card-cvc-element" class="field my-input form-control"></div> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cardHolderName">{% trans "Card Holder Name"%}</label>
|
||||
<input type="text" class="form-control" data-bv-field="cardholdername" id="cardholder-name" required="" value="" placeholder="Card Holder Name">
|
||||
</div>
|
||||
<div class="card-element brand text-6">
|
||||
<i class="fab fa-credit-card" id="brand-icon"></i>
|
||||
</div>
|
||||
<button class="btn btn-primary float-right mt-2" id="card-button" type="submit">{% trans "Add Card"%}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js_extra %}
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script>
|
||||
var stripe = Stripe('{{ stripe_pk }}');
|
||||
var setupForm = document.getElementById('addCard');
|
||||
var clientSecret = setupForm.dataset.secret;
|
||||
window.cardNumberElement = loadStripe(stripe);
|
||||
var cardholderName = document.getElementById('cardholder-name');
|
||||
var messageContainer = document.getElementById('card-errors');
|
||||
setupForm.addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
stripe.confirmCardSetup(
|
||||
clientSecret,
|
||||
{
|
||||
payment_method: {
|
||||
card: window.cardNumberElement,
|
||||
billing_details: {
|
||||
name: cardholderName.value,
|
||||
},
|
||||
},
|
||||
}
|
||||
).then(function(result) {
|
||||
if (result.error) {
|
||||
var message = document.createTextNode('Error:' + result.error.message);
|
||||
messageContainer.appendChild(message);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('.activate-btn').click(function (e) {
|
||||
e.preventDefault();
|
||||
var url = $(e.target).data('href');
|
||||
var card_id = $(e.target).parent().data('card');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: {card_id: card_id, csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('.delete-card').click(function (e) {
|
||||
e.preventDefault();
|
||||
var url = $(e.target).data('href');
|
||||
var card_id = $(e.target).parent().data('card');
|
||||
$.ajax({
|
||||
type: 'DELETE',
|
||||
url: url,
|
||||
data: {csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||
dataType: 'json',
|
||||
success: function (result) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#make_deposit").submit(function(e){
|
||||
var form = $(this);
|
||||
$('#deposit-button').html('<span class="spinner-border spinner-border-sm mr-2" role="status" aria-hidden="true"></span>Loading...').attr('disabled', true);
|
||||
$.ajax({
|
||||
url : form.attr('action'),
|
||||
type : form.attr('method'),
|
||||
data : form.serialize(),
|
||||
success: function(response) {
|
||||
location.reload();
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
alert(xhr.responseJSON);
|
||||
$('#deposit-button').html("{% trans 'Confirm'%}").attr('disabled', false);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock js_extra %}
|
|
@ -24,15 +24,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="card-errors"></div>
|
||||
<div id='payment_error'>
|
||||
{% for message in messages %}
|
||||
{% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags or 'error' in message.tags %}
|
||||
<ul class="list-unstyled">
|
||||
<li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button class="btn btn-vm-contact btn-primary btn-wide" type="submit" name="payment-form">{%trans "Checkout" %}</button>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<li><a class="dropdown-item text-center text-primary px-0" href="">See all Notifications</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown profile ml-2"> <a class="px-0 dropdown-toggle" href="#"><span class="text-5"><i class="far fa-user"></i></span></a>
|
||||
<li class="dropdown profile ml-2"> <a class="px-2 dropdown-toggle" href="#"><span class="text-5"><i class="far fa-user"></i></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="text-center text-3 py-2">Hi, {{request.user.username}}</li>
|
||||
<li class="dropdown-divider mx-n3"></li>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<li><a class="dropdown-item" href=""><i class="fas fa-tachometer-alt"></i>{%trans "Dashboard" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:orders' %}"><i class="fas fa-shopping-cart"></i>{%trans "Orders" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:billing' %}"><i class="fas fa-file-invoice"></i>{%trans "Billing" %}</a></li>
|
||||
<li><a class="dropdown-item" href=""><i class="fas fa-cloud"></i>{%trans "Instances" %}</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'matrix:instances' %}"><i class="fas fa-cloud"></i>{%trans "Instances" %}</a></li>
|
||||
<li class="dropdown-divider mx-n3"></li>
|
||||
<li><a class="dropdown-item" href=""><i class="fas fa-life-ring"></i>Need Help?</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'account_logout' %}"><i class="fas fa-sign-out-alt"></i>Sign Out</a></li>
|
||||
|
|
53
matrixhosting/templates/matrixhosting/instances.html
Normal file
53
matrixhosting/templates/matrixhosting/instances.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
{% extends "matrixhosting/base.html" %}
|
||||
|
||||
{% load static i18n compress %}
|
||||
|
||||
{% block title %} Instances {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Content -->
|
||||
{% csrf_token %}
|
||||
<div class="container">
|
||||
<div class="row p-1 mt-4">
|
||||
<div class="col-lg-12 bg-white shadow-sm border border-light rounded py-4 mb-4">
|
||||
<h3 class="text-5 font-weight-400 d-flex align-items-center px-1 mb-4">{% trans "Instances"%}</h3>
|
||||
<!-- Title
|
||||
=============================== -->
|
||||
<div class="transaction-title py-2 px-1">
|
||||
<div class="row">
|
||||
<div class="col-1 col-sm-1 text-center"><span class="">{% trans "ID"%}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center"><span class="">{% trans "Creation Date"%}</span></div>
|
||||
<div class="col-3 col-sm-3 d-none d-sm-block text-center">{% trans "Homeserver Domain"%}</div>
|
||||
<div class="col-3 col-sm-3 text-right">{% trans "WebClient Domain"%}</div>
|
||||
<div class="col-1 col-sm-1 text-center"><span class="">{% trans "Order"%}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center">{% trans "Termination Date"%}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Title End -->
|
||||
|
||||
<!-- Instances List
|
||||
=============================== -->
|
||||
<div class="transaction-list">
|
||||
{% for instance in object_list %}
|
||||
<div class="transaction-item px-1 py-4" data-id={{instance.id}}>
|
||||
<div class="row align-items-center flex-row">
|
||||
<div class="col-1 col-sm-1 text-center"> <span class="d-block text-3 font-weight-400">#{{instance.id}}</span></div>
|
||||
<div class="col-2 col-sm-2 text-center"> <span class="d-block text-2 font-weight-300">{{instance.creation_date|date:"Y-m-d"}}</span></div>
|
||||
<div class="col-3 col-sm-3 d-none d-sm-block text-right text-1"> <span>{{instance.homeserver_domain}}</span> </div>
|
||||
<div class="col-3 col-sm-3 d-none d-sm-block text-right text-1"> <span>{{instance.webclient_domain}}</span> </div>
|
||||
<div class="col-1 col-sm-1 text-center text-2"><span class="">#{{instance.order.id}}</span></div>
|
||||
<div class="col-2 col-sm-2 d-none d-sm-block text-center text-2"> <span class=" text-uppercase">{{order.termination_date|date:"Y-m-d"}}</span> </div>
|
||||
</div>
|
||||
</div>
|
||||
{%endfor%}
|
||||
</div>
|
||||
<!-- Instances List End -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_extra %}
|
||||
{% endblock %}
|
|
@ -180,8 +180,14 @@
|
|||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
{% if stripe_deposit_amount > 0 %}
|
||||
<div class="col-sm-12">
|
||||
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
|
||||
By clicking "Confirm order" you agree to charge your active credit card with <strong>{{stripe_deposit_amount}} CHF</strong> to handle the wallet deficit.
|
||||
<hr>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-12">
|
||||
By clicking "Confirm order" you agree to our <a href="">Terms of Service</a> and this plan will charge your account balance with {{pricing.total|floatformat:2}} CHF
|
||||
</div>
|
||||
<div class="col-sm-12 order-confirm-btn mt-2 text-right">
|
||||
<button class="btn choice-btn btn-primary" id="btn-create-vm" type="submit">
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
<div class="row align-items-center flex-row">
|
||||
<div class="col col-sm-6"><div class="text-14 text-light"><i class="fas fa-wallet"></i></div></div>
|
||||
<div class="col col-sm-6 text-right text-2">
|
||||
<h3 class="text-6 font-weight-400 {% if balance >= 0 %}text-success{%else%}text-danger{%endif%}">{{ balance }} CHF</h3>
|
||||
<h3 id="balance" class="text-6 font-weight-400 {% if balance >= 0 %}text-success{%else%}text-danger{%endif%}" data-balance='{{ balance }}'>{{ balance }} CHF</h3>
|
||||
<span class="text-muted text-3 opacity-8">{% trans "Available Balance"%}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -197,6 +197,7 @@
|
|||
<p>{% trans "Discount"%} <span class="float-right text-danger"> - {{matrix_vm_pricing.discount_amount}} CHF</span></p>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<p>{% trans "VAT" %}<span class="float-right" id="vat"> {{request.session.pricing.vat_amount}} CHF</span></p>
|
||||
<p class="text-4 font-weight-500">{% trans "Total To Pay"%}
|
||||
<small>
|
||||
({% if matrix_vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
|
||||
|
@ -206,8 +207,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr class="mt-2 mx-n3">
|
||||
{% if show_cards %}
|
||||
<div id="cards-section">
|
||||
<div id="cards-section" {% if not show_cards %}style="display:none;"{% endif %}>
|
||||
{% with cards_len=cards|length %}
|
||||
<p class="text-muted">
|
||||
{% if cards_len > 0 %}
|
||||
|
@ -252,14 +252,14 @@
|
|||
</div>
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">
|
||||
{% blocktrans %}Your wallet has enough balance, Press Continue to fill the VM instance settings.{% endblocktrans %}
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<button id="continue-btn" class="btn btn-primary btn-wide" type="submit">{%trans "Continue" %}</button>
|
||||
<div id="has-enough-balance" {% if show_cards %}style="display:none;"{% endif %}>
|
||||
<p class="text-muted">
|
||||
{% blocktrans %}Your wallet has enough balance, Press Continue to fill the VM instance settings.{% endblocktrans %}
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<button id="continue-btn" class="btn btn-primary btn-wide">{%trans "Continue" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs flex-column" id="myTabVertical" role="tablist">
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" data-toggle="tab" href="#transactions" role="tab" aria-controls="firstTab" aria-selected="true">All Transactions</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" data-toggle="tab" href="#cards" role="tab" aria-controls="fourthTab" aria-selected="false">Payment Methods</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link active" id="first-tab" href="{% url 'matrixhosting:billing' %}">Payment Moves</a> </li>
|
||||
<li class="nav-item"> <a class="nav-link" id="fourth-tab" href="{% url 'matrixhosting:cards' %}">Payment Methods</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
@ -42,7 +42,7 @@
|
|||
<div class="col-12 mb-3" id="allFilters">
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="allTransactions" name="filter" data-url="{% url 'matrix:billing' %}" class="custom-control-input" value="all" {% if not type %} checked=""{% endif %}>
|
||||
<label class="custom-control-label" for="allTransactions">{% trans "All Transactions"%}</label>
|
||||
<label class="custom-control-label" for="allTransactions">{% trans "All"%}</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="withdrawal" name="filter" class="custom-control-input" data-url="{% url 'matrix:billing' %}?type=withdraw" value="withdraw" {% if type == 'withdraw' %} checked=""{% endif %}>
|
||||
|
@ -63,7 +63,7 @@
|
|||
<!-- All Transactions
|
||||
============================================= -->
|
||||
<div class="bg-white shadow-sm border border-light rounded py-4 mb-4">
|
||||
<h3 class="text-5 font-weight-400 d-flex align-items-center px-4 mb-4">{% trans "All Transactions"%}</h3>
|
||||
<h3 class="text-5 font-weight-400 d-flex align-items-center px-4 mb-4">{% trans "Payment Moves"%}</h3>
|
||||
<!-- Title
|
||||
=============================== -->
|
||||
<div class="transaction-title py-2 px-4">
|
||||
|
@ -111,8 +111,6 @@
|
|||
<!-- All Transactions End -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="cards" role="tabpanel" aria-labelledby="cards">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,10 +8,12 @@ app_name = 'matrixhosting'
|
|||
|
||||
urlpatterns = [
|
||||
path('order/new/', OrderPaymentView.as_view(), name='payment'),
|
||||
path('order/confirm/', OrderDetailsView.as_view(), name='order_confirmation'),
|
||||
path('order/confirm/', OrderConfirmationView.as_view(), name='order_confirmation'),
|
||||
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
|
||||
path('order/invoice/download', InvoiceDownloadView.as_view(), name='invoice_download'),
|
||||
path('billing/', PaymentsView.as_view(), name='billing'),
|
||||
path('billing/cards', CardsView.as_view(), name='cards'),
|
||||
path('instances/', InstancesView.as_view(), name='instances'),
|
||||
path('orders/', OrdersView.as_view(), name='orders'),
|
||||
path('', IndexView.as_view(), name='index'),
|
||||
]
|
||||
|
|
|
@ -115,11 +115,6 @@ class OrderPaymentView(FormView):
|
|||
context.update({'details_form': details_form,
|
||||
'billing_address_form': billing_address_form})
|
||||
return self.render_to_response(context)
|
||||
|
||||
this_user = {
|
||||
'email': self.request.user.email,
|
||||
'username': self.request.user.username
|
||||
}
|
||||
address = get_billing_address_for_user(self.request.user)
|
||||
if address:
|
||||
form = BillingAddressForm(self.request.POST, instance=address)
|
||||
|
@ -131,11 +126,14 @@ class OrderPaymentView(FormView):
|
|||
self.request.session['billing_address_data'] = billing_address_form.cleaned_data
|
||||
self.request.session['billing_address_data']['owner'] = self.request.user.id
|
||||
id_payment_method = self.request.POST.get('id_payment_method', False)
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
selected_card = False
|
||||
if id_payment_method and id_payment_method != 'undefined':
|
||||
uncloud_stripe.attach_payment_method(id_payment_method, customer_id)
|
||||
uncloud_stripe.attach_payment_method(id_payment_method, self.request.user)
|
||||
selected_card = StripeCreditCard.objects.filter(card_id=id_payment_method).first()
|
||||
selected_card.activate()
|
||||
vat_number = billing_address_form.cleaned_data.get('vat_number').strip()
|
||||
if vat_number:
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
validate_result = validate_vat_number(
|
||||
stripe_customer_id=customer_id,
|
||||
billing_address_id=billing_address_ins.id
|
||||
|
@ -156,24 +154,21 @@ class OrderPaymentView(FormView):
|
|||
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):
|
||||
try:
|
||||
payment_id = Payment.deposit(request.user, abs(amount), source='stripe')
|
||||
except CardError as e:
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, e.user_message,
|
||||
extra_tags='error'
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse('matrix:payment')
|
||||
)
|
||||
self.request.session['pricing'] = pricing
|
||||
self.request.session['order'] = specs
|
||||
self.request.session['vat_validation_status'] = vat_validation_status
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(pricing["total"])
|
||||
if (amount < 0 and not selected_card):
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, "You haven't enough balance please select credit card to continue",
|
||||
extra_tags='error'
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse('matrix:payment')
|
||||
)
|
||||
return HttpResponseRedirect(reverse('matrix:order_confirmation'))
|
||||
|
||||
class OrderDetailsView(DetailView):
|
||||
class OrderConfirmationView(DetailView):
|
||||
template_name = "matrixhosting/order_confirmation.html"
|
||||
context_object_name = "order"
|
||||
model = Order
|
||||
|
@ -197,7 +192,12 @@ class OrderDetailsView(DetailView):
|
|||
if ('order' not in request.session):
|
||||
return HttpResponseRedirect(reverse('matrix:index'))
|
||||
elif 'pricing' not in self.request.session or 'vat_validation_status' not in self.request.session:
|
||||
return HttpResponseRedirect(reverse('matrix:payment'))
|
||||
return HttpResponseRedirect(reverse('matrix:payment'))
|
||||
|
||||
total = self.request.session['pricing']['total']
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)
|
||||
if (amount < 0):
|
||||
context['stripe_deposit_amount'] = max(amount, settings.MIN_PER_TRANSACTION)
|
||||
return render(request, self.template_name, context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
@ -210,19 +210,31 @@ class OrderDetailsView(DetailView):
|
|||
self.request.session['order']['homeserver_domain'] = domains_form.cleaned_data.get('homeserver_name') + ".matrix.ungleich.cloud"
|
||||
self.request.session['order']['webclient_domain'] = domains_form.cleaned_data.get('webclient_name') + ".matrix.0co2.cloud"
|
||||
self.request.session['order']['is_open_registration'] = domains_form.cleaned_data.get('is_open_registration')
|
||||
order = finalize_order(request, customer,
|
||||
try:
|
||||
amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)
|
||||
if (amount < 0):
|
||||
Payment.deposit(request.user, abs(max(amount, settings.MIN_PER_TRANSACTION)), source='stripe')
|
||||
order = finalize_order(request, customer,
|
||||
billing_address,
|
||||
total,
|
||||
PricingPlan.get_by_name(self.request.session['pricing']['name']),
|
||||
request.session.get('order'))
|
||||
if order:
|
||||
bill = Bill.create_next_bill_for_order(order)
|
||||
self.request.session['bill_id'] = bill.id
|
||||
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
|
||||
if payment:
|
||||
#Close the bill as the payment has been added
|
||||
bill.close()
|
||||
return HttpResponseRedirect(reverse('matrix:order_success'))
|
||||
if order:
|
||||
bill = Bill.create_next_bill_for_order(order)
|
||||
self.request.session['bill_id'] = bill.id
|
||||
payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
|
||||
if payment:
|
||||
#Close the bill as the payment has been added
|
||||
bill.close()
|
||||
return HttpResponseRedirect(reverse('matrix:order_success'))
|
||||
except CardError as e:
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, e.user_message,
|
||||
extra_tags='error'
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse('matrix:order_confirmation')
|
||||
)
|
||||
context = self.get_context_data()
|
||||
context['domains_form'] = domains_form
|
||||
return self.render_to_response(context)
|
||||
|
@ -289,15 +301,17 @@ class OrdersView(ListView):
|
|||
order.cancel()
|
||||
return JsonResponse({'message': 'Successfully Cancelled'})
|
||||
|
||||
class InstancesView(ListView):
|
||||
template_name = "matrixhosting/instances.html"
|
||||
model = VMInstance
|
||||
|
||||
class MachineViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = VMInstanceSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return VMInstance.objects.filter(owner=self.request.user)
|
||||
|
||||
|
||||
class PaymentsView(ListView):
|
||||
template_name = "matrixhosting/payments.html"
|
||||
model = Payment
|
||||
|
@ -316,6 +330,34 @@ class PaymentsView(ListView):
|
|||
|
||||
def get_queryset(self):
|
||||
if self.request.GET.get('type'):
|
||||
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type'))
|
||||
return Payment.objects.filter(owner=self.request.user)
|
||||
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type')).order_by('-timestamp')
|
||||
return Payment.objects.filter(owner=self.request.user).order_by('-timestamp')
|
||||
|
||||
class CardsView(ListView):
|
||||
template_name = "matrixhosting/cards.html"
|
||||
model = StripeCreditCard
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super().dispatch(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CardsView, self).get_context_data(**kwargs)
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
setup_intent = uncloud_stripe.create_setup_intent(customer_id)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'balance': get_balance_for_user(self.request.user),
|
||||
'client_secret': setup_intent.client_secret,
|
||||
'username': self.request.user.username,
|
||||
'stripe_pk':uncloud_stripe.public_api_key,
|
||||
'min_amount': settings.MIN_PER_TRANSACTION
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
uncloud_stripe.sync_cards_for_user(self.request.user)
|
||||
return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')
|
||||
|
||||
|
||||
|
|
|
@ -15,4 +15,5 @@ GITLAB_YAML_DIR=
|
|||
GITLAB_PROJECT_ID=
|
||||
GITLAB_OAUTH_TOKEN=
|
||||
GITLAB_AUTHOR_EMAIL=
|
||||
GITLAB_AUTHOR_NAME=
|
||||
GITLAB_AUTHOR_NAME=
|
||||
WKHTMLTOPDF_CMD=/usr/bin/wkhtmltopdf
|
|
@ -92,7 +92,7 @@ MIDDLEWARE = [
|
|||
]
|
||||
|
||||
ROOT_URLCONF = 'uncloud.urls'
|
||||
WKHTMLTOPDF_CMD = "/usr/local/bin/wkhtmltopdf"
|
||||
WKHTMLTOPDF_CMD = env('WKHTMLTOPDF_CMD')
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
|
@ -217,6 +217,7 @@ OPENNEBULA_USER_PASS = 'user:password'
|
|||
STRIPE_KEY=env('STRIPE_KEY')
|
||||
STRIPE_PUBLIC_KEY=env('STRIPE_PUBLIC_KEY')
|
||||
BILL_PAYMENT_DELAY = 0
|
||||
MIN_PER_TRANSACTION = 5
|
||||
# The django secret key
|
||||
SECRET_KEY=get_random_secret_key()
|
||||
|
||||
|
@ -279,6 +280,7 @@ REPORT_FORMAT = {
|
|||
'header_line': False,
|
||||
}
|
||||
|
||||
|
||||
# Overwrite settings with local settings, if existing
|
||||
try:
|
||||
from uncloud.local_settings import *
|
||||
|
|
|
@ -41,7 +41,6 @@ router.register(r'v2/payment/balance', payviews.BalanceViewSet, basename='paymen
|
|||
router.register(r'v2/payment/address', payviews.BillingAddressViewSet, basename='billingaddress')
|
||||
router.register(r'v2/orders', payviews.OrderViewSet, basename='orders')
|
||||
router.register(r'v2/bill', payviews.BillViewSet, basename='bills')
|
||||
router.register(r'v2/machines', matrixviews.MachineViewSet, basename='machines')
|
||||
|
||||
# Generic helper views that are usually not needed
|
||||
router.register(r'v2/generic/vat-rate', payviews.VATRateViewSet, basename='vatrate')
|
||||
|
@ -62,6 +61,7 @@ urlpatterns = [
|
|||
path('pricing/<slug:name>/calculate/', payviews.PricingView.as_view(), name='pricing_calculator'),
|
||||
path('cc/reg/', payviews.RegisterCard.as_view(), name="cc_register"),
|
||||
path('inbox/notifications/', include(notifications.urls, namespace='notifications')),
|
||||
path('payments/', include('uncloud_pay.urls', namespace='payments')),
|
||||
path('', include('matrixhosting.urls', namespace='matrix')),
|
||||
# path('', uncloudviews.UncloudIndex.as_view(), name="uncloudindex"),
|
||||
]
|
||||
|
|
|
@ -58,7 +58,7 @@ class StripeCustomer(models.Model):
|
|||
class StripeCreditCard(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
||||
|
||||
card_name = models.CharField(null=False, max_length=128, default="My credit card")
|
||||
card_name = models.CharField(null=False, max_length=128, default="")
|
||||
card_id = models.CharField(null=False, max_length=32)
|
||||
last4 = models.CharField(null=False, max_length=4)
|
||||
brand = models.CharField(null=False, max_length=64)
|
||||
|
@ -76,6 +76,16 @@ class StripeCreditCard(models.Model):
|
|||
def __str__(self):
|
||||
return f"{self.card_name}: {self.brand} {self.last4} ({self.expiry_date})"
|
||||
|
||||
def delete(self, **kwargs):
|
||||
uncloud_pay.stripe.delete_card(self.card_id)
|
||||
super().delete(**kwargs)
|
||||
|
||||
|
||||
def activate(self):
|
||||
StripeCreditCard.objects.filter(owner=self.owner, active=True).update(active=False)
|
||||
self.active = True
|
||||
self.save()
|
||||
|
||||
class Payment(models.Model):
|
||||
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
|
||||
type = models.CharField(max_length=256,
|
||||
|
@ -125,6 +135,7 @@ class Payment(models.Model):
|
|||
return 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
|
||||
class RecurringPeriodDefaultChoices(models.IntegerChoices):
|
||||
|
|
|
@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model
|
|||
from rest_framework import serializers
|
||||
from uncloud_auth.serializers import UserSerializer
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from stripe.error import CardError
|
||||
|
||||
from .models import *
|
||||
import uncloud_pay.stripe as uncloud_stripe
|
||||
|
@ -25,13 +26,17 @@ class PaymentSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = [ "external_reference", "source", "timestamp" ]
|
||||
|
||||
def validate(self, data):
|
||||
payment_intent = uncloud_stripe.charge_customer(data['owner'],
|
||||
data['amount'])
|
||||
|
||||
data["external_reference"] = payment_intent["id"]
|
||||
data["source"] = "stripe"
|
||||
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
try:
|
||||
if validated_data['type'] == 'deposit':
|
||||
return Payment.deposit(validated_data['owner'], validated_data['amount'], validated_data['source'], currency=validated_data['currency'], notes=validated_data['notes'])
|
||||
else:
|
||||
return Payment.objects.create(**validated_data)
|
||||
except CardError as err:
|
||||
raise serializers.ValidationError(err.user_message)
|
||||
|
||||
class BalanceSerializer(serializers.Serializer):
|
||||
balance = serializers.DecimalField(max_digits=AMOUNT_MAX_DIGITS, decimal_places=AMOUNT_DECIMALS)
|
||||
|
|
|
@ -94,8 +94,11 @@ def get_card_from_payment(user, payment_method_id):
|
|||
|
||||
|
||||
@handle_stripe_error
|
||||
def attach_payment_method(payment_method_id, customer_id):
|
||||
return stripe.PaymentMethod.attach(payment_method_id, customer=customer_id)
|
||||
def attach_payment_method(payment_method_id, user):
|
||||
customer_id = get_customer_id_for(user)
|
||||
ret = stripe.PaymentMethod.attach(payment_method_id, customer=customer_id)
|
||||
sync_cards_for_user(user)
|
||||
return ret
|
||||
|
||||
@handle_stripe_error
|
||||
def create_customer(name, email):
|
||||
|
@ -107,7 +110,6 @@ def get_customer(customer_id):
|
|||
|
||||
@handle_stripe_error
|
||||
def get_customer_cards(customer_id):
|
||||
print(f"getting cards for: {customer_id}")
|
||||
|
||||
cards = []
|
||||
stripe_cards = stripe.PaymentMethod.list(
|
||||
|
@ -127,6 +129,10 @@ def get_customer_cards(customer_id):
|
|||
|
||||
return cards
|
||||
|
||||
@handle_stripe_error
|
||||
def delete_card(card_id):
|
||||
return stripe.PaymentMethod.detach(card_id)
|
||||
|
||||
def sync_cards_for_user(user):
|
||||
customer_id = get_customer_id_for(user)
|
||||
cards = get_customer_cards(customer_id)
|
||||
|
|
9
uncloud_pay/urls.py
Normal file
9
uncloud_pay/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from .views import *
|
||||
|
||||
app_name = 'uncloud_pay'
|
||||
|
||||
urlpatterns = [
|
||||
path('cards/activate', CardActivateView.as_view(), name='card_activate'),
|
||||
]
|
|
@ -38,14 +38,32 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class PricingView(View):
|
||||
def get(self, request, **args):
|
||||
address = get_billing_address_for_user(self.request.user)
|
||||
vat_rate = False
|
||||
vat_validation_status = False
|
||||
if address:
|
||||
vat_rate = VATRate.get_vat_rate(address)
|
||||
vat_validation_status = "verified" if address.vat_number_validated_on and address.vat_number_verified else False
|
||||
pricing = get_order_total_with_vat(
|
||||
request.GET.get('cores'),
|
||||
request.GET.get('memory'),
|
||||
request.GET.get('storage'),
|
||||
pricing_name = args['name']
|
||||
pricing_name = args['name'],
|
||||
vat_rate = vat_rate * 100,
|
||||
vat_validation_status = vat_validation_status
|
||||
)
|
||||
return JsonResponse(pricing)
|
||||
|
||||
class CardActivateView(View):
|
||||
def post(self, request, **args):
|
||||
card_id = request.POST.get('card_id')
|
||||
if card_id:
|
||||
matched_card = StripeCreditCard.objects.filter(owner=self.request.user, card_id=card_id).first()
|
||||
matched_card.activate()
|
||||
return JsonResponse({'success': 1})
|
||||
else:
|
||||
return JsonResponse({'error': "Please select a card"})
|
||||
|
||||
class RegisterCard(TemplateView):
|
||||
template_name = "uncloud_pay/register_stripe.html"
|
||||
|
||||
|
@ -55,7 +73,6 @@ class RegisterCard(TemplateView):
|
|||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||||
setup_intent = uncloud_stripe.create_setup_intent(customer_id)
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
|
Loading…
Reference in a new issue