feat: add com.mercadopago.payment.render_artifact extension - #635
feat: add com.mercadopago.payment.render_artifact extension#635gforgab wants to merge 3 commits into
Conversation
|
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. |
fe0370c to
cd63abe
Compare
cd63abe to
a81ebc8
Compare
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "https://ucp.dev/schemas/shopping/payment_display.json", | ||
| "name": "com.mercadopago.payments.display", |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I.e. as opposed to "this object defines display info for a payment that occurred", it instead describes a display-based payment system artifact.
There was a problem hiding this comment.
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_artifactnames 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.
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>
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.
150c748 to
35485f7
Compare
|
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 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) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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": { |
There was a problem hiding this comment.
Two points related to expires_at:
-
expires_atis not marked as arequiredfield. However, the spec strongly relies on its presence, since the rendered artifact is only valid until a specific expiration timestamp. -
The parent capability,
checkout, also has anexpires_atfield. 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.
There was a problem hiding this comment.
Done in 27db19a — expires_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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
…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.
|
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):
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` |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
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. |
|
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 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 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 ( @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. |
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.
27db19a to
6140ef5
Compare
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.
6140ef5 to
30406de
Compare
|
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. |
|
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 The shape itself has no Mercado Pago API fields. The vendor-specific parts are the 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? |
What
Adds
com.mercadopago.payment.render_artifact, a vendor-namespaced Checkoutextension that models in-session, out-of-band payment methods (e.g.
Pix) as a pure-render Action on top of the generic
actionsprimitiveintroduced 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
actionsprimitive (feat: extension-defined Actions primitive #582). Instance is{ id, config? }; norequiredfield — gating is expressed via arecoverableerror Message whosepathpoints at the action occurrence,per the outcome of the [RFC] Proposal: Modeling 3DS and Step-Up Actions in UCP #517 discussion.
[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).(
render_artifact) names what the Platform does — render an inert artifact thebuyer 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).PAN/OTP/credentials),
imageis inert,codeis display text, andinstructions_urlis the only loadable field (https origin allowlist).Scope
settle after the session ends (boleto, cash vouchers) are out of scope and
will be proposed separately at the order level (async-payments EP, [Proposal]: Out-of-session payment settlement — order-level actions + order.payment_status #641).
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
actionsprimitive).Under review — feedback welcome on the extension shape and the render/trust
contract wording.