Checkout
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.
| Method | Requires | Result |
|---|---|---|
whatsappPhone | Opens a pre-filled order message in a new tab | |
| Pix | pixKey and currency: 'BRL' | Shows a scannable BR Code and a copy-paste string |
| Card | — | A demo flow, not a real acquirer |
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'.
- Emits a
begin_checkoutanalytics event. - Builds a
CheckoutPayloadfrom the current cart: items, subtotal, discount, shipping cost, total, the shipping info and the method. - Runs the adapter for that method.
- On success, calls
createOrderon your data provider if it implements one. - Emits
purchase, clears the cart, and either opens the WhatsApp tab or redirects to the payment URL whenautoRedirectis 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.
createOrderis 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.