· 5 years ago · May 08, 2020, 04:28 AM
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="utf-8" />
6 <title>CHECKOUT</title>
7 <meta name="description" content="A demo of a card payment on Stripe" />
8 <meta name="viewport" content="width=device-width, initial-scale=1" />
9 <link rel="stylesheet" href="global.css" />
10 <script src="https://js.stripe.com/v3/"></script>
11 <script>
12 // A reference to Stripe.js initialized with your real test publishable API key.
13 var stripe = Stripe("xxxxx");
14 // The items the customer wants to buy
15 var purchase = {
16 items: [{
17 id: "xl-tshirt"
18 }]
19 };
20
21 // Calls stripe.confirmCardPayment
22 // If the card requires authentication Stripe shows a pop-up modal to
23 // prompt the user to enter authentication details without leaving your page.
24 var payWithCard = function(stripe, card, clientSecret) {
25 loading(true);
26 stripe
27 .confirmCardPayment(clientSecret, {
28 payment_method: {
29 card: card
30 }
31 })
32 .then(function(result) {
33 if (result.error) {
34 // Show error to your customer
35 showError(result.error.message);
36 } else {
37 // The payment succeeded!
38 orderComplete(result.paymentIntent.id);
39 }
40 });
41 };
42 /* ------- UI helpers ------- */
43 // Shows a success message when the payment is complete
44 var orderComplete = function(paymentIntentId) {
45 loading(false);
46 document
47 .querySelector(".result-message a")
48 .setAttribute(
49 "href",
50 "https://dashboard.stripe.com/test/payments/" + paymentIntentId
51 );
52 document.querySelector(".result-message").classList.remove("hidden");
53 document.querySelector("button").disabled = true;
54 };
55 // Show the customer the error from Stripe if their card fails to charge
56 var showError = function(errorMsgText) {
57 loading(false);
58 var errorMsg = document.querySelector("#card-errors");
59 errorMsg.textContent = errorMsgText;
60 setTimeout(function() {
61 errorMsg.textContent = "";
62 }, 4000);
63 };
64 // Show a spinner on payment submission
65 var loading = function(isLoading) {
66 if (isLoading) {
67 // Disable the button and show a spinner
68 document.querySelector("button").disabled = true;
69 document.querySelector("#spinner").classList.remove("hidden");
70 document.querySelector("#button-text").classList.add("hidden");
71 } else {
72 document.querySelector("button").disabled = false;
73 document.querySelector("#spinner").classList.add("hidden");
74 document.querySelector("#button-text").classList.remove("hidden");
75 }
76 };
77 </script>
78 <style>
79 /* Variables */
80 * {
81 box-sizing: border-box;
82 }
83
84 body {
85 font-family: -apple-system, BlinkMacSystemFont, sans-serif;
86 font-size: 16px;
87 -webkit-font-smoothing: antialiased;
88 display: flex;
89 justify-content: center;
90 align-content: center;
91 height: 100vh;
92 width: 100vw;
93 }
94
95 form {
96 width: 30vw;
97 min-width: 500px;
98 align-self: center;
99 box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1),
100 0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07);
101 border-radius: 7px;
102 padding: 40px;
103 }
104
105 input {
106 border-radius: 6px;
107 margin-bottom: 6px;
108 padding: 12px;
109 border: 1px solid rgba(50, 50, 93, 0.1);
110 height: 44px;
111 font-size: 16px;
112 width: 100%;
113 background: white;
114 }
115
116 .result-message {
117 line-height: 22px;
118 font-size: 16px;
119 }
120
121 .result-message a {
122 color: rgb(89, 111, 214);
123 font-weight: 600;
124 text-decoration: none;
125 }
126
127 .hidden {
128 display: none;
129 }
130
131 .card-error {
132 color: rgb(105, 115, 134);
133 text-align: left;
134 font-size: 13px;
135 line-height: 17px;
136 margin-top: 12px;
137 }
138
139 #card-element {
140 border-radius: 4px 4px 0 0;
141 padding: 12px;
142 border: 1px solid rgba(50, 50, 93, 0.1);
143 height: 44px;
144 width: 100%;
145 background: white;
146 }
147
148 #payment-request-button {
149 margin-bottom: 32px;
150 }
151
152 /* Buttons and links */
153 button {
154 background: #5469d4;
155 color: #ffffff;
156 font-family: Arial, sans-serif;
157 border-radius: 0 0 4px 4px;
158 border: 0;
159 padding: 12px 16px;
160 font-size: 16px;
161 font-weight: 600;
162 cursor: pointer;
163 display: block;
164 transition: all 0.2s ease;
165 box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07);
166 width: 100%;
167 }
168
169 button:hover {
170 filter: contrast(115%);
171 }
172
173 button:disabled {
174 opacity: 0.5;
175 cursor: default;
176 }
177
178 /* spinner/processing state, errors */
179 .spinner,
180 .spinner:before,
181 .spinner:after {
182 border-radius: 50%;
183 }
184
185 .spinner {
186 color: #ffffff;
187 font-size: 22px;
188 text-indent: -99999px;
189 margin: 0px auto;
190 position: relative;
191 width: 20px;
192 height: 20px;
193 box-shadow: inset 0 0 0 2px;
194 -webkit-transform: translateZ(0);
195 -ms-transform: translateZ(0);
196 transform: translateZ(0);
197 }
198
199 .spinner:before,
200 .spinner:after {
201 position: absolute;
202 content: "";
203 }
204
205 .spinner:before {
206 width: 10.4px;
207 height: 20.4px;
208 background: #5469d4;
209 border-radius: 20.4px 0 0 20.4px;
210 top: -0.2px;
211 left: -0.2px;
212 -webkit-transform-origin: 10.4px 10.2px;
213 transform-origin: 10.4px 10.2px;
214 -webkit-animation: loading 2s infinite ease 1.5s;
215 animation: loading 2s infinite ease 1.5s;
216 }
217
218 .spinner:after {
219 width: 10.4px;
220 height: 10.2px;
221 background: #5469d4;
222 border-radius: 0 10.2px 10.2px 0;
223 top: -0.1px;
224 left: 10.2px;
225 -webkit-transform-origin: 0px 10.2px;
226 transform-origin: 0px 10.2px;
227 -webkit-animation: loading 2s infinite ease;
228 animation: loading 2s infinite ease;
229 }
230
231 @-webkit-keyframes loading {
232 0% {
233 -webkit-transform: rotate(0deg);
234 transform: rotate(0deg);
235 }
236
237 100% {
238 -webkit-transform: rotate(360deg);
239 transform: rotate(360deg);
240 }
241 }
242
243 @keyframes loading {
244 0% {
245 -webkit-transform: rotate(0deg);
246 transform: rotate(0deg);
247 }
248
249 100% {
250 -webkit-transform: rotate(360deg);
251 transform: rotate(360deg);
252 }
253 }
254
255 @media only screen and (max-width: 600px) {
256 form {
257 width: 80vw;
258 }
259 }
260 </style>
261</head>
262
263<body>
264 <!-- Display a payment form -->
265 <form id="payment-form">
266 <div id="card-element"></div>
267 <button id="submit">
268 <div class="spinner hidden" id="spinner"></div>
269 <span id="button-text">Pay</span>
270 </button>
271 <p id="card-errors" role="alert"></p>
272 <p class="result-message hidden">
273 Payment succeeded, see the result in your
274 <a href="" target="_blank">Stripe dashboard.</a> Refresh the page to pay again.
275 </p>
276 </form>
277</body>
278
279<script>
280
281 // Disable the button until we have Stripe set up on the page
282 document.querySelector("button").disabled = true;
283 fetch("/payment_intent", {
284 method: "POST",
285 headers: {
286 "Content-Type": "application/json"
287 },
288 body: JSON.stringify(purchase)
289 })
290 .then(function(result) {
291 return result.json();
292 })
293 .then(function(data) {
294 var elements = stripe.elements();
295 var style = {
296 base: {
297 color: "#32325d",
298 fontFamily: 'Arial, sans-serif',
299 fontSmoothing: "antialiased",
300 fontSize: "16px",
301 "::placeholder": {
302 color: "#32325d"
303 }
304 },
305 invalid: {
306 fontFamily: 'Arial, sans-serif',
307 color: "#fa755a",
308 iconColor: "#fa755a"
309 }
310 };
311 var card = elements.create("card", {
312 style: style
313 });
314 // Stripe injects an iframe into the DOM
315 card.mount("#card-element");
316 card.on("change", function(event) {
317 // Disable the Pay button if there are no card details in the Element
318 document.querySelector("button").disabled = event.empty;
319 document.querySelector("#card-errors").textContent = event.error ? event.error.message : "";
320 });
321 var form = document.getElementById("payment-form");
322 form.addEventListener("submit", function(event) {
323 event.preventDefault();
324 // Complete payment when the submit button is clicked
325 payWithCard(stripe, card, data.clientSecret);
326 });
327 });
328</script>
329
330</html>