Skip to content

Instantly share code, notes, and snippets.

@roni5
Forked from pasindu1/gist:afd415430378bb68fc29a341d61262fd
Last active June 10, 2024 12:11
Show Gist options
  • Select an option

  • Save roni5/d7e52d552df7e291db5f4ad72fa17aa3 to your computer and use it in GitHub Desktop.

Select an option

Save roni5/d7e52d552df7e291db5f4ad72fa17aa3 to your computer and use it in GitHub Desktop.
folder-structure
import { useState } from "react";
import { useFormState } from "react-dom";
import { addToCart } from "./actions.js";
function AddToCartForm({ itemID, itemTitle }) {
const [message, formAction] = useFormState(addToCart, null);
return (
<form action={formAction}>
<h2>{itemTitle}</h2>
<input type="hidden" name="itemID" value={itemID} />
<button type="submit">Add to Cart</button>
{message}
</form>
);
}
export default function App() {
return (
<>
<AddToCartForm itemID="1" itemTitle="JavaScript: The Definitive Guide" />
<AddToCartForm itemID="2" itemTitle="JavaScript: The Good Parts" />
</>
);
}
action
// components/CheckoutForm.tsx
import React, { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import axios from 'axios';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string);
const CheckoutForm: React.FC = () => {
const [input, setInput] = useState<{ cardholderName: string }>({
cardholderName: '',
});
const [clientSecret, setClientSecret] = useState<string | null>(null);
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const { data } = await axios.post('/api/create-payment-intent', {
items: [
{ priceId: 'price_1Hh1Ya2eZvKYlo2CYz6LFqx2', quantity: 1 }, // Example price ID from Stripe
],
});
setClientSecret(data.clientSecret);
if (clientSecret) {
const cardElement = elements.getElement(CardElement);
if (cardElement) {
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: input.cardholderName,
},
},
});
if (error) {
console.error(error);
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
console.log('Payment succeeded!');
}
}
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Cardholder Name
<input
type="text"
value={input.cardholderName}
onChange={(e) => setInput({ ...input, cardholderName: e.target.value })}
/>
</label>
<CardElement />
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
const WrappedCheckoutForm: React.FC = () => (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
export default WrappedCheckoutForm;
Backend Endpoint: The /api/create-payment-intent endpoint calculates the total amount based on product prices retrieved from Stripe and creates a payment intent.
Price IDs: Use Stripe price IDs (priceId) to retrieve the unit amount for each product item.
Calculate Total Amount: Sum the unit amounts multiplied by their respective quantities to get the total amount.
// pages/api/create-payment-intent.ts
import { NextApiRequest, NextApiResponse } from 'next';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: '2023-08-16',
});
type CartItem = {
priceId: string;
quantity: number;
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'POST') {
try {
const { items }: { items: CartItem[] } = req.body;
// Fetch product prices from Stripe
const prices = await Promise.all(
items.map(async (item) => {
const price = await stripe.prices.retrieve(item.priceId);
return {
unit_amount: price.unit_amount,
quantity: item.quantity,
};
})
);
const amount = prices.reduce(
(total, price) => total + (price.unit_amount ?? 0) * price.quantity,
0
);
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
automatic_payment_methods: { enabled: true },
});
res.status(200).send({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
console.error(error);
res.status(500).send({ error: 'Failed to create payment intent' });
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
"use client";
import type { StripeError } from "@stripe/stripe-js";
import * as React from "react";
import {
useStripe,
useElements,
PaymentElement,
Elements,
} from "@stripe/react-stripe-js";
import StripeTestCards from "./StripeTestCards";
import { formatAmountForDisplay } from "@/utils/stripe-helpers";
import * as config from "@/config";
import getStripe from "@/utils/get-stripejs";
import { createPaymentIntent } from "@/actions/cart/stripe";
type Props = {
productPrices: {
productId: string;
priceId: string;
productName: string;
unit_amount: number | null;
currency: string | null;
}[];
};
function CheckoutForm5({ productPrices }: Props) {
const [selectedPriceId, setSelectedPriceId] = React.useState<string | null>(
productPrices.length > 0 ? productPrices[0].priceId : null
);
const [input, setInput] = React.useState<{
amount: number;
cardholderName: string;
quantity: number;
}>({
amount:
productPrices.find((p) => p.priceId === selectedPriceId)?.unit_amount || 0,
cardholderName: "",
quantity: 1,
});
const [paymentType, setPaymentType] = React.useState<string>("");
const [payment, setPayment] = React.useState<{
status: "initial" | "processing" | "error";
}>({ status: "initial" });
const [errorMessage, setErrorMessage] = React.useState<string>("");
const stripe = useStripe();
const elements = useElements();
const PaymentStatus = ({ status }: { status: string }) => {
switch (status) {
case "processing":
case "requires_payment_method":
case "requires_confirmation":
return <h2>Processing...</h2>;
case "requires_action":
return <h2>Authenticating...</h2>;
case "succeeded":
return <h2>Payment Succeeded 🎉</h2>;
case "error":
return (
<>
<h2>Error ❌</h2>
<p className="error-message">{errorMessage}</p>
</>
);
default:
return null;
}
};
const handleInputChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement
> = (e) => {
if (e.target.name === "priceId") {
const price = productPrices.find((p) => p.priceId === e.target.value);
setSelectedPriceId(e.target.value);
if (price?.unit_amount) {
setInput({ ...input, amount: price.unit_amount * input.quantity });
elements?.update({
amount: price.unit_amount * input.quantity,
});
}
} else {
setInput({
...input,
[e.target.name]: e.target.value,
});
if (e.target.name === "quantity" && selectedPriceId) {
const price = productPrices.find((p) => p.priceId === selectedPriceId);
if (price?.unit_amount) {
elements?.update({
amount: price.unit_amount * Number(e.target.value),
});
}
}
}
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
try {
e.preventDefault();
// Abort if form isn't valid
if (!e.currentTarget.reportValidity()) return;
if (!elements || !stripe) return;
setPayment({ status: "processing" });
const { error: submitError } = await elements.submit();
if (submitError) {
setPayment({ status: "error" });
setErrorMessage(submitError.message ?? "An unknown error occurred");
return;
}
const formData = new FormData(e.target as HTMLFormElement);
formData.append(
"amount",
(
(productPrices.find((p) => p.priceId === selectedPriceId)
?.unit_amount || 0) * input.quantity
).toString()
);
formData.append("priceId", selectedPriceId || "");
const { client_secret: clientSecret } = await createPaymentIntent(
formData
);
const { error: confirmError } = await stripe!.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/front`,
payment_method_data: {
billing_details: {
name: input.cardholderName,
},
},
},
});
if (confirmError) {
setPayment({ status: "error" });
setErrorMessage(confirmError.message ?? "An unknown error occurred");
}
} catch (err) {
const { message } = err as StripeError;
setPayment({ status: "error" });
setErrorMessage(message ?? "An unknown error occurred");
}
};
return (
<>
<form onSubmit={handleSubmit}>
<StripeTestCards />
<fieldset className="elements-style">
<legend>Your payment details:</legend>
{paymentType === "card" ? (
<input
placeholder="Cardholder name"
className="elements-style"
type="Text"
name="cardholderName"
onChange={handleInputChange}
required
/>
) : null}
<div className="FormRow elements-style">
<PaymentElement
onChange={(e) => {
setPaymentType(e.value.type);
}}
/>
</div>
<label htmlFor="priceId">Amount:</label>
<select
id="priceId"
name="priceId"
value={selectedPriceId || ""}
onChange={handleInputChange}
className="elements-style"
>
{productPrices.map((price) => (
<option key={price.priceId} value={price.priceId}>
{price.productName} -{" "}
{formatAmountForDisplay(
price.unit_amount || 0,
price.currency || config.CURRENCY
)}
</option>
))}
</select>
</fieldset>
<label>
Quantity:
<input
type="number"
name="quantity"
className="elements-style"
value={input.quantity}
onChange={handleInputChange}
min="1"
/>
</label>
<button
className="elements-style-background"
type="submit"
disabled={
!["initial", "succeeded", "error"].includes(payment.status) ||
!stripe ||
!selectedPriceId
}
>
Pay Now
</button>
</form>
<PaymentStatus status={payment.status} />
</>
);
}
export default function ElementsForm({
productPrices,
}: {
productPrices: Props["productPrices"];
}): JSX.Element {
return (
<Elements
stripe={getStripe()}
options={{
appearance: {
variables: {
colorIcon: "#5469d4",
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
},
},
currency: config.CURRENCY,
mode: "payment",
amount:
productPrices.length > 0
? productPrices[0].unit_amount || 1400 * 100 // Provide a default if no product prices are available
: 1400 * 100,
}}
>
<CheckoutForm5 productPrices={productPrices} />
</Elements>
);
}
"use client";
import type { StripeError } from "@stripe/stripe-js";
import * as React from "react";
import { useState, useEffect } from 'react';
import {
useStripe,
useElements,
CardElement,
PaymentElement,
Elements,
} from "@stripe/react-stripe-js";
//import CustomDonationInput from "./CustomDonationInput";
import StripeTestCards from "./StripeTestCards";
import { formatAmountForDisplay } from "@/utils/stripe-helpers";
import * as config from "@/config";
import getStripe from "@/utils/get-stripejs";
import { createPaymentIntent } from "@/actions/cart/stripe";
// import { loadStripe } from '@stripe/stripe-js';
// const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string);
type ProductPrice = {
productId: string;
priceId: string;
productName: string;
unit_amount: number;
currency: string;
quantity: number | 1;
};
function CheckoutForm3(): JSX.Element {
const [paymentType, setPaymentType] = React.useState<string>("");
const [payment, setPayment] = React.useState<{
status: "initial" | "processing" | "error";
}>({ status: "initial" });
const [errorMessage, setErrorMessage] = React.useState<string>("");
const [products, setProducts] = useState<ProductPrice[]>([]);
const [selectedProduct, setSelectedProduct] = useState<string | null>(null);
const [inputCard, setInputCard] = useState<{ cardholderName: string }>({ cardholderName: '' });
const [input, setInput] = React.useState<{
cardholderName: string
quantity: number;}>({
cardholderName: '',
quantity: 1,
});
const [quantity, setQuantity] = useState<number>(1);
const [clientSecret, setClientSecret] = useState<string | null>(null);
const stripe = useStripe();
const elements = useElements();
const PaymentStatus = ({ status }: { status: string }) => {
switch (status) {
case "processing":
case "requires_payment_method":
case "requires_confirmation":
return <h2>Processing...</h2>;
case "requires_action":
return <h2>Authenticating...</h2>;
case "succeeded":
return <h2>Payment Succeeded 🎉</h2>;
case "error":
return (
<>
<h2>Error ❌</h2>
<p className="error-message">{errorMessage}</p>
</>
);
default:
return null;
}
};
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setInput({
...input,
[e.currentTarget.name]: e.currentTarget.value,
});
//elements?.update({ amount: 1400 * 100 });
};
useEffect(() => {
// Fetch product prices from the backend
const fetchProductPrices = async () => {
try {
const response = await fetch('/api/get-product-prices');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setProducts(data);
} catch (error) {
console.error('Error fetching product prices:', error);
}
};
fetchProductPrices();
}, []);
// const handleSubmit = async (event: React.FormEvent) => {
// event.preventDefault();
// if (!stripe || !elements || !selectedProduct) {
// return;
// }
// const product = products.find(product => product.productId === selectedProduct);
// if (!product) {
// console.error('Selected product not found');
// return;
// }
// try {
// const response = await fetch('/api/create-payment-intent', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// items: [{ priceId: product.priceId, quantity }],
// }),
// });
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// const data = await response.json();
// setClientSecret(data.clientSecret);
// if (clientSecret) {
// const cardElement = elements.getElement(CardElement);
// if (cardElement) {
// const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
// payment_method: {
// card: cardElement,
// billing_details: {
// name: input.cardholderName,
// },
// },
// });
// if (error) {
// console.error(error);
// } else if (paymentIntent && paymentIntent.status === 'succeeded') {
// console.log('Payment succeeded!');
// }
// }
// }
// } catch (error) {
// console.error('Error:', error);
// }
// };
// const handleSubmit = async (event: React.FormEvent) => {
// event.preventDefault();
// if (!stripe || !elements || !selectedProduct) {
// return;
// }
// const product = products.find(product => product.productId === selectedProduct);
// if (!product) {
// console.error('Selected product not found');
// return;
// }
// try {
// // Create a FormData object
// const formData = new FormData();
// formData.append('priceId', product.priceId);
// formData.append('quantity', input.quantity.toString());
// // Call createPaymentIntent with the FormData object
// const { client_secret: clientSecret } = await createPaymentIntent(formData);
// if (!clientSecret) {
// console.error('Failed to retrieve client secret for payment');
// return;
// }
// const cardElement = elements.getElement(CardElement);
// if (!cardElement) {
// console.error('Card element not found');
// return;
// }
// const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
// payment_method: {
// card: cardElement,
// billing_details: {
// name: input.cardholderName,
// },
// },
// });
// if (error) {
// console.error('Error confirming card payment:', error);
// } else if (paymentIntent && paymentIntent.status === 'succeeded') {
// console.log('Payment succeeded!');
// }
// } catch (error) {
// console.error('Error processing payment:', error);
// }
// };
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements || !selectedProduct) {
return;
}
const product = products.find(product => product.productId === selectedProduct);
if (!product) {
console.error('Selected product not found');
return;
}
try {
// Calculate the total amount based on the selected product and quantity
const totalAmount = product.unit_amount * input.quantity;
// Create a FormData object
const formData = new FormData();
formData.append('priceId', product.priceId);
formData.append('quantity', input.quantity.toString());
formData.append('amount', totalAmount.toString()); // Pass the total amount to the backend
// Call createPaymentIntent with the FormData object
const { client_secret: clientSecret } = await createPaymentIntent(formData);
if (!clientSecret) {
console.error('Failed to retrieve client secret for payment');
return;
}
const cardElement = elements.getElement(CardElement);
if (!cardElement) {
console.error('Card element not found');
return;
}
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: input.cardholderName,
},
},
});
if (error) {
console.error('Error confirming card payment:', error);
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
console.log('Payment succeeded!');
}
} catch (error) {
console.error('Error processing payment:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Cardholder Name
<input
type="text"
value={input.cardholderName}
onChange={(e) => setInput({ ...input, cardholderName: e.target.value })}
/>
</label>
<label>
Select Product
<select onChange={(e) => setSelectedProduct(e.target.value)} value={selectedProduct || ''}>
<option value="" disabled>
Select a product
</option>
{products.map((product) => (
<option key={product.productId} value={product.productId}>
{product.productName} - {product.unit_amount / 100} {product.currency.toUpperCase()}
</option>
))}
</select>
</label>
<label>
Quantity:
<input
type="number"
name="quantity"
className="elements-style"
value={input.quantity}
onChange={handleInputChange}
min="1"
/>
</label>
<label>
Quantity
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(parseInt(e.target.value, 10))}
min="1"
/>
</label>
<CardElement />
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
export default function ElementsForm(): JSX.Element {
return (
<Elements
stripe={getStripe()}
options={{
appearance: {
variables: {
colorIcon: "#5469d4",
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
},
},
currency: config.CURRENCY,
mode: "payment",
}}
>
<CheckoutForm3 />
</Elements>
);
}
src/
|--app/
| |--layout.tsx
| |--page.tsx
| |--api/
| | |--route.ts
| | | |users/
| | | |--route.ts
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
"use client";
import type { StripeError } from "@stripe/stripe-js";
import * as React from "react";
import {
useStripe,
useElements,
PaymentElement,
Elements,
} from "@stripe/react-stripe-js";
import StripeTestCards from "./StripeTestCards";
import { formatAmountForDisplay } from "@/utils/stripe-helpers";
import * as config from "@/config";
import getStripe from "@/utils/get-stripejs";
import { createPaymentIntent } from "@/actions/cart/stripe";
function CheckoutForm(): JSX.Element {
const [input, setInput] = React.useState<{
amount: number;
cardholderName: string;
quantity: number;
productId: string;
}>({
amount: 1400,
cardholderName: "",
quantity: 1,
productId: "",
});
const [paymentType, setPaymentType] = React.useState<string>("");
const [payment, setPayment] = React.useState<{
status: "initial" | "processing" | "error";
}>({ status: "initial" });
const [errorMessage, setErrorMessage] = React.useState<string>("");
const stripe = useStripe();
const elements = useElements();
const PaymentStatus = ({ status }: { status: string }) => {
switch (status) {
case "processing":
case "requires_payment_method":
case "requires_confirmation":
return <h2>Processing...</h2>;
case "requires_action":
return <h2>Authenticating...</h2>;
case "succeeded":
return <h2>Payment Succeeded 🎉</h2>;
case "error":
return (
<>
<h2>Error ❌</h2>
<p className="error-message">{errorMessage}</p>
</>
);
default:
return null;
}
};
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setInput({
...input,
[e.currentTarget.name]: e.currentTarget.value,
});
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
try {
e.preventDefault();
if (!e.currentTarget.reportValidity()) return;
if (!elements || !stripe) return;
setPayment({ status: "processing" });
const { error: submitError } = await elements.submit();
if (submitError) {
setPayment({ status: "error" });
setErrorMessage(submitError.message ?? "An unknown error occurred");
return;
}
// Fetch product price from the API
const response = await fetch("/api/get-product-prices");
if (!response.ok) {
throw new Error("Failed to fetch product prices");
}
const productPrices = await response.json();
// Find the product price based on the selected product ID
const selectedProductPrice = productPrices.find(
(productPrice: any) => productPrice.productId === input.productId
);
if (!selectedProductPrice) {
throw new Error("Selected product price not found");
}
// Calculate the total amount based on the selected product price and quantity
const totalAmount = selectedProductPrice.price * input.quantity;
console.log("Total Amount:", totalAmount); // Debugging line
// Create a PaymentIntent with the specified amount
const formData = new FormData(e.target as HTMLFormElement);
formData.append("amount", totalAmount.toString());
const { client_secret: clientSecret } = await createPaymentIntent(formData);
console.log("Client Secret:", clientSecret); // Debugging line
// Use your card Element with other Stripe.js APIs
const { error: confirmError } = await stripe!.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/front`,
payment_method_data: {
billing_details: {
name: input.cardholderName,
},
},
},
});
if (confirmError) {
setPayment({ status: "error" });
setErrorMessage(confirmError.message ?? "An unknown error occurred");
} else {
setPayment({ status: "succeeded" });
}
} catch (err) {
const { message } = err as StripeError;
setPayment({ status: "error" });
setErrorMessage(message ?? "An unknown error occurred");
}
};
return (
<>
<form onSubmit={handleSubmit}>
<StripeTestCards />
<fieldset className="elements-style">
<legend>Your payment details:</legend>
{paymentType === "card" ? (
<input
placeholder="Cardholder name"
className="elements-style"
type="text"
name="cardholderName"
onChange={handleInputChange}
required
/>
) : null}
<div className="FormRow elements-style">
<PaymentElement
onChange={(e) => {
setPaymentType(e.value.type);
}}
/>
</div>
<input
type="hidden"
name="productId"
value={input.productId}
onChange={handleInputChange}
/>
</fieldset>
<label>
Quantity:
<input
type="number"
name="quantity"
className="elements-style"
value={input.quantity}
onChange={handleInputChange}
min="1"
/>
</label>
<button
className="elements-style-background"
type="submit"
disabled={
!["initial", "succeeded", "error"].includes(payment.status) ||
!stripe
}
>
Pay Now
</button>
</form>
<PaymentStatus status={payment.status} />
</>
);
}
export default function ElementsForm(): JSX.Element {
return (
<Elements
stripe={getStripe()}
options={{
appearance: {
variables: {
colorIcon: "#5469d4",
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
},
},
currency: config.CURRENCY,
mode: "payment",
}}
>
<CheckoutForm />
</Elements>
);
}
"use client";
import type { StripeError } from "@stripe/stripe-js";
import * as React from "react";
import {
useStripe,
useElements,
PaymentElement,
Elements,
} from "@stripe/react-stripe-js";
import StripeTestCards from "./StripeTestCards";
import { formatAmountForDisplay } from "@/utils/stripe-helpers";
import * as config from "@/config";
import getStripe from "@/utils/get-stripejs";
import { createPaymentIntent } from "@/actions/cart/stripe";
function CheckoutForm(): JSX.Element {
const [input, setInput] = React.useState<{
amount: number;
cardholderName: string;
quantity: number;
productId: string;
}>({
amount: 1400,
cardholderName: "",
quantity: 1,
productId: "",
});
const [paymentType, setPaymentType] = React.useState<string>("");
const [payment, setPayment] = React.useState<{
status: "initial" | "processing" | "error";
}>({ status: "initial" });
const [errorMessage, setErrorMessage] = React.useState<string>("");
const stripe = useStripe();
const elements = useElements();
const PaymentStatus = ({ status }: { status: string }) => {
switch (status) {
case "processing":
case "requires_payment_method":
case "requires_confirmation":
return <h2>Processing...</h2>;
case "requires_action":
return <h2>Authenticating...</h2>;
case "succeeded":
return <h2>Payment Succeeded 🎉</h2>;
case "error":
return (
<>
<h2>Error ❌</h2>
<p className="error-message">{errorMessage}</p>
</>
);
default:
return null;
}
};
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setInput({
...input,
[e.currentTarget.name]: e.currentTarget.value,
});
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
try {
e.preventDefault();
if (!e.currentTarget.reportValidity()) return;
if (!elements || !stripe) return;
setPayment({ status: "processing" });
const { error: submitError } = await elements.submit();
if (submitError) {
setPayment({ status: "error" });
setErrorMessage(submitError.message ?? "An unknown error occurred");
return;
}
// Fetch product price from the API
const response = await fetch("/api/get-product-prices");
if (!response.ok) {
throw new Error("Failed to fetch product prices");
}
const productPrices = await response.json();
// Find the product price based on the selected product ID
const selectedProductPrice = productPrices.find(
(productPrice: any) => productPrice.productId === input.productId
);
if (!selectedProductPrice) {
throw new Error("Selected product price not found");
}
// Calculate the total amount based on the selected product price and quantity
const totalAmount = selectedProductPrice.price * input.quantity;
console.log("Total Amount:", totalAmount); // Debugging line
// Create a PaymentIntent with the specified amount
const formData = new FormData(e.target as HTMLFormElement);
formData.append("amount", totalAmount.toString());
const { client_secret: clientSecret } = await createPaymentIntent(formData);
console.log("Client Secret:", clientSecret); // Debugging line
// Use your card Element with other Stripe.js APIs
const { error: confirmError } = await stripe!.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/front`,
payment_method_data: {
billing_details: {
name: input.cardholderName,
},
},
},
});
if (confirmError) {
setPayment({ status: "error" });
setErrorMessage(confirmError.message ?? "An unknown error occurred");
} else {
setPayment({ status: "succeeded" });
}
} catch (err) {
const { message } = err as StripeError;
setPayment({ status: "error" });
setErrorMessage(message ?? "An unknown error occurred");
}
};
return (
<>
<form onSubmit={handleSubmit}>
<StripeTestCards />
<fieldset className="elements-style">
<legend>Your payment details:</legend>
{paymentType === "card" ? (
<input
placeholder="Cardholder name"
className="elements-style"
type="text"
name="cardholderName"
onChange={handleInputChange}
required
/>
) : null}
<div className="FormRow elements-style">
<PaymentElement
onChange={(e) => {
setPaymentType(e.value.type);
}}
/>
</div>
<input
type="hidden"
name="productId"
value={input.productId}
onChange={handleInputChange}
/>
</fieldset>
<label>
Quantity:
<input
type="number"
name="quantity"
className="elements-style"
value={input.quantity}
onChange={handleInputChange}
min="1"
/>
</label>
<button
className="elements-style-background"
type="submit"
disabled={
!["initial", "succeeded", "error"].includes(payment.status) ||
!stripe
}
>
Pay Now
</button>
</form>
<PaymentStatus status={payment.status} />
</>
);
}
export default function ElementsForm(): JSX.Element {
return (
<Elements
stripe={getStripe()}
options={{
appearance: {
variables: {
colorIcon: "#5469d4",
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
},
},
currency: config.CURRENCY,
mode: "payment",
}}
>
<CheckoutForm />
</Elements>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
export default async function Page() {
return (
<main>
<h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
Dashboard
</h1>
https://dev.to/w3tsa/nextjs-14-fetching-data-elm
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
{/* <Card title="Collected" value={totalPaidInvoices} type="collected" /> */}
{/* <Card title="Pending" value={totalPendingInvoices} type="pending" /> */}
{/* <Card title="Total Invoices" value={numberOfInvoices} type="invoices" /> */}
{/* <Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/> */}
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
{/* <RevenueChart revenue={revenue} /> */}
{/* <LatestInvoices latestInvoices={latestInvoices} /> */}
</div>
</main>
);
}
// pages/api/get-product-prices.ts
import { NextApiRequest, NextApiResponse } from 'next';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: '2023-08-16',
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const products = await stripe.products.list();
const prices = await stripe.prices.list();
const productPrices = products.data.map(product => {
const price = prices.data.find(price => price.product === product.id);
return {
productId: product.id,
priceId: price?.id,
productName: product.name,
unit_amount: price?.unit_amount,
currency: price?.currency,
};
});
res.status(200).json(productPrices);
} catch (error) {
console.error(error);
res.status(500).send({ error: 'Failed to fetch product prices' });
}
}
"use server";
import type { Stripe } from "stripe";
import { headers } from "next/headers";
import { CURRENCY } from "@/config";
import { stripe } from "@/lib/stripe";
//import { calculateOrderAmount } from "../calculateOrderAmount";
import {ShoppingCartList} from "app/api/users/data";
import {calculateTotalPrice} from "app/api/users/data";
import { type ShoppingCart } from "app/api/users/data";
// const amount = calculateOrderAmount(items);
//console.log(amount); // has const items: CartItem[] = [{}]
// const calculateOrderAmount = (items: CartItem[]): number => {
// return items.reduce(
// (total, item) => total + item.price_data.unit_amount * item.quantity,
// 0
// );
// };
const calculateOrderAmount = (items: ShoppingCart[]): number => {
return calculateTotalPrice(items);
};
// const calculateOrderAmount = (quantity: number): number => {
// const pricePerItem = 1400; // Adjust this to your actual item price
// return pricePerItem * quantity;
// };
export async function createCheckoutSession(
data: FormData,
): Promise<{ client_secret: string | null; url: string | null }> {
const ui_mode = data.get(
"uiMode",
) as Stripe.Checkout.SessionCreateParams.UiMode;
const quantity = parseInt(data.get("quantity") as string, 10);
//const amount = calculateOrderAmount(quantity);
const amount = calculateOrderAmount(ShoppingCartList);
const origin: string = headers().get("origin") as string;
const checkoutSession: Stripe.Checkout.Session =
await stripe.checkout.sessions.create({
mode: "payment",
submit_type: "auto",
line_items: [
{
quantity: 1,
price_data: {
currency: CURRENCY,
product_data: {
name: "Custom amount donation",
},
unit_amount: amount / quantity,
// unit_amount: parseInt(data.get("amount") as string),
//unit_amount: 1400,
},
},
],
//console.log(amount);
...(ui_mode === "hosted" && {
success_url: `${origin}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/donate-with-checkout`,
}),
...(ui_mode === "embedded" && {
return_url: `${origin}/donate-with-embedded-checkout/result?session_id={CHECKOUT_SESSION_ID}`,
}),
ui_mode,
});
return {
client_secret: checkoutSession.client_secret,
url: checkoutSession.url,
};
}
// amount: calculateOrderAmount(items),
// amount: parseInt(data.get("amount") as string),
export async function createPaymentIntent(
data: FormData,
): Promise<{ client_secret: string }> {
const quantity = parseInt(data.get("quantity") as string, 10);
const amount = calculateOrderAmount(quantity);
const paymentIntent: Stripe.PaymentIntent =
await stripe.paymentIntents.create({
amount,
automatic_payment_methods: { enabled: true },
currency: CURRENCY,
});
return { client_secret: paymentIntent.client_secret as string };
}
"use server";
import type { Stripe } from "stripe";
import { headers } from "next/headers";
import { CURRENCY } from "@/config";
import { stripe } from "@/lib/stripe";
//import { calculateOrderAmount } from "../calculateOrderAmount";
import {ShoppingCartList} from "app/api/users/data";
import {calculateTotalPrice} from "app/api/users/data";
import { type ShoppingCart } from "app/api/users/data";
// const amount = calculateOrderAmount(items);
//console.log(amount); // has const items: CartItem[] = [{}]
// const calculateOrderAmount = (items: CartItem[]): number => {
// return items.reduce(
// (total, item) => total + item.price_data.unit_amount * item.quantity,
// 0
// );
// };
const calculateOrderAmount = (items: ShoppingCart[]): number => {
return calculateTotalPrice(items);
};
// const calculateOrderAmount = (quantity: number): number => {
// const pricePerItem = 1400; // Adjust this to your actual item price
// return pricePerItem * quantity;
// };
export async function createCheckoutSession(
data: FormData,
): Promise<{ client_secret: string | null; url: string | null }> {
const ui_mode = data.get(
"uiMode",
) as Stripe.Checkout.SessionCreateParams.UiMode;
const quantity = parseInt(data.get("quantity") as string, 10);
//const amount = calculateOrderAmount(quantity);
const amount = calculateOrderAmount(ShoppingCartList);
const origin: string = headers().get("origin") as string;
const checkoutSession: Stripe.Checkout.Session =
await stripe.checkout.sessions.create({
mode: "payment",
submit_type: "auto",
// line_items: [
// {
// quantity: 1,
// price_data: {
// currency: CURRENCY,
// product_data: {
// name: "Custom amount donation",
// },
// unit_amount: amount / quantity,
// // unit_amount: parseInt(data.get("amount") as string),
// //unit_amount: 1400,
// },
// },
// ],
line_items: ShoppingCartList.map(item => ({
quantity: item.quantity,
price_data: {
currency: CURRENCY,
product_data: {
name: item.name,
},
unit_amount: item.price * 100, // Ensure unit amount is in cents
},
})),
...(ui_mode === "hosted" && {
success_url: `${origin}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/donate-with-checkout`,
}),
...(ui_mode === "embedded" && {
return_url: `${origin}/donate-with-embedded-checkout/result?session_id={CHECKOUT_SESSION_ID}`,
}),
ui_mode,
});
return {
client_secret: checkoutSession.client_secret,
url: checkoutSession.url,
};
}
// amount: calculateOrderAmount(items),
// amount: parseInt(data.get("amount") as string),
export async function createPaymentIntent(
data: FormData,
): Promise<{ client_secret: string }> {
const quantity = parseInt(data.get("quantity") as string, 10);
//const amount = calculateOrderAmount(quantity);
const amount = calculateOrderAmount(ShoppingCartList);
const paymentIntent: Stripe.PaymentIntent =
await stripe.paymentIntents.create({
amount,
automatic_payment_methods: { enabled: true },
currency: CURRENCY,
});
return { client_secret: paymentIntent.client_secret as string };
}
// components/CheckoutForm.tsx
import React, { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import axios from 'axios';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string);
type ProductPrice = {
productId: string;
priceId: string;
productName: string;
unit_amount: number;
currency: string;
};
const CheckoutForm: React.FC = () => {
const [products, setProducts] = useState<ProductPrice[]>([]);
const [selectedProduct, setSelectedProduct] = useState<string | null>(null);
const [input, setInput] = useState<{ cardholderName: string }>({ cardholderName: '' });
const [clientSecret, setClientSecret] = useState<string | null>(null);
const stripe = useStripe();
const elements = useElements();
useEffect(() => {
// Fetch product prices from the backend
const fetchProductPrices = async () => {
try {
const response = await axios.get('/api/get-product-prices');
setProducts(response.data);
} catch (error) {
console.error('Error fetching product prices:', error);
}
};
fetchProductPrices();
}, []);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements || !selectedProduct) {
return;
}
const product = products.find(product => product.productId === selectedProduct);
if (!product) {
console.error('Selected product not found');
return;
}
const { data } = await axios.post('/api/create-payment-intent', {
items: [{ priceId: product.priceId, quantity: 1 }],
});
setClientSecret(data.clientSecret);
if (clientSecret) {
const cardElement = elements.getElement(CardElement);
if (cardElement) {
const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: input.cardholderName,
},
},
});
if (error) {
console.error(error);
} else if (paymentIntent && paymentIntent.status === 'succeeded') {
console.log('Payment succeeded!');
}
}
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Cardholder Name
<input
type="text"
value={input.cardholderName}
onChange={(e) => setInput({ ...input, cardholderName: e.target.value })}
/>
</label>
<label>
Select Product
<select onChange={(e) => setSelectedProduct(e.target.value)} value={selectedProduct || ''}>
<option value="" disabled>
Select a product
</option>
{products.map((product) => (
<option key={product.productId} value={product.productId}>
{product.productName} - {product.unit_amount / 100} {product.currency.toUpperCase()}
</option>
))}
</select>
</label>
<CardElement />
<button type="submit" disabled={!stripe}>
Pay
</button>
</form>
);
};
const WrappedCheckoutForm: React.FC = () => (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
export default WrappedCheckoutForm;
"use client";
import type { StripeError } from "@stripe/stripe-js";
import * as React from "react";
import {
useStripe,
useElements,
PaymentElement,
Elements,
} from "@stripe/react-stripe-js";
//import CustomDonationInput from "./CustomDonationInput";
import StripeTestCards from "./StripeTestCards";
import { formatAmountForDisplay } from "@/utils/stripe-helpers";
import * as config from "@/config";
import getStripe from "@/utils/get-stripejs";
import { createPaymentIntent } from "@/actions/cart/stripe";
function CheckoutForm(): JSX.Element {
const [input, setInput] = React.useState<{
amount: number;
cardholderName: string;
quantity: number;
}>({
amount: 1400 * 100,
cardholderName: "",
quantity: 1,
});
const [paymentType, setPaymentType] = React.useState<string>("");
const [payment, setPayment] = React.useState<{
status: "initial" | "processing" | "error";
}>({ status: "initial" });
const [errorMessage, setErrorMessage] = React.useState<string>("");
const stripe = useStripe();
const elements = useElements();
const PaymentStatus = ({ status }: { status: string }) => {
switch (status) {
case "processing":
case "requires_payment_method":
case "requires_confirmation":
return <h2>Processing...</h2>;
case "requires_action":
return <h2>Authenticating...</h2>;
case "succeeded":
return <h2>Payment Succeeded 🎉</h2>;
case "error":
return (
<>
<h2>Error ❌</h2>
<p className="error-message">{errorMessage}</p>
</>
);
default:
return null;
}
};
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setInput({
...input,
[e.currentTarget.name]: e.currentTarget.value,
});
elements?.update({ amount: 1400 * 100 });
};
const handleSubmit: React.FormEventHandler<HTMLFormElement> = async (e) => {
try {
e.preventDefault();
// Abort if form isn't valid
if (!e.currentTarget.reportValidity()) return;
if (!elements || !stripe) return;
setPayment({ status: "processing" });
const { error: submitError } = await elements.submit();
if (submitError) {
setPayment({ status: "error" });
setErrorMessage(submitError.message ?? "An unknown error occurred");
return;
}
// Create a PaymentIntent with the specified amount.
// const { client_secret: clientSecret } = await createPaymentIntent(
// new FormData(e.target as HTMLFormElement)
// );
const formData = new FormData(e.target as HTMLFormElement);
formData.append("amount", (1400 * input.quantity).toString());
const { client_secret: clientSecret } = await createPaymentIntent(formData);
// Use your card Element with other Stripe.js APIs
//donate-with-elements/result
const { error: confirmError } = await stripe!.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/front`,
payment_method_data: {
billing_details: {
name: input.cardholderName,
},
},
},
});
if (confirmError) {
setPayment({ status: "error" });
setErrorMessage(confirmError.message ?? "An unknown error occurred");
}
} catch (err) {
const { message } = err as StripeError;
setPayment({ status: "error" });
setErrorMessage(message ?? "An unknown error occurred");
}
};
return (
<>
<form onSubmit={handleSubmit}>
<StripeTestCards />
<fieldset className="elements-style">
<legend>Your payment details:</legend>
{paymentType === "card" ? (
<input
placeholder="Cardholder name"
className="elements-style"
type="Text"
name="cardholderName"
onChange={handleInputChange}
required
/>
) : null}
<div className="FormRow elements-style">
<PaymentElement
onChange={(e) => {
setPaymentType(e.value.type);
}}
/>
</div>
</fieldset>
<label>
Quantity:
<input
type="number"
name="quantity"
className="elements-style"
value={input.quantity}
onChange={handleInputChange}
min="1"
/>
</label>
<button
className="elements-style-background"
type="submit"
disabled={
!["initial", "succeeded", "error"].includes(payment.status) ||
!stripe
}
>
Pay Now
{/* Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)} */}
</button>
</form>
<PaymentStatus status={payment.status} />
</>
);
}
export default function ElementsForm(): JSX.Element {
return (
<Elements
stripe={getStripe()}
options={{
appearance: {
variables: {
colorIcon: "#5469d4",
fontFamily: "Roboto, Open Sans, Segoe UI, sans-serif",
},
},
currency: config.CURRENCY,
mode: "payment",
amount: 1400 * 100,
}}
>
<CheckoutForm />
</Elements>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment