An overview of the process works as follows:

1

Order Creation or Update

Your user selects the chain, token, and wallet they want to pay from via your application and you create or update an existing order with this information.

2

API Response

The API response returned from create or update order will include a serializedTransaction that your application uses to request payment from the user’s wallet.

3

User Confirms Transaction

After your application initiates the payment request the user must confirm the transaction.

4

Poll for Status

Your application will poll the GET order status and update the UI as the order progresses to the next phase.

During the initial quote phase of the order the payment status will be requires-quote.

Once the quote phase is completed, the order enters the payment phase.

For most orders the payment phase will begin with the status awaiting-payment, which indicates that the order is ready to be paid. However, it can also begin with requires-crypto-payer-address if this information is missing.

See below the full list of possible statuses:

Payment StatusExplanation
requires-quotestill in the quote phase
requires-crypto-payer-addresspayment.payerAddress is missing
crypto-payer-insufficient-fundspayerAddress cannot cover purchase for chain/currency given
awaiting-paymentready to submit payment
completedorder is in the delivery or order completion phase

Setting the Payer Address

The order must know the address that will be sending the crypto payment. This enables Crossmint’s payment listeners to associate incoming transactions with the correct order. To update the order with the payer address, call the update API as demonstrated below:

PATCH /api/2022-06-09/orders/<orderId>

{
    "payment": {
        "currency": "eth",
        "method": "base-sepolia",
        "payerAddress": "0x1234abcd…"
    }
}

The payerAddress is the wallet the user will be sending the payment from. Note that you must send the entire payment object even if the currency and/or method values are not changing.

Submitting the Payment

When you’ve fully prepared the order such that the payment status is awaiting-payment you’ll have everything necessary to request the crypto payment from your user. The details will be returned in the order.payment.preparation property.

The order.payment.preparation property contains details about the chain that Crossmint is expecting the payment to be received on, the payer address, and a serializedTransaction that you can use to open a payment request in the user’s wallet. See examples of how to parse the response and request transaction confirmation from the user below:

import { parseTransaction } from "viem";
import { useSendTransaction } from "wagmi";

const { sendTransactionAsync } = useSendTransaction();

const signAndSendTransaction = async (serializedTransaction) => {
    const txn = parseTransaction(serializedTransaction)

    try {
      await sendTransactionAsync(txn);
    } catch (error) {
      console.error("Error sending transaction:", error);
    }
};

You should never alter the values in the parsed transaction object. Simply parse the transaction object as shown in the example above. Changing any of these values may result in Crossmint not being able to validate the payment.

Calling the signAndSendTransaction function in the code snippet(s) above will open the user’s wallet and enable them to confirm the crypto payment.

Awaiting Payment Confirmation

We recommend a polling interval of about 2500ms and never below 500ms.

You can use client-side or server-side API keys to implement headless checkout. Check the code samples for the type of API key you’re using in your application.

const getOrderPaymentStatus = async () => {
    const apiUrl = "https://staging.crossmint.com/api/2022-06-09";
    try {
        const res = await fetch(`${apiUrl}/orders/${order.orderId}`, {
            method: "GET",
            headers: {
                "Content-Type": "application/json",
                "x-api-key": process.env.NEXT_PUBLIC_CROSSMINT_API_KEY,
                authorization: clientSecret, // saved from response of create order call
            },
        });

        const refreshedOrder = await res.json();

        setOrder(refreshedOrder);

        return refreshedOrder.payment.status;
    } catch (e) {
        console.error(e);
        throw new Error("Failed to fetch order");
    }
};

const pollPaymentStatus = async () => {
    const intervalId = setInterval(async () => {
        const status = await getOrderPaymentStatus();
        console.log("payment status: ", status);
        if (status === "completed") {
            clearInterval(intervalId);
        }
    }, 2500);

    // Set a timeout to stop polling after 60 seconds
    setTimeout(() => {
        clearInterval(intervalId);
        console.log("Taking longer than expected...");
    }, 60000);
};

Once the payment is confirmed, you can move on to the delivery phase of the order lifecycle.