Last active
August 2, 2022 21:44
-
-
Save tk42/bb408d68eaf92c091db7241f178795c7 to your computer and use it in GitHub Desktop.
Stripe Custom Payment flow Example by tsx in Nextjs
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
// (1) import Layer | |
import React from "react"; | |
import { Stripe, StripeElements, PaymentIntentResult } from '@stripe/stripe-js'; | |
import { PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js"; | |
import styled from 'styled-components' | |
import { useTheme } from 'next-themes' | |
// (2) Types Layer | |
export type ContainerProps = { | |
} | |
type Props = { | |
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void | |
isLoading: boolean | |
stripe: Stripe | null | |
elements: StripeElements | null | |
message: string | undefined | |
} & ContainerProps | |
// (3) DOM Layer | |
const Component: React.FC<Props> = props => ( | |
<> | |
<form id="payment-form" onSubmit={props.handleSubmit}> | |
<PaymentElement id="payment-element" /> | |
<button disabled={props.isLoading || !props.stripe || !props.elements} id="submit"> | |
<span id="button-text"> | |
{props.isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"} | |
</span> | |
</button> | |
{/* Show any error or success messages */} | |
{props.message && <div id="payment-message">{props.message}</div>} | |
</form> | |
</> | |
) | |
// (4) Style Layer | |
const StyledComponent = styled(Component)(({ theme }) => ({ | |
textEmphasisColor: (useTheme().resolvedTheme! === 'dark') ? 'white' : 'black' | |
})) | |
// (5) Container Layer | |
export const Container: React.FC<ContainerProps> = props => { | |
const stripe = useStripe(); | |
const elements = useElements(); | |
const [message, setMessage] = React.useState<string | undefined>(undefined); | |
const [isLoading, setIsLoading] = React.useState(false); | |
React.useEffect(() => { | |
if (!stripe) { | |
return; | |
} | |
const clientSecret = new URLSearchParams(window.location.search).get( | |
"payment_intent_client_secret" | |
); | |
if (!clientSecret) { | |
return; | |
} | |
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }: PaymentIntentResult) => { | |
switch (paymentIntent!.status) { | |
case "succeeded": | |
setMessage("Payment succeeded!"); | |
break; | |
case "processing": | |
setMessage("Your payment is processing."); | |
break; | |
case "requires_payment_method": | |
setMessage("Your payment was not successful, please try again."); | |
break; | |
default: | |
setMessage("Something went wrong."); | |
break; | |
} | |
}); | |
}, [stripe]); | |
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => { | |
e.preventDefault(); | |
if (!stripe || !elements) { | |
// Stripe.js has not yet loaded. | |
// Make sure to disable form submission until Stripe.js has loaded. | |
return; | |
} | |
setIsLoading(true); | |
const { error } = await stripe.confirmPayment({ | |
elements, | |
confirmParams: { | |
// Make sure to change this to your payment completion page | |
return_url: "http://localhost:3000", | |
}, | |
}); | |
// 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") { | |
setMessage(error.message); | |
} else { | |
setMessage("An unexpected error occurred."); | |
} | |
setIsLoading(false); | |
}; | |
return <StyledComponent {...{ | |
handleSubmit, | |
isLoading, | |
stripe, | |
elements, | |
message, | |
...props | |
}} /> | |
} |
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
import { NextApiRequest, NextApiResponse } from 'next'; | |
// This is your test secret API key. | |
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); | |
const calculateOrderAmount = (items: any) => { | |
// Replace this constant with a calculation of the order's amount | |
// Calculate the order total on the server to prevent | |
// people from directly manipulating the amount on the client | |
return 5; | |
}; | |
export default async function handler( | |
req: NextApiRequest, | |
res: NextApiResponse | |
) { | |
const { items } = req.body; | |
// Create a PaymentIntent with the order amount and currency | |
const paymentIntent = await stripe.paymentIntents.create({ | |
amount: calculateOrderAmount(items), | |
currency: "usd", | |
automatic_payment_methods: { | |
enabled: true, | |
}, | |
}); | |
res.send({ | |
clientSecret: paymentIntent.client_secret, | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment