@artos-commerce/ucp-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/ap2/canonical-json.d.ts +13 -0
- package/dist/ap2/canonical-json.js +28 -0
- package/dist/ap2/canonical-json.js.map +1 -0
- package/dist/ap2/index.d.ts +3 -0
- package/dist/ap2/index.js +4 -0
- package/dist/ap2/index.js.map +1 -0
- package/dist/ap2/signer.d.ts +67 -0
- package/dist/ap2/signer.js +79 -0
- package/dist/ap2/signer.js.map +1 -0
- package/dist/ap2/verify.d.ts +15 -0
- package/dist/ap2/verify.js +60 -0
- package/dist/ap2/verify.js.map +1 -0
- package/dist/checkout/handlers.d.ts +76 -0
- package/dist/checkout/handlers.js +288 -0
- package/dist/checkout/handlers.js.map +1 -0
- package/dist/checkout/index.d.ts +4 -0
- package/dist/checkout/index.js +4 -0
- package/dist/checkout/index.js.map +1 -0
- package/dist/checkout/money.d.ts +24 -0
- package/dist/checkout/money.js +41 -0
- package/dist/checkout/money.js.map +1 -0
- package/dist/checkout/rails.d.ts +36 -0
- package/dist/checkout/rails.js +62 -0
- package/dist/checkout/rails.js.map +1 -0
- package/dist/checkout/types.d.ts +56 -0
- package/dist/checkout/types.js +2 -0
- package/dist/checkout/types.js.map +1 -0
- package/dist/config.d.ts +54 -0
- package/dist/config.js +41 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto/deps.d.ts +27 -0
- package/dist/crypto/deps.js +23 -0
- package/dist/crypto/deps.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/sui/signer.d.ts +27 -0
- package/dist/sui/signer.js +69 -0
- package/dist/sui/signer.js.map +1 -0
- package/dist/ucp/client.d.ts +169 -0
- package/dist/ucp/client.js +269 -0
- package/dist/ucp/client.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { isUcpError, messageOf, } from '../ucp/client.js';
|
|
2
|
+
import { verifyMerchantAuthorization } from '../ap2/verify.js';
|
|
3
|
+
import { availableRails, currencyOf, grandTotal, resolveRail, asAllowanceId, } from './rails.js';
|
|
4
|
+
import { normalizeSearchFilters } from './money.js';
|
|
5
|
+
/**
|
|
6
|
+
* Placeholder PTB sender forwarded on a buyer-bound crypto prepare call. The API
|
|
7
|
+
* ignores it and resolves the real sender from the buyer's authorization; it
|
|
8
|
+
* exists only to satisfy the required `buyer_address` field.
|
|
9
|
+
*/
|
|
10
|
+
const RESOLVED_SERVER_SIDE = `0x${'0'.repeat(64)}`;
|
|
11
|
+
/**
|
|
12
|
+
* UCP eligibility claim for Artos cross-store coupons. Attached to the checkout
|
|
13
|
+
* context so the server resolves + applies any eligible coupon during the crypto
|
|
14
|
+
* prepare step (coupons redeem on-chain, crypto rail only).
|
|
15
|
+
*/
|
|
16
|
+
const ARTOS_COUPON_CLAIM = 'sh.artos.coupon';
|
|
17
|
+
/** Builds an error outcome. */
|
|
18
|
+
function err(code, message, checkout) {
|
|
19
|
+
return { status: 'error', code, message, checkout };
|
|
20
|
+
}
|
|
21
|
+
/** True when a UCP error envelope names a recoverable payment-selection gate. */
|
|
22
|
+
function isPaymentSelection(body) {
|
|
23
|
+
return (body.messages ?? []).some((m) => m.code === 'payment_selection_required');
|
|
24
|
+
}
|
|
25
|
+
function lineItems(items) {
|
|
26
|
+
return items.map((i) => ({
|
|
27
|
+
item: { id: i.id },
|
|
28
|
+
quantity: i.quantity ?? 1,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Pure checkout orchestration over an injected {@link CheckoutDeps}. Read methods
|
|
33
|
+
* return the raw UCP envelope (inspect with `isUcpError`); `confirmPurchase`
|
|
34
|
+
* returns a structured {@link CheckoutOutcome}. No presentation, no MCP — a host
|
|
35
|
+
* (e.g. the Artos MCP bridge) maps the result to text/widgets.
|
|
36
|
+
*/
|
|
37
|
+
export function createCheckoutHandlers(deps) {
|
|
38
|
+
const { client, signer, crypto } = deps;
|
|
39
|
+
return {
|
|
40
|
+
/** Cross-store catalog search (price filters in MAJOR units). */
|
|
41
|
+
async searchProducts(args) {
|
|
42
|
+
return client.globalSearch({
|
|
43
|
+
query: args.query,
|
|
44
|
+
filters: normalizeSearchFilters(args.filters),
|
|
45
|
+
sort: args.sort,
|
|
46
|
+
pagination: args.pagination,
|
|
47
|
+
context: args.context,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
/** Resolve products by id/handle/SKU/barcode (global). */
|
|
51
|
+
async lookupProducts(args) {
|
|
52
|
+
return client.callGlobalTool('lookup_catalog', {
|
|
53
|
+
ids: args.ids,
|
|
54
|
+
context: args.context,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
/** Fetch a single product by id (global, no store slug needed). */
|
|
58
|
+
async getProductGlobal(args) {
|
|
59
|
+
return client.callGlobalTool('get_product', {
|
|
60
|
+
id: args.id,
|
|
61
|
+
selected: args.selected,
|
|
62
|
+
context: args.context,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
/** Fetch a single product at a specific store. */
|
|
66
|
+
async viewProduct(args) {
|
|
67
|
+
return client.callStoreTool(args.storeSlug, 'get_product', {
|
|
68
|
+
catalog: { id: args.id },
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
async createCart(args) {
|
|
72
|
+
return client.callStoreTool(args.storeSlug, 'create_cart', { cart: { line_items: lineItems(args.items) } }, { idempotent: true });
|
|
73
|
+
},
|
|
74
|
+
async updateCart(args) {
|
|
75
|
+
return client.callStoreTool(args.storeSlug, 'update_cart', { id: args.id, cart: { line_items: lineItems(args.items) } }, { idempotent: true });
|
|
76
|
+
},
|
|
77
|
+
async viewCart(args) {
|
|
78
|
+
return client.callStoreTool(args.storeSlug, 'get_cart', { id: args.id });
|
|
79
|
+
},
|
|
80
|
+
async createCheckout(args) {
|
|
81
|
+
const checkout = {};
|
|
82
|
+
if (args.cartId)
|
|
83
|
+
checkout.cart_id = args.cartId;
|
|
84
|
+
if (args.items)
|
|
85
|
+
checkout.line_items = lineItems(args.items);
|
|
86
|
+
if (args.buyer)
|
|
87
|
+
checkout.buyer = args.buyer;
|
|
88
|
+
if (args.shippingAddress)
|
|
89
|
+
checkout.shipping_address = args.shippingAddress;
|
|
90
|
+
return client.callStoreTool(args.storeSlug, 'create_checkout', { checkout }, { idempotent: true });
|
|
91
|
+
},
|
|
92
|
+
async updateCheckout(args) {
|
|
93
|
+
const checkout = {};
|
|
94
|
+
if (args.buyer)
|
|
95
|
+
checkout.buyer = args.buyer;
|
|
96
|
+
if (args.shippingAddress)
|
|
97
|
+
checkout.shipping_address = args.shippingAddress;
|
|
98
|
+
if (args.shippingMethodId)
|
|
99
|
+
checkout.shipping_method_id = args.shippingMethodId;
|
|
100
|
+
if (args.discounts)
|
|
101
|
+
checkout.discounts = args.discounts;
|
|
102
|
+
return client.callStoreTool(args.storeSlug, 'update_checkout', { id: args.id, checkout }, { idempotent: true });
|
|
103
|
+
},
|
|
104
|
+
async getOrder(args) {
|
|
105
|
+
return client.callStoreTool(args.storeSlug, 'get_order', { id: args.id });
|
|
106
|
+
},
|
|
107
|
+
/**
|
|
108
|
+
* Re-prices the checkout, verifies the store-signed terms, mints the AP2
|
|
109
|
+
* mandate(s), and places the order on the card, crypto, or $0 rail. Returns a
|
|
110
|
+
* {@link CheckoutOutcome} for every branch (no exceptions for business
|
|
111
|
+
* outcomes; transport failures still throw {@link UcpClientError}).
|
|
112
|
+
*/
|
|
113
|
+
async confirmPurchase(input) {
|
|
114
|
+
const slug = input.storeSlug;
|
|
115
|
+
const checkoutId = input.checkoutId;
|
|
116
|
+
const paymentMandateId = asAllowanceId(input.paymentMandateId);
|
|
117
|
+
const paymentMethod = input.paymentMethod;
|
|
118
|
+
// Re-price immediately before minting so the mandate total matches the
|
|
119
|
+
// server's quote (else completion fails with mandate_scope_mismatch).
|
|
120
|
+
let fresh = await client.callStoreTool(slug, 'get_checkout', {
|
|
121
|
+
id: checkoutId,
|
|
122
|
+
});
|
|
123
|
+
if (isUcpError(fresh)) {
|
|
124
|
+
return err('store_error', messageOf(fresh) ?? 'Checkout error.', fresh);
|
|
125
|
+
}
|
|
126
|
+
const signingKeys = await client.fetchStoreProfile(slug);
|
|
127
|
+
// Completes the checkout and maps the result. Shared by every rail.
|
|
128
|
+
const finish = async (checkout, auth) => {
|
|
129
|
+
const done = await client.callStoreTool(slug, 'complete_checkout', { id: checkoutId, checkout }, { idempotent: true, auth });
|
|
130
|
+
if (isUcpError(done)) {
|
|
131
|
+
if (isPaymentSelection(done)) {
|
|
132
|
+
return {
|
|
133
|
+
status: 'payment_selection_required',
|
|
134
|
+
rails: availableRails(fresh),
|
|
135
|
+
checkout: done,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return err('store_error', messageOf(done) ?? 'The store returned an error.', done);
|
|
139
|
+
}
|
|
140
|
+
const continueUrl = done.continue_url;
|
|
141
|
+
if (done.status === 'requires_escalation' &&
|
|
142
|
+
continueUrl) {
|
|
143
|
+
return {
|
|
144
|
+
status: 'escalation_required',
|
|
145
|
+
continueUrl,
|
|
146
|
+
checkout: done,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return { status: 'completed', checkout: done };
|
|
150
|
+
};
|
|
151
|
+
const rail = resolveRail(fresh, paymentMethod);
|
|
152
|
+
// Free order ($0 total): no rail to run, settle directly with no payment
|
|
153
|
+
// instrument. Rail-independent (no crypto config or buyer sign-in needed)
|
|
154
|
+
// and sidesteps the crypto prepare step, which rejects a $0 total.
|
|
155
|
+
if (grandTotal(fresh) === 0) {
|
|
156
|
+
if (!verifyMerchantAuthorization(fresh, signingKeys)) {
|
|
157
|
+
return err('merchant_authorization_invalid', 'The store did not return a valid merchant_authorization for these terms; aborting.');
|
|
158
|
+
}
|
|
159
|
+
const completionAuth = client.hasBuyerToken()
|
|
160
|
+
? 'buyer'
|
|
161
|
+
: 'platform';
|
|
162
|
+
return finish({
|
|
163
|
+
ap2: {
|
|
164
|
+
checkout_mandate: signer.mintCheckoutMandate({ checkout: fresh, merchant: slug }, { paymentMandateId }),
|
|
165
|
+
},
|
|
166
|
+
}, completionAuth);
|
|
167
|
+
}
|
|
168
|
+
// Crypto rail: the buyer pays from their own Sui wallet via the agent's
|
|
169
|
+
// alias key. An eligible Artos coupon is resolved + applied server-side
|
|
170
|
+
// during prepare_checkout_payment, which reduces the total and re-signs the
|
|
171
|
+
// terms. So for crypto we MUST attach the coupon eligibility claim, prepare,
|
|
172
|
+
// and RE-FETCH the priced checkout BEFORE minting — otherwise the mandate is
|
|
173
|
+
// bound to the pre-coupon total and completion fails (mandate_scope_mismatch).
|
|
174
|
+
if (rail?.kind === 'crypto') {
|
|
175
|
+
if (!crypto) {
|
|
176
|
+
return err('crypto_not_configured', 'This client is not configured to pay with crypto. Provide the agent Sui key, or choose the card rail.');
|
|
177
|
+
}
|
|
178
|
+
if (!client.hasBuyerToken()) {
|
|
179
|
+
return err('authentication_required', 'Paying with crypto requires the buyer to sign in so their wallet and authorization can be resolved.');
|
|
180
|
+
}
|
|
181
|
+
// Opt into on-chain coupon redemption: attach the `sh.artos.coupon`
|
|
182
|
+
// eligibility claim (merged with any present) so the server applies an
|
|
183
|
+
// eligible coupon during prepare. Harmless when the buyer has none.
|
|
184
|
+
const existingClaims = fresh.context
|
|
185
|
+
?.eligibility ?? [];
|
|
186
|
+
if (!existingClaims.includes(ARTOS_COUPON_CLAIM)) {
|
|
187
|
+
const updated = await client.callStoreTool(slug, 'update_checkout', {
|
|
188
|
+
id: checkoutId,
|
|
189
|
+
checkout: {
|
|
190
|
+
context: {
|
|
191
|
+
eligibility: [...existingClaims, ARTOS_COUPON_CLAIM],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
}, { idempotent: true });
|
|
195
|
+
if (isUcpError(updated)) {
|
|
196
|
+
return err('store_error', messageOf(updated) ?? 'The store returned an error.', updated);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const prepared = await client.callStoreTool(slug, 'prepare_checkout_payment', {
|
|
200
|
+
id: checkoutId,
|
|
201
|
+
payment_intent: {
|
|
202
|
+
buyer_address: RESOLVED_SERVER_SIDE,
|
|
203
|
+
input_coin_type: crypto.inputCoinType,
|
|
204
|
+
},
|
|
205
|
+
}, { idempotent: true, auth: 'buyer' });
|
|
206
|
+
if (isUcpError(prepared)) {
|
|
207
|
+
return err('store_error', messageOf(prepared) ?? 'The store returned an error.', prepared);
|
|
208
|
+
}
|
|
209
|
+
const unsignedTx = prepared.payment_intent?.unsigned_tx;
|
|
210
|
+
if (!unsignedTx) {
|
|
211
|
+
return err('payment_intent_invalid', 'The store did not return an unsigned payment transaction; aborting.');
|
|
212
|
+
}
|
|
213
|
+
// Re-fetch the priced checkout: prepare may have applied a coupon,
|
|
214
|
+
// changing the total and the store's merchant_authorization. Verify and
|
|
215
|
+
// mint the mandate over THESE final terms.
|
|
216
|
+
fresh = await client.callStoreTool(slug, 'get_checkout', {
|
|
217
|
+
id: checkoutId,
|
|
218
|
+
});
|
|
219
|
+
if (isUcpError(fresh)) {
|
|
220
|
+
return err('store_error', messageOf(fresh) ?? 'Checkout error.', fresh);
|
|
221
|
+
}
|
|
222
|
+
if (!verifyMerchantAuthorization(fresh, signingKeys)) {
|
|
223
|
+
return err('merchant_authorization_invalid', 'The store did not return a valid merchant_authorization for these terms; aborting.');
|
|
224
|
+
}
|
|
225
|
+
const total = grandTotal(fresh);
|
|
226
|
+
const currency = currencyOf(fresh);
|
|
227
|
+
if (crypto.maxSpendAmount !== undefined &&
|
|
228
|
+
total > crypto.maxSpendAmount) {
|
|
229
|
+
return err('spend_cap_exceeded', `Order total ${total} ${currency} exceeds the agent spending cap of ${crypto.maxSpendAmount}; refusing to pay.`);
|
|
230
|
+
}
|
|
231
|
+
// Embed the verbatim (re-fetched) checkout body so the server can
|
|
232
|
+
// re-check its nested merchant_authorization; bind to the store slug.
|
|
233
|
+
const ap2 = {
|
|
234
|
+
checkout_mandate: signer.mintCheckoutMandate({ checkout: fresh, merchant: slug }, { paymentMandateId }),
|
|
235
|
+
payment_mandate: signer.mintPaymentMandate({ amount: total, currency, merchant: slug }, { paymentMandateId }),
|
|
236
|
+
};
|
|
237
|
+
let digest;
|
|
238
|
+
try {
|
|
239
|
+
digest = await crypto.sui.signAndSubmit(unsignedTx);
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
return err('payment_execution_failed', e instanceof Error
|
|
243
|
+
? e.message
|
|
244
|
+
: 'Signing the payment transaction failed.');
|
|
245
|
+
}
|
|
246
|
+
// No inline intent_mandate: the API loads the buyer's STORED standing
|
|
247
|
+
// authorization (and enforces its caps) from the forwarded bearer.
|
|
248
|
+
return finish({
|
|
249
|
+
ap2,
|
|
250
|
+
payment: {
|
|
251
|
+
instruments: [
|
|
252
|
+
{
|
|
253
|
+
handler_id: rail.handlerId,
|
|
254
|
+
type: 'crypto',
|
|
255
|
+
selected: true,
|
|
256
|
+
credential: { token: digest },
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
}, 'buyer');
|
|
261
|
+
}
|
|
262
|
+
// Card / hosted rail (or a multi-rail store with no choice yet): no coupon
|
|
263
|
+
// redemption, so verify and mint the mandate over the current terms.
|
|
264
|
+
if (!verifyMerchantAuthorization(fresh, signingKeys)) {
|
|
265
|
+
return err('merchant_authorization_invalid', 'The store did not return a valid merchant_authorization for these terms; aborting.');
|
|
266
|
+
}
|
|
267
|
+
const checkout = {
|
|
268
|
+
ap2: {
|
|
269
|
+
checkout_mandate: signer.mintCheckoutMandate({ checkout: fresh, merchant: slug }, { paymentMandateId }),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
if (rail) {
|
|
273
|
+
// Forward the buyer's chosen (or sole) rail; the API routes by
|
|
274
|
+
// handler_id. With no rail (multi-rail store, no choice) we send no
|
|
275
|
+
// instrument and the API returns a recoverable payment_selection_required
|
|
276
|
+
// (surfaced as that outcome) so the caller asks the buyer.
|
|
277
|
+
checkout.payment = {
|
|
278
|
+
instruments: [{ handler_id: rail.handlerId, selected: true }],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const completionAuth = client.hasBuyerToken()
|
|
282
|
+
? 'buyer'
|
|
283
|
+
: 'platform';
|
|
284
|
+
return finish(checkout, completionAuth);
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/checkout/handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createCheckoutHandlers, type CheckoutHandlers } from './handlers.js';
|
|
2
|
+
export { toMinorUnits, normalizeSearchFilters, type SearchFilters, } from './money.js';
|
|
3
|
+
export { availableRails, resolveRail, railKind, grandTotal, currencyOf, asAllowanceId, type ResolvedRail, } from './rails.js';
|
|
4
|
+
export type { CheckoutDeps, CheckoutLineItem, ConfirmPurchaseInput, CheckoutErrorCode, CheckoutOutcome, } from './types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checkout/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAyB,MAAM,eAAe,CAAC;AAC9E,OAAO,EACL,YAAY,EACZ,sBAAsB,GAEvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,WAAW,EACX,QAAQ,EACR,UAAU,EACV,UAAU,EACV,aAAa,GAEd,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Converts a major-unit amount (dollars) to integer minor units (cents). */
|
|
2
|
+
export declare function toMinorUnits(amount: number, currency: string | undefined): number;
|
|
3
|
+
/** Model-facing search filters (price in MAJOR units, with optional currency). */
|
|
4
|
+
export interface SearchFilters {
|
|
5
|
+
categories?: string[];
|
|
6
|
+
price?: {
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
currency?: string;
|
|
10
|
+
};
|
|
11
|
+
tags?: string[];
|
|
12
|
+
attributes?: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
value: string;
|
|
15
|
+
}>;
|
|
16
|
+
seller?: string;
|
|
17
|
+
available?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Translates the caller-facing filters to the UCP wire shape: price is given in
|
|
21
|
+
* major units with an optional `currency`, which we convert to the integer
|
|
22
|
+
* minor units the API expects (the API has no notion of major units).
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeSearchFilters(filters: SearchFilters | undefined): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 4217 minor-unit exponents for the currencies that don't use 2 decimals.
|
|
3
|
+
* Used to turn a caller's major-unit price (dollars) into the minor units
|
|
4
|
+
* (cents) the API expects. Anything not listed defaults to 2 decimals.
|
|
5
|
+
*/
|
|
6
|
+
const CURRENCY_MINOR_UNITS = {
|
|
7
|
+
JPY: 0,
|
|
8
|
+
KRW: 0,
|
|
9
|
+
VND: 0,
|
|
10
|
+
CLP: 0,
|
|
11
|
+
ISK: 0,
|
|
12
|
+
BHD: 3,
|
|
13
|
+
KWD: 3,
|
|
14
|
+
OMR: 3,
|
|
15
|
+
TND: 3,
|
|
16
|
+
};
|
|
17
|
+
/** Converts a major-unit amount (dollars) to integer minor units (cents). */
|
|
18
|
+
export function toMinorUnits(amount, currency) {
|
|
19
|
+
const exp = CURRENCY_MINOR_UNITS[(currency ?? 'USD').toUpperCase()] ?? 2;
|
|
20
|
+
return Math.round(amount * 10 ** exp);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Translates the caller-facing filters to the UCP wire shape: price is given in
|
|
24
|
+
* major units with an optional `currency`, which we convert to the integer
|
|
25
|
+
* minor units the API expects (the API has no notion of major units).
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeSearchFilters(filters) {
|
|
28
|
+
if (!filters)
|
|
29
|
+
return undefined;
|
|
30
|
+
const { price, ...rest } = filters;
|
|
31
|
+
if (!price || (price.min == null && price.max == null)) {
|
|
32
|
+
return { ...rest };
|
|
33
|
+
}
|
|
34
|
+
const converted = {};
|
|
35
|
+
if (price.min != null)
|
|
36
|
+
converted.min = toMinorUnits(price.min, price.currency);
|
|
37
|
+
if (price.max != null)
|
|
38
|
+
converted.max = toMinorUnits(price.max, price.currency);
|
|
39
|
+
return { ...rest, price: converted };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=money.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"money.js","sourceRoot":"","sources":["../../src/checkout/money.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,oBAAoB,GAA2B;IACnD,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;IACN,GAAG,EAAE,CAAC;CACP,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,QAA4B;IAE5B,MAAM,GAAG,GAAG,oBAAoB,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC;IACzE,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC;AACxC,CAAC;AAYD;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAkC;IAElC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACnC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;IACrB,CAAC;IACD,MAAM,SAAS,GAAmC,EAAE,CAAC;IACrD,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI;QACnB,SAAS,CAAC,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,GAAG,IAAI,IAAI;QACnB,SAAS,CAAC,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1D,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { UcpResponse } from '../ucp/client.js';
|
|
2
|
+
/** The payment rail a checkout settles on. */
|
|
3
|
+
export interface ResolvedRail {
|
|
4
|
+
handlerId: string;
|
|
5
|
+
kind: 'card' | 'crypto';
|
|
6
|
+
}
|
|
7
|
+
/** A rail's kind from its handler id (`artos.crypto` -> crypto, else card). */
|
|
8
|
+
export declare function railKind(handlerId: string): 'card' | 'crypto';
|
|
9
|
+
/** Every payment rail the store advertises on a (re-priced) checkout body. */
|
|
10
|
+
export declare function availableRails(fresh: UcpResponse): ResolvedRail[];
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the EFFECTIVE rail for auth + instrument routing from the buyer's
|
|
13
|
+
* choice or the store's advertised handlers, or `undefined` to defer to the API.
|
|
14
|
+
*
|
|
15
|
+
* The API drives multi-rail selection: with no selected instrument a >1-rail
|
|
16
|
+
* store returns a recoverable `payment_selection_required`. We only resolve a
|
|
17
|
+
* rail when the buyer chose one (`paymentMethod`) or the store advertises
|
|
18
|
+
* exactly one — the latter so a sole CRYPTO rail is completed with the buyer
|
|
19
|
+
* bearer instead of the platform key (which would trip the API's intent-mandate
|
|
20
|
+
* gate). Otherwise return `undefined`: send no instrument and let the API ask.
|
|
21
|
+
* The choice is passed through verbatim (no local allowlist) so the API
|
|
22
|
+
* validates the handler id.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveRail(fresh: UcpResponse, paymentMethod: string | undefined): ResolvedRail | undefined;
|
|
25
|
+
/** The `total` line amount from a checkout/order body (0 when absent). */
|
|
26
|
+
export declare function grandTotal(body: UcpResponse): number;
|
|
27
|
+
/** The body currency (top-level, else first total's currency, else USD). */
|
|
28
|
+
export declare function currencyOf(body: UcpResponse): string;
|
|
29
|
+
/**
|
|
30
|
+
* A `payment_mandate_id` is a server-side allowance (PaymentMandate) UUID, NOT a
|
|
31
|
+
* payment rail. Callers routinely confuse it with a handler id (e.g.
|
|
32
|
+
* `sh.artos.card`); embedding that in the AP2 mandate makes the API reject the
|
|
33
|
+
* order (`mandate_scope_mismatch` from a uuid-cast failure). Drop anything that
|
|
34
|
+
* isn't a UUID so the chosen rail proceeds unconstrained.
|
|
35
|
+
*/
|
|
36
|
+
export declare function asAllowanceId(value: unknown): string | undefined;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2
|
+
/** A rail's kind from its handler id (`artos.crypto` -> crypto, else card). */
|
|
3
|
+
export function railKind(handlerId) {
|
|
4
|
+
return handlerId.includes('crypto') ? 'crypto' : 'card';
|
|
5
|
+
}
|
|
6
|
+
/** Every payment rail the store advertises on a (re-priced) checkout body. */
|
|
7
|
+
export function availableRails(fresh) {
|
|
8
|
+
const handlers = fresh.ucp?.payment_handlers ?? {};
|
|
9
|
+
const rails = [];
|
|
10
|
+
for (const entries of Object.values(handlers)) {
|
|
11
|
+
for (const entry of entries ?? []) {
|
|
12
|
+
if (entry?.id) {
|
|
13
|
+
rails.push({ handlerId: entry.id, kind: railKind(entry.id) });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return rails;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the EFFECTIVE rail for auth + instrument routing from the buyer's
|
|
21
|
+
* choice or the store's advertised handlers, or `undefined` to defer to the API.
|
|
22
|
+
*
|
|
23
|
+
* The API drives multi-rail selection: with no selected instrument a >1-rail
|
|
24
|
+
* store returns a recoverable `payment_selection_required`. We only resolve a
|
|
25
|
+
* rail when the buyer chose one (`paymentMethod`) or the store advertises
|
|
26
|
+
* exactly one — the latter so a sole CRYPTO rail is completed with the buyer
|
|
27
|
+
* bearer instead of the platform key (which would trip the API's intent-mandate
|
|
28
|
+
* gate). Otherwise return `undefined`: send no instrument and let the API ask.
|
|
29
|
+
* The choice is passed through verbatim (no local allowlist) so the API
|
|
30
|
+
* validates the handler id.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveRail(fresh, paymentMethod) {
|
|
33
|
+
if (paymentMethod) {
|
|
34
|
+
return { handlerId: paymentMethod, kind: railKind(paymentMethod) };
|
|
35
|
+
}
|
|
36
|
+
const rails = availableRails(fresh);
|
|
37
|
+
return rails.length === 1 ? rails[0] : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** The `total` line amount from a checkout/order body (0 when absent). */
|
|
40
|
+
export function grandTotal(body) {
|
|
41
|
+
const totals = body.totals ?? [];
|
|
42
|
+
return totals.find((t) => t.type === 'total')?.amount ?? 0;
|
|
43
|
+
}
|
|
44
|
+
/** The body currency (top-level, else first total's currency, else USD). */
|
|
45
|
+
export function currencyOf(body) {
|
|
46
|
+
if (typeof body.currency === 'string')
|
|
47
|
+
return body.currency;
|
|
48
|
+
const totals = body.totals ?? [];
|
|
49
|
+
return totals.find((t) => t.currency)?.currency ?? 'USD';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A `payment_mandate_id` is a server-side allowance (PaymentMandate) UUID, NOT a
|
|
53
|
+
* payment rail. Callers routinely confuse it with a handler id (e.g.
|
|
54
|
+
* `sh.artos.card`); embedding that in the AP2 mandate makes the API reject the
|
|
55
|
+
* order (`mandate_scope_mismatch` from a uuid-cast failure). Drop anything that
|
|
56
|
+
* isn't a UUID so the chosen rail proceeds unconstrained.
|
|
57
|
+
*/
|
|
58
|
+
export function asAllowanceId(value) {
|
|
59
|
+
const s = typeof value === 'string' ? value : '';
|
|
60
|
+
return UUID_RE.test(s) ? s : undefined;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=rails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rails.js","sourceRoot":"","sources":["../../src/checkout/rails.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,GACX,iEAAiE,CAAC;AAEpE,+EAA+E;AAC/E,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,KAAkB;IAC/C,MAAM,QAAQ,GACX,KAAK,CAAC,GAAG,EAAE,gBACyC,IAAI,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAClC,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;gBACd,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAChE,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CACzB,KAAkB,EAClB,aAAiC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;IACrE,CAAC;IACD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,IAAiB;IAC1C,MAAM,MAAM,GACT,IAAI,CAAC,MAAoD,IAAI,EAAE,CAAC;IACnE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,IAAiB;IAC1C,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAC;IAC5D,MAAM,MAAM,GAAI,IAAI,CAAC,MAAuC,IAAI,EAAE,CAAC;IACnE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,QAAQ,IAAI,KAAK,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Ap2Signer } from '../ap2/signer.js';
|
|
2
|
+
import type { CryptoDeps } from '../crypto/deps.js';
|
|
3
|
+
import type { UcpClient, UcpResponse } from '../ucp/client.js';
|
|
4
|
+
import type { ResolvedRail } from './rails.js';
|
|
5
|
+
/** Dependencies the checkout handlers operate over (all injectable for tests). */
|
|
6
|
+
export interface CheckoutDeps {
|
|
7
|
+
client: UcpClient;
|
|
8
|
+
signer: Ap2Signer;
|
|
9
|
+
/** Present only when the crypto settlement rail is fully configured. */
|
|
10
|
+
crypto?: CryptoDeps;
|
|
11
|
+
}
|
|
12
|
+
/** A line item in major-unit, friendly shape (`{ id, quantity }`). */
|
|
13
|
+
export interface CheckoutLineItem {
|
|
14
|
+
id: string;
|
|
15
|
+
quantity?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ConfirmPurchaseInput {
|
|
18
|
+
storeSlug: string;
|
|
19
|
+
checkoutId: string;
|
|
20
|
+
/**
|
|
21
|
+
* The chosen rail's handler id (e.g. `artos.card` / `artos.crypto`). Omit on a
|
|
22
|
+
* multi-rail store to receive a `payment_selection_required` outcome.
|
|
23
|
+
*/
|
|
24
|
+
paymentMethod?: string;
|
|
25
|
+
/** Optional server-side allowance (PaymentMandate) UUID to bind the mandate to. */
|
|
26
|
+
paymentMandateId?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Why a `confirmPurchase` could not place the order. These map 1:1 to the inline
|
|
30
|
+
* UCP error envelopes the hosted bridge builds today; a host (e.g. the bridge)
|
|
31
|
+
* turns each into agent-facing text.
|
|
32
|
+
*/
|
|
33
|
+
export type CheckoutErrorCode = 'crypto_not_configured' | 'authentication_required' | 'merchant_authorization_invalid' | 'payment_intent_invalid' | 'spend_cap_exceeded' | 'payment_execution_failed'
|
|
34
|
+
/** A UCP business-error envelope returned by the store, passed through. */
|
|
35
|
+
| 'store_error';
|
|
36
|
+
/**
|
|
37
|
+
* The structured result of a `confirmPurchase`. The SDK never renders text;
|
|
38
|
+
* callers branch on `status` and present as they like.
|
|
39
|
+
*/
|
|
40
|
+
export type CheckoutOutcome = {
|
|
41
|
+
status: 'completed';
|
|
42
|
+
checkout: UcpResponse;
|
|
43
|
+
} | {
|
|
44
|
+
status: 'escalation_required';
|
|
45
|
+
continueUrl: string;
|
|
46
|
+
checkout: UcpResponse;
|
|
47
|
+
} | {
|
|
48
|
+
status: 'payment_selection_required';
|
|
49
|
+
rails: ResolvedRail[];
|
|
50
|
+
checkout: UcpResponse;
|
|
51
|
+
} | {
|
|
52
|
+
status: 'error';
|
|
53
|
+
code: CheckoutErrorCode;
|
|
54
|
+
message: string;
|
|
55
|
+
checkout?: UcpResponse;
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/checkout/types.ts"],"names":[],"mappings":""}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UCP spec version the Artos API implements. Sent in the `UCP-Agent` header and
|
|
3
|
+
* the `meta["ucp-agent"]` envelope; must match the server.
|
|
4
|
+
*/
|
|
5
|
+
export declare const UCP_VERSION = "2026-04-08";
|
|
6
|
+
/** A JSON Web Key. The DOM `JsonWebKey` lacks `kid`, so widen it here. */
|
|
7
|
+
export type Jwk = JsonWebKey & {
|
|
8
|
+
kid?: string;
|
|
9
|
+
};
|
|
10
|
+
/** Sui networks the agent can settle crypto payments on. */
|
|
11
|
+
export type SuiNetwork = 'mainnet' | 'testnet' | 'devnet' | 'localnet';
|
|
12
|
+
/**
|
|
13
|
+
* The minimum configuration the {@link import('./ucp/client.js').UcpClient}
|
|
14
|
+
* needs: where the Artos API lives, the platform (cross-store) API key, and the
|
|
15
|
+
* HTTPS URL of the agent's published UCP profile (its `signing_keys` + caps).
|
|
16
|
+
*/
|
|
17
|
+
export interface UcpClientConfig {
|
|
18
|
+
/** Base URL of the Artos API, e.g. `https://api.artos.sh`. No trailing slash. */
|
|
19
|
+
artosBaseUrl: string;
|
|
20
|
+
/** Platform (cross-store) UCP API key: `<clientId>.<secret>`. */
|
|
21
|
+
platformApiKey: string;
|
|
22
|
+
/** HTTPS URL of the agent's published UCP profile (signing_keys + caps). */
|
|
23
|
+
platformProfileUrl: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The AP2 signing configuration: the agent's EC P-256 private key (JWK) and the
|
|
27
|
+
* key id, which MUST match the public JWK `kid` published in the agent profile.
|
|
28
|
+
*/
|
|
29
|
+
export interface AgentAp2Config {
|
|
30
|
+
agentPrivateJwk: Jwk;
|
|
31
|
+
agentKid: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Crypto-rail settlement configuration. Optional: absent => card-only. The Sui
|
|
35
|
+
* key signs payment PTBs as the buyer's on-chain alias; the buyer the spend is
|
|
36
|
+
* drawn from (and the caps) are resolved server-side from their authorization.
|
|
37
|
+
*/
|
|
38
|
+
export interface AgentCryptoConfig {
|
|
39
|
+
/** Agent Sui Ed25519 secret key (`suiprivkey1...` bech32). */
|
|
40
|
+
agentSuiPrivateKey?: string;
|
|
41
|
+
/** Sui network for crypto settlement. */
|
|
42
|
+
suiNetwork: SuiNetwork;
|
|
43
|
+
/** Optional client-side hard cap (ISO-4217 minor units) refused before signing. */
|
|
44
|
+
agentMaxSpendAmount?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Strips trailing slashes so URL joins never produce a double slash. */
|
|
47
|
+
export declare function normalizeBaseUrl(url: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Validates that a JWK is an EC P-256 PRIVATE key (carries `d`) suitable for
|
|
50
|
+
* minting AP2 mandates. Throws with an actionable message otherwise.
|
|
51
|
+
*/
|
|
52
|
+
export declare function assertEcP256PrivateJwk(jwk: Jwk): void;
|
|
53
|
+
/** Narrows an arbitrary string to a known {@link SuiNetwork}, defaulting to mainnet. */
|
|
54
|
+
export declare function parseSuiNetwork(value: string | undefined): SuiNetwork;
|