Plaidly Docs

Shopify Integration

Accept crypto payments on Shopify using Plaidly as a custom payment provider

Shopify Integration

Plaidly integrates with Shopify via the Payments App API, allowing you to offer cryptocurrency as a payment option alongside credit cards and PayPal at checkout.

Prerequisites

  • A Shopify Partner account
  • A Shopify app with the payment_gateways permission
  • Your Plaidly API key and webhook secret

Overview

Shopify's Payments App API calls your backend when a customer selects your payment option. Your backend:

  1. Creates a Plaidly payment session
  2. Redirects the customer to a payment page showing the crypto address
  3. Receives a webhook from Plaidly when payment is confirmed
  4. Resolves the Shopify payment session as complete

1. Register as a payment provider

In your Shopify app's GraphQL mutation, register your payment provider:

mutation {
  paymentsAppConfigure(
    externalHandle: "plaidly-crypto"
    ready: true
  ) {
    paymentsAppConfiguration {
      externalHandle
      ready
    }
    userErrors {
      field
      message
    }
  }
}

2. Handle payment session creation

Shopify sends a POST request to your payment_session_create endpoint when a customer checks out:

// app/api/shopify/payment-session/route.ts
import { PlaidlyClient } from '@plaidly/node';
import { NextRequest } from 'next/server';
import { verifyShopifyHmac } from '@/lib/shopify';

const plaidly = new PlaidlyClient({
  apiKey: process.env.PLAIDLY_API_KEY!,
});

export async function POST(req: NextRequest) {
  const body = await req.json();

  // Verify the request is from Shopify
  const hmac = req.headers.get('x-shopify-hmac-sha256') ?? '';
  if (!verifyShopifyHmac(JSON.stringify(body), hmac)) {
    return new Response('Unauthorized', { status: 401 });
  }

  const { id: shopifySessionId, amount, currency, customer } = body;

  // Create a Plaidly payment session
  const session = await plaidly.sessions.create({
    amount: amount.amount,
    currency: 'USDC',
    chain: 'solana',
    network: 'mainnet',
    callbackUrl: `${process.env.BASE_URL}/api/shopify/webhook`,
    metadata: {
      shopifySessionId,
      shopifyOrderId: body.kind,
      customerEmail: customer?.email,
    },
  });

  // Return redirect URL to your payment page
  return Response.json({
    redirect_url: `${process.env.BASE_URL}/pay/${session.id}`,
  });
}

3. Create the payment page

Show the customer the crypto address and payment details:

// app/pay/[sessionId]/page.tsx
import { PlaidlyClient } from '@plaidly/node';

const plaidly = new PlaidlyClient({
  apiKey: process.env.PLAIDLY_API_KEY!,
});

export default async function PayPage({
  params,
}: {
  params: Promise<{ sessionId: string }>;
}) {
  const { sessionId } = await params;
  const session = await plaidly.sessions.get(sessionId);

  return (
    <div>
      <h1>Send {session.amount} {session.currency}</h1>
      <p>To address: {session.walletAddress}</p>
      <p>On network: {session.chain}</p>
      <p>Session expires: {session.expiresAt}</p>
      {/* Add QR code component here */}
    </div>
  );
}

4. Handle Plaidly webhooks

When Plaidly confirms a payment, resolve the Shopify payment session:

// app/api/shopify/webhook/route.ts
import { verifyWebhookSignature } from '@plaidly/node/webhooks';
import { NextRequest } from 'next/server';
import { resolveShopifyPayment } from '@/lib/shopify';

export async function POST(req: NextRequest) {
  const body = await req.text();
  const sig = req.headers.get('x-plaidly-signature') ?? '';

  let event;
  try {
    event = verifyWebhookSignature(
      body,
      sig,
      process.env.PLAIDLY_WEBHOOK_SECRET!,
    );
  } catch {
    return new Response('Invalid signature', { status: 400 });
  }

  if (event.event === 'payment.confirmed') {
    const { shopifySessionId } = event.metadata;

    // Resolve the Shopify payment session
    await resolveShopifyPayment(shopifySessionId, {
      status: 'resolve',
      transactionId: event.session.id,
    });
  }

  return new Response('OK');
}

5. Resolve a Shopify payment session

// lib/shopify.ts
export async function resolveShopifyPayment(
  sessionId: string,
  resolution: { status: 'resolve' | 'reject'; transactionId?: string },
) {
  const mutation = `
    mutation PaymentSessionResolve($id: ID!) {
      paymentSessionResolve(id: $id) {
        paymentSession {
          id
          state { ... on PaymentSessionStateResolved { code } }
        }
        userErrors { field message }
      }
    }
  `;

  const response = await fetch(
    `https://${process.env.SHOPIFY_SHOP}/admin/api/2024-01/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': process.env.SHOPIFY_ACCESS_TOKEN!,
      },
      body: JSON.stringify({
        query: mutation,
        variables: { id: `gid://shopify/PaymentSession/${sessionId}` },
      }),
    },
  );

  return response.json();
}

Testing

Use Shopify's Payments App simulator and set network: 'testnet' in Plaidly to test the full flow without real funds.

Supported currencies

You can let customers choose their preferred chain/token on your payment page. The Shopify order currency is the fiat reference — Plaidly handles the crypto equivalent calculation.

On this page