import { callbackKind, str } from './callback-fields'; /** * Framework-free callback routing for a DIRECT SELLER. Your route handler: * * 1. parse the body (PayMe POSTs application/x-www-form-urlencoded) * 2. const decision = decideSellerCallback(body) * 3. record the event; for 'verify-by-query', look the record up by the id * YOU stored, query PayMe and apply only what it confirms: * sale -> get-transactions -> verifySaleAgainstTransactions() * subscription -> get-subscriptions -> verifySubscriptionAgainstQuery() * 4. answer HTTP 200 for every decision you recorded * * A direct seller cannot check `payme_signature` (it is computed with partner * credentials), and subscription callbacks carry none at all, so no callback * body is ever trusted on its own. * * Reference: https://docs.payme.io/payments/getting-started/callbacks */ export type SellerCallbackKind = 'sale' | 'subscription' | 'partner' | 'unknown'; export type SellerCallbackDecision = /** Confirm with get-transactions, then apply PayMe's answer — never the body. */ | { action: 'verify-by-query'; kind: 'sale'; paymeSaleId: string; reason: string } /** Confirm with get-subscriptions, then apply PayMe's answer — never the body. */ | { action: 'verify-by-query'; kind: 'subscription'; subPaymeId: string; reason: string } /** Record it; change nothing because of it. */ | { action: 'record'; kind: SellerCallbackKind; reason: string }; export function decideSellerCallback( body: Record, ): SellerCallbackDecision { const kind = callbackKind(body); switch (kind) { case 'sale': { const paymeSaleId = str(body.payme_sale_id); return paymeSaleId ? { action: 'verify-by-query', kind, paymeSaleId, reason: 'Sale callback: confirm with get-transactions before acting.', } : { action: 'record', kind, reason: 'Sale callback without payme_sale_id: nothing to look up.', }; } case 'subscription': { const subPaymeId = str(body.sub_payme_id); return subPaymeId ? { action: 'verify-by-query', kind, subPaymeId, reason: 'Subscription callback: confirm with get-subscriptions before acting.', } : { action: 'record', kind, reason: 'Subscription callback without sub_payme_id: nothing to look up.', }; } case 'seller': case 'withdrawal': // PayMe sends these to the partner's callback URL, not to a seller's. return { action: 'record', kind: 'partner', reason: 'Seller and payout notifications are sent to partners; not expected here.', }; default: return { action: 'record', kind, reason: `Unknown notify_type ${JSON.stringify(body.notify_type ?? null)}.`, }; } }