Sell NFTs with Stripe

This guide uses thirdweb the transactions API to sell NFTs with credit card:

  • A buyer pays with credit card.
  • Upon payment, your backend calls the transactions API.
  • the transactions API mints an NFT to the buyer's wallet.

The buyer receives the NFT without requiring wallet signatures or gas funds.

NFT checkout overview

Prerequisites

  • A thirdweb client ID and secret key from your Team > Project > Settings page.
  • A server wallet
  • A deployed NFT contract that can be used by the server wallet
  • A Stripe account on test mode

Frontend: Add Connect Wallet and credit card form

Use <ConnectButton> to prompt the buyer for their wallet address. The buyer provides their credit card details and selects Pay now to send payment details directly to Stripe.

import { ThirdwebProvider } from "thirdweb/react";
function Home() {
return (
<ThirdwebProvider>
<PurchasePage />
</ThirdwebProvider>
);
}
import { createThirdwebClient } from "thirdweb";
import {
ThirdwebProvider,
ConnectButton,
useActiveAccount,
} from "thirdweb/react";
const client = createThirdwebClient({
clientId: "your-client-id",
});
// src/app/page.tsx
function PurchasePage() {
const buyerWalletAddress = useActiveAccount()?.address;
const [clientSecret, setClientSecret] = useState("");
// Retrieve a Stripe client secret to display the credit card form.
const onClick = async () => {
const resp = await fetch("/api/stripe-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ buyerWalletAddress }),
});
const json = await resp.json();
setClientSecret(json.clientSecret);
};
const stripe = loadStripe("<stripe_publishable_key>");
return (
<main>
<ConnectButton client={client} />
{!clientSecret ? (
<button onClick={onClick}>Buy with credit card</button>
) : (
<Elements stripe={stripe} options={{ clientSecret }}>
<CreditCardForm />
</Elements>
)}
</main>
);
}
const CreditCardForm = () => {
const elements = useElements();
const stripe = useStripe();
const onClick = async () => {
// Submit payment to Stripe. The NFT is minted later in the webhook.
await stripe.confirmPayment({
elements,
confirmParams: { return_url: "http://localhost:3000" },
redirect: "if_required",
});
alert(
"Payment success. The NFT will be delivered to your wallet shortly.",
);
};
return (
<>
<PaymentElement />
<button onClick={onClick}>Pay now</button>
</>
);
};

Backend: Get a Stripe client secret

POST /api/stripe-intent returns a client secret which is needed to display the credit card form.

// src/app/api/stripe-intent/route.ts
import { NextResponse } from "next/server";
import { Stripe } from "stripe";
export async function POST(req: Request) {
const { buyerWalletAddress } = await req.json();
const stripe = new Stripe("<stripe_secret_key>", {
apiVersion: "2023-10-16",
});
const paymentIntent = await stripe.paymentIntents.create({
amount: 100_00,
currency: "usd",
payment_method_types: ["card"],
// buyerWalletAddress is needed in the webhook.
metadata: { buyerWalletAddress },
});
return NextResponse.json({
clientSecret: paymentIntent.client_secret,
});
}

Backend: Configure the Stripe webhook

POST /api/stripe-webhook calls the transactions API to mint an NFT when a buyer is successfully charged.

// src/app/api/stripe-webhook/route.ts
import { createThirdwebClient, getContract, Engine } from "thirdweb";
import * as ERC1155 from "thirdweb/extensions/erc1155";
const client = createThirdwebClient({
secretKey: "<thirdweb_secret_key>",
});
export const config = {
api: { bodyParser: false },
};
export async function POST(req: NextRequest) {
// Validate the webhook signature
// Source: https://stripe.com/docs/webhooks#secure-webhook
const body = await req.text();
const signature = headers().get("stripe-signature");
const stripe = new Stripe("<stripe_secret_key>", {
apiVersion: "2023-10-16",
});
// Validate and parse the payload.
const event = stripe.webhooks.constructEvent(
body,
signature,
"<webhook_secret_key>",
);
if (event.type === "charge.succeeded") {
const { buyerWalletAddress } = event.data.object.metadata;
// Mint an NFT to the buyer with your server wallet.
// 1. get the contract, here we're using a TokenERC1155 contract (Edition)
// we also already created an NFT with token id 0
const contract = await getContract({
client,
address: "<nft_contract_address>", // the address of the NFT contract
chain: defineChain(1), // the chain id of the NFT contract
});
// 2. prepare the transaction, here we're using the ERC1155 claimTo extension
const transaction = ERC1155.mintAdditionalSupplyTo({
contract,
to: buyerWalletAddress, // the recipient address
tokenId: 0n, // the tokenId of the NFT to mint
supply: 1n, // minting 1 copy of the NFT
});
// 3. get the server wallet
const serverWallet = Engine.serverWallet({
client,
address: "<server_wallet_address>",
});
// 4. send the transaction
const { transactionId } = await serverWallet.enqueueTransaction({
transaction,
});
// 5. return the transaction id to the frontend for status polling
return NextResponse.json({ transactionId });
}
return NextResponse.json({ message: "OK" });
}

Configure Stripe webhooks

Navigate to the Stripe webhooks dashboard (test mode) and add the /api/stripe-webhook endpoint and send the charge.succeeded event.

Try it out!

Here’s what the user flow looks like.

The buyer is prompted to provide their credit card.

Initial page load

They provide their card details.

Tip: Stripe testmode accepts 4242 4242 4242 4242 as a valid credit card.

Prompted for card details

They are informed when their payment is submitted.

Successful payment