Skip to content

Commit 26ff6cc

Browse files
committed
added a payment system Braintree in test mode
1 parent 631bdf1 commit 26ff6cc

18 files changed

+212
-20
lines changed

myshop/settings.py

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
'shop',
4444
'cart',
4545
'orders',
46+
'payment',
4647
]
4748

4849
MIDDLEWARE = [
@@ -135,3 +136,17 @@
135136
CART_SESSION_ID = 'cart'
136137

137138
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
139+
140+
141+
BRAINTREE_MERCHANT_ID = ''
142+
BRAINTREE_PUBLIC_KEY = ''
143+
BRAINTREE_PRIVATE_KEY = ''
144+
145+
from braintree import Configuration, Environment
146+
147+
Configuration.configure(
148+
Environment.Sandbox,
149+
BRAINTREE_MERCHANT_ID,
150+
BRAINTREE_PUBLIC_KEY,
151+
BRAINTREE_PRIVATE_KEY
152+
)

myshop/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
path('admin/', admin.site.urls),
2323
path('cart/', include('cart.urls', namespace='cart')),
2424
path('orders/', include('orders.urls', namespace='orders')),
25+
path('payment/', include('payment.urls', namespace='payment')),
2526
path('', include('shop.urls', namespace='shop')),
2627
]
2728
if settings.DEBUG:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.1.5 on 2021-01-11 12:03
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('orders', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='order',
15+
name='braintree_id',
16+
field=models.CharField(blank=True, max_length=150),
17+
),
18+
]

orders/models.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Order(models.Model):
1212
created = models.DateTimeField(auto_now_add=True)
1313
updated = models.DateTimeField(auto_now=True)
1414
paid = models.BooleanField(default=False)
15+
braintree_id = models.CharField(max_length=150, blank=True)
1516

1617
class Meta:
1718
ordering = ('-created',)
@@ -33,4 +34,4 @@ def __str__(self):
3334
return f'{self.id}'
3435

3536
def get_cost(self):
36-
self.price * self.quantity
37+
return self.price * self.quantity

orders/views.py

+17-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.shortcuts import render
1+
from django.shortcuts import render, redirect, reverse
22
from .models import OrderItem
33
from .forms import OrderCreateForm
44
from cart.cart import Cart
@@ -7,16 +7,19 @@
77

88
def order_create(request):
99
cart = Cart(request)
10-
if request.method == 'POST':
11-
form = OrderCreateForm(request.POST)
12-
if form.is_valid():
13-
order = form.save()
14-
for item in cart:
15-
OrderItem.objects.create(order=order, product=item['product'],
16-
price=item['price'], quantity=item['quantity'])
17-
cart.clear()
18-
order_created(order.id)
19-
return render(request, 'orders/order/created.html', {'order': order})
20-
else:
21-
form = OrderCreateForm()
22-
return render(request, 'orders/order/create.html', {'cart': cart, 'form': form})
10+
if cart:
11+
if request.method == 'POST':
12+
form = OrderCreateForm(request.POST)
13+
if form.is_valid():
14+
order = form.save()
15+
for item in cart:
16+
OrderItem.objects.create(order=order, product=item['product'],
17+
price=item['price'], quantity=item['quantity'])
18+
cart.clear()
19+
order_created.delay(order.id)
20+
request.session['order_id'] = order.id
21+
return redirect(reverse('payment:process'))
22+
else:
23+
form = OrderCreateForm()
24+
return render(request, 'orders/order/create.html', {'cart': cart, 'form': form})
25+
return redirect('/')

payment/__init__.py

Whitespace-only changes.

payment/admin.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

payment/apps.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class PaymentConfig(AppConfig):
5+
name = 'payment'

payment/migrations/__init__.py

Whitespace-only changes.

payment/models.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.db import models
2+
3+
# Create your models here.
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% extends 'shop/base.html' %}
2+
3+
{% block content %}
4+
<h1>Your payment has not been processed</h1>
5+
<p>There was a problem processing your payment.</p>
6+
{% endblock %}

payment/templates/payment/done.html

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% extends 'shop/base.html' %}
2+
3+
{% block content %}
4+
<h1>Your payment was successful</h1>
5+
<p>Your payment has been processed successfully.</p>
6+
{% endblock %}
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{% extends 'shop/base.html' %}
2+
3+
{% block title %}
4+
Pay by credit card
5+
{% endblock %}
6+
7+
{% block content %}
8+
<h1>Pay by credit card</h1>
9+
<form action="." id="payment" method="post">
10+
{% csrf_token %}
11+
<label for="card-number">Card number</label>
12+
<div id="card-number" class="field"></div>
13+
14+
<label for="cvv">CVV</label>
15+
<div id="cvv" class="field"></div>
16+
17+
<label for="expiration-date">Expiration Date</label>
18+
<div id="expiration-date" class="field"></div>
19+
20+
<input type="hidden" id="nonce" name="payment_method_nonce" value="">
21+
22+
<input type="submit" value="Pay">
23+
</form>
24+
25+
<script src="https://js.braintreegateway.com/web/3.29.0/js/client.min.js"></script>
26+
<script src="https://js.braintreegateway.com/web/3.29.0/js/hosted-fields.min.js"></script>
27+
28+
<script>
29+
var form = document.querySelector('#payment');
30+
var submit = document.querySelector('input[type="submit"]');
31+
braintree.client.create({
32+
authorization: '{{ client_token }}'
33+
}, function (clientErr, clientInstance) {
34+
if (clientErr) {
35+
console.error(clientErr);
36+
return;
37+
}
38+
braintree.hostedFields.create({
39+
client: clientInstance,
40+
styles: {
41+
'input': {'font-size': '13px'},
42+
'input.invalid': {'color': 'red'},
43+
'input.valid': {'color': 'green'}
44+
},
45+
fields: {
46+
number: {selector: '#card-number'},
47+
cvv: {selector: '#cvv'},
48+
expirationDate: {selector: '#expiration-date'}
49+
}
50+
}, function (hostedFieldsErr, hostedFieldsInstance) {
51+
if (hostedFieldsErr) {
52+
console.error(hostedFieldsErr);
53+
return;
54+
}
55+
submit.removeAttribute('disabled');
56+
form.addEventListener('submit', function (event) {
57+
event.preventDefault();
58+
hostedFieldsInstance.tokenize(function (tokenizeErr, payload) {
59+
if (tokenizeErr) {
60+
console.error(tokenizeErr);
61+
return;
62+
}
63+
// Задаем значение поля для отправки токена на сервер.
64+
document.getElementById('nonce').value = payload.nonce;
65+
// Отправляем форму на сервер.
66+
document.getElementById('payment').submit();
67+
});
68+
}, false);
69+
});
70+
});
71+
</script>
72+
{% endblock %}

payment/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

payment/urls.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from django.urls import path
2+
from . import views
3+
4+
app_name = 'payment'
5+
6+
urlpatterns = [
7+
path('process/', views.payment_process, name='process'),
8+
path('done/', views.payment_done, name='done'),
9+
path('canceled/', views.payment_canceled, name='canceled'),
10+
]

payment/views.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from django.shortcuts import render, redirect, get_object_or_404
2+
import braintree
3+
from orders.models import Order
4+
5+
6+
def payment_process(request):
7+
order_id = request.session.get('order_id')
8+
order = get_object_or_404(Order, id=order_id)
9+
10+
if request.method == 'POST':
11+
nonce = request.POST.get('payment_method_nonce', None)
12+
result = braintree.Transaction.sale({
13+
'amount': '{:.2f}'.format(order.get_total_cost()),
14+
'payment_method_nonce': nonce,
15+
'options': {
16+
'submit_for_settlement': True
17+
}
18+
})
19+
if result.is_success:
20+
order.paid = True
21+
order.braintree_id = result.transaction.id
22+
order.save()
23+
return redirect('payment:done')
24+
else:
25+
return redirect('payment:canceled')
26+
else:
27+
client_token = braintree.ClientToken.generate()
28+
return render(request, 'payment/process.html', {'order': order, 'client_token': client_token})
29+
30+
31+
def payment_done(request):
32+
return render(request, 'payment/done.html')
33+
34+
35+
def payment_canceled(request):
36+
return render(request, 'payment/canceled.html')

requirements.txt

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
amqp==5.0.2
1+
amqp==2.6.1
22
asgiref==3.3.1
33
billiard==3.6.3.0
4-
celery==5.0.5
4+
braintree==4.5.0
5+
celery==4.4.7
6+
certifi==2020.12.5
7+
chardet==4.0.0
58
click==7.1.2
69
click-didyoumean==0.0.3
710
click-plugins==1.1.1
811
click-repl==0.1.6
912
Django==3.1.5
1013
django-crispy-forms==1.10.0
11-
kombu==5.0.2
14+
flower==0.9.7
15+
humanize==3.2.0
16+
idna==2.10
17+
kombu==4.6.11
1218
Pillow==8.1.0
19+
prometheus-client==0.8.0
1320
prompt-toolkit==3.0.10
1421
pytz==2020.5
22+
requests==2.25.1
1523
six==1.15.0
1624
sqlparse==0.4.1
17-
vine==5.0.0
25+
tornado==6.1
26+
urllib3==1.26.2
27+
vine==1.3.0
1828
wcwidth==0.2.5

shop/templates/shop/base.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</head>
1010
<body>
1111
<div class="header">
12-
<h1><a href="#">My shop</a></h1>
12+
<h1><a href="{% url 'shop:product_list' %}">My shop</a></h1>
1313
</div>
1414
<div class="subheader">
1515
<div class="cart">

0 commit comments

Comments
 (0)