Skip to content

feat: add com.mercadopago.payment.render_artifact extension - #635

Open
gforgab wants to merge 3 commits into
Universal-Commerce-Protocol:mainfrom
gforgab:gforgab/payment-display-extension
Open

feat: add com.mercadopago.payment.render_artifact extension#635
gforgab wants to merge 3 commits into
Universal-Commerce-Protocol:mainfrom
gforgab:gforgab/payment-display-extension

Conversation

@gforgab

@gforgab gforgab commented Jul 24, 2026

Copy link
Copy Markdown

What

Adds com.mercadopago.payment.render_artifact, a vendor-namespaced Checkout
extension
that models in-session, out-of-band payment methods (e.g.
Pix) as a pure-render Action on top of the generic actions primitive
introduced in #582.

When the buyer picks such a method, the Business surfaces an inert display
artifact (QR image, 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 by polling
Get Checkout — no redirect, no continue_url.

Why this shape

  • Builds directly on the merged actions primitive (feat: extension-defined Actions primitive #582). Instance is
    { id, config? }; no required field — gating is expressed via a
    recoverable error Message whose path points at the action occurrence,
    per the outcome of the [RFC] Proposal: Modeling 3DS and Step-Up Actions in UCP #517 discussion.
  • Vendor namespace, not core. Consistent with the Tech Council's steer on
    [RFC] Proposal: Modeling 3DS and Step-Up Actions in UCP #517 to keep core small and let independent, community-driven implementations
    land first. Namespace derives from the handler domain (com.mercadopago.*,
    matching com.mercadopago.checkout).
  • The Action name describes the mechanism. The final segment
    (render_artifact) names what the Platform does — render an inert artifact the
    buyer acts on out of band — rather than "display info about a payment", and
    mirrors the core payment-action convention merged in feat: Add Payment Authentication Actions #458
    (dev.ucp.payment.device_data_collection, dev.ucp.payment.three_ds_challenge).
  • Carries an explicit render/trust contract: display data only (no
    PAN/OTP/credentials), image is inert, code is display text, and
    instructions_url is the only loadable field (https origin allowlist).

Scope

Changes

  • source/schemas/shopping/payment_render_artifact.json — extension schema.
  • docs/specification/payment-render-artifact.md — spec (discovery, schema,
    render/trust contract, resolution flow).
  • mkdocs.yml — nav under Checkout Capability + llmstxt entry.
  • .cspell/custom-words.txt — domain terms.

Related

Under review — feedback welcome on the extension shape and the render/trust
contract wording.

@google-cla

google-cla Bot commented Jul 24, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gforgab
gforgab force-pushed the gforgab/payment-display-extension branch 3 times, most recently from fe0370c to cd63abe Compare July 27, 2026 15:30
@gforgab
gforgab marked this pull request as ready for review July 27, 2026 15:32
@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Jul 27, 2026
@gforgab
gforgab force-pushed the gforgab/payment-display-extension branch from cd63abe to a81ebc8 Compare July 27, 2026 15:35
@damaz91 damaz91 added status:under-review area:payments gov:needs-tc-review and removed status:needs-triage Signal that the PR is ready for human triage labels Jul 28, 2026
@gforgab gforgab changed the title feat: add com.mercadopago.payment.display extension feat: add com.mercadopago.payments.display extension Jul 28, 2026
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/payment_display.json",
"name": "com.mercadopago.payments.display",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming on this object is a somewhat confusing - at a glance it reads like "payment display" object in a generic sense but I understand it is more specific focused for a particular payment system. Could you please revise the naming and/or organization?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I.e. as opposed to "this object defines display info for a payment that occurred", it instead describes a display-based payment system artifact.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dkoch74 — you're right. com.mercadopago.payments.display reads as "display info about a payment that occurred," when what it actually models is a payment method whose completion is a rendered, out-of-band artifact.

I've renamed the Action type to com.mercadopago.payment.render_artifact:

  • singular payment + a concrete action as the final segment, matching the convention just merged for core payment actions in feat: Add Payment Authentication Actions #458 (dev.ucp.payment.device_data_collection, dev.ucp.payment.three_ds_challenge);
  • render_artifact names the mechanism — the Platform renders an inert artifact the buyer acts on out of band — rather than display metadata about a payment.

The rename is applied throughout: Action-type key, schema $id and filename (payment_render_artifact.json), titles, and the spec doc. Pushed in the latest commit.

If by "organization" you also meant structure beyond the name (e.g. how this sits relative to the core payment actions from #458), happy to align further — just let me know.

gforgab added a commit to gforgab/ucp that referenced this pull request Aug 5, 2026
Addresses review feedback on Universal-Commerce-Protocol#635: `com.mercadopago.payments.display` read as
"display metadata about a payment" rather than what it models — a payment method
whose completion is a rendered, out-of-band artifact. Rename to a payment Action
whose final segment is the concrete action, aligning with the core payment-action
convention merged in Universal-Commerce-Protocol#458 (dev.ucp.payment.device_data_collection,
dev.ucp.payment.three_ds_challenge). Applied throughout: Action-type key, schema
$id and filename, titles, and the spec doc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gforgab added a commit to gforgab/ucp that referenced this pull request Aug 5, 2026
Addresses review feedback on Universal-Commerce-Protocol#635: `com.mercadopago.payments.display` read as
"display metadata about a payment" rather than what it models — a payment method
whose completion is a rendered, out-of-band artifact. Rename to a payment Action
whose final segment is the concrete action, aligning with the core payment-action
convention merged in Universal-Commerce-Protocol#458 (dev.ucp.payment.device_data_collection,
dev.ucp.payment.three_ds_challenge). Applied throughout: Action-type key, schema
$id and filename, titles, and the spec doc.
@gforgab
gforgab force-pushed the gforgab/payment-display-extension branch from 150c748 to 35485f7 Compare August 5, 2026 14:09
@gforgab gforgab changed the title feat: add com.mercadopago.payments.display extension feat: add com.mercadopago.payment.render_artifact extension Aug 5, 2026
@sumudul

sumudul commented Aug 9, 2026

Copy link
Copy Markdown

Strong +1 on the shape of this. At PPRO we're working through our own UCP integration and have the same need. Several of the payment methods we support interact exactly this way: the buyer gets a code or a QR, leaves to their banking app and the outcome arrives out of band with no redirect or continue_url.

There's no standard way to express that today in UCP. The closest option is handing checkout completion back to the business, which degrades the native experience. And absent something like this, each provider ends up defining its own incompatible Action type that no Platform can render generically. An inert display artifact resolved in-session is the right primitive. It's the shape we'd been hoping to find.

The in-session vs out-of-session cut is also well thought out. Drawing that line temporally rather than by method label is correct. A long-expiry Pix really does behave like boleto for this purpose.

Going through the PR, there are a few details I think need pinning down before two independent implementations would interoperate; mainly the polling contract and the platform-side render declaration. I'll leave line comments in the relevant places. All meant constructively, none of it changes the shape, just the parts a second implementer would trip on. We're implementing against this pattern for our own methods regardless, so happy to feed back whatever we hit in practice if that's useful evidence for the extension.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action relies on this polling mechanism to know the outcome, but it doesn't define a clear polling contract. According to checkout.md, ideally, the action should define one. Otherwise it will be subjected to different platform policies.

The Platform MAY repeat Get Checkout with bounded backoff set by the Action contract or Platform policy, and MUST stop repeated requests at expires_at.

With no contract in the action definition, it falls through to Platform policy, and conforming implementations can diverge wildly.

For reference, the payment-authentication action defines a 30-second polling interval with backoff, but that is not suitable here.

I think this action should state a normative minimum interval and maximum duration (as a MUST/SHOULD prose).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 27db19a — added a normative ## Polling section: the Platform MUST NOT poll more than once every 2s, SHOULD apply exponential backoff, and MUST stop at the Action-level expires_at. No longer left to Platform policy.

"type": "string",
"description": "Opaque payment reference (e.g., Pix end-to-end id) for reconciliation. Not buyer-facing; not a credential."
},
"expires_at": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two points related to expires_at:

  1. expires_at is not marked as a required field. However, the spec strongly relies on its presence, since the rendered artifact is only valid until a specific expiration timestamp.

  2. The parent capability, checkout, also has an expires_at field. For clarity and to avoid ambiguity, we should explicitly define the precedence between the two. Otherwise, an implementer might prioritize the capability-level value over the action-level one, which would be incorrect.

Therefore, I suggest marking the action-level expires_at as required and explicitly stating that polling is bounded by this action-level value. Also, the action-level expires_at should not exceed the checkout-level expires_at.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 27db19aexpires_at is now in the config required set, explicitly bounds polling, and MUST NOT exceed the checkout-level expires_at (Action-level takes precedence for the Action). Precedence is stated in both the schema field description and the render/trust contract.

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ucp.dev/schemas/shopping/payment_render_artifact.json",
"name": "com.mercadopago.payment.render_artifact",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the extension name and the underlying action type are conflated here, as both are called com.mercadopago.payment.render_artifact. All the core extensions are under the dev.ucp.shopping namespace. Therefore, I believe your extension name could be something like com.mercadopago.shopping.render_artifact (or perhaps com.mercadopago.shopping.payment_render_artifact). The action type name can remain as is.

In addition to being more consistent, this would allow us to add additional action types under the same extension in the future without making the naming look awkward.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 27db19a — split them: the capability is now com.mercadopago.shopping.render_artifact, while the Action type stays com.mercadopago.payment.render_artifact. The doc now states the distinction explicitly so the capability can declare further Action types later.

gforgab added a commit to gforgab/ucp that referenced this pull request Aug 12, 2026
…ocol#635 review

Addresses @sumudul's review on the (as-yet unmerged) extension:

- Polling: define a normative polling contract — MUST NOT poll Get Checkout more
  than once every 2s, SHOULD apply exponential backoff, MUST stop at the
  action-level expires_at — instead of deferring backoff to Platform policy.
- expires_at: make the action-level field required, bound polling by it, and
  define precedence over the checkout-level expires_at (action-level MUST NOT
  exceed it, and takes precedence for the Action). Tightens the extension's own
  unreleased schema; no released consumers, so not a protocol breaking change.
- Naming: split the capability name (com.mercadopago.shopping.render_artifact)
  from the Action type (com.mercadopago.payment.render_artifact) so the
  capability can declare further Action types later without overloading a name.
@gforgab

gforgab commented Aug 12, 2026

Copy link
Copy Markdown
Author

Thanks @sumudul — really valuable review, and great to have a second independent implementer (PPRO) on the same pattern. Addressed all three in the latest commit (27db19a):

  • Polling contract: added a normative ## Polling section — the Platform MUST NOT poll Get Checkout more than once every 2s, SHOULD apply exponential backoff, and MUST stop at the Action-level expires_at. No longer left to Platform policy.
  • expires_at: now required, explicitly bounds polling, and MUST NOT exceed the checkout-level expires_at (Action-level takes precedence for the Action).
  • Naming: split the capability from the Action type — the capability is now com.mercadopago.shopping.render_artifact, the Action type stays com.mercadopago.payment.render_artifact, so the capability can declare further Action types later.

On the polling floor (2s + exponential backoff): picked to be safe for an immediate-Pix artifact — very open to your input from PPRO's methods if a different minimum fits better. And please do feed back whatever you hit implementing against this; that's exactly the second-implementer evidence that helps it graduate.

# Payment Render Artifact Extension

* **Capability Name:** `com.mercadopago.shopping.render_artifact`
* **Schema:** `https://ucp.dev/schemas/shopping/payment_render_artifact.json`

@khegde-ppro khegde-ppro Aug 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The render artifact design is clean and the render/trust contract is well specified.

One structural concern worth discussing: source/schemas/shopping/payment_render_artifact.json currently sits in the same flat directory as first-party core schemas like checkout.json, order.json, and payment.json. Since this is explicitly a vendor-namespaced capability (com.mercadopago.*), it would help consumers immediately distinguish stable core contracts from optional vendor extensions if these were kept in a separate directory.

Something like:
source/schemas/shopping/extensions/com.mercadopago/payment_render_artifact.json docs/specification/extensions/com.mercadopago/payment-render-artifact.md

This also matters for the pattern going forward to accommodate similar use cases for other handlers like PPRO, establishing a clear extensions/ home now avoids multiple vendor handlers landing alongside core schemas.
Worth aligning on the directory structure before the pattern solidifies.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong +1 — worth settling before it solidifies. This is the first external-vendor namespace in the repo (com.mercadopago.*), so there's no extensions/ precedent yet, and I'd rather we agree the layout once than have each handler improvise.

Proposed starting point, mirroring your suggestion:

source/schemas/shopping/extensions/<vendor>/<name>.json
docs/specification/extensions/<vendor>/<name>.md

Mechanically this shifts the schema $id, the relative $refs (order.json../../order.json, ../common/...), the doc macro path, and the mkdocs nav/llmstxt entries — all fine, just wants doing consistently.

Since this sets a repo-wide convention for every vendor handler (yours included), @igrigorik / @raginpirate — could you say where these should live? Once it's confirmed I'll migrate both this PR and #719 (com.mercadopago.shopping.digital_delivery, same flat-dir situation) to it in one go.

@dkoch74

dkoch74 commented Aug 17, 2026

Copy link
Copy Markdown
Member

Hi @gforgab. Given this is namespaced to your own organization, it would make more sense as an extension hosted by your own organization. If you would like this considered for inclusion as a generic feature, please refactor as such - the spec should not reference individual org/org technologies but represent generic objects/processes.

@gforgab

gforgab commented Aug 17, 2026

Copy link
Copy Markdown
Author

Thanks @dkoch74 — genuinely happy to go whichever way the TC prefers, but I want to flag that I'm getting conflicting steers on where vendor-namespaced extensions belong, and it decides the fate of more than this one PR.

The namespace wasn't accidental. When I asked in #517 whether to bless a generic render Action as core or keep it vendor-scoped and revisit core once there's proven adoption, @raginpirate steered toward keeping core small and seeing independent implementations land first before building an abstract base into core — explicitly hesitant to abstract early. I read that as "ship it as an extension and prove it," so I made it com.mercadopago.* rather than proposing core changes up front.

Where it should physically live is exactly what I'm now getting split guidance on. In this PR, @khegde-ppro (PPRO — a second handler with the same need) proposed a dedicated extensions/<vendor>/ home in this repo — i.e. assuming vendor extensions do live here, just organized. Your steer points the other way: host it out-of-repo, or genericize it for core so the spec carries no org-specific tech.

Those two are directly at odds on the underlying question — do vendor-namespaced extensions belong in this repo at all? — and the same answer applies to #719 (com.mercadopago.shopping.digital_delivery) and to every future handler's extension, PPRO's included. Rather than pick one and risk reworking both PRs and setting a precedent others follow, could the TC settle the convention once: do vendor extensions live in this repo (flat, or under extensions/<vendor>/), get hosted out-of-repo, or only enter as genericized core features?

@igrigorik / @raginpirate — a ruling here would unblock #635 and #719 and give PPRO and others a clear path. And if the answer is "genericize for core," I'm glad to do it: render-artifact and digital-delivery are both fairly generic patterns (out-of-band render; delivered redeemable/entitlement), and I'd rather converge on one shared shape than have each handler ship its own.

gforgab added a commit to gforgab/ucp that referenced this pull request Aug 17, 2026
Addresses review feedback on Universal-Commerce-Protocol#635: `com.mercadopago.payments.display` read as
"display metadata about a payment" rather than what it models — a payment method
whose completion is a rendered, out-of-band artifact. Rename to a payment Action
whose final segment is the concrete action, aligning with the core payment-action
convention merged in Universal-Commerce-Protocol#458 (dev.ucp.payment.device_data_collection,
dev.ucp.payment.three_ds_challenge). Applied throughout: Action-type key, schema
$id and filename, titles, and the spec doc.
gforgab added a commit to gforgab/ucp that referenced this pull request Aug 17, 2026
…ocol#635 review

Addresses @sumudul's review on the (as-yet unmerged) extension:

- Polling: define a normative polling contract — MUST NOT poll Get Checkout more
  than once every 2s, SHOULD apply exponential backoff, MUST stop at the
  action-level expires_at — instead of deferring backoff to Platform policy.
- expires_at: make the action-level field required, bound polling by it, and
  define precedence over the checkout-level expires_at (action-level MUST NOT
  exceed it, and takes precedence for the Action). Tightens the extension's own
  unreleased schema; no released consumers, so not a protocol breaking change.
- Naming: split the capability name (com.mercadopago.shopping.render_artifact)
  from the Action type (com.mercadopago.payment.render_artifact) so the
  capability can declare further Action types later without overloading a name.
@gforgab
gforgab force-pushed the gforgab/payment-display-extension branch from 27db19a to 6140ef5 Compare August 17, 2026 21:00
Addresses review feedback on Universal-Commerce-Protocol#635: `com.mercadopago.payments.display` read as
"display metadata about a payment" rather than what it models — a payment method
whose completion is a rendered, out-of-band artifact. Rename to a payment Action
whose final segment is the concrete action, aligning with the core payment-action
convention merged in Universal-Commerce-Protocol#458 (dev.ucp.payment.device_data_collection,
dev.ucp.payment.three_ds_challenge). Applied throughout: Action-type key, schema
$id and filename, titles, and the spec doc.
…ocol#635 review

Addresses @sumudul's review on the (as-yet unmerged) extension:

- Polling: define a normative polling contract — MUST NOT poll Get Checkout more
  than once every 2s, SHOULD apply exponential backoff, MUST stop at the
  action-level expires_at — instead of deferring backoff to Platform policy.
- expires_at: make the action-level field required, bound polling by it, and
  define precedence over the checkout-level expires_at (action-level MUST NOT
  exceed it, and takes precedence for the Action). Tightens the extension's own
  unreleased schema; no released consumers, so not a protocol breaking change.
- Naming: split the capability name (com.mercadopago.shopping.render_artifact)
  from the Action type (com.mercadopago.payment.render_artifact) so the
  capability can declare further Action types later without overloading a name.
@gforgab
gforgab force-pushed the gforgab/payment-display-extension branch from 6140ef5 to 30406de Compare August 24, 2026 12:48
@dkoch74

dkoch74 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Hi @gforgab. I work with the UCP payments advisory group and reviewed your concern. The group's position is that vendor extensions be self-hosted and not incorporated into the spec which is for general purpose description of the protocol. Contributions in the spec should be namespaced and applicable for UCP at-large, not individual companies/technologies.

@gforgab

gforgab commented Aug 25, 2026

Copy link
Copy Markdown
Author

Thanks, that clarifies the hosting rule.

Before moving this out of the UCP repository, could you confirm whether the evidence in this thread is enough to begin the path toward a generic UCP feature?

@sumudul confirmed that PPRO has the same need across several payment methods and is implementing against this pattern for its own methods. The review also identified the interoperability details that needed to be explicit: a normative polling contract, required action-level expires_at with clear precedence, and separating the capability name from the Action type.

The shape itself has no Mercado Pago API fields. The vendor-specific parts are the com.mercadopago.* names and the Pix/Mercado Pago examples. Underneath, it is a generic in-session, out-of-band payment Action: inert display code or image, an allowlisted instructions_url when needed, and completion observed by polling Get Checkout.

I do not want to treat two implementers as proven widespread adoption. But would Mercado Pago and PPRO implementing the same generic contract be sufficient evidence to open an EP for TC discussion, remove vendor-specific normative references, and validate the shape through both implementations? Or should both vendors self-host first and return with that evidence?

@sumudul @khegde-ppro, would PPRO be interested in validating a generic version of this contract if the TC considers that path?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants