Class: Portage::Ucp::Adapter Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/portage/ucp/adapter.rb

Overview

This class is abstract.

Subclass and override the methods for the capabilities you support. Unoverridden methods leave that capability out of the manifest.

Direct Known Subclasses

ReferenceAdapter

Instance Method Summary collapse

Instance Method Details

#cancel_cart(cart_id:, idempotency_key:) ⇒ Portage::Ucp::Cart

Returns:



38
# File 'lib/portage/ucp/adapter.rb', line 38

def cancel_cart(cart_id:, idempotency_key:) = not_implemented

#cancel_checkout(checkout_id:, idempotency_key:) ⇒ Portage::Ucp::Checkout



85
# File 'lib/portage/ucp/adapter.rb', line 85

def cancel_checkout(checkout_id:, idempotency_key:) = not_implemented

#cancel_order(order_id:, idempotency_key:, reason: nil) ⇒ Portage::Ucp::Order

Cancels a placed order. reason is an optional human-readable note, not a closed enum — the platform-specific enum mapping (if any) is an adapter concern.

Returns:



94
95
96
97
98
99
100
# File 'lib/portage/ucp/adapter.rb', line 94

def cancel_order(order_id:, idempotency_key:, reason: nil) = not_implemented
# Requests a return for one or more order line items. `line_items` is an
# array of request-shaped hashes (`{id:, quantity:}`, unsigned) — same
# request/response asymmetry as `create_cart`'s `line_items:`. A return
# is a request the merchant still has to process; it shows up as a
# `pending` `Portage::Ucp::Adjustment` until they do.
# @return [Portage::Ucp::Order]

#complete_checkout(checkout_id:, payment_token:, idempotency_key:, mandate: nil) ⇒ Portage::Ucp::Checkout

Re-checks stock at the point of committing money, since search_catalog/ get_product (dev.ucp.shopping.catalog) don't promise live inventory and nothing else re-checks between browsing and buying. An adapter whose platform rejects completion because a line item is out of stock or otherwise unavailable should raise Portage::Ucp::OutOfStockError rather than a generic/platform error, so callers can distinguish a stale-stock failure from e.g. a declined payment.

Parameters:

  • payment_token (String) —

    single-use token from a UCP payment handler / AP2 exchange — NEVER a raw PAN.

  • mandate (Portage::Ucp::Ap2::PaymentMandate, nil) (defaults to: nil) —

    an AP2 mandate authorizing this charge, alongside or instead of relying on payment_token alone (design-log §33/Phase B). Validated for shape (not cryptographically) by Dispatcher#call via Ap2::MandateGuard before an adapter ever sees it. Optional and nil by default — no adapter in this repo has a real PSP to verify a mandate's signature against, so this is a pass-through slot, same posture as payment_token on this same method.

Returns:

Raises:



83
84
# File 'lib/portage/ucp/adapter.rb', line 83

def complete_checkout(checkout_id:, payment_token:, idempotency_key:, mandate: nil) = not_implemented
# @return [Portage::Ucp::Checkout]

#create_cart(line_items:, idempotency_key:, discount_codes: nil) ⇒ Portage::Ucp::Cart

discount_codes: is the dev.ucp.shopping.discount extension — nil (the default) means the request didn't touch discounts at all; an adapter that doesn't override #discount_codes_supported? never sees anything but nil here (see below). Full-replacement like line_items: once codes are involved, [] clears them, same as UCP's own discounts_object semantics.

Returns:



34
35
# File 'lib/portage/ucp/adapter.rb', line 34

def create_cart(line_items:, idempotency_key:, discount_codes: nil) = not_implemented
# @return [Portage::Ucp::Cart]

#create_checkout(line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Portage::Ucp::Checkout

--- Checkout (dev.ucp.shopping.checkout) --- discount_codes: carries the same dev.ucp.shopping.discount semantics as create_cart/update_cart above. fulfillment: is the dev.ucp.shopping.fulfillment extension — nil (the default) means the request didn't touch fulfillment at all; an adapter that doesn't override #fulfillment_supported? never sees anything but nil here (see below). On create it carries the agent's desired methods (type + line_item_ids per shipping/pickup group — Portage::Ucp::FulfillmentMethod#id/#destinations/#groups are omitted since the merchant generates those); on update it carries the agent's selected_destination_id/selected_option_id choices against the methods/groups the merchant already returned.



53
54
# File 'lib/portage/ucp/adapter.rb', line 53

def create_checkout(line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) = not_implemented
# @return [Portage::Ucp::Checkout]

#create_payment_enrollment(idempotency_key:, mandate: nil) ⇒ Portage::Ucp::PaymentEnrollment

--- Payment Enrollment (app.portage-ucp.payment_enrollment — Portage extension, not part of the UCP spec) --- Starts a card-on-file enrollment. Card data never touches this process: #create_payment_enrollment returns a setup_url for a gateway-hosted page where the human enters their card, and the caller polls #get_payment_enrollment until status leaves "pending". See docs/plans/agentic-payments.md Phase 1.

Parameters:

  • mandate (Portage::Ucp::Ap2::PaymentMandate, nil) (defaults to: nil) —

    an AP2 mandate presented at enrollment time (design-log §33/Phase B), same optional/pass-through posture as complete_checkout's mandate: above — validated for shape by Dispatcher#call, never cryptographically, before an adapter ever sees it.

Returns:



152
153
# File 'lib/portage/ucp/adapter.rb', line 152

def create_payment_enrollment(idempotency_key:, mandate: nil) = not_implemented
# @return [Portage::Ucp::PaymentEnrollment, nil] nil if the enrollment isn't found

#delete_address(oauth_token:, address_id:, idempotency_key:) ⇒ Boolean

Returns:

  • (Boolean)


185
# File 'lib/portage/ucp/adapter.rb', line 185

def delete_address(oauth_token:, address_id:, idempotency_key:) = not_implemented

#delete_payment_method(oauth_token:, payment_method_id:, idempotency_key:) ⇒ Boolean

Returns:

  • (Boolean)


178
# File 'lib/portage/ucp/adapter.rb', line 178

def delete_payment_method(oauth_token:, payment_method_id:, idempotency_key:) = not_implemented

#delete_shopper_data(oauth_token:, idempotency_key:) ⇒ Portage::Ucp::ShopperDataErasure

Erases every payment method, address, and linked identity Portage holds for this shopper. Idempotent and safe to repeat: a second call on an already-erased subject returns zero counts, never raises. A real adapter must forward the deletion to the PSP/platform's own API — Portage core never holds the credential behind psp_reference, only the opaque reference itself.



194
# File 'lib/portage/ucp/adapter.rb', line 194

def delete_shopper_data(oauth_token:, idempotency_key:) = not_implemented

#discount_codes_supported? ⇒ Boolean

--- Discount (dev.ucp.shopping.discount) --- Extends Cart/Checkout with the discount_codes: param above rather than adding actions of its own — Capability::DISCOUNT advertises off this predicate instead of an overridden action method, since there's no dedicated method for #advertised_for? to detect an override on.

Returns:

  • (Boolean)


125
# File 'lib/portage/ucp/adapter.rb', line 125

def discount_codes_supported? = false

#fulfillment_supported? ⇒ Boolean

--- Fulfillment (dev.ucp.shopping.fulfillment) --- Extends Checkout with the fulfillment: param above rather than adding actions of its own — Capability::FULFILLMENT advertises off this predicate instead of an overridden action method, same rationale as #discount_codes_supported? above.

Returns:

  • (Boolean)


133
# File 'lib/portage/ucp/adapter.rb', line 133

def fulfillment_supported? = false

#get_cart(cart_id:) ⇒ Portage::Ucp::Cart

--- Cart (dev.ucp.shopping.cart) --- Full-replacement semantics, matching UCP's real cart methods: create/ update take the complete desired line_items list, not a single item. line_items: is an array of request-shaped hashes (e.g. {product_id:, quantity:}) — the adapter looks up product data and builds the response's Item/Total/LineItem itself.

Returns:



26
27
28
29
30
31
32
33
# File 'lib/portage/ucp/adapter.rb', line 26

def get_cart(cart_id:) = not_implemented
# `discount_codes:` is the dev.ucp.shopping.discount extension — nil
# (the default) means the request didn't touch discounts at all; an
# adapter that doesn't override #discount_codes_supported? never sees
# anything but nil here (see below). Full-replacement like line_items:
# once codes are involved, [] clears them, same as UCP's own
# discounts_object semantics.
# @return [Portage::Ucp::Cart]

#get_checkout(checkout_id:) ⇒ Portage::Ucp::Checkout



55
# File 'lib/portage/ucp/adapter.rb', line 55

def get_checkout(checkout_id:) = not_implemented

#get_order(order_id:) ⇒ Portage::Ucp::Order?

--- Order (dev.ucp.shopping.order) ---

Returns:



89
90
91
92
93
# File 'lib/portage/ucp/adapter.rb', line 89

def get_order(order_id:) = not_implemented
# Cancels a placed order. `reason` is an optional human-readable note,
# not a closed enum — the platform-specific enum mapping (if any) is an
# adapter concern.
# @return [Portage::Ucp::Order]

#get_payment_enrollment(enrollment_id:) ⇒ Portage::Ucp::PaymentEnrollment?

Returns nil if the enrollment isn't found.

Returns:



154
# File 'lib/portage/ucp/adapter.rb', line 154

def get_payment_enrollment(enrollment_id:) = not_implemented

#get_product(product_id:) ⇒ Portage::Ucp::ProductDetail?

nil when the product isn't found, same not-found posture as get_cart/get_checkout/get_order.

Returns:



12
13
14
15
16
# File 'lib/portage/ucp/adapter.rb', line 12

def get_product(product_id:) = not_implemented
# Batch fetch — same result shape as search_catalog, for a caller that
# already has a set of product ids (e.g. hydrating a cart/order) and
# wants one round trip instead of N #get_product calls.
# @return [Portage::Ucp::CatalogSearchResult]

--- Identity Linking (dev.ucp.shopping.identity, OAuth 2.0) ---

Returns:



137
# File 'lib/portage/ucp/adapter.rb', line 137

def link_identity(oauth_token:) = not_implemented

#list_addresses(oauth_token:) ⇒ Array<Portage::Ucp::SavedAddress>

Returns:



183
184
# File 'lib/portage/ucp/adapter.rb', line 183

def list_addresses(oauth_token:) = not_implemented
# @return [Boolean]

#list_payment_methods(oauth_token:) ⇒ Array<Portage::Ucp::PaymentMethodRef>

Returns:



176
177
# File 'lib/portage/ucp/adapter.rb', line 176

def list_payment_methods(oauth_token:) = not_implemented
# @return [Boolean]

#lookup_catalog(product_ids:) ⇒ Portage::Ucp::CatalogSearchResult

Batch fetch — same result shape as search_catalog, for a caller that already has a set of product ids (e.g. hydrating a cart/order) and wants one round trip instead of N #get_product calls.



17
# File 'lib/portage/ucp/adapter.rb', line 17

def lookup_catalog(product_ids:) = not_implemented

#refund_order(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Portage::Ucp::Order

Refunds one or more order line items.

Returns:



104
# File 'lib/portage/ucp/adapter.rb', line 104

def refund_order(order_id:, line_items:, idempotency_key:, reason: nil) = not_implemented

#reorder(order_id:, idempotency_key:) ⇒ Portage::Ucp::ReorderResult?

--- Reorder (app.portage-ucp.reorder — Portage extension, not part of the UCP spec: dev.ucp.dev has no reorder/cart-hydration capability as of the 2026-04-08 spec) --- Hydrates a cart from a previous order's line items so a caller doesn't have to re-walk get_order + create_cart itself. Re-checks each item's current price/availability rather than replaying the order's snapshot totals — order_line_item.json's totals are historical, not live, same posture as complete_checkout's stock re-check above — and drops anything no longer purchasable instead of failing the whole call, reporting what got dropped via ReorderResult#unavailable_items.

Returns:



117
# File 'lib/portage/ucp/adapter.rb', line 117

def reorder(order_id:, idempotency_key:) = not_implemented

#request_return(order_id:, line_items:, idempotency_key:, reason: nil) ⇒ Portage::Ucp::Order

Requests a return for one or more order line items. line_items is an array of request-shaped hashes ({id:, quantity:}, unsigned) — same request/response asymmetry as create_cart's line_items:. A return is a request the merchant still has to process; it shows up as a pending Portage::Ucp::Adjustment until they do.

Returns:



101
102
103
# File 'lib/portage/ucp/adapter.rb', line 101

def request_return(order_id:, line_items:, idempotency_key:, reason: nil) = not_implemented
# Refunds one or more order line items.
# @return [Portage::Ucp::Order]

#save_address(oauth_token:, address:, idempotency_key:) ⇒ Portage::Ucp::SavedAddress



181
182
# File 'lib/portage/ucp/adapter.rb', line 181

def save_address(oauth_token:, address:, idempotency_key:) = not_implemented
# @return [Array<Portage::Ucp::SavedAddress>]

#save_payment_method(oauth_token:, payment_token:, idempotency_key:) ⇒ Portage::Ucp::PaymentMethodRef

--- Payment Method / Saved Address / Shopper Data (app.portage-ucp.* — Portage extensions, not part of the UCP spec) --- oauth_token: — not subject: — is the authorization boundary on every method below, including the two list_* reads. Mcp::Server treats a call as mutating only when it takes an idempotency_key (mcp/server.rb:38), and both the authorize and rate_limit guards skip non-mutating calls entirely. A bare subject: string would let any caller who knows (or guesses) a subject enumerate another shopper's saved payment references and addresses — the exact risk §16 (design-log.md:816-819) calls out lookups against this data for. Carrying the credential on every call closes that: possession of a subject grants nothing without a valid oauth_token to derive it from. Do not "simplify" this back to subject:.

Returns:

  • (Portage::Ucp::PaymentMethodRef) —

    stores an already-tokenized payment_token; PaymentTokenGuard runs on it via Dispatcher#call before this method is ever reached (dispatcher.rb:75), so a raw PAN never arrives here.



174
175
# File 'lib/portage/ucp/adapter.rb', line 174

def save_payment_method(oauth_token:, payment_token:, idempotency_key:) = not_implemented
# @return [Array<Portage::Ucp::PaymentMethodRef>]

#search_catalog(query:, limit:) ⇒ Portage::Ucp::CatalogSearchResult

--- Catalog (dev.ucp.shopping.catalog) ---



8
9
10
11
# File 'lib/portage/ucp/adapter.rb', line 8

def search_catalog(query:, limit:) = not_implemented
# nil when the product isn't found, same not-found posture as
# get_cart/get_checkout/get_order.
# @return [Portage::Ucp::ProductDetail, nil]

#update_cart(cart_id:, line_items:, idempotency_key:, discount_codes: nil) ⇒ Portage::Ucp::Cart

Returns:



36
37
# File 'lib/portage/ucp/adapter.rb', line 36

def update_cart(cart_id:, line_items:, idempotency_key:, discount_codes: nil) = not_implemented
# @return [Portage::Ucp::Cart]

#update_checkout(checkout_id:, line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil) ⇒ Portage::Ucp::Checkout

Full-replacement, same as update_cart — line_items is required on checkout update per the real spec.



60
61
62
# File 'lib/portage/ucp/adapter.rb', line 60

def update_checkout(checkout_id:, line_items:, idempotency_key:, discount_codes: nil, fulfillment: nil)
  not_implemented
end