Created
December 20, 2021 23:26
-
-
Save nohehf/ebb3a64d195705bf057dddf612095dd9 to your computer and use it in GitHub Desktop.
Implementation of the stripe custom checkout in a vue component (see: https://stripe.com/docs/payments/quickstart)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<form id="payment-form" @submit.prevent="handleSubmit"> | |
<div id="payment-element"> | |
<!--Stripe.js injects the Payment Element--> | |
</div> | |
<button id="submit" v-if="!(isLoading)"> | |
<div class="spinner hidden" id="spinner"></div> | |
<span id="button-text">Pay now</span> | |
</button> | |
<div id="payment-message" class="hidden"></div> | |
</form> | |
</template> | |
<script> | |
import {loadStripe} from '@stripe/stripe-js/pure'; | |
export default { | |
data() { | |
return { | |
elements: null, | |
stripe: null, | |
items: [{ id: "xl-tshirt" }], | |
} | |
}, | |
methods: { | |
handleSubmit: async function(e) { | |
e.preventDefault(); | |
this.setLoading(true); | |
const elements = this.elements | |
const { error } = await this.stripe.confirmPayment({ | |
elements, | |
confirmParams: { | |
// Make sure to change this to your payment completion page | |
return_url: "http://localhost:3000/success", | |
}, | |
}); | |
// This point will only be reached if there is an immediate error when | |
// confirming the payment. Otherwise, your customer will be redirected to | |
// your `return_url`. For some payment methods like iDEAL, your customer will | |
// be redirected to an intermediate site first to authorize the payment, then | |
// redirected to the `return_url`. | |
if (error.type === "card_error" || error.type === "validation_error") { | |
this.showMessage(error.message); | |
} else { | |
this.showMessage("An unexpected error occured."); | |
} | |
this.setLoading(false); | |
this.isLoading = false; | |
}, | |
showMessage: function (messageText) { | |
const messageContainer = document.querySelector("#payment-message"); | |
messageContainer.classList.remove("hidden"); | |
messageContainer.textContent = messageText; | |
setTimeout(function () { | |
messageContainer.classList.add("hidden"); | |
messageText.textContent = ""; | |
}, 4000); | |
}, | |
setLoading: function(isLoading) { | |
if (isLoading) { | |
// Disable the button and show a spinner | |
document.querySelector("#submit").disabled = true; | |
document.querySelector("#spinner").classList.remove("hidden"); | |
document.querySelector("#button-text").classList.add("hidden"); | |
} else { | |
document.querySelector("#submit").disabled = false; | |
document.querySelector("#spinner").classList.add("hidden"); | |
document.querySelector("#button-text").classList.remove("hidden"); | |
} | |
} | |
}, | |
async mounted () { | |
const stripe = await loadStripe("YOUR_TEST_PUBLIC_TOKEN") | |
this.stripe = stripe | |
initStripe.bind(this)(); | |
checkStatus.bind(this)(); | |
async function initStripe() { | |
const items = this.items | |
const response = await fetch("/create-payment-intent", { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ items }), | |
}); | |
const { clientSecret } = await response.json(); | |
const appearance = { | |
theme: 'stripe', | |
}; | |
this.elements = stripe.elements({ appearance, clientSecret }); | |
const paymentElement = this.elements.create("payment"); | |
paymentElement.mount("#payment-element"); | |
} | |
async function checkStatus() { | |
const clientSecret = new URLSearchParams(window.location.search).get( | |
"payment_intent_client_secret" | |
); | |
if (!clientSecret) { | |
return; | |
} | |
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret); | |
switch (paymentIntent.status) { | |
case "succeeded": | |
this.showMessage("Payment succeeded!"); | |
break; | |
case "processing": | |
this.showMessage("Your payment is processing."); | |
break; | |
case "requires_payment_method": | |
this.showMessage("Your payment was not successful, please try again."); | |
break; | |
default: | |
this.showMessage("Something went wrong."); | |
break; | |
} | |
} | |
// ------- UI helpers ------- | |
}, | |
} | |
</script> | |
<style scoped> | |
/* Variables */ | |
* { | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, sans-serif; | |
font-size: 16px; | |
-webkit-font-smoothing: antialiased; | |
display: flex; | |
justify-content: center; | |
align-content: center; | |
height: 100vh; | |
width: 100vw; | |
} | |
form { | |
width: 30vw; | |
min-width: 500px; | |
align-self: center; | |
box-shadow: 0px 0px 0px 0.5px rgba(50, 50, 93, 0.1), | |
0px 2px 5px 0px rgba(50, 50, 93, 0.1), 0px 1px 1.5px 0px rgba(0, 0, 0, 0.07); | |
border-radius: 7px; | |
padding: 40px; | |
} | |
.hidden { | |
display: none; | |
} | |
#payment-message { | |
color: rgb(105, 115, 134); | |
font-size: 16px; | |
line-height: 20px; | |
padding-top: 12px; | |
text-align: center; | |
} | |
#payment-element { | |
margin-bottom: 24px; | |
} | |
/* Buttons and links */ | |
button { | |
background: #5469d4; | |
font-family: Arial, sans-serif; | |
color: #ffffff; | |
border-radius: 4px; | |
border: 0; | |
padding: 12px 16px; | |
font-size: 16px; | |
font-weight: 600; | |
cursor: pointer; | |
display: block; | |
transition: all 0.2s ease; | |
box-shadow: 0px 4px 5.5px 0px rgba(0, 0, 0, 0.07); | |
width: 100%; | |
} | |
button:hover { | |
filter: contrast(115%); | |
} | |
button:disabled { | |
opacity: 0.5; | |
cursor: default; | |
} | |
/* spinner/processing state, errors */ | |
.spinner, | |
.spinner:before, | |
.spinner:after { | |
border-radius: 50%; | |
} | |
.spinner { | |
color: #ffffff; | |
font-size: 22px; | |
text-indent: -99999px; | |
margin: 0px auto; | |
position: relative; | |
width: 20px; | |
height: 20px; | |
box-shadow: inset 0 0 0 2px; | |
-webkit-transform: translateZ(0); | |
-ms-transform: translateZ(0); | |
transform: translateZ(0); | |
} | |
.spinner:before, | |
.spinner:after { | |
position: absolute; | |
content: ""; | |
} | |
.spinner:before { | |
width: 10.4px; | |
height: 20.4px; | |
background: #5469d4; | |
border-radius: 20.4px 0 0 20.4px; | |
top: -0.2px; | |
left: -0.2px; | |
-webkit-transform-origin: 10.4px 10.2px; | |
transform-origin: 10.4px 10.2px; | |
-webkit-animation: loading 2s infinite ease 1.5s; | |
animation: loading 2s infinite ease 1.5s; | |
} | |
.spinner:after { | |
width: 10.4px; | |
height: 10.2px; | |
background: #5469d4; | |
border-radius: 0 10.2px 10.2px 0; | |
top: -0.1px; | |
left: 10.2px; | |
-webkit-transform-origin: 0px 10.2px; | |
transform-origin: 0px 10.2px; | |
-webkit-animation: loading 2s infinite ease; | |
animation: loading 2s infinite ease; | |
} | |
@-webkit-keyframes loading { | |
0% { | |
-webkit-transform: rotate(0deg); | |
transform: rotate(0deg); | |
} | |
100% { | |
-webkit-transform: rotate(360deg); | |
transform: rotate(360deg); | |
} | |
} | |
@keyframes loading { | |
0% { | |
-webkit-transform: rotate(0deg); | |
transform: rotate(0deg); | |
} | |
100% { | |
-webkit-transform: rotate(360deg); | |
transform: rotate(360deg); | |
} | |
} | |
@media only screen and (max-width: 600px) { | |
form { | |
width: 80vw; | |
min-width: initial; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment