# Payments Implemented State ## Current Split - Ballbox owns the admin surface, native MercadoPago webhook intake, and native QR order creation. - `/notification/mercadopago` is the first-party MercadoPago QR webhook route that fetches the upstream order, ignores unsupported merchant-order topics, and persists approved orders. - `/api/integrations/payments/orders` is the first-party QR order creation route. - `/api/integrations/payments/sessions` lists Ballbox-local approved payment records plus native MercadoPago config status. - `/admin/payments` now manages local approved payment sessions directly for reconciliation/correction and also generates live MercadoPago QR orders for arbitrary amounts or product-priced orders. - The admin QR generator is an operator test/debug harness for Mercado Pago and webhook validation. It is not the current production payment UX for TCN machines. - The QR generator accepts money values in major units with decimals (for example `15.00` or `1000.00`) and converts them to Mercado Pago minor units before submission. ## TCN Machine Reality Boundary - Current TCN machine production payments do not run through this Ballbox-native Mercado Pago path. - Current machine truth is documented in `docs/tcn-machine-reality-2026-05-26.html`. - TCN does not currently provide direct Mercado Pago support. - TCN does not currently provide a generic third-party payment setup path. - The only current real machine payment path is an external physical device flow, such as EasyCoin. - Do not treat Ballbox native QR generation as the production TCN machine payment integration unless new evidence changes the machine reality. ## APIs - `/api/admin/payments/operations` - `/api/admin/payments/sessions` - `/api/admin/payments/sessions/[sessionId]` - `/api/integrations/payments/health` - `/api/integrations/payments/sessions` - `/api/integrations/payments/orders` - `/notification/mercadopago` ## Persistence - Ballbox persists approved payment records as `PaymentSession` rows in Ballbox Postgres. - Native MercadoPago webhook intake fetches the upstream order, maps approved orders into the canonical Ballbox payment shape, and persists them locally. - QR Code notifications use the upstream order fetch plus canonical-reference mapping as the acceptance gate. - Admin operators can now create, update, and delete local `PaymentSession` rows for reconciliation. - Post-`pnpm db:push` plus `pnpm db:seed` verification confirms Ballbox Postgres keeps canonical `PaymentSession.vendingMachineId` and does not expose a `PaymentSessionRef` table or `machineId` compatibility column. - Payments admin session routes now share Prisma error classification through `lib/prisma-errors.ts` instead of importing those helpers from `lib/ballbox-store.ts`. - Playwright now covers local `/admin/payments` create, edit, and delete flows with deterministic cleanup of E2E `PaymentSession` rows. ## Integration Contract - Ballbox native MercadoPago webhook path is `/notification/mercadopago`. - Ballbox native QR order creation path is `/api/integrations/payments/orders`. - For the current Ballbox Mercado Pago account/API surface, QR order create relies on the account-level webhook configuration instead of sending inline `notification_url` in the order payload, because Mercado Pago rejects that field on `POST /v1/orders` for this setup. - That route accepts an explicit amount for arbitrary charges or falls back to the Ballbox `Product.priceMinor` when the amount is omitted, which powers per-product QR generation in the admin UI. - The order route rejects values below Mercado Pago's minimum charge (`15.00 ARS`) before calling the provider. - QR webhook persistence now depends on the upstream order fetch plus canonical-reference mapping; merchant-order topic events are ignored. - Legacy approved-callback path `/api/integrations/payments/approved` is removed as an operational integration and now returns `410 PAYMENTS_APPROVED_CALLBACK_REMOVED`. - Native payment correlation requires canonical `externalReference` values shaped as `payment-vm_{vendingMachineId}-txn_{machineTxnId}`. - `NEXT_PUBLIC_SITE_URL` is required if Ballbox should expose a concrete `recommendedMercadoPagoWebhookUrl`. ## Validation Status - Host app now owns the native MercadoPago order creation + webhook persistence path inside Ballbox. - Real-world validation is now closed: one real public webhook and one real approved payment were persisted and verified in `/admin/payments` on 2026-05-13. - Live MercadoPago payload quirks handled during closure: `topic_merchant_order_wh` remains intentionally ignored, `items[0].external_code` is accepted as product-id fallback, and `total_amount` is parsed from either string or number before persistence. - The Ballbox-native Mercado Pago path is real and validated as a Ballbox capability. - The Ballbox-native Mercado Pago path is not the current production payment path for TCN machines. - The webhook route is fetch-first: the webhook is just the trigger, and Ballbox verifies the order state by fetching Mercado Pago before persisting. - Recent replay validation against two older live `ORD...` ids returned `MERCADOPAGO_ORDER_NOT_APPROVED_expired`; see `docs/mercadopago-webhook-replay-notes-2026-04-23.md` when resuming webhook persistence checks. - For current TCN machine payment truth, use `docs/tcn-machine-reality-2026-05-26.html`. ## Manual E2E Result - End-to-end payment validation succeeded against Ballbox native MercadoPago webhook configuration. - Confirmed setup used: - Ballbox `BALLBOX_MERCADOPAGO_ACCESS_TOKEN` - Ballbox `BALLBOX_MERCADOPAGO_WEBHOOK_SECRET` - Ballbox `BALLBOX_MERCADOPAGO_EXTERNAL_POS_ID` - public HTTPS webhook endpoint `/notification/mercadopago` - Confirmed outcome: - a real payment was approved upstream - MercadoPago reached Ballbox webhook publicly - Ballbox fetched order details and recorded the approved payment locally - a `PaymentSession` row was persisted locally - the session was visible in `/admin/payments`