Easy Labs
PaymentsGuides

Accept a wallet payment

Let buyers pay with a Solana wallet through the embedded checkout.

Goal

Accept payment in stablecoins from a buyer's self-custodial wallet (currently Solana / USDC) and reconcile the on-chain transaction back to an Order in your dashboard. Wallet payments are exposed through the same Embedded Checkout surface as card payments — you opt into them by including "crypto" in the session's payment_methods. The iframe handles wallet connection, message signing, and broadcasting the transaction; your code listens for the confirmed-on-chain event.

Prerequisites

  • Easy Labs API key with crypto enabled on your account — see Quickstart.
  • @easylabs/node (server) and @easylabs/react or @easylabs/browser (frontend) installed.
  • Your origin added to allowed_origins via client.updateEmbeddedCheckoutConfig.

Implementation

1. Create a session that allows crypto

import { createClient } from "@easylabs/node";

const easy = await createClient({ apiKey: process.env.EASY_API_KEY! });

const { data: session } = await easy.createEmbeddedCheckoutSession({
  mode: "payment",
  line_items: [{ price_id: "price_01HXXXXXXXXXXX", quantity: 1 }],
  success_url: "https://your-app.com/checkout/success",
  cancel_url: "https://your-app.com/checkout/cancel",
  payment_methods: ["card", "crypto"], // both, or ["crypto"] only
});

Pass ["crypto"] alone to force the wallet flow; pass both to let the buyer pick. The session's response carries a crypto_payment block with the destination address, USDC amount, and Solana Pay URL the iframe will use.

2. Mount the iframe and listen for confirmation

In React, EmbeddedCheckoutProvider's onSuccess callback fires for both card and crypto completions. The crypto-specific event includes the on-chain transaction signature:

"use client";
import { EmbeddedCheckout, EmbeddedCheckoutProvider } from "@easylabs/react";

export function CryptoCheckout({ clientSecret }: { clientSecret: string }) {
  return (
    <EmbeddedCheckoutProvider
      config={{
        clientSecret,
        onSuccess: ({ sessionId, status, tx_signature }) => {
          // For crypto payments, tx_signature is the Solana transaction signature.
          console.log("paid", { sessionId, status, tx_signature });
        },
      }}
    >
      <EmbeddedCheckout clientSecret={clientSecret} />
    </EmbeddedCheckoutProvider>
  );
}

In vanilla JS:

import { mountEmbeddedCheckout } from "@easylabs/browser";

const handle = mountEmbeddedCheckout("#checkout", {
  clientSecret,
  onCryptoConfirmed: (data) => {
    console.log("crypto confirmed", data);
  },
});

3. (Optional) Poll the chain status server-side

If the buyer leaves the page before the iframe reports confirmation, you can poll the session's crypto status from your backend:

const { data: cryptoStatus } = await easy.getCryptoPaymentStatus(session.id);
// cryptoStatus.status: "pending" | "confirmed" | "expired" | "failed"
// cryptoStatus.tx_signature: the on-chain signature once confirmed

The canonical signal is still the checkout.session.crypto_confirmed webhook — register it and use it to fulfill orders without depending on the browser staying open.

Tradeoffs

  • Crypto payments are USDC-only on Solana today. Other chains and tokens are on the roadmap.
  • The iframe handles wallet connection — you do not need to bundle a wallet adapter or @solana/web3.js in your app.
  • Refunds for crypto transactions are not automatic; they require an off-chain reconciliation. Open a support ticket if you need to refund a confirmed crypto Order.
  • tx_signature is the buyer's proof of payment on-chain. Store it alongside your Order for audit / customer service.

On this page