@palomma/payment-widget
v0.1.0
Published
React payment button for Palomma. Creates an invoice, opens the hosted payment page in a modal, and reports the transaction result.
Downloads
78
Maintainers
Readme
@palomma/payment-widget
React payment button for Palomma. One click:
- Opens a centered popup window (synchronously, so popup blockers allow it) with a loading screen.
- Creates an invoice (
POST /invoices) and navigates the popup to the hosted payment page (paymentUrl). A real browser window — not an iframe — so bank flows like PSE that block framing work normally. - Polls
GET /invoices/{id}while the customer pays. - When the invoice reaches a terminal status (
paid,cancelled,chargeback), closes the popup and optionally shows the result below the button.
Pending payments are never lost or duplicated:
- If the customer closes the popup mid-payment, polling continues in the
background (
onDismissfires, the button switches to "Continuar pago"). A payment that resolves at the bank after the window closed still settles. - Clicking again reopens the same invoice instead of creating a new one.
- The pending invoice is persisted in localStorage keyed by
invoice.reference, so a page reload resumes it too. Use a stable reference per logical payment (your order id) — if you generate a fresh reference on every render, resume cannot match it after a reload. - When the invoice expires (its
expirationDate, or 30 minutes without one), the button locks as "Verificando el pago…" and polling continues: only a terminal status from the API (Palomma cancels expired invoices server-side) releases the button and allows a new invoice.
Install
npm install @palomma/payment-widgetReact 17+ is a peer dependency.
Quick start (sandbox)
import { PalommaPaymentButton } from '@palomma/payment-widget';
export function Checkout() {
return (
<PalommaPaymentButton
apiKey={import.meta.env.VITE_PALOMMA_SANDBOX_KEY}
environment="sandbox"
invoice={{
reference: `INV-${Date.now()}`,
amount: 50000, // COP
description: 'Suscripción mensual',
customerDocumentNumber: '900123456',
documentType: 'nit',
customerName: 'Empresa Ejemplo SAS',
}}
onPaymentEnd={(invoice) => console.log('final status:', invoice.status)}
>
Pagar con Palomma
</PalommaPaymentButton>
);
}Production: keep the API key off the browser
An apiKey passed to the component is visible to anyone who opens your page's
source. For production, proxy the two calls through your backend and pass
callbacks instead — the component behaves identically:
<PalommaPaymentButton
invoice={{ ... }}
createInvoice={async (params) => {
const res = await fetch('/api/palomma/invoices', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
return res.json();
}}
getInvoice={async (id) => {
const res = await fetch(`/api/palomma/invoices/${id}`);
return res.json();
}}
/>Your backend endpoints simply forward to the Palomma API with the secret key in
the Authorization: Bearer header.
Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| invoice | CreateInvoiceParams | required | Invoice created on click. redirectUrl defaults to the current page URL. |
| apiKey | string | — | Palomma API key (sandbox/internal use). |
| environment | 'production' \| 'sandbox' | 'production' | Which API base URL to use with apiKey. |
| createInvoice | (params) => Promise<Invoice> | — | Backend-proxied creation; takes precedence over apiKey. |
| getInvoice | (id) => Promise<Invoice> | — | Backend-proxied lookup used for polling. |
| pollIntervalMs | number | 3000 | Status polling interval while the popup is open. |
| showResult | boolean | true | Render the transaction result below the button when the payment ends. |
| resume | boolean | true | Persist the pending invoice (localStorage, keyed by reference) and resume it across clicks and reloads instead of creating duplicates. |
| popupWidth / popupHeight | number | 480 / 760 | Payment popup window size. |
| onPaymentStart | (invoice) => void | — | Invoice created, popup navigated to the payment page. |
| onPaymentEnd | (invoice) => void | — | Invoice reached paid, cancelled, or chargeback. With resume, may fire shortly after mount if the payment finished before a reload. |
| onError | (error) => void | — | Invoice creation failed, or the popup was blocked. |
| onDismiss | () => void | — | Customer closed the popup while the payment was still pending (background polling continues). |
| children | ReactNode | 'Pagar' | Button label. |
| className / style / disabled | — | — | Standard button customization. When className is set, the default inline styles are dropped entirely so your CSS is in full control. |
The API helpers are also exported for standalone use: createInvoice(options, params), getInvoiceById(options, id), and PalommaApiError.
Notes
- Amounts are in COP, integer values between 3,000 and 550,000,000.
- Payment completion is detected by polling the invoice status, not by the
redirectUrl— so the redirect target does not need any special handling. - The popup is opened synchronously inside the click handler, which is what keeps popup blockers happy. If a blocker still intervenes (rare), the button shows an error asking the customer to allow popups. Note that some mobile browsers open the popup as a new tab; the flow works the same there.
Development
npm install
npm run build # bundles ESM + CJS + .d.ts into dist/
npm run typecheckPublishing
npm version patch
npm publish --access public