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
- Go to Crossmint Console > Collections
- Click Create Collection
- Choose Flow blockchain
- 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_11const 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:
- Go to Payments > Settings
- Choose fee structure:
- Buyer pays fees: User pays NFT price + fees
- Seller pays fees: User pays exact price, you pay fees
- Set accepted payment methods
- 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_39import React from 'react';_39_39export 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_42export 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_44import React from 'react';_44import { CrossmintPayButton } from '@crossmint/embed-react';_44_44export 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_73export 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_136import { CrossmintSDK } from '@crossmint/client-sdk';_136_136const crossmint = new CrossmintSDK({_136 apiKey: process.env.CROSSMINT_API_KEY!,_136 environment: 'staging'_136});_136_136export interface CheckoutOrder {_136 id: string;_136 status: string;_136 clientSecret: string;_136 paymentIntent?: any;_136}_136_136export 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_136export const checkoutService = new CheckoutService();
Custom Checkout Component
_128// src/components/CustomCheckout.tsx_128import React, { useState } from 'react';_128import { loadStripe } from '@stripe/stripe-js';_128import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';_128import { checkoutService } from '../services/checkoutService';_128_128const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY!);_128_128interface CheckoutFormProps {_128 collectionId: string;_128 nftId: string;_128 onSuccess: (result: any) => void;_128 onError: (error: any) => void;_128}_128_128function 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_128export 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)_63import { NextApiRequest, NextApiResponse } from 'next';_63import crypto from 'crypto';_63_63export 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_63async 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_63async 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_63async 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_112import React, { useState } from 'react';_112import { EmbeddedCheckout } from './EmbeddedCheckout';_112import { CustomCheckout } from './CustomCheckout';_112import { HostedCheckout } from './HostedCheckout';_112_112interface UniversalCheckoutProps {_112 collectionId: string;_112 nftId: string;_112 pricing: {_112 usd: number;_112 flow: number;_112 };_112 onCheckoutComplete: (result: any) => void;_112}_112_112export 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