Skip to content

Commit 872f28e

Browse files
Matthew FreireMatthew Freire
authored andcommitted
Stripe payments
1 parent ddc4972 commit 872f28e

File tree

13 files changed

+226
-7
lines changed

13 files changed

+226
-7
lines changed

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
STRIPE_TEST_PUBLIC_KEY=''
3+
STRIPE_TEST_SECRET_KEY=''

core/api/urls.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
from .views import (
33
ItemListView,
44
AddToCartView,
5-
OrderDetailView
5+
OrderDetailView,
6+
PaymentView
67
)
78

89
urlpatterns = [
910
path('product-list/', ItemListView.as_view(), name='product-list'),
1011
path('add-to-cart/', AddToCartView.as_view(), name='add-to-cart'),
11-
path('order-summary/', OrderDetailView.as_view(), name='order-summary')
12+
path('order-summary/', OrderDetailView.as_view(), name='order-summary'),
13+
path('checkout/', PaymentView.as_view(), name='checkout')
1214
]

core/api/views.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
1010
from core.models import Item, OrderItem, Order
1111
from .serializers import ItemSerializer, OrderSerializer
12+
from core.models import Item, OrderItem, Order, Address, Payment, Coupon, Refund, UserProfile
13+
14+
15+
import stripe
16+
17+
stripe.api_key = settings.STRIPE_SECRET_KEY
1218

1319

1420
class ItemListView(ListAPIView):
@@ -58,3 +64,105 @@ def get_object(self):
5864
return order
5965
except ObjectDoesNotExist:
6066
return Response({"message": "You do not have an active order"}, status=HTTP_400_BAD_REQUEST)
67+
68+
69+
class PaymentView(APIView):
70+
71+
def post(self, request, *args, **kwargs):
72+
order = Order.objects.get(user=self.request.user, ordered=False)
73+
userprofile = UserProfile.objects.get(user=self.request.user)
74+
token = request.data.get('stripeToken')
75+
# save = form.cleaned_data.get('save')
76+
# use_default = form.cleaned_data.get('use_default')
77+
save = False
78+
use_default = False
79+
80+
if save:
81+
if userprofile.stripe_customer_id != '' and userprofile.stripe_customer_id is not None:
82+
customer = stripe.Customer.retrieve(
83+
userprofile.stripe_customer_id)
84+
customer.sources.create(source=token)
85+
86+
else:
87+
customer = stripe.Customer.create(
88+
email=self.request.user.email,
89+
)
90+
customer.sources.create(source=token)
91+
userprofile.stripe_customer_id = customer['id']
92+
userprofile.one_click_purchasing = True
93+
userprofile.save()
94+
95+
amount = int(order.get_total() * 100)
96+
97+
try:
98+
99+
if use_default or save:
100+
# charge the customer because we cannot charge the token more than once
101+
charge = stripe.Charge.create(
102+
amount=amount, # cents
103+
currency="usd",
104+
customer=userprofile.stripe_customer_id
105+
)
106+
else:
107+
# charge once off on the token
108+
charge = stripe.Charge.create(
109+
amount=amount, # cents
110+
currency="usd",
111+
source=token
112+
)
113+
114+
# create the payment
115+
payment = Payment()
116+
payment.stripe_charge_id = charge['id']
117+
payment.user = self.request.user
118+
payment.amount = order.get_total()
119+
payment.save()
120+
121+
# assign the payment to the order
122+
123+
order_items = order.items.all()
124+
order_items.update(ordered=True)
125+
for item in order_items:
126+
item.save()
127+
128+
order.ordered = True
129+
order.payment = payment
130+
# order.ref_code = create_ref_code()
131+
order.save()
132+
133+
return Response(status=HTTP_200_OK)
134+
135+
except stripe.error.CardError as e:
136+
body = e.json_body
137+
err = body.get('error', {})
138+
return Response({"message": f"{err.get('message')}"}, status=HTTP_400_BAD_REQUEST)
139+
140+
except stripe.error.RateLimitError as e:
141+
# Too many requests made to the API too quickly
142+
messages.warning(self.request, "Rate limit error")
143+
return Response({"message": "Rate limit error"}, status=HTTP_400_BAD_REQUEST)
144+
145+
except stripe.error.InvalidRequestError as e:
146+
# Invalid parameters were supplied to Stripe's API
147+
return Response({"message": "Invalid parameters"}, status=HTTP_400_BAD_REQUEST)
148+
149+
except stripe.error.AuthenticationError as e:
150+
# Authentication with Stripe's API failed
151+
# (maybe you changed API keys recently)
152+
return Response({"message": "Not authenticated"}, status=HTTP_400_BAD_REQUEST)
153+
154+
except stripe.error.APIConnectionError as e:
155+
# Network communication with Stripe failed
156+
return Response({"message": "Network error"}, status=HTTP_400_BAD_REQUEST)
157+
158+
except stripe.error.StripeError as e:
159+
# Display a very generic error to the user, and maybe send
160+
# yourself an email
161+
return Response({"message": "Something went wrong. You were not charged. Please try again."}, status=HTTP_400_BAD_REQUEST)
162+
163+
except Exception as e:
164+
# send an email to ourselves
165+
print(e)
166+
return Response({"message": "A serious error occurred. We have been notifed."}, status=HTTP_400_BAD_REQUEST)
167+
168+
return Response({"message": "Invalid data received"}, status=HTTP_400_BAD_REQUEST)

core/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ def post(self, *args, **kwargs):
311311

312312
except stripe.error.InvalidRequestError as e:
313313
# Invalid parameters were supplied to Stripe's API
314-
print(e)
315314
messages.warning(self.request, "Invalid parameters")
316315
return redirect("/")
317316

home/settings/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from decouple import config
23

34
BASE_DIR = os.path.dirname(os.path.dirname(
45
os.path.dirname(os.path.abspath(__file__))))

home/settings/dev.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,9 @@
1616

1717
CORS_ORIGIN_WHITELIST = (
1818
'http://localhost:3000',
19-
)
19+
)
20+
21+
# Stripe
22+
23+
STRIPE_PUBLIC_KEY = config('STRIPE_TEST_PUBLIC_KEY')
24+
STRIPE_SECRET_KEY = config('STRIPE_TEST_SECRET_KEY')

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"react-redux": "^7.1.0",
1111
"react-router-dom": "^5.0.1",
1212
"react-scripts": "^3.0.1",
13+
"react-stripe-elements": "^4.0.0",
1314
"redux": "^4.0.1",
1415
"redux-thunk": "^2.3.0",
1516
"semantic-ui-css": "^2.4.1",

public/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
88
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
99
<title>Django React Boilerplate</title>
10+
<script src="https://js.stripe.com/v3/"></script>
1011
</head>
1112
<body>
1213
<noscript>

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ requests==2.22.0
2525
requests-oauthlib==1.2.0
2626
six==1.12.0
2727
sqlparse==0.3.0
28+
stripe==2.33.0
2829
typed-ast==1.4.0
2930
urllib3==1.25.3
3031
wrapt==1.11.2

src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export const endpoint = `${localhost}${apiURL}`;
77
export const productListURL = `${endpoint}/product-list/`;
88
export const addToCartURL = `${endpoint}/add-to-cart/`;
99
export const orderSummaryURL = `${endpoint}/order-summary/`;
10+
export const checkoutURL = `${endpoint}/checkout/`;

src/containers/Checkout.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { Component } from "react";
2+
import {
3+
CardElement,
4+
injectStripe,
5+
Elements,
6+
StripeProvider
7+
} from "react-stripe-elements";
8+
import { Button, Container, Message } from "semantic-ui-react";
9+
import { authAxios } from "../utils";
10+
import { checkoutURL } from "../constants";
11+
12+
class CheckoutForm extends Component {
13+
state = {
14+
loading: false,
15+
error: null,
16+
success: false
17+
};
18+
19+
submit = ev => {
20+
ev.preventDefault();
21+
this.setState({ loading: true });
22+
if (this.props.stripe) {
23+
this.props.stripe.createToken().then(result => {
24+
if (result.error) {
25+
this.setState({ error: result.error.message, loading: false });
26+
} else {
27+
authAxios
28+
.post(checkoutURL, { stripeToken: result.token.id })
29+
.then(res => {
30+
this.setState({ loading: false, success: true });
31+
// redirect the user
32+
})
33+
.catch(err => {
34+
this.setState({ loading: false, error: err });
35+
});
36+
}
37+
});
38+
} else {
39+
console.log("Stripe is not loaded");
40+
}
41+
};
42+
43+
render() {
44+
const { error, loading, success } = this.state;
45+
return (
46+
<div>
47+
{error && (
48+
<Message negative>
49+
<Message.Header>Your payment was unsuccessful</Message.Header>
50+
<p>{JSON.stringify(error)}</p>
51+
</Message>
52+
)}
53+
{success && (
54+
<Message positive>
55+
<Message.Header>Your payment was successful</Message.Header>
56+
<p>
57+
Go to your <b>profile</b> to see the order delivery status.
58+
</p>
59+
</Message>
60+
)}
61+
<p>Would you like to complete the purchase?</p>
62+
<CardElement />
63+
<Button
64+
loading={loading}
65+
disabled={loading}
66+
primary
67+
onClick={this.submit}
68+
style={{ marginTop: "10px" }}
69+
>
70+
Submit
71+
</Button>
72+
</div>
73+
);
74+
}
75+
}
76+
77+
const InjectedForm = injectStripe(CheckoutForm);
78+
79+
const WrappedForm = () => (
80+
<Container text>
81+
<StripeProvider apiKey="">
82+
<div>
83+
<h1>Complete your order</h1>
84+
<Elements>
85+
<InjectedForm />
86+
</Elements>
87+
</div>
88+
</StripeProvider>
89+
</Container>
90+
);
91+
92+
export default WrappedForm;

src/containers/OrderSummary.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Message,
1212
Segment
1313
} from "semantic-ui-react";
14+
import { Link } from "react-router-dom";
1415
import { authAxios } from "../utils";
1516
import { orderSummaryURL } from "../constants";
1617

@@ -103,9 +104,11 @@ class OrderSummary extends React.Component {
103104
<Table.Footer>
104105
<Table.Row>
105106
<Table.HeaderCell colSpan="5">
106-
<Button floated="right" color="yellow">
107-
Checkout
108-
</Button>
107+
<Link to="/checkout">
108+
<Button floated="right" color="yellow">
109+
Checkout
110+
</Button>
111+
</Link>
109112
</Table.HeaderCell>
110113
</Table.Row>
111114
</Table.Footer>

src/routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import Signup from "./containers/Signup";
77
import HomepageLayout from "./containers/Home";
88
import ProductList from "./containers/ProductList";
99
import OrderSummary from "./containers/OrderSummary";
10+
import Checkout from "./containers/Checkout";
1011

1112
const BaseRouter = () => (
1213
<Hoc>
1314
<Route path="/products" component={ProductList} />
1415
<Route path="/login" component={Login} />
1516
<Route path="/signup" component={Signup} />
1617
<Route path="/order-summary" component={OrderSummary} />
18+
<Route path="/checkout" component={Checkout} />
1719
<Route exact path="/" component={HomepageLayout} />
1820
</Hoc>
1921
);

0 commit comments

Comments
 (0)