Skip to main content

Checkout

By the end of this page

A working checkout on WhatsApp, Pix or your own payment provider — and you will know exactly what PlugStore does and does not handle.

What ships​

<CatalogApp /> includes a checkout page with a payment-method picker. No code required: fill in whatsappPhone and/or pixKey in config and the corresponding options appear.

MethodRequiresResult
WhatsAppwhatsappPhoneOpens a pre-filled order message in a new tab
PixpixKey and currency: 'BRL'Shows a scannable BR Code and a copy-paste string
Card—A demo flow, not a real acquirer
The card option is a demo

It exists so the flow is complete on a fresh install. It does not talk to any acquirer and takes no money. Replace it with a real adapter — see Custom adapters — before going live.

useCheckout​

For your own checkout UI on top of <CatalogProvider />:

import { useCheckout } from '@neverleans-labs/plug-store-core';
import type { ShippingInfo } from '@neverleans-labs/plug-store-core';

function PayButton({ shipping }: { shipping: ShippingInfo }) {
const { processCheckout, loading, result, error } = useCheckout();

const pay = async () => {
const res = await processCheckout(shipping, 'pix');
if (res.success) console.log(res.pixCode);
};

return (
<>
<button onClick={pay} disabled={loading}>
{loading ? 'Processing…' : 'Pay with Pix'}
</button>
{error && <p role="alert">{error}</p>}
</>
);
}

ShippingInfo​

interface ShippingInfo {
firstName: string;
lastName: string;
email: string;
address: string;
city: string;
state: string;
zip: string;
country: string;
}

What processCheckout does​

processCheckout(shippingInfo, method?, notes?) — method defaults to 'whatsapp'.

  1. Emits a begin_checkout analytics event.
  2. Builds a CheckoutPayload from the current cart: items, subtotal, discount, shipping cost, total, the shipping info and the method.
  3. Runs the adapter for that method.
  4. On success, calls createOrder on your data provider if it implements one.
  5. Emits purchase, clears the cart, and either opens the WhatsApp tab or redirects to the payment URL when autoRedirect is set.

PaymentResult​

interface PaymentResult {
success: boolean;
orderId?: string;
paymentUrl?: string; // Stripe / Mercado Pago redirect
pixCode?: string; // BR Code, copy-paste
pixQrCodeUrl?: string; // QR image URL
whatsappUrl?: string;
error?: string;
}

processCheckout never throws — a failure comes back as { success: false, error } and is also exposed on the hook's error.

Bundled adapters​

An adapter is a single function:

type PaymentGatewayAdapter = (payload: CheckoutPayload) => Promise<PaymentResult>;

whatsappGateway(phoneOrOptions)​

Builds a https://wa.me/… link with the order formatted as a message: customer name, address, each line item, discount, shipping and total. Fails with a clear error when no phone is configured.

whatsappGateway({
phone: '5511999999999',
currency: 'BRL', // amounts are formatted for this currency
language: 'pt', // the message is written in this language
});

whatsappGateway('5511999999999') still works and defaults to BRL and Portuguese. useCheckout passes the store's own currency and language, so a BRL store writes R$ 12,90 and a USD store writes $12.90.

pixGateway(keyOrOptions)​

Generates a real static Pix BR Code. See Pix payments.

pixGateway({
pixKey: 'bloom@example.com',
merchantName: 'Bloom Cosmetics',
merchantCity: 'Sao Paulo',
});

stripeGateway(endpoint, options?)​

POSTs the payload to your endpoint and expects { url } or { paymentUrl } back. Your server creates the Stripe Checkout Session — the secret key never touches the browser.

stripeGateway('/api/checkout/stripe', {
headers: { Authorization: `Bearer ${token}` },
});

mercadopagoGateway(endpoint, options?)​

Same shape. Your server creates the preference and returns init_point, sandbox_init_point or paymentUrl.

Both default to /api/checkout/stripe and /api/checkout/mercadopago when selected through the built-in checkout page.

Custom adapters​

Override any method by passing your own function:

import { useCheckout } from '@neverleans-labs/plug-store-core';
import type { PaymentGatewayAdapter } from '@neverleans-labs/plug-store-core';

const pagarme: PaymentGatewayAdapter = async (payload) => {
const res = await fetch('/api/pagarme/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});

if (!res.ok) return { success: false, error: `Gateway error ${res.status}` };

const data = await res.json();
return { success: true, orderId: data.id, paymentUrl: data.checkout_url };
};

function Checkout() {
const { processCheckout } = useCheckout({
adapters: { stripe: pagarme },
autoRedirect: true,
});
// …
}

adapters is keyed by PaymentMethod — 'whatsapp' | 'pix' | 'stripe' | 'mercadopago'. There is no way to add a fifth key today, so reuse the slot whose semantics match: a redirect-based provider fits stripe.

What is not handled​

  • Payment confirmation. Nothing tells the store that a Pix transfer landed. That needs a PSP webhook on your server.
  • Inventory decrement. createOrder is called; what it does is yours.
  • Tax and fiscal documents. Out of scope.
  • Shipping quotes. The shipping cost comes from cart logic, not a carrier API.

Each of these is deliberate — they need a server, a contract or a tax regime, and guessing at them in a client-side library is how stores lose money quietly. If one of them is blocking a store you are shipping, tell us which.

Next​