diff --git a/.cspell/custom-words.txt b/.cspell/custom-words.txt index edf126445..2adbf649c 100644 --- a/.cspell/custom-words.txt +++ b/.cspell/custom-words.txt @@ -158,3 +158,10 @@ punycode userinfo examplecorp zapatillas +Pix +pix +Mercadopago +mercadopago +Pago +boleto +copia diff --git a/docs/specification/payment-display.md b/docs/specification/payment-display.md new file mode 100644 index 000000000..2f9970b66 --- /dev/null +++ b/docs/specification/payment-display.md @@ -0,0 +1,140 @@ + + +# Payment Display Extension + +* **Capability Name:** `com.mercadopago.payment.display` +* **Schema:** `https://ucp.dev/schemas/shopping/payment_display.json` + +## Overview + +The Payment Display extension lets a Business complete an **in-session, +out-of-band payment method** — such as **Pix** in Brazil — without redirecting +the buyer or handing off a `continue_url`. When the buyer chooses such a method, +the Business surfaces an **inert display artifact** (a QR image, a +copy-and-paste code, and/or hosted instructions) as an outstanding Action on the +checkout. The Platform renders it, the buyer pays in their bank app, and the +**same** Complete Checkout operation resolves once payment is confirmed. + +This is a concrete, vendor-namespaced Action type stacked on the generic +[Actions](overview.md#actions) primitive. It defines the Action type +`com.mercadopago.payment.display` and the shape of its `config`. + +**Key features:** + +* Display Pix (and equivalent QR/code methods) natively — no iframe, no redirect +* Works across surfaces: web, native app, and voice (a `code` fallback exists + when no screen can show an image) +* Payment resolves in-session by polling Get Checkout — the Platform never + re-drives Complete +* Strict render/trust contract: display data only, inert fields, single + allowlisted loadable field + +**Dependencies:** + +* Checkout Capability +* The [Actions](overview.md#actions) primitive on Checkout + +## Discovery + +Businesses advertise this extension in their profile, extending the Checkout +capability: + + +```json +{ + "ucp": { + "version": "{{ ucp_version }}", + "capabilities": { + "com.mercadopago.payment.display": [ + { + "version": "{{ ucp_version }}", + "extends": ["dev.ucp.shopping.checkout"], + "spec": "https://ucp.dev/{{ ucp_version }}/specification/payment-display", + "schema": "https://ucp.dev/{{ ucp_version }}/schemas/shopping/payment_display.json" + } + ] + } + } +} +``` + +## Schema + +When this extension is active, the checkout `actions` map MAY carry outstanding +`com.mercadopago.payment.display` instances. Each instance is a common Action +instance (`id`, optional `config`) whose `config` is the display artifact below. + +### Payment Display Config + +{{ extension_schema_fields('payment_display.json#/$defs/config', 'payment_display') }} + +At least one of `code`, `image`, or `instructions_url` MUST be present so the +Platform always has something renderable. + +## Render and Trust Contract + +The artifact carries **public display data only** and is designed to be safe to +render on any surface. Implementations MUST honor the following: + +* **No secrets transit the artifact.** It never contains a PAN, CVV, OTP, + token, credential, or any other sensitive value — only data already meant to + be shown to the buyer. +* **`image` is inert.** It is rendered as a static image only. The Platform MUST + NOT execute, interpret, or fetch it as anything other than image bytes, and + MUST restrict it to `data:` or `https:` URIs. +* **`code` is display text.** The Platform SHOULD present it with a copy + affordance. It MUST NOT be interpreted as a URL, deep link, or executable + content. +* **`instructions_url` is the only loadable field.** When present, the Platform + MUST constrain it to an `https:` origin allowlist advertised for the handler, + MUST open it as a plain document, and MUST NOT auto-submit forms or forward + buyer data to it. +* **`expires_at` bounds the artifact.** After it passes, the Platform stops + rendering the artifact and re-fetches checkout state rather than continuing to + show a stale QR/code. + +## Resolution Flow + +```text +1. Platform → Business : Complete Checkout (buyer chose Pix) +2. Business → Platform : status = complete_in_progress + actions { + "com.mercadopago.payment.display": [ + { "id": "...", "config": { "type": "qr_code", ... } } + ] + } +3. Platform : renders QR image + copy button for `code` + (works on web / native / voice — no frame) +4. Buyer : pays via bank app (out of band) +5. Platform → Business : polls Get Checkout (MUST NOT re-drive Complete) +6. Mercado Pago : confirms payment → Business resolves +7. Business → Platform : status = completed, order present, Action gone +``` + +On expiry or failure, the Business returns the checkout with a `recoverable` +error [Message](overview.md#messages) whose `path` points at the exact Action +occurrence, e.g. `$.actions['com.mercadopago.payment.display'][0]`. The Platform +surfaces the recovery path (for example, refreshing the artifact) rather than +re-driving Complete. + +## Relationship to Async Payment Methods + +This extension covers methods that resolve **within the checkout session** +(seconds), so the display artifact lives on the checkout and the outcome arrives +by polling. Methods that settle **after the session ends** (hours to days) — +such as boleto or cash vouchers — are out of scope here and are addressed +separately at the order level via order lifecycle webhooks. diff --git a/mkdocs.yml b/mkdocs.yml index 3c512e000..88e36abc0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ nav: - Discounts Extension: specification/discount.md - Fulfillment Extension: specification/fulfillment.md - Loyalty Extension: specification/loyalty.md + - Payment Display Extension: specification/payment-display.md - Split Payments Extension: specification/split-payments.md - Cart Capability: - Overview: specification/cart.md @@ -328,6 +329,12 @@ plugins: memberships, tiers, benefits, and reward earning forecasts while managing provisional eligibility claims and enforcing data-minimization privacy rules. + - specification/payment-display.md: >- + Payment Display Extension, defining an in-session, out-of-band + payment Action (e.g., Pix) that surfaces an inert display artifact + (QR image, copy-and-paste code, hosted instructions) for the + Platform to render without redirect or continue_url, resolved by + polling Get Checkout, under a strict render and trust contract. Cart Capability: - specification/cart.md: >- Pre-purchase Cart Capability, detailing item collection, state diff --git a/source/schemas/shopping/payment_display.json b/source/schemas/shopping/payment_display.json new file mode 100644 index 000000000..14dfb30dc --- /dev/null +++ b/source/schemas/shopping/payment_display.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://ucp.dev/schemas/shopping/payment_display.json", + "name": "com.mercadopago.payment.display", + "title": "Payment Display Extension", + "description": "Extends Checkout with a display Action for in-session, out-of-band payment methods (e.g., Pix). The Business surfaces an inert display artifact (QR image, copy-and-paste code, hosted instructions) that the Platform renders so the buyer can complete payment in a separate app; the same Complete Checkout operation resolves afterward via polling Get Checkout, with no redirect or continue_url handoff. Declares the Action type 'com.mercadopago.payment.display' on top of the common Actions primitive.", + "$defs": { + "config": { + "type": "object", + "title": "Payment Display Config", + "description": "Public, inert display artifact for one outstanding payment method. Contains display data only: no PAN, OTP, credential, or other secret ever transits this artifact. Render fields are non-executable — 'image' is shown as a static image (never as HTML or script), 'code' is display text, and 'instructions_url' is the only loadable field.", + "required": ["type"], + "properties": { + "type": { + "type": "string", + "description": "The display artifact kind, so the Platform knows how to render it. Reverse-domain or short token defined by this extension.", + "examples": ["qr_code"] + }, + "code": { + "type": "string", + "minLength": 1, + "description": "Copy-and-paste payment string (Pix 'copia e cola'). Display text only — the Platform SHOULD offer a copy affordance. MUST NOT be interpreted as a URL or executed." + }, + "image": { + "type": "string", + "format": "uri", + "description": "QR (or equivalent) image encoding the payment, as a 'data:' or 'https:' URI. The Platform MUST render it as a static image only and MUST NOT execute, parse, or fetch it as anything other than image bytes." + }, + "instructions_url": { + "type": "string", + "format": "uri", + "description": "Optional hosted, human-readable payment instructions. The ONLY loadable field in this artifact; the Platform MUST constrain it to an 'https:' origin allowlist advertised for the handler and MUST NOT auto-submit or pass buyer data to it." + }, + "reference": { + "type": "string", + "description": "Opaque payment reference (e.g., Pix end-to-end id) for reconciliation. Not buyer-facing; not a credential." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "RFC 3339 timestamp after which this artifact is no longer valid. On expiry the Platform stops rendering it and re-fetches checkout state." + } + }, + "anyOf": [ + { "required": ["code"] }, + { "required": ["image"] }, + { "required": ["instructions_url"] } + ] + }, + "instance": { + "title": "Payment Display Action Instance", + "description": "One outstanding payment-display Action: the common Action instance with a payment-display 'config'.", + "allOf": [ + { "$ref": "../common/types/actions.json#/$defs/instance" }, + { + "type": "object", + "properties": { + "config": { "$ref": "#/$defs/config" } + } + } + ] + }, + "dev.ucp.shopping.checkout": { + "title": "Checkout with Payment Display", + "description": "Checkout whose 'actions' map MAY carry outstanding 'com.mercadopago.payment.display' instances while payment is pending out of band.", + "allOf": [ + { "$ref": "checkout.json" }, + { + "type": "object", + "properties": { + "actions": { + "type": "object", + "properties": { + "com.mercadopago.payment.display": { + "type": "array", + "minItems": 1, + "description": "Outstanding payment-display Actions. Typically a single instance for the buyer's chosen method.", + "items": { "$ref": "#/$defs/instance" } + } + } + } + } + } + ] + } + } +}