UNPKG

@gpmpay/sdk

Version:

Official Node.js SDK for GPM Pay — VietQR codes, transaction webhooks, and payment reconciliation.

76 lines (65 loc) • 2.61 kB
'use server'; import { GpmPay } from '@gpmpay/sdk'; import { GpmPayPermissionError } from '@gpmpay/sdk'; import { buildPaymentInstructions, type PaymentInstructions as SdkPaymentInstructions, } from '@gpmpay/sdk/vietqr'; // Module scope: constructed once per server instance. Throws at import time if // GPMPAY_API_TOKEN is missing — which is what you want, so a misconfigured // deploy fails immediately rather than at the first checkout. const client = new GpmPay({ apiToken: process.env.GPMPAY_API_TOKEN! }); /** * Only these fields cross to the browser. Never the client, never the token. * * Derived from the SDK's own return type rather than re-declared, so a change * to `buildPaymentInstructions` breaks this build instead of silently drifting. */ export type CheckoutPayment = SdkPaymentInstructions & { code: string }; /** * Mint the payment code and build the QR. * * GPM Pay has no "create order" endpoint — the code is yours, derived from the * cart so a double-submitted form produces the *same* code rather than a second * pending payment. Store it against the cart before returning. */ export async function createPayment( cartId: string, amountVnd: number, ): Promise<CheckoutPayment> { const amount = Math.round(amountVnd); // must be an integer number of VND // Keep it A-Z0-9 and short: VietQR truncates the description to 25 chars and // some banks prepend their own prefix to the transfer content. const code = `ORD${cartId}`; try { const account = await client.bankAccounts.retrieve( process.env.GPMPAY_BANK_ACCOUNT_ID!, ); const instructions = buildPaymentInstructions({ bankAccount: account, amount, transferContent: code, }); // await db.carts.update(cartId, { paymentCode: code, status: 'AWAITING_PAYMENT' }); return { code, ...instructions }; } catch (error) { if (error instanceof GpmPayPermissionError) { console.error( `GPM Pay token rejected (${error.reason}): ${error.missingScope ?? 'n/a'}`, ); throw new Error('Payment is misconfigured — please contact support.'); } throw error; } } /** * Let the page poll your own database while it waits. * * There is nothing to poll on the GPM Pay side: payment status lives in your * system, written by the webhook route. Polling GPM Pay's transaction list * would work but costs a round-trip per check and races with the webhook. */ export async function getPaymentStatus(code: string) { // return db.carts.findByPaymentCode(code); return { code, status: 'PENDING' as const, paidAt: null }; }