@devlab.group/agent-commerce 1.4.0 → 1.5.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/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, openSqliteDatabase, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-7NKWFGBY.js';
2
- import { resolveX402Deployment, resolveDeploymentMode, requireNetworkProfile } from './chunk-IUR3Q7DE.js';
3
- import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, toErrorEnvelope, isHttpProtocolAdapter, isCommerceError, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER } from './chunk-ZDVJ7EVL.js';
4
- export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope } from './chunk-ZDVJ7EVL.js';
1
+ import { AP2_SPEC_VERSION, AP2_MODES, AP2_DEFAULT_CLOCK_SKEW_SECONDS, AP2_JWK_MEMBERS, AP2_KEY_TYPE, AP2_SIGNING_ALGORITHM, AP2_JWK_CURVE, AP2_JWK_COORDINATE_BYTES, openSqliteDatabase, AP2_MAX_CLOCK_SKEW_SECONDS } from './chunk-US622BKF.js';
2
+ import { MPP_DEFAULT_NETWORK, isMppNetwork, MPP_NETWORKS, MPP_MIN_CHALLENGE_SECRET_LENGTH, MPP_PROFILE } from './chunk-6D2EZ5ME.js';
3
+ import { resolveX402Deployment, resolveDeploymentMode, requireNetworkProfile } from './chunk-PBYN7EK2.js';
4
+ import { RESERVED_INPUT_FIELDS, PROTOCOL_NAMES, PAYMENT_METHOD_NAMES, PAYMENT_HEADER, AUTHORIZATION_HEADER, CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION, toCommerceError, DEFAULT_BACKEND_TIMEOUT_MS, toErrorEnvelope, isHttpProtocolAdapter, isCommerceError, parseAuthorizationHeader, toPaymentRequiredEnvelope, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER } from './chunk-5GEWKNIH.js';
5
+ export { AUTHORIZATION_HEADER, AUTHORIZATION_INPUT_FIELD, COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, MAX_AUTHORIZATION_HEADER_BYTES, NOOP_LOGGER, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_METHOD_NAMES, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PROTOCOL_NAMES, RESERVED_INPUT_FIELDS, RETRYABLE_ERROR_CODES, extractReservedInputFields, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, parseAuthorizationHeader, parseAuthorizationSubmission, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope } from './chunk-5GEWKNIH.js';
5
6
  import { promises, accessSync, constants } from 'fs';
6
7
  import * as path from 'path';
7
8
  import { parse, YAMLError } from 'yaml';
@@ -85,6 +86,7 @@ var DEFAULT_CONFIG_FILENAME = "config.yaml";
85
86
 
86
87
  // src/core/execution/backend-http.ts
87
88
  var MAX_BODY_SNIPPET_LENGTH = 512;
89
+ var IDEMPOTENCY_KEY_HEADER = "idempotency-key";
88
90
  var PATH_PARAM_PATTERN = /\{([a-zA-Z0-9_.-]+)\}/g;
89
91
  var MAX_RESPONSE_BODY_BYTES = 1024 * 1024;
90
92
  var HttpBackendExecutor = class {
@@ -126,6 +128,10 @@ var HttpBackendExecutor = class {
126
128
  if (error instanceof CommerceError) throw error;
127
129
  }
128
130
  const headers = { ...handler.headers ?? {} };
131
+ if (request.idempotencyKey !== void 0) {
132
+ deleteHeader(headers, IDEMPOTENCY_KEY_HEADER);
133
+ headers[IDEMPOTENCY_KEY_HEADER] = request.idempotencyKey;
134
+ }
129
135
  let body;
130
136
  checkQueryCollision(target, parts.query, context);
131
137
  for (const [key, value] of Object.entries(parts.query)) {
@@ -334,6 +340,12 @@ function hasHeader(headers, name) {
334
340
  const target = name.toLowerCase();
335
341
  return Object.keys(headers).some((key) => key.toLowerCase() === target);
336
342
  }
343
+ function deleteHeader(headers, name) {
344
+ const target = name.toLowerCase();
345
+ for (const key of Object.keys(headers)) {
346
+ if (key.toLowerCase() === target) delete headers[key];
347
+ }
348
+ }
337
349
  function headersToRecord(headers) {
338
350
  const record = {};
339
351
  headers.forEach((value, key) => {
@@ -811,7 +823,8 @@ function createExecutionPipeline(options) {
811
823
  const mapped = isCommerceError(error) ? error : new CommerceError("PAYMENT_INVALID", "Payment verification failed", {
812
824
  requestId: request.requestId,
813
825
  resourceId: resource.id,
814
- cause: error
826
+ cause: error,
827
+ ...retryChallenge(requirement)
815
828
  });
816
829
  await safeEmit(
817
830
  buildEvent({
@@ -843,7 +856,8 @@ function createExecutionPipeline(options) {
843
856
  verification.rejectionReason ?? "Payment was rejected",
844
857
  {
845
858
  requestId: request.requestId,
846
- resourceId: resource.id
859
+ resourceId: resource.id,
860
+ ...retryChallenge(requirement)
847
861
  }
848
862
  );
849
863
  }
@@ -924,17 +938,16 @@ function createExecutionPipeline(options) {
924
938
  verification
925
939
  });
926
940
  } catch (error) {
927
- const uncertainTxHash = uncertainSettlementTxHash(error);
928
- await hold?.finalize(uncertainTxHash !== void 0 ? "markUncertain" : "release");
941
+ const txHash = settlementTxHash(error);
942
+ await hold?.finalize("markUncertain");
929
943
  await safePersist(
930
- () => options.store.updatePaymentAttempt(
931
- uncertainTxHash !== void 0 ? { replayKey, status: "settlement-uncertain", externalReference: uncertainTxHash } : {
932
- replayKey,
933
- status: "failed",
934
- ...error instanceof Error ? { rejectionReason: error.message } : {}
935
- }
936
- ),
937
- uncertainTxHash !== void 0 ? "updatePaymentAttempt(settlement-uncertain)" : "updatePaymentAttempt(failed)",
944
+ () => options.store.updatePaymentAttempt({
945
+ replayKey,
946
+ status: "settlement-uncertain",
947
+ ...txHash !== void 0 ? { externalReference: txHash } : {},
948
+ ...error instanceof Error ? { rejectionReason: error.message } : {}
949
+ }),
950
+ "updatePaymentAttempt(settlement-uncertain)",
938
951
  request.requestId
939
952
  );
940
953
  await safeEmit(
@@ -945,19 +958,21 @@ function createExecutionPipeline(options) {
945
958
  adapter: request.protocol,
946
959
  paymentProvider: provider.name,
947
960
  status: "error",
948
- data: uncertainTxHash !== void 0 ? { reason: "settlement-uncertain", transactionHash: uncertainTxHash } : { reason: "settlement-threw" }
961
+ data: {
962
+ reason: "settlement-uncertain",
963
+ ...txHash !== void 0 ? { transactionHash: txHash } : {}
964
+ }
949
965
  })
950
966
  );
951
- throw new CommerceError(
952
- "PAYMENT_SETTLEMENT_FAILED",
953
- uncertainTxHash !== void 0 ? "Settlement could not be confirmed" : "Payment settlement failed",
954
- {
955
- requestId: request.requestId,
956
- resourceId: resource.id,
957
- cause: error,
958
- ...uncertainTxHash !== void 0 ? { details: { settlementUncertain: true, transactionHash: uncertainTxHash } } : {}
967
+ throw new CommerceError("PAYMENT_SETTLEMENT_FAILED", "Settlement could not be confirmed", {
968
+ requestId: request.requestId,
969
+ resourceId: resource.id,
970
+ cause: error,
971
+ details: {
972
+ settlementUncertain: true,
973
+ ...txHash !== void 0 ? { transactionHash: txHash } : {}
959
974
  }
960
- );
975
+ });
961
976
  }
962
977
  if (settlement.status !== "settled") {
963
978
  await hold?.finalize("release");
@@ -1018,7 +1033,8 @@ function createExecutionPipeline(options) {
1018
1033
  backendResponse = await options.backend.call(resource.handler, {
1019
1034
  requestId: request.requestId,
1020
1035
  resourceId: resource.id,
1021
- input: validInput
1036
+ input: validInput,
1037
+ ...request.idempotencyKey !== void 0 ? { idempotencyKey: request.idempotencyKey } : {}
1022
1038
  });
1023
1039
  } catch (error) {
1024
1040
  const commerceError = toCommerceError(error, "BACKEND_ERROR", "Backend call failed");
@@ -1064,7 +1080,8 @@ function createExecutionPipeline(options) {
1064
1080
  amount: paymentResult.amount,
1065
1081
  currency: paymentResult.currency,
1066
1082
  ...paymentResult.network !== void 0 ? { network: paymentResult.network } : {},
1067
- ...paymentResult.externalReference !== void 0 ? { externalReference: paymentResult.externalReference } : {}
1083
+ ...paymentResult.externalReference !== void 0 ? { externalReference: paymentResult.externalReference } : {},
1084
+ ...typeof paymentResult.metadata?.["receipt"] === "string" ? { receipt: paymentResult.metadata["receipt"] } : {}
1068
1085
  }
1069
1086
  }
1070
1087
  }
@@ -1124,9 +1141,8 @@ function backendErrorStatus(error) {
1124
1141
  const status = error.details?.["status"];
1125
1142
  return typeof status === "number" ? status : 0;
1126
1143
  }
1127
- function uncertainSettlementTxHash(error) {
1128
- if (!isCommerceError(error) || error.code !== "PAYMENT_PROVIDER_UNAVAILABLE") return void 0;
1129
- const hash = error.details?.["transactionHash"];
1144
+ function settlementTxHash(error) {
1145
+ const hash = isCommerceError(error) ? error.details?.["transactionHash"] : void 0;
1130
1146
  return typeof hash === "string" ? hash : void 0;
1131
1147
  }
1132
1148
  function resolveAuthorizationProviders(providers, resource, requestId) {
@@ -1143,6 +1159,10 @@ function resolveAuthorizationProviders(providers, resource, requestId) {
1143
1159
  return found;
1144
1160
  });
1145
1161
  }
1162
+ function retryChallenge(requirement) {
1163
+ const envelope = requirement.challenge.envelope;
1164
+ return envelope === void 0 ? {} : { details: { challenge: envelope } };
1165
+ }
1146
1166
  function pickProvider(providers, methods) {
1147
1167
  for (const method of methods) {
1148
1168
  const found = providers.find((provider) => provider.name === method);
@@ -1410,8 +1430,26 @@ var X402Schema = z.object({
1410
1430
  /** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
1411
1431
  allowUnauthenticatedFacilitator: BooleanOrString.optional()
1412
1432
  }).strict();
1433
+ var MppSchema = z.object({
1434
+ enabled: BooleanOrString,
1435
+ network: z.string().min(1).optional(),
1436
+ rpcUrl: z.string().min(1),
1437
+ asset: z.string().min(1),
1438
+ assetName: z.string().min(1),
1439
+ assetVersion: z.string().min(1),
1440
+ recipient: z.string().min(1),
1441
+ realm: z.string().min(1),
1442
+ challengeSecret: z.string().min(1),
1443
+ challengeTtlSeconds: NumberOrString.optional(),
1444
+ facilitator: FacilitatorSchema,
1445
+ // Real funds. Never defaulted - see src/payments/x402/guardrails.ts
1446
+ allowMainnet: BooleanOrString.optional(),
1447
+ // Accepts a mainnet facilitator that takes no credential. Never defaulted
1448
+ allowUnauthenticatedFacilitator: BooleanOrString.optional()
1449
+ }).strict();
1413
1450
  var PaymentsSchema = z.object({
1414
- x402: X402Schema.optional()
1451
+ x402: X402Schema.optional(),
1452
+ mpp: MppSchema.optional()
1415
1453
  }).strict();
1416
1454
  var Ap2KeySchema = z.object({
1417
1455
  kid: z.string().min(1),
@@ -1547,7 +1585,7 @@ function toBoolean(value, path2) {
1547
1585
  );
1548
1586
  }
1549
1587
  var SUPPORTED_PROTOCOLS = new Set(PROTOCOL_NAMES);
1550
- var SUPPORTED_PAYMENT_METHODS = /* @__PURE__ */ new Set(["x402"]);
1588
+ var SUPPORTED_PAYMENT_METHODS = new Set(PAYMENT_METHOD_NAMES);
1551
1589
  var SUPPORTED_AUTHORIZATION_METHODS = /* @__PURE__ */ new Set(["ap2"]);
1552
1590
  function normalise(raw) {
1553
1591
  const protocols = {
@@ -1564,12 +1602,7 @@ function normalise(raw) {
1564
1602
  };
1565
1603
  validateMountPaths(protocols);
1566
1604
  const x402Raw = raw.payments.x402;
1567
- const facilitator = x402Raw === void 0 ? void 0 : x402Raw.facilitator.mode === "local" ? { mode: "local", signerPrivateKey: x402Raw.facilitator.signerPrivateKey } : {
1568
- mode: "remote",
1569
- url: x402Raw.facilitator.url,
1570
- auth: x402Raw.facilitator.auth ?? { type: "none" }
1571
- };
1572
- const x402 = x402Raw && facilitator ? {
1605
+ const x402 = x402Raw !== void 0 ? {
1573
1606
  enabled: toBoolean(x402Raw.enabled, "payments.x402.enabled"),
1574
1607
  network: x402Raw.network,
1575
1608
  rpcUrl: x402Raw.rpcUrl,
@@ -1588,7 +1621,7 @@ function normalise(raw) {
1588
1621
  min: 1
1589
1622
  }
1590
1623
  ),
1591
- facilitator,
1624
+ facilitator: normaliseFacilitator(x402Raw.facilitator),
1592
1625
  ...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {},
1593
1626
  ...x402Raw.allowUnauthenticatedFacilitator !== void 0 ? {
1594
1627
  allowUnauthenticatedFacilitator: toBoolean(
@@ -1611,12 +1644,13 @@ function normalise(raw) {
1611
1644
  ...x402.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: x402.allowUnauthenticatedFacilitator } : {}
1612
1645
  });
1613
1646
  }
1647
+ const mpp = normaliseMpp(raw.payments.mpp);
1614
1648
  const ap2 = normaliseAp2(raw.authorization?.ap2);
1615
1649
  if (ap2?.enabled) {
1616
1650
  validateReplayStoreIsolated(ap2.replay.path, raw.storage.receipts.path, protocols.acp);
1617
1651
  }
1618
1652
  const resources = Object.entries(raw.resources).map(
1619
- ([id, entry]) => normaliseResource(id, entry, protocols, x402, ap2)
1653
+ ([id, entry]) => normaliseResource(id, entry, protocols, x402, mpp, ap2)
1620
1654
  );
1621
1655
  if (protocols.acp.enabled) validateAcpCheckoutMapping(protocols.acp, resources);
1622
1656
  return {
@@ -1638,7 +1672,8 @@ function normalise(raw) {
1638
1672
  protocols,
1639
1673
  resources,
1640
1674
  payments: {
1641
- ...x402 !== void 0 ? { x402 } : {}
1675
+ ...x402 !== void 0 ? { x402 } : {},
1676
+ ...mpp !== void 0 ? { mpp } : {}
1642
1677
  },
1643
1678
  ...ap2 !== void 0 ? { authorization: { ap2 } } : {}
1644
1679
  };
@@ -1990,7 +2025,74 @@ function validateAcpOperationInput(path2, operation, resource) {
1990
2025
  }
1991
2026
  }
1992
2027
  }
1993
- function normaliseResource(id, entry, protocols, x402, ap2) {
2028
+ function normaliseFacilitator(raw) {
2029
+ return raw.mode === "local" ? { mode: "local", signerPrivateKey: raw.signerPrivateKey } : { mode: "remote", url: raw.url, auth: raw.auth ?? { type: "none" } };
2030
+ }
2031
+ function normaliseMpp(raw) {
2032
+ if (raw === void 0) return void 0;
2033
+ const network = raw.network ?? MPP_DEFAULT_NETWORK;
2034
+ if (!isMppNetwork(network)) {
2035
+ throw new CommerceError(
2036
+ "CONFIG_INVALID",
2037
+ `payments.mpp.network must be one of ${MPP_NETWORKS.join(", ")}`,
2038
+ { details: { path: "payments.mpp.network" } }
2039
+ );
2040
+ }
2041
+ const mpp = {
2042
+ enabled: toBoolean(raw.enabled, "payments.mpp.enabled"),
2043
+ network,
2044
+ rpcUrl: raw.rpcUrl,
2045
+ asset: raw.asset,
2046
+ assetName: raw.assetName,
2047
+ assetVersion: raw.assetVersion,
2048
+ recipient: raw.recipient,
2049
+ realm: raw.realm,
2050
+ challengeSecret: raw.challengeSecret,
2051
+ ...raw.challengeTtlSeconds !== void 0 ? {
2052
+ challengeTtlSeconds: toNumber(
2053
+ raw.challengeTtlSeconds,
2054
+ "payments.mpp.challengeTtlSeconds",
2055
+ { min: 1 }
2056
+ )
2057
+ } : {},
2058
+ facilitator: normaliseFacilitator(raw.facilitator),
2059
+ ...raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(raw.allowMainnet, "payments.mpp.allowMainnet") } : {},
2060
+ ...raw.allowUnauthenticatedFacilitator !== void 0 ? {
2061
+ allowUnauthenticatedFacilitator: toBoolean(
2062
+ raw.allowUnauthenticatedFacilitator,
2063
+ "payments.mpp.allowUnauthenticatedFacilitator"
2064
+ )
2065
+ } : {}
2066
+ };
2067
+ validateAddress("payments.mpp.recipient", mpp.recipient);
2068
+ validateAddress("payments.mpp.asset", mpp.asset);
2069
+ if (/[\r\n]/.test(mpp.realm)) {
2070
+ throw new CommerceError("CONFIG_INVALID", "payments.mpp.realm must be a single line", {
2071
+ details: { path: "payments.mpp.realm" }
2072
+ });
2073
+ }
2074
+ if (mpp.challengeSecret.length < MPP_MIN_CHALLENGE_SECRET_LENGTH) {
2075
+ throw new CommerceError(
2076
+ "CONFIG_INVALID",
2077
+ `payments.mpp.challengeSecret must have length at least ${MPP_MIN_CHALLENGE_SECRET_LENGTH}`,
2078
+ { details: { path: "payments.mpp.challengeSecret" } }
2079
+ );
2080
+ }
2081
+ resolveX402Deployment({
2082
+ network: mpp.network,
2083
+ payTo: mpp.recipient,
2084
+ asset: mpp.asset,
2085
+ assetName: mpp.assetName,
2086
+ assetVersion: mpp.assetVersion,
2087
+ facilitator: mpp.facilitator,
2088
+ ...mpp.allowMainnet !== void 0 ? { allowMainnet: mpp.allowMainnet } : {},
2089
+ ...mpp.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: mpp.allowUnauthenticatedFacilitator } : {},
2090
+ configPath: "payments.mpp",
2091
+ payToField: "recipient"
2092
+ });
2093
+ return mpp;
2094
+ }
2095
+ function normaliseResource(id, entry, protocols, x402, mpp, ap2) {
1994
2096
  if (entry.input !== void 0) validateResourceSchemaKeywords(id, "input", entry.input);
1995
2097
  const inputProperties = entry.input?.["properties"];
1996
2098
  if (inputProperties && typeof inputProperties === "object") {
@@ -2066,7 +2168,7 @@ function normaliseResource(id, entry, protocols, x402, ap2) {
2066
2168
  );
2067
2169
  }
2068
2170
  if (entry.pricing.type === "fixed") {
2069
- validatePricingAmount(id, entry.pricing.amount, x402);
2171
+ validatePricingAmount(id, entry.pricing.amount, x402?.assetDecimals);
2070
2172
  }
2071
2173
  const paymentMethods = entry.payments ?? [];
2072
2174
  if (entry.pricing.type === "fixed") {
@@ -2081,17 +2183,28 @@ function normaliseResource(id, entry, protocols, x402, ap2) {
2081
2183
  if (!SUPPORTED_PAYMENT_METHODS.has(method)) {
2082
2184
  throw new CommerceError(
2083
2185
  "CONFIG_INVALID",
2084
- `Resource "${id}" names unsupported payment method "${method}". Supported: x402.`,
2186
+ `Resource "${id}" names unsupported payment method "${method}". Supported: ${PAYMENT_METHOD_NAMES.join(", ")}.`,
2085
2187
  { details: { path: `resources.${id}.payments`, resourceId: id, method } }
2086
2188
  );
2087
2189
  }
2088
- if (method === "x402" && (!x402 || !x402.enabled)) {
2190
+ }
2191
+ const enabledRails = [...x402?.enabled ? ["x402"] : [], ...mpp?.enabled ? ["mpp"] : []];
2192
+ if (!paymentMethods.some((method) => enabledRails.includes(method))) {
2193
+ throw new CommerceError(
2194
+ "CONFIG_INVALID",
2195
+ `Resource "${id}" names payment method(s) "${paymentMethods.join(", ")}", none of which is configured and enabled under "payments"`,
2196
+ { details: { path: `resources.${id}.payments`, resourceId: id, paymentMethods } }
2197
+ );
2198
+ }
2199
+ if (mpp?.enabled && paymentMethods.includes("mpp")) {
2200
+ if (entry.pricing.currency !== MPP_PROFILE.assetSymbol) {
2089
2201
  throw new CommerceError(
2090
2202
  "CONFIG_INVALID",
2091
- `Resource "${id}" names payment method "x402" which is not configured or not enabled under payments.x402`,
2092
- { details: { path: `resources.${id}.payments`, resourceId: id, method } }
2203
+ `Resource "${id}" is priced in ${entry.pricing.currency}, but MPP charges ${MPP_PROFILE.assetSymbol}`,
2204
+ { details: { path: `resources.${id}.pricing.currency`, resourceId: id } }
2093
2205
  );
2094
2206
  }
2207
+ validatePricingAmount(id, entry.pricing.amount, MPP_PROFILE.assetDecimals);
2095
2208
  }
2096
2209
  }
2097
2210
  const pricing = entry.pricing.type === "free" ? { type: "free" } : { type: "fixed", amount: entry.pricing.amount, currency: entry.pricing.currency };
@@ -2233,7 +2346,7 @@ function validateResourceSchemaKeywords(id, path2, schema) {
2233
2346
  }
2234
2347
  var PRICING_AMOUNT_PATTERN = /^\d+(?:\.\d+)?$/;
2235
2348
  var ZERO_AMOUNT_PATTERN = /^0(?:\.0+)?$/;
2236
- function validatePricingAmount(id, amount, x402) {
2349
+ function validatePricingAmount(id, amount, decimals) {
2237
2350
  const path2 = `resources.${id}.pricing.amount`;
2238
2351
  if (!PRICING_AMOUNT_PATTERN.test(amount)) {
2239
2352
  throw new CommerceError(
@@ -2249,12 +2362,12 @@ function validatePricingAmount(id, amount, x402) {
2249
2362
  { details: { path: path2, resourceId: id } }
2250
2363
  );
2251
2364
  }
2252
- if (x402 !== void 0) {
2365
+ if (decimals !== void 0) {
2253
2366
  const fractionalDigits = amount.includes(".") ? amount.split(".")[1]?.length ?? 0 : 0;
2254
- if (fractionalDigits > x402.assetDecimals) {
2367
+ if (fractionalDigits > decimals) {
2255
2368
  throw new CommerceError(
2256
2369
  "CONFIG_INVALID",
2257
- `Resource "${id}" has pricing.amount "${amount}" with more precision (${fractionalDigits} fractional digits) than the configured asset supports (${x402.assetDecimals} decimals)`,
2370
+ `Resource "${id}" has pricing.amount "${amount}" with more precision (${fractionalDigits} fractional digits) than the configured asset supports (${decimals} decimals)`,
2258
2371
  { details: { path: path2, resourceId: id } }
2259
2372
  );
2260
2373
  }
@@ -2513,6 +2626,7 @@ var SECRET_FIELD_NAMES = [
2513
2626
  "seed",
2514
2627
  "mnemonic",
2515
2628
  "secret",
2629
+ "challengeSecret",
2516
2630
  "apiKey",
2517
2631
  "adminToken",
2518
2632
  "token"
@@ -2578,7 +2692,7 @@ function wrapPino(instance) {
2578
2692
  }
2579
2693
  var CORS_METHODS = "GET,POST,DELETE,OPTIONS";
2580
2694
  var CORS_HEADERS = "content-type,payment-signature,x-request-id,authorization";
2581
- var CORS_EXPOSED_HEADERS = "payment-required,payment-response,x-request-id";
2695
+ var CORS_EXPOSED_HEADERS = "payment-required,payment-response,www-authenticate,payment-receipt,x-request-id";
2582
2696
  function buildAccessControlHook(options) {
2583
2697
  const allowedOrigins = new Set(options.allowedOrigins.map((origin) => origin.toLowerCase()));
2584
2698
  const publicHostname = hostnameOf(options.publicBaseUrl);
@@ -2991,7 +3105,7 @@ async function buildWellKnownDocument(options) {
2991
3105
  return { ...runtime.adapter.descriptor, health: publicHealth(health) };
2992
3106
  })
2993
3107
  );
2994
- const x402 = options.config.payments.x402;
3108
+ const { x402, mpp } = options.config.payments;
2995
3109
  return {
2996
3110
  gateway: {
2997
3111
  implementationVersion: PACKAGE_VERSION,
@@ -3027,6 +3141,22 @@ async function buildWellKnownDocument(options) {
3027
3141
  x402.facilitator.mode
3028
3142
  )
3029
3143
  }
3144
+ } : {},
3145
+ ...mpp !== void 0 ? {
3146
+ mpp: {
3147
+ enabled: mpp.enabled,
3148
+ network: mpp.network,
3149
+ asset: mpp.asset,
3150
+ assetName: mpp.assetName,
3151
+ assetVersion: mpp.assetVersion,
3152
+ assetDecimals: MPP_PROFILE.assetDecimals,
3153
+ recipient: mpp.recipient,
3154
+ facilitator: { mode: mpp.facilitator.mode },
3155
+ mode: resolveDeploymentMode(
3156
+ requireNetworkProfile(mpp.network, "payments.mpp.network"),
3157
+ mpp.facilitator.mode
3158
+ )
3159
+ }
3030
3160
  } : {}
3031
3161
  }
3032
3162
  };
@@ -3148,9 +3278,8 @@ async function handleInvoke(request, reply, options) {
3148
3278
  resourceId
3149
3279
  });
3150
3280
  }
3151
- const paymentHeader = request.headers[PAYMENT_HEADER];
3152
- const paymentValue = Array.isArray(paymentHeader) ? paymentHeader[0] : paymentHeader;
3153
3281
  const paymentMethod = resource.paymentMethods[0];
3282
+ const paymentValue = paymentProof(request, paymentMethod);
3154
3283
  const payment = paymentValue !== void 0 && paymentMethod !== void 0 ? { method: paymentMethod, payload: paymentValue } : void 0;
3155
3284
  const authorization = parseAuthorizationHeader(
3156
3285
  request.headers[AUTHORIZATION_HEADER],
@@ -3168,7 +3297,10 @@ async function handleInvoke(request, reply, options) {
3168
3297
  const outcome = await options.pipeline.execute(canonicalRequest);
3169
3298
  if (outcome.kind === "payment-required") {
3170
3299
  const envelope = toPaymentRequiredEnvelope(outcome);
3171
- if (envelope.payment.envelope !== void 0) {
3300
+ if (envelope.payment.provider === "mpp") {
3301
+ const challenge = envelope.payment.envelope?.["wwwAuthenticate"];
3302
+ if (typeof challenge === "string") reply.header("www-authenticate", challenge);
3303
+ } else if (envelope.payment.envelope !== void 0) {
3172
3304
  reply.header(PAYMENT_REQUIRED_HEADER, encodeHeaderDocument(envelope.payment.envelope));
3173
3305
  }
3174
3306
  reply.header("cache-control", "no-store");
@@ -3177,6 +3309,10 @@ async function handleInvoke(request, reply, options) {
3177
3309
  }
3178
3310
  if (outcome.payment) {
3179
3311
  reply.header(PAYMENT_RESPONSE_HEADER, encodePaymentSummary(outcome.payment));
3312
+ const receipt = outcome.payment.metadata?.["receipt"];
3313
+ if (outcome.payment.provider === "mpp" && typeof receipt === "string") {
3314
+ reply.header("payment-receipt", receipt);
3315
+ }
3180
3316
  }
3181
3317
  reply.status(outcome.backendStatus).send(outcome.body);
3182
3318
  } catch (error) {
@@ -3184,15 +3320,31 @@ async function handleInvoke(request, reply, options) {
3184
3320
  const settledPayment = errorPaymentSummary(commerceError);
3185
3321
  if (settledPayment !== void 0) {
3186
3322
  reply.header(PAYMENT_RESPONSE_HEADER, encodePaymentSummary(settledPayment));
3323
+ if (settledPayment.provider === "mpp" && settledPayment.receipt !== void 0) {
3324
+ reply.header("payment-receipt", settledPayment.receipt);
3325
+ }
3326
+ }
3327
+ const challenge = commerceError.details?.["challenge"];
3328
+ if (commerceError.code === "PAYMENT_INVALID" && typeof challenge?.["wwwAuthenticate"] === "string") {
3329
+ reply.header("www-authenticate", challenge["wwwAuthenticate"]);
3330
+ reply.header("cache-control", "no-store");
3187
3331
  }
3188
3332
  reply.status(commerceError.httpStatus).send(toErrorEnvelope(commerceError));
3189
3333
  }
3190
3334
  }
3335
+ function paymentProof(request, method) {
3336
+ if (method === "mpp") {
3337
+ const value2 = request.headers.authorization;
3338
+ return value2 !== void 0 && /^Payment\s+\S/i.test(value2) ? value2 : void 0;
3339
+ }
3340
+ const value = request.headers[PAYMENT_HEADER];
3341
+ return Array.isArray(value) ? value[0] : value;
3342
+ }
3191
3343
  function errorPaymentSummary(error) {
3192
3344
  const payment = error.details?.["payment"];
3193
3345
  if (typeof payment !== "object" || payment === null) return void 0;
3194
3346
  const rec = payment;
3195
- const { status, provider, amount, currency, network, externalReference } = rec;
3347
+ const { status, provider, amount, currency, network, externalReference, receipt } = rec;
3196
3348
  if (typeof status !== "string" || typeof provider !== "string" || typeof amount !== "string" || typeof currency !== "string") {
3197
3349
  return void 0;
3198
3350
  }
@@ -3202,7 +3354,8 @@ function errorPaymentSummary(error) {
3202
3354
  amount,
3203
3355
  currency,
3204
3356
  ...typeof network === "string" ? { network } : {},
3205
- ...typeof externalReference === "string" ? { externalReference } : {}
3357
+ ...typeof externalReference === "string" ? { externalReference } : {},
3358
+ ...typeof receipt === "string" ? { receipt } : {}
3206
3359
  };
3207
3360
  }
3208
3361
  function encodeHeaderDocument(document) {
@@ -3244,13 +3397,25 @@ function parseLimit(value) {
3244
3397
  // src/gateway/server.ts
3245
3398
  var REQUEST_ID_HEADER = "x-request-id";
3246
3399
  var CLIENT_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,64}$/;
3400
+ function withProviderBackedMethodsFirst(resource, providers) {
3401
+ const backed = (method) => providers.some((provider) => provider.name === method);
3402
+ const ordered = [
3403
+ ...resource.paymentMethods.filter(backed),
3404
+ ...resource.paymentMethods.filter((method) => !backed(method))
3405
+ ];
3406
+ return ordered.some((method, i) => method !== resource.paymentMethods[i]) ? { ...resource, paymentMethods: ordered } : resource;
3407
+ }
3247
3408
  async function createGateway(options) {
3248
3409
  const clock = options.clock ?? systemClock;
3249
3410
  const ids = options.ids ?? createDefaultIdGenerator();
3250
3411
  const logger = options.logger ?? createGatewayLogger({ name: options.config.merchant.id }).core;
3251
3412
  const backend = options.backend ?? new HttpBackendExecutor({ logger });
3252
3413
  const authorizationProviders = options.authorizationProviders ?? [];
3253
- const resources = createResourceRegistry(options.config.resources);
3414
+ const resources = createResourceRegistry(
3415
+ options.config.resources.map(
3416
+ (resource) => withProviderBackedMethodsFirst(resource, options.paymentProviders)
3417
+ )
3418
+ );
3254
3419
  const eventBus = createEventBus({ store: options.store, logger });
3255
3420
  const pipeline = createExecutionPipeline({
3256
3421
  resources,
@@ -3334,13 +3499,14 @@ async function createGateway(options) {
3334
3499
 
3335
3500
  // src/protocols/acp/checkout-mapping.ts
3336
3501
  function toCanonicalRequest(options) {
3337
- const { request, resourceId, requestId, receivedAt } = options;
3502
+ const { request, resourceId, requestId, receivedAt, idempotencyKey } = options;
3338
3503
  return {
3339
3504
  requestId,
3340
3505
  resourceId,
3341
3506
  input: canonicalInput(request),
3342
3507
  protocol: "acp",
3343
- receivedAt
3508
+ receivedAt,
3509
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {}
3344
3510
  };
3345
3511
  }
3346
3512
  function canonicalInput(request) {
@@ -3448,8 +3614,8 @@ function writeAcpJson(res, status, body, headers = {}) {
3448
3614
  function writeAcpFailure(res, failure, headers = {}) {
3449
3615
  writeAcpJson(res, failure.status, failure.error, headers);
3450
3616
  }
3451
- function identityHash(token) {
3452
- return sha256(`acp-auth:${token}`);
3617
+ function operationKey(scope) {
3618
+ return sha256(`acp-operation:${scope.deployment}:${scope.endpoint}:${scope.key}`);
3453
3619
  }
3454
3620
  function requestFingerprint(body) {
3455
3621
  return sha256(canonicalize(body));
@@ -3468,6 +3634,7 @@ function sha256(value) {
3468
3634
  }
3469
3635
 
3470
3636
  // src/protocols/acp/idempotency/store.ts
3637
+ var SCHEMA_VERSION = 2;
3471
3638
  var ACP_MAX_IDEMPOTENCY_KEY_LENGTH = 255;
3472
3639
  function createAcpIdempotencyStore(options) {
3473
3640
  const logger = options.logger ?? NOOP_LOGGER;
@@ -3478,33 +3645,39 @@ function createAcpIdempotencyStore(options) {
3478
3645
  label: "ACP idempotency database",
3479
3646
  logger
3480
3647
  });
3481
- migrate(db);
3648
+ migrate(db, logger);
3482
3649
  const selectStmt = db.prepare(
3483
3650
  `SELECT state, fingerprint, status, body_json FROM acp_idempotency
3484
- WHERE identity_hash = ? AND endpoint = ? AND idempotency_key = ?`
3651
+ WHERE deployment = ? AND endpoint = ? AND idempotency_key = ?`
3485
3652
  );
3486
3653
  const insertStmt = db.prepare(
3487
3654
  `INSERT INTO acp_idempotency
3488
- (identity_hash, endpoint, idempotency_key, fingerprint, state, created_at, expires_at)
3489
- VALUES (@identity_hash, @endpoint, @idempotency_key, @fingerprint, 'in_flight', @created_at, @expires_at)`
3655
+ (deployment, endpoint, idempotency_key, fingerprint, state, created_at, expires_at)
3656
+ VALUES (@deployment, @endpoint, @idempotency_key, @fingerprint, 'in_flight', @created_at, @expires_at)`
3490
3657
  );
3491
3658
  const completeStmt = db.prepare(
3492
3659
  `UPDATE acp_idempotency SET state = 'completed', status = @status, body_json = @body_json
3493
- WHERE identity_hash = @identity_hash AND endpoint = @endpoint AND idempotency_key = @idempotency_key`
3660
+ WHERE deployment = @deployment AND endpoint = @endpoint AND idempotency_key = @idempotency_key`
3494
3661
  );
3495
3662
  const deleteStmt = db.prepare(
3496
3663
  `DELETE FROM acp_idempotency
3497
- WHERE identity_hash = ? AND endpoint = ? AND idempotency_key = ?`
3664
+ WHERE deployment = ? AND endpoint = ? AND idempotency_key = ?`
3665
+ );
3666
+ const unresolvedStmt = db.prepare(
3667
+ `UPDATE acp_idempotency SET state = 'unresolved'
3668
+ WHERE deployment = @deployment AND endpoint = @endpoint AND idempotency_key = @idempotency_key`
3669
+ );
3670
+ const expireStmt = db.prepare(
3671
+ "DELETE FROM acp_idempotency WHERE expires_at <= ? AND state = 'completed'"
3498
3672
  );
3499
- const expireStmt = db.prepare("DELETE FROM acp_idempotency WHERE expires_at <= ?");
3500
3673
  const claimTx = db.transaction(
3501
3674
  (scope, fingerprint) => {
3502
3675
  const at = now();
3503
3676
  expireStmt.run(new Date(at).toISOString());
3504
- const existing = selectStmt.get(scope.identityHash, scope.endpoint, scope.key);
3677
+ const existing = selectStmt.get(scope.deployment, scope.endpoint, scope.key);
3505
3678
  if (existing === void 0) {
3506
3679
  insertStmt.run({
3507
- identity_hash: scope.identityHash,
3680
+ deployment: scope.deployment,
3508
3681
  endpoint: scope.endpoint,
3509
3682
  idempotency_key: scope.key,
3510
3683
  fingerprint,
@@ -3514,6 +3687,7 @@ function createAcpIdempotencyStore(options) {
3514
3687
  return { kind: "reserved" };
3515
3688
  }
3516
3689
  if (existing.fingerprint !== fingerprint) return { kind: "conflict" };
3690
+ if (existing.state === "unresolved") return { kind: "unresolved" };
3517
3691
  if (existing.state !== "completed") return { kind: "in-flight" };
3518
3692
  return {
3519
3693
  kind: "replay",
@@ -3529,15 +3703,22 @@ function createAcpIdempotencyStore(options) {
3529
3703
  },
3530
3704
  complete(scope, response) {
3531
3705
  completeStmt.run({
3532
- identity_hash: scope.identityHash,
3706
+ deployment: scope.deployment,
3533
3707
  endpoint: scope.endpoint,
3534
3708
  idempotency_key: scope.key,
3535
3709
  status: response.status,
3536
3710
  body_json: JSON.stringify(response.body ?? null)
3537
3711
  });
3538
3712
  },
3713
+ markUnresolved(scope) {
3714
+ unresolvedStmt.run({
3715
+ deployment: scope.deployment,
3716
+ endpoint: scope.endpoint,
3717
+ idempotency_key: scope.key
3718
+ });
3719
+ },
3539
3720
  release(scope) {
3540
- deleteStmt.run(scope.identityHash, scope.endpoint, scope.key);
3721
+ deleteStmt.run(scope.deployment, scope.endpoint, scope.key);
3541
3722
  },
3542
3723
  close() {
3543
3724
  if (closed) return;
@@ -3546,13 +3727,23 @@ function createAcpIdempotencyStore(options) {
3546
3727
  }
3547
3728
  };
3548
3729
  }
3549
- function migrate(db) {
3730
+ function migrate(db, logger) {
3550
3731
  const current = db.pragma("user_version", { simple: true });
3551
- if (current >= 1) return;
3732
+ if (current >= SCHEMA_VERSION) return;
3552
3733
  const apply = db.transaction(() => {
3734
+ if (current === 1) {
3735
+ const carried = db.prepare("SELECT COUNT(*) AS count FROM acp_idempotency WHERE state != 'completed'").get();
3736
+ if (carried.count > 0) {
3737
+ logger.warn(
3738
+ { unresolved: carried.count },
3739
+ "acp idempotency: schema v1 -> v2 discards claims keyed by the old bearer-token digest; reconcile these operations against the merchant before trusting a retry"
3740
+ );
3741
+ }
3742
+ db.exec("DROP TABLE acp_idempotency");
3743
+ }
3553
3744
  db.exec(`
3554
3745
  CREATE TABLE IF NOT EXISTS acp_idempotency (
3555
- identity_hash TEXT NOT NULL,
3746
+ deployment TEXT NOT NULL,
3556
3747
  endpoint TEXT NOT NULL,
3557
3748
  idempotency_key TEXT NOT NULL,
3558
3749
  fingerprint TEXT NOT NULL,
@@ -3561,12 +3752,12 @@ function migrate(db) {
3561
3752
  body_json TEXT,
3562
3753
  created_at TEXT NOT NULL,
3563
3754
  expires_at TEXT NOT NULL,
3564
- PRIMARY KEY (identity_hash, endpoint, idempotency_key)
3755
+ PRIMARY KEY (deployment, endpoint, idempotency_key)
3565
3756
  );
3566
3757
  CREATE INDEX IF NOT EXISTS idx_acp_idempotency_expires_at
3567
3758
  ON acp_idempotency(expires_at);
3568
3759
  `);
3569
- db.pragma("user_version = 1");
3760
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
3570
3761
  });
3571
3762
  apply();
3572
3763
  }
@@ -7974,6 +8165,28 @@ function asResponse(failure) {
7974
8165
 
7975
8166
  // src/protocols/acp/adapter.ts
7976
8167
  var DISCOVERY_CACHE_CONTROL = "public, max-age=3600";
8168
+ var PRE_BACKEND_ERROR_CODES = /* @__PURE__ */ new Set([
8169
+ "CONFIG_INVALID",
8170
+ "RESOURCE_NOT_FOUND",
8171
+ "INPUT_INVALID",
8172
+ "PROTOCOL_UNSUPPORTED",
8173
+ "GATEWAY_BUSY",
8174
+ "PAYMENT_REQUIRED",
8175
+ "PAYMENT_INVALID",
8176
+ "PAYMENT_REPLAYED",
8177
+ "PAYMENT_PROVIDER_UNAVAILABLE",
8178
+ "PAYMENT_SETTLEMENT_FAILED",
8179
+ "AUTHORIZATION_REQUIRED",
8180
+ "AUTHORIZATION_INVALID",
8181
+ "AUTHORIZATION_REPLAYED",
8182
+ "AUTHORIZATION_PROVIDER_UNAVAILABLE"
8183
+ ]);
8184
+ function reachedFor(code) {
8185
+ return PRE_BACKEND_ERROR_CODES.has(code) ? "no" : "unknown";
8186
+ }
8187
+ function notReached(failure) {
8188
+ return { response: asResponse(failure), reached: "no" };
8189
+ }
7977
8190
  var AcpProtocolAdapter = class {
7978
8191
  name = "acp";
7979
8192
  mountPath;
@@ -7982,8 +8195,14 @@ var AcpProtocolAdapter = class {
7982
8195
  token;
7983
8196
  operations;
7984
8197
  idempotencyOptions;
7985
- /** Derived once: the token is fixed, and only its digest may be persisted. */
7986
- identityHash;
8198
+ /**
8199
+ * What an idempotency claim is scoped to, from `context.publicBaseUrl`.
8200
+ *
8201
+ * Not readonly because, unlike the token, it is known only once the adapter
8202
+ * starts. Worth that: scoping by the credential meant a rotation freed
8203
+ * every outstanding claim.
8204
+ */
8205
+ deployment;
7987
8206
  discoveryMetadata;
7988
8207
  idempotency;
7989
8208
  resources = /* @__PURE__ */ new Map();
@@ -7998,7 +8217,6 @@ var AcpProtocolAdapter = class {
7998
8217
  this.token = options.token;
7999
8218
  this.operations = options.operations;
8000
8219
  this.idempotencyOptions = options.idempotency;
8001
- this.identityHash = identityHash(options.token);
8002
8220
  this.discoveryMetadata = options.discovery;
8003
8221
  this.descriptor = buildDescriptor(PACKAGE_VERSION);
8004
8222
  this.additionalHttpRoutes = [
@@ -8042,6 +8260,7 @@ var AcpProtocolAdapter = class {
8042
8260
  resolved.set(operation, resource);
8043
8261
  }
8044
8262
  this.resources = resolved;
8263
+ this.deployment = context.publicBaseUrl;
8045
8264
  this.idempotency = createAcpIdempotencyStore({
8046
8265
  path: this.idempotencyOptions.path,
8047
8266
  retentionHours: this.idempotencyOptions.retentionHours,
@@ -8130,22 +8349,33 @@ var AcpProtocolAdapter = class {
8130
8349
  */
8131
8350
  async dispatch(request) {
8132
8351
  const store = this.idempotency;
8352
+ const deployment = this.deployment;
8133
8353
  const key = request.idempotencyKey;
8134
- if (store === void 0 || key === void 0) return this.runCheckout(request);
8135
- const scope = {
8136
- identityHash: this.identityHash,
8137
- endpoint: request.route.path,
8138
- key
8139
- };
8354
+ if (store === void 0 || deployment === void 0 || key === void 0) {
8355
+ return (await this.runCheckout(request)).response;
8356
+ }
8357
+ const scope = { deployment, endpoint: request.route.path, key };
8140
8358
  const claim = store.claim(scope, requestFingerprint(request.body));
8141
8359
  switch (claim.kind) {
8142
8360
  case "in-flight":
8361
+ return {
8362
+ ...asResponse(
8363
+ acpFailure(
8364
+ 409,
8365
+ "invalid_request",
8366
+ "idempotency_in_flight",
8367
+ "A request with this Idempotency-Key is still being processed."
8368
+ )
8369
+ ),
8370
+ retryAfterSeconds: ACP_IN_FLIGHT_RETRY_AFTER_SECONDS
8371
+ };
8372
+ case "unresolved":
8143
8373
  return asResponse(
8144
8374
  acpFailure(
8145
8375
  409,
8146
- "invalid_request",
8147
- "idempotency_in_flight",
8148
- "A request with this Idempotency-Key is still being processed."
8376
+ "processing_error",
8377
+ "idempotency_unresolved",
8378
+ "An earlier request with this Idempotency-Key reached the merchant and its outcome is unknown. Check the operation with the merchant before retrying."
8149
8379
  )
8150
8380
  );
8151
8381
  case "conflict":
@@ -8160,16 +8390,21 @@ var AcpProtocolAdapter = class {
8160
8390
  case "replay":
8161
8391
  return { status: claim.status, body: claim.body, replayed: true };
8162
8392
  }
8163
- let response;
8393
+ let outcome;
8164
8394
  try {
8165
- response = await this.runCheckout(request);
8395
+ outcome = await this.runCheckout(request, operationKey(scope));
8166
8396
  } catch (err) {
8167
- store.release(scope);
8397
+ store.markUnresolved(scope);
8168
8398
  throw err;
8169
8399
  }
8170
- if (response.status >= 500) store.release(scope);
8171
- else store.complete(scope, response);
8172
- return response;
8400
+ if (outcome.response.status < 500) {
8401
+ store.complete(scope, outcome.response);
8402
+ } else if (outcome.reached === "no") {
8403
+ store.release(scope);
8404
+ } else {
8405
+ store.markUnresolved(scope);
8406
+ }
8407
+ return outcome.response;
8173
8408
  }
8174
8409
  /**
8175
8410
  * One accepted request, one `pipeline.execute()`.
@@ -8178,11 +8413,11 @@ var AcpProtocolAdapter = class {
8178
8413
  * merchant backend: the adapter builds a canonical request and reads back
8179
8414
  * what the pipeline decided.
8180
8415
  */
8181
- async runCheckout(request) {
8416
+ async runCheckout(request, idempotencyKey) {
8182
8417
  const context = this.context;
8183
8418
  const resource = this.resources.get(request.route.operation);
8184
8419
  if (context === void 0 || resource === void 0) {
8185
- return asResponse(
8420
+ return notReached(
8186
8421
  acpFailure(
8187
8422
  503,
8188
8423
  "service_unavailable",
@@ -8195,7 +8430,8 @@ var AcpProtocolAdapter = class {
8195
8430
  request,
8196
8431
  resourceId: resource.id,
8197
8432
  requestId: context.ids.next("acp"),
8198
- receivedAt: context.clock.nowIso()
8433
+ receivedAt: context.clock.nowIso(),
8434
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {}
8199
8435
  });
8200
8436
  try {
8201
8437
  const outcome = await context.pipeline.execute(canonical);
@@ -8204,7 +8440,7 @@ var AcpProtocolAdapter = class {
8204
8440
  { resourceId: resource.id, requestId: canonical.requestId },
8205
8441
  "acp adapter: mapped checkout resource returned payment-required - it must be priced free"
8206
8442
  );
8207
- return asResponse(
8443
+ return notReached(
8208
8444
  acpFailure(
8209
8445
  500,
8210
8446
  "processing_error",
@@ -8220,14 +8456,22 @@ var AcpProtocolAdapter = class {
8220
8456
  "acp adapter: refusing to forward a non-conformant merchant response"
8221
8457
  );
8222
8458
  }
8223
- return mapped.response;
8459
+ return { response: mapped.response, reached: "yes" };
8224
8460
  } catch (err) {
8225
8461
  const error = toCommerceError(err);
8226
8462
  context.logger.warn(
8227
- { resourceId: resource.id, requestId: canonical.requestId, err: error.toInfo() },
8463
+ {
8464
+ resourceId: resource.id,
8465
+ requestId: canonical.requestId,
8466
+ reached: reachedFor(error.code),
8467
+ err: error.toInfo()
8468
+ },
8228
8469
  "acp adapter: checkout execution failed"
8229
8470
  );
8230
- return asResponse(mapCommerceErrorToAcp(error, request.route.operation));
8471
+ return {
8472
+ response: asResponse(mapCommerceErrorToAcp(error, request.route.operation)),
8473
+ reached: reachedFor(error.code)
8474
+ };
8231
8475
  }
8232
8476
  }
8233
8477
  /**
@@ -8240,7 +8484,7 @@ var AcpProtocolAdapter = class {
8240
8484
  ...request.requestId !== void 0 ? { [ACP_REQUEST_ID_HEADER]: request.requestId } : {},
8241
8485
  ...request.idempotencyKey !== void 0 ? { [ACP_IDEMPOTENCY_KEY_HEADER]: request.idempotencyKey } : {},
8242
8486
  ...replayed ? { [ACP_IDEMPOTENT_REPLAYED_HEADER]: "true" } : {},
8243
- ...response?.status === 409 ? { "retry-after": String(ACP_IN_FLIGHT_RETRY_AFTER_SECONDS) } : {}
8487
+ ...response?.retryAfterSeconds !== void 0 ? { "retry-after": String(response.retryAfterSeconds) } : {}
8244
8488
  };
8245
8489
  }
8246
8490
  fail(res, err, message) {
@@ -8267,6 +8511,7 @@ var AcpProtocolAdapter = class {
8267
8511
  this.resources = /* @__PURE__ */ new Map();
8268
8512
  this.idempotency?.close();
8269
8513
  this.idempotency = void 0;
8514
+ this.deployment = void 0;
8270
8515
  this.context = void 0;
8271
8516
  }
8272
8517
  };
@@ -8443,7 +8688,7 @@ var MIGRATIONS = [
8443
8688
  }
8444
8689
  }
8445
8690
  ];
8446
- var SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
8691
+ var SCHEMA_VERSION2 = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
8447
8692
  function migrate2(db) {
8448
8693
  const current = db.pragma("user_version", { simple: true });
8449
8694
  for (const migration of MIGRATIONS) {
@@ -8529,17 +8774,17 @@ function createSqliteReceiptStore(options) {
8529
8774
  name: "sqlite-receipt-store",
8530
8775
  kind: "storage",
8531
8776
  implementationVersion: PACKAGE_VERSION,
8532
- supportedSpec: `sqlite-schema-v${SCHEMA_VERSION}`,
8777
+ supportedSpec: `sqlite-schema-v${SCHEMA_VERSION2}`,
8533
8778
  capabilities: ["receipts", "events", "payment-attempts"],
8534
8779
  status: "stable"
8535
8780
  };
8536
8781
  const store = {
8537
8782
  async init() {
8538
8783
  const version = db.pragma("user_version", { simple: true });
8539
- if (version !== SCHEMA_VERSION) {
8784
+ if (version !== SCHEMA_VERSION2) {
8540
8785
  throw new CommerceError(
8541
8786
  "STORAGE_ERROR",
8542
- `Receipt store schema version mismatch (found ${version}, expected ${SCHEMA_VERSION})`
8787
+ `Receipt store schema version mismatch (found ${version}, expected ${SCHEMA_VERSION2})`
8543
8788
  );
8544
8789
  }
8545
8790
  },
@@ -8673,9 +8918,9 @@ function createSqliteReceiptStore(options) {
8673
8918
  }
8674
8919
  try {
8675
8920
  const version = db.pragma("user_version", { simple: true });
8676
- if (version !== SCHEMA_VERSION) {
8921
+ if (version !== SCHEMA_VERSION2) {
8677
8922
  logger.warn(
8678
- { found: version, expected: SCHEMA_VERSION },
8923
+ { found: version, expected: SCHEMA_VERSION2 },
8679
8924
  "receipt-store: health check failed (schema version mismatch)"
8680
8925
  );
8681
8926
  return {
@@ -8687,7 +8932,7 @@ function createSqliteReceiptStore(options) {
8687
8932
  }
8688
8933
  return {
8689
8934
  status: "pass",
8690
- detail: `sqlite schema v${SCHEMA_VERSION} writable`,
8935
+ detail: `sqlite schema v${SCHEMA_VERSION2} writable`,
8691
8936
  checkedAt: clock.nowIso(),
8692
8937
  durationMs: clock.monotonicMs() - startedAt
8693
8938
  };