Skip to main content

Payment Checkout Integration Guide

Enable seamless fiat and cryptocurrency payments for your Flow assets. Crossmint's checkout solution supports credit cards, Apple Pay, Google Pay, and cross-chain crypto payments, allowing users to buy Flow NFTs and tokens without holding FLOW tokens.

Overview

Crossmint Checkout eliminates payment friction by supporting multiple payment methods and handling complex blockchain interactions behind the scenes. Users can buy your Flow assets using familiar payment methods.

Key Benefits:

  • No wallet required - guest checkout available
  • Global coverage - 197 countries supported
  • No buyer KYC for most transactions
  • Cross-chain payments - Pay with any crypto, receive on Flow

What You'll Build

You'll integrate checkout functionality that enables:

  • Credit card payments for Flow NFTs and tokens
  • Apple Pay and Google Pay support
  • Cross-chain crypto payments
  • Guest checkout (no wallet required)

Prerequisites

  • Crossmint account with checkout enabled
  • Flow collection created or imported
  • Basic understanding of payment flows
  • For production: KYB verification completed

Step 1: Collection Setup

Create or Import Collection

Option A: Create New Collection

  1. Go to Crossmint Console > Collections
  2. Click Create Collection
  3. Choose Flow blockchain
  4. Configure collection settings:
    • Network: Flow Testnet/Mainnet
    • Contract type: ERC-721 (EVM) or Cadence NFT
    • Pricing in USD or FLOW
    • Maximum supply and metadata

Option B: Import Existing Collection


_11
// Import existing Flow contract
_11
const collection = await crossmint.collections.import({
_11
blockchain: "flow",
_11
contractAddress: "0x1234567890abcdef", // Your contract address
_11
type: "erc-721", // or "cadence-nft"
_11
metadata: {
_11
name: "My Flow Collection",
_11
symbol: "MFC",
_11
description: "Amazing NFTs on Flow"
_11
}
_11
});

Configure Payment Settings

In your collection settings:

  1. Go to Payments > Settings
  2. Choose fee structure:
    • Buyer pays fees: User pays NFT price + fees
    • Seller pays fees: User pays exact price, you pay fees
  3. Set accepted payment methods
  4. Configure webhooks for order updates

Step 2: Hosted Checkout Integration

The fastest way to get started - Crossmint hosts the entire checkout experience.

Basic Hosted Checkout


_39
// src/components/HostedCheckout.jsx
_39
import React from 'react';
_39
_39
export function HostedCheckout({ collectionId, nftId, onSuccess }) {
_39
const openCheckout = () => {
_39
const checkoutUrl = `https://www.crossmint.com/checkout?` +
_39
`clientId=${process.env.REACT_APP_CROSSMINT_CLIENT_ID}&` +
_39
`collectionId=${collectionId}&` +
_39
`templateId=${nftId}&` +
_39
`successCallbackURL=${encodeURIComponent(window.location.origin + '/success')}&` +
_39
`cancelCallbackURL=${encodeURIComponent(window.location.origin + '/cancel')}`;
_39
_39
// Open in new window
_39
const popup = window.open(
_39
checkoutUrl,
_39
'crossmint-checkout',
_39
'width=500,height=700,scrollbars=yes,resizable=yes'
_39
);
_39
_39
// Listen for completion
_39
const checkClosed = setInterval(() => {
_39
if (popup.closed) {
_39
clearInterval(checkClosed);
_39
onSuccess?.();
_39
}
_39
}, 1000);
_39
};
_39
_39
return (
_39
<div className="hosted-checkout">
_39
<button
_39
onClick={openCheckout}
_39
className="checkout-btn primary"
_39
>
_39
🛒 Buy with Crossmint
_39
</button>
_39
</div>
_39
);
_39
}

Advanced Hosted Checkout


_42
// More control over hosted checkout
_42
export function AdvancedHostedCheckout({
_42
collectionId,
_42
nftId,
_42
customization,
_42
onSuccess,
_42
onError
_42
}) {
_42
const openCheckout = () => {
_42
const params = new URLSearchParams({
_42
clientId: process.env.REACT_APP_CROSSMINT_CLIENT_ID,
_42
collectionId,
_42
templateId: nftId,
_42
// Customization options
_42
theme: customization.theme || 'dark',
_42
accentColor: customization.accentColor || '#00D4AA',
_42
backgroundColor: customization.backgroundColor || '#1A1A1A',
_42
// Callback URLs
_42
successCallbackURL: `${window.location.origin}/checkout/success`,
_42
cancelCallbackURL: `${window.location.origin}/checkout/cancel`,
_42
// Payment options
_42
enableApplePay: 'true',
_42
enableGooglePay: 'true',
_42
enableCrypto: 'true',
_42
// User experience
_42
showConnectWallet: 'true',
_42
collectEmail: 'true'
_42
});
_42
_42
window.open(
_42
`https://www.crossmint.com/checkout?${params}`,
_42
'crossmint-checkout',
_42
'width=500,height=700,scrollbars=yes,resizable=yes'
_42
);
_42
};
_42
_42
return (
_42
<button onClick={openCheckout} className="crossmint-checkout-btn">
_42
Buy Now - Credit Card or Crypto
_42
</button>
_42
);
_42
}


Step 3: Embedded Checkout Integration

Embed checkout directly in your application with full UI control.

Basic Embedded Checkout


_44
// src/components/EmbeddedCheckout.jsx
_44
import React from 'react';
_44
import { CrossmintPayButton } from '@crossmint/embed-react';
_44
_44
export function EmbeddedCheckout({ collectionId, nftId, recipient }) {
_44
return (
_44
<div className="embedded-checkout">
_44
<CrossmintPayButton
_44
collectionId={collectionId}
_44
projectId={process.env.REACT_APP_CROSSMINT_PROJECT_ID}
_44
mintConfig={{
_44
type: "erc-721",
_44
quantity: 1,
_44
...(nftId && { templateId: nftId })
_44
}}
_44
recipient={{
_44
email: recipient?.email,
_44
walletAddress: recipient?.walletAddress
_44
}}
_44
checkoutProps={{
_44
paymentMethods: ['fiat', 'ETH', 'SOL', 'MATIC'],
_44
showWalletOptions: true,
_44
theme: 'dark'
_44
}}
_44
onEvent={(event) => {
_44
console.log('Checkout event:', event);
_44
_44
switch (event.type) {
_44
case 'payment:process.succeeded':
_44
console.log('✅ Payment succeeded:', event.payload);
_44
break;
_44
case 'payment:process.failed':
_44
console.log('❌ Payment failed:', event.payload);
_44
break;
_44
case 'ui:payment-method.selected':
_44
console.log('Payment method selected:', event.payload);
_44
break;
_44
}
_44
}}
_44
environment="staging" // or "production"
_44
/>
_44
</div>
_44
);
_44
}

Custom Styled Embedded Checkout


_73
// Advanced embedded checkout with custom styling
_73
export function CustomEmbeddedCheckout({
_73
collectionId,
_73
nftId,
_73
pricing,
_73
onCheckoutComplete
_73
}) {
_73
return (
_73
<div className="custom-checkout-container">
_73
<div className="checkout-header">
_73
<h3>Complete Your Purchase</h3>
_73
<div className="price-display">
_73
<span className="price">${pricing.usd}</span>
_73
<span className="price-alt">≈ {pricing.flow} FLOW</span>
_73
</div>
_73
</div>
_73
_73
<CrossmintPayButton
_73
collectionId={collectionId}
_73
projectId={process.env.REACT_APP_CROSSMINT_PROJECT_ID}
_73
mintConfig={{
_73
type: "erc-721",
_73
quantity: 1,
_73
templateId: nftId,
_73
totalPrice: pricing.usd.toString()
_73
}}
_73
checkoutProps={{
_73
paymentMethods: [
_73
'fiat', // Credit cards
_73
'ETH', // Ethereum
_73
'MATIC', // Polygon
_73
'SOL', // Solana
_73
'BTC', // Bitcoin
_73
'FLOW' // Flow native
_73
],
_73
theme: {
_73
colors: {
_73
primary: '#00D4AA',
_73
background: '#FFFFFF',
_73
textPrimary: '#1A1A1A',
_73
textSecondary: '#6B7280'
_73
},
_73
borderRadius: '8px',
_73
fontFamily: 'Inter, sans-serif'
_73
},
_73
locale: 'en-US',
_73
currency: 'USD'
_73
}}
_73
onEvent={handleCheckoutEvent}
_73
className="custom-crossmint-button"
_73
/>
_73
</div>
_73
);
_73
_73
function handleCheckoutEvent(event) {
_73
switch (event.type) {
_73
case 'payment:process.succeeded':
_73
onCheckoutComplete?.({
_73
success: true,
_73
transactionId: event.payload.transactionId,
_73
nftId: event.payload.nftId
_73
});
_73
break;
_73
_73
case 'payment:process.failed':
_73
onCheckoutComplete?.({
_73
success: false,
_73
error: event.payload.error
_73
});
_73
break;
_73
}
_73
}
_73
}


Step 4: Headless Checkout Integration

For maximum customization, use the headless API to build completely custom checkout flows.

Order Creation Service


_136
// src/services/checkoutService.ts
_136
import { CrossmintSDK } from '@crossmint/client-sdk';
_136
_136
const crossmint = new CrossmintSDK({
_136
apiKey: process.env.CROSSMINT_API_KEY!,
_136
environment: 'staging'
_136
});
_136
_136
export interface CheckoutOrder {
_136
id: string;
_136
status: string;
_136
clientSecret: string;
_136
paymentIntent?: any;
_136
}
_136
_136
export class CheckoutService {
_136
// Create fiat payment order
_136
async createFiatOrder(params: {
_136
collectionId: string;
_136
nftId?: string;
_136
recipientEmail: string;
_136
recipientWallet?: string;
_136
quantity?: number;
_136
}): Promise<CheckoutOrder> {
_136
try {
_136
const order = await crossmint.orders.create({
_136
payment: {
_136
method: "fiat",
_136
currency: "usd"
_136
},
_136
lineItems: [{
_136
collectionLocator: `crossmint:${params.collectionId}`,
_136
...(params.nftId && { templateId: params.nftId }),
_136
quantity: params.quantity || 1
_136
}],
_136
recipient: {
_136
email: params.recipientEmail,
_136
...(params.recipientWallet && { walletAddress: params.recipientWallet })
_136
},
_136
metadata: {
_136
source: 'custom_checkout'
_136
}
_136
});
_136
_136
return {
_136
id: order.id,
_136
status: order.status,
_136
clientSecret: order.clientSecret,
_136
paymentIntent: order.paymentIntent
_136
};
_136
} catch (error) {
_136
console.error('❌ Order creation failed:', error);
_136
throw error;
_136
}
_136
}
_136
_136
// Create crypto payment order
_136
async createCryptoOrder(params: {
_136
collectionId: string;
_136
nftId?: string;
_136
recipientWallet: string;
_136
paymentToken: string; // 'ETH', 'MATIC', 'SOL', etc.
_136
quantity?: number;
_136
}): Promise<CheckoutOrder> {
_136
try {
_136
const order = await crossmint.orders.create({
_136
payment: {
_136
method: "crypto",
_136
currency: params.paymentToken.toLowerCase()
_136
},
_136
lineItems: [{
_136
collectionLocator: `crossmint:${params.collectionId}`,
_136
...(params.nftId && { templateId: params.nftId }),
_136
quantity: params.quantity || 1
_136
}],
_136
recipient: {
_136
walletAddress: params.recipientWallet
_136
}
_136
});
_136
_136
return {
_136
id: order.id,
_136
status: order.status,
_136
clientSecret: order.clientSecret
_136
};
_136
} catch (error) {
_136
console.error('❌ Crypto order creation failed:', error);
_136
throw error;
_136
}
_136
}
_136
_136
// Check order status
_136
async getOrderStatus(orderId: string) {
_136
try {
_136
const order = await crossmint.orders.get(orderId);
_136
return order;
_136
} catch (error) {
_136
console.error('❌ Order status check failed:', error);
_136
throw error;
_136
}
_136
}
_136
_136
// Handle order completion
_136
async handleOrderComplete(orderId: string) {
_136
try {
_136
const order = await crossmint.orders.get(orderId);
_136
_136
if (order.status === 'succeeded') {
_136
// Order completed successfully
_136
return {
_136
success: true,
_136
nft: order.nft,
_136
transaction: order.transaction
_136
};
_136
} else if (order.status === 'failed') {
_136
// Order failed
_136
return {
_136
success: false,
_136
error: order.error
_136
};
_136
}
_136
_136
// Order still processing
_136
return {
_136
success: false,
_136
processing: true,
_136
status: order.status
_136
};
_136
} catch (error) {
_136
console.error('❌ Order completion check failed:', error);
_136
throw error;
_136
}
_136
}
_136
}
_136
_136
export const checkoutService = new CheckoutService();

Custom Checkout Component


_128
// src/components/CustomCheckout.tsx
_128
import React, { useState } from 'react';
_128
import { loadStripe } from '@stripe/stripe-js';
_128
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
_128
import { checkoutService } from '../services/checkoutService';
_128
_128
const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY!);
_128
_128
interface CheckoutFormProps {
_128
collectionId: string;
_128
nftId: string;
_128
onSuccess: (result: any) => void;
_128
onError: (error: any) => void;
_128
}
_128
_128
function CheckoutForm({ collectionId, nftId, onSuccess, onError }: CheckoutFormProps) {
_128
const stripe = useStripe();
_128
const elements = useElements();
_128
const [isProcessing, setIsProcessing] = useState(false);
_128
const [paymentMethod, setPaymentMethod] = useState<'fiat' | 'crypto'>('fiat');
_128
const [recipientEmail, setRecipientEmail] = useState('');
_128
_128
const handleFiatPayment = async (e: React.FormEvent) => {
_128
e.preventDefault();
_128
if (!stripe || !elements) return;
_128
_128
setIsProcessing(true);
_128
_128
try {
_128
// Create order
_128
const order = await checkoutService.createFiatOrder({
_128
collectionId,
_128
nftId,
_128
recipientEmail
_128
});
_128
_128
// Confirm payment with Stripe
_128
const { error, paymentIntent } = await stripe.confirmPayment({
_128
elements,
_128
clientSecret: order.clientSecret,
_128
confirmParams: {
_128
return_url: `${window.location.origin}/checkout/complete`
_128
}
_128
});
_128
_128
if (error) {
_128
onError(error);
_128
} else if (paymentIntent?.status === 'succeeded') {
_128
// Poll for NFT delivery
_128
const result = await checkoutService.handleOrderComplete(order.id);
_128
onSuccess(result);
_128
}
_128
} catch (error) {
_128
onError(error);
_128
} finally {
_128
setIsProcessing(false);
_128
}
_128
};
_128
_128
const handleCryptoPayment = async () => {
_128
// Implement crypto payment flow
_128
// This would integrate with wallet providers
_128
console.log('Crypto payment not implemented in this example');
_128
};
_128
_128
return (
_128
<div className="custom-checkout-form">
_128
<div className="payment-method-selector">
_128
<button
_128
className={paymentMethod === 'fiat' ? 'active' : ''}
_128
onClick={() => setPaymentMethod('fiat')}
_128
>
_128
💳 Card / Apple Pay
_128
</button>
_128
<button
_128
className={paymentMethod === 'crypto' ? 'active' : ''}
_128
onClick={() => setPaymentMethod('crypto')}
_128
>
_128
🪙 Crypto
_128
</button>
_128
</div>
_128
_128
{paymentMethod === 'fiat' ? (
_128
<form onSubmit={handleFiatPayment} className="fiat-payment-form">
_128
<div className="form-group">
_128
<label>Email Address</label>
_128
<input
_128
type="email"
_128
value={recipientEmail}
_128
onChange={(e) => setRecipientEmail(e.target.value)}
_128
required
_128
placeholder="your@email.com"
_128
/>
_128
</div>
_128
_128
<div className="payment-element-container">
_128
<PaymentElement />
_128
</div>
_128
_128
<button
_128
type="submit"
_128
disabled={!stripe || isProcessing}
_128
className="pay-button"
_128
>
_128
{isProcessing ? 'Processing...' : 'Complete Purchase'}
_128
</button>
_128
</form>
_128
) : (
_128
<div className="crypto-payment-form">
_128
<div className="crypto-options">
_128
<button onClick={handleCryptoPayment}>Pay with ETH</button>
_128
<button onClick={handleCryptoPayment}>Pay with MATIC</button>
_128
<button onClick={handleCryptoPayment}>Pay with SOL</button>
_128
<button onClick={handleCryptoPayment}>Pay with FLOW</button>
_128
</div>
_128
</div>
_128
)}
_128
</div>
_128
);
_128
}
_128
_128
export function CustomCheckout(props: CheckoutFormProps) {
_128
return (
_128
<Elements stripe={stripePromise}>
_128
<CheckoutForm {...props} />
_128
</Elements>
_128
);
_128
}


Step 5: Webhook Integration

Set up webhooks to handle order status updates in real-time.

Webhook Handler


_63
// src/api/webhooks/crossmint.ts (Next.js API route example)
_63
import { NextApiRequest, NextApiResponse } from 'next';
_63
import crypto from 'crypto';
_63
_63
export default function handler(req: NextApiRequest, res: NextApiResponse) {
_63
if (req.method !== 'POST') {
_63
return res.status(405).json({ error: 'Method not allowed' });
_63
}
_63
_63
// Verify webhook signature
_63
const signature = req.headers['x-crossmint-signature'] as string;
_63
const payload = JSON.stringify(req.body);
_63
const expectedSignature = crypto
_63
.createHmac('sha256', process.env.CROSSMINT_WEBHOOK_SECRET!)
_63
.update(payload)
_63
.digest('hex');
_63
_63
if (signature !== expectedSignature) {
_63
return res.status(401).json({ error: 'Invalid signature' });
_63
}
_63
_63
const event = req.body;
_63
_63
switch (event.type) {
_63
case 'order.succeeded':
_63
handleOrderSucceeded(event.data);
_63
break;
_63
case 'order.failed':
_63
handleOrderFailed(event.data);
_63
break;
_63
case 'order.delivered':
_63
handleOrderDelivered(event.data);
_63
break;
_63
default:
_63
console.log('Unhandled webhook event:', event.type);
_63
}
_63
_63
res.status(200).json({ received: true });
_63
}
_63
_63
async function handleOrderSucceeded(orderData: any) {
_63
console.log('✅ Order succeeded:', orderData.orderId);
_63
_63
// Update your database
_63
// Send confirmation email
_63
// Trigger any post-purchase flows
_63
}
_63
_63
async function handleOrderFailed(orderData: any) {
_63
console.log('❌ Order failed:', orderData.orderId, orderData.error);
_63
_63
// Handle failed order
_63
// Notify user
_63
// Log for analysis
_63
}
_63
_63
async function handleOrderDelivered(orderData: any) {
_63
console.log('📦 NFT delivered:', orderData.orderId, orderData.nft);
_63
_63
// NFT successfully delivered to user
_63
// Update user's account
_63
// Send delivery confirmation
_63
}


Step 6: Multi-Payment Method Component

Create a comprehensive checkout that supports all payment methods:


_112
// src/components/UniversalCheckout.tsx
_112
import React, { useState } from 'react';
_112
import { EmbeddedCheckout } from './EmbeddedCheckout';
_112
import { CustomCheckout } from './CustomCheckout';
_112
import { HostedCheckout } from './HostedCheckout';
_112
_112
interface UniversalCheckoutProps {
_112
collectionId: string;
_112
nftId: string;
_112
pricing: {
_112
usd: number;
_112
flow: number;
_112
};
_112
onCheckoutComplete: (result: any) => void;
_112
}
_112
_112
export function UniversalCheckout({
_112
collectionId,
_112
nftId,
_112
pricing,
_112
onCheckoutComplete
_112
}: UniversalCheckoutProps) {
_112
const [checkoutMode, setCheckoutMode] = useState<'hosted' | 'embedded' | 'custom'>('embedded');
_112
const [showPaymentMethods, setShowPaymentMethods] = useState(false);
_112
_112
return (
_112
<div className="universal-checkout">
_112
<div className="checkout-header">
_112
<h2>🛒 Purchase NFT</h2>
_112
<div className="pricing-info">
_112
<div className="price-primary">${pricing.usd}</div>
_112
<div className="price-secondary">≈ {pricing.flow} FLOW</div>
_112
</div>
_112
</div>
_112
_112
<div className="payment-methods-preview">
_112
<div className="payment-icons">
_112
<span className="payment-icon">💳</span>
_112
<span className="payment-icon">🍎</span>
_112
<span className="payment-icon">🅿️</span>
_112
<span className="payment-icon">⚡</span>
_112
<span className="payment-icon">🪙</span>
_112
</div>
_112
<p>Credit Card, Apple Pay, Google Pay, and 40+ cryptocurrencies</p>
_112
</div>
_112
_112
<div className="checkout-mode-selector">
_112
<button
_112
className={checkoutMode === 'embedded' ? 'active' : ''}
_112
onClick={() => setCheckoutMode('embedded')}
_112
>
_112
🎨 Styled Checkout
_112
</button>
_112
<button
_112
className={checkoutMode === 'hosted' ? 'active' : ''}
_112
onClick={() => setCheckoutMode('hosted')}
_112
>
_112
🚀 Quick Checkout
_112
</button>
_112
<button
_112
className={checkoutMode === 'custom' ? 'active' : ''}
_112
onClick={() => setCheckoutMode('custom')}
_112
>
_112
⚙️ Custom Checkout
_112
</button>
_112
</div>
_112
_112
<div className="checkout-container">
_112
{checkoutMode === 'hosted' && (
_112
<HostedCheckout
_112
collectionId={collectionId}
_112
nftId={nftId}
_112
onSuccess={onCheckoutComplete}
_112
/>
_112
)}
_112
_112
{checkoutMode === 'embedded' && (
_112
<EmbeddedCheckout
_112
collectionId={collectionId}
_112
nftId={nftId}
_112
pricing={pricing}
_112
onCheckoutComplete={onCheckoutComplete}
_112
/>
_112
)}
_112
_112
{checkoutMode === 'custom' && (
_112
<CustomCheckout
_112
collectionId={collectionId}
_112
nftId={nftId}
_112
onSuccess={onCheckoutComplete}
_112
onError={(error) => console.error('Checkout error:', error)}
_112
/>
_112
)}
_112
</div>
_112
_112
<div className="checkout-benefits">
_112
<div className="benefit">
_112
<span className="benefit-icon">🔒</span>
_112
<span>Secure & trusted by Fortune 500</span>
_112
</div>
_112
<div className="benefit">
_112
<span className="benefit-icon">🌍</span>
_112
<span>Available in 197 countries</span>
_112
</div>
_112
<div className="benefit">
_112
<span className="benefit-icon">⚡</span>
_112
<span>Instant delivery to wallet</span>
_112
</div>
_112
</div>
_112
</div>
_112
);
_112
}

Key Takeaways

  • Multiple Integration Options: Hosted, embedded, or headless - choose what fits your needs
  • Universal Payment Support: Credit cards, mobile payments, and 40+ cryptocurrencies
  • Flow Native: Optimized for both Flow EVM and Cadence ecosystems
  • Global Scale: Support for 197 countries with no buyer KYC