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