Skip to content

Instantly share code, notes, and snippets.

@tk42
Last active August 2, 2022 21:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tk42/bb408d68eaf92c091db7241f178795c7 to your computer and use it in GitHub Desktop.
Save tk42/bb408d68eaf92c091db7241f178795c7 to your computer and use it in GitHub Desktop.
Stripe Custom Payment flow Example by tsx in Nextjs
// (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
}} />
}
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