@agent-cards/sdk 0.23.9 → 0.23.11

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.
Files changed (65) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/PREFLIGHT.md +7 -5
  3. package/README.md +211 -102
  4. package/dist/aci.d.ts +7 -5
  5. package/dist/aci.generated.d.ts +2 -1
  6. package/dist/aci.generated.js +14 -4
  7. package/dist/aci.js +28 -7
  8. package/dist/amadeus-checkout.generated.js +2 -2
  9. package/dist/builtin-registry.generated.js +1 -1
  10. package/dist/card-fields.generated.js +165 -10
  11. package/dist/cdp.js +86 -34
  12. package/dist/client.d.ts +73 -33
  13. package/dist/client.js +136 -45
  14. package/dist/creditguard.generated.d.ts +1 -1
  15. package/dist/creditguard.generated.js +1 -3
  16. package/dist/cse-body.d.ts +7 -6
  17. package/dist/cse-body.js +8 -7
  18. package/dist/datatrans.generated.js +8 -5
  19. package/dist/elavon-converge.d.ts +5 -2
  20. package/dist/elavon-converge.generated.js +9 -5
  21. package/dist/elavon-converge.js +8 -4
  22. package/dist/fiserv.d.ts +9 -5
  23. package/dist/fiserv.generated.d.ts +3 -3
  24. package/dist/fiserv.generated.js +50 -20
  25. package/dist/fiserv.js +10 -6
  26. package/dist/lifecycle.d.ts +9 -1
  27. package/dist/lifecycle.js +34 -0
  28. package/dist/payrails.d.ts +78 -18
  29. package/dist/payrails.generated.d.ts +12 -2
  30. package/dist/payrails.generated.js +213 -18
  31. package/dist/payrails.js +165 -44
  32. package/dist/pre-claim.d.ts +7 -0
  33. package/dist/pre-claim.js +98 -36
  34. package/dist/preflight-capabilities.generated.d.ts +0 -106
  35. package/dist/preflight-capabilities.generated.js +0 -153
  36. package/dist/preflight-catalog.json +61 -181
  37. package/dist/preflight-schemas.json +6 -6
  38. package/dist/preflight.generated.d.ts +0 -106
  39. package/dist/preflight.generated.js +27 -15
  40. package/dist/preparation.d.ts +2 -0
  41. package/dist/preparation.js +4 -2
  42. package/dist/prepared-processor.d.ts +19 -5
  43. package/dist/prepared-processor.js +21 -5
  44. package/dist/primer.generated.d.ts +5 -1
  45. package/dist/primer.generated.js +202 -34
  46. package/dist/registry.d.ts +18 -12
  47. package/dist/registry.js +11 -8
  48. package/dist/shiji-sts.d.ts +24 -22
  49. package/dist/shiji-sts.generated.d.ts +1 -1
  50. package/dist/shiji-sts.generated.js +12 -8
  51. package/dist/shiji-sts.js +49 -46
  52. package/dist/stripe-checkout.generated.js +157 -49
  53. package/dist/verifone.generated.js +192 -17
  54. package/dist/verifone.js +1 -1
  55. package/dist/worldline-mycheckout.d.ts +27 -0
  56. package/dist/worldline-mycheckout.generated.d.ts +29 -0
  57. package/dist/worldline-mycheckout.generated.js +162 -0
  58. package/dist/worldline-mycheckout.js +93 -0
  59. package/dist/wspay.d.ts +17 -16
  60. package/dist/wspay.generated.d.ts +6 -1
  61. package/dist/wspay.generated.js +13 -3
  62. package/dist/wspay.js +9 -11
  63. package/examples/preflight/kernel-native/README.md +1 -1
  64. package/examples/preflight/kernel-native/inventory.json +1 -19
  65. package/package.json +3 -3
package/dist/fiserv.d.ts CHANGED
@@ -17,8 +17,9 @@ export declare function isFiservCaptureHost(url: string): boolean;
17
17
  /** The pinned key with this id, in either environment, or null. */
18
18
  export declare function fiservKeyPinById(id: unknown): FiservKeyPin | null;
19
19
  /**
20
- * The id of the pin a paused capture's card may be sealed under (its envelope's key,
21
- * pinned for its endpoint's environment and for the merchant its body names), or null.
20
+ * The id of the pin a paused capture's card may be sealed under (its envelope's key or,
21
+ * for a credentials session's key, the key pinned for the merchant its body names; for its
22
+ * endpoint's environment and that merchant), or null.
22
23
  */
23
24
  export declare function fiservCapturePinId(url: string, body: string | null): string | null;
24
25
  /** The card-capture endpoint a Fiserv preparation names for its environment. */
@@ -59,7 +60,10 @@ export declare function isFiservSubstitution(sub: unknown): boolean;
59
60
  * The paused Fiserv capture with the approval's envelope written over the four members
60
61
  * its source.encryptionData already carries, and every other byte the frame's own. It
61
62
  * throws, writing nothing, for a body that is not the capture Agentcard covers, for any
62
- * substitution but Fiserv's envelope, and for an envelope under another key than the one
63
- * the paused capture names. Run this on a Fiserv approval, never substituteEncryptedFields.
63
+ * substitution but Fiserv's envelope, and for an envelope under another key than the
64
+ * paused capture's card goes under: the key it names or, when it names a credentials
65
+ * session's key, the key pinned for its merchant on the endpoint `url` names (the paused
66
+ * capture's own URL, which a session's key requires), whose id then replaces the session's.
67
+ * Run this on a Fiserv approval, never substituteEncryptedFields.
64
68
  */
65
- export declare function substituteFiservEnvelope(body: string, sub: Substitutions): string;
69
+ export declare function substituteFiservEnvelope(body: string, sub: Substitutions, url?: string): string;
@@ -15,6 +15,7 @@ declare var FISERV_KEY_PINS: any;
15
15
  declare function fiservKeyPin(keyId: any, environment: any): any;
16
16
  declare function isFiservPinnedKeyId(keyId: any): any;
17
17
  declare function fiservKeyRefusal(keyId: any, environment: any, nowMs: any): "fiserv_key_unpinned" | "fiserv_key_inactive" | "fiserv_key_outside_validity" | null;
18
+ declare function fiservCheckoutHost(pinId: any, origin: any): string | null;
18
19
  declare function parseFiservPublishedKey(text2: any): {
19
20
  keyId: any;
20
21
  encryptionType: any;
@@ -38,7 +39,6 @@ declare function fiservErrorIdentifiers(text2: any): {
38
39
  declare function fiservCardCaptureRefusal(request: any): "fiserv_request_invalid" | "fiserv_endpoint_invalid" | "fiserv_body_invalid" | "fiserv_body_member_not_allowed" | "fiserv_source_not_encrypted_card" | "fiserv_envelope_invalid" | "fiserv_merchant_invalid" | "fiserv_transaction_details_invalid" | "fiserv_billing_address_invalid" | "fiserv_method_invalid" | "fiserv_headers_invalid" | "fiserv_header_not_allowed" | "fiserv_content_type_invalid" | "fiserv_auth_token_type_invalid" | "fiserv_merchant_header_mismatch" | "fiserv_terminal_header_mismatch" | "fiserv_header_value_invalid" | null;
39
40
  declare function isFiservCardCaptureRequest(request: any): boolean;
40
41
  declare function isFiservPausableBody(body: any): boolean;
41
- declare function fiservTemplateKeyId(body: any): any;
42
42
  declare function fiservCaptureMerchant(body: any): {
43
43
  merchantId: any;
44
44
  terminalId: any;
@@ -69,5 +69,5 @@ declare function fiservSubstitutions(envelope: any): {
69
69
  [k: string]: any;
70
70
  };
71
71
  };
72
- declare function substituteFiservEnvelope(body: any, sub: any): string;
73
- export { FISERV_CARD_CAPTURE_ENDPOINTS, FISERV_ENVELOPE_CONSTANTS, FISERV_KEY_PINS, FISERV_SIMULATOR_PANS, FISERV_SUBSTITUTION, encryptFiservCard, fiservApprovedEnvelopeRefusal, fiservCaptureCardMismatch, fiservCaptureKeyRefusal, fiservCaptureMerchant, fiservCapturePin, fiservCardCaptureRefusal, fiservEnvironment, fiservErrorIdentifiers, fiservKeyPin, fiservKeyRefusal, fiservPreparationUrl, fiservPublishedKeyRefusal, fiservSubstitutionFieldsRefusal, fiservSubstitutions, fiservTemplateKeyId, fiservTimestampMs, isFiservCardCaptureRequest, isFiservCardCaptureUrl, isFiservPausableBody, isFiservPinnedKeyId, isPreparedFiservRequest, parseFiservPublishedKey, sanitizeFiservCaptureResponse, substituteFiservEnvelope };
72
+ declare function substituteFiservEnvelope(body: any, sub: any, url?: null): string;
73
+ export { FISERV_CARD_CAPTURE_ENDPOINTS, FISERV_ENVELOPE_CONSTANTS, FISERV_KEY_PINS, FISERV_SIMULATOR_PANS, FISERV_SUBSTITUTION, encryptFiservCard, fiservApprovedEnvelopeRefusal, fiservCaptureCardMismatch, fiservCaptureKeyRefusal, fiservCaptureMerchant, fiservCapturePin, fiservCardCaptureRefusal, fiservCheckoutHost, fiservEnvironment, fiservErrorIdentifiers, fiservKeyPin, fiservKeyRefusal, fiservPreparationUrl, fiservPublishedKeyRefusal, fiservSubstitutionFieldsRefusal, fiservSubstitutions, fiservTimestampMs, isFiservCardCaptureRequest, isFiservCardCaptureUrl, isFiservPausableBody, isFiservPinnedKeyId, isPreparedFiservRequest, parseFiservPublishedKey, sanitizeFiservCaptureResponse, substituteFiservEnvelope };
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: abb8663c9be443f4f09ced0b06df7e3997cfbc7cae7bea1a1c341af1cea93524
3
+ // artifact-sha256: 0e3d991aa970b75f82e71cdec7a1c8dd9211626cd1821485492de7dfc37aad96
4
4
  // src/fiserv-environment.js
5
5
  var FISERV_CARD_CAPTURE_ENDPOINTS = /* @__PURE__ */ Object.freeze({
6
6
  production: "https://connect.fiservapis.com/ch/payments-vas/v1/card-capture",
@@ -320,12 +320,16 @@ var FISERV_KEY_PINS = /* @__PURE__ */ deepFreeze([
320
320
  id: "homedepot_orangepay",
321
321
  merchant: "Home Depot",
322
322
  environment: "production",
323
- // Empty until a card-free dry run records the merchantId Home Depot's
324
- // capture carries, so this key encrypts no card yet. env.js names
325
- // merchantId TZOPBKED, but that is what OrangePay sends its own authenticate
326
- // call and may not be the Commerce Hub id in the capture body, and the
327
- // payment step has never been observed.
328
- merchantIds: [],
323
+ // The Commerce Hub merchant OrangePay's capture names on Blinds.com, Home
324
+ // Depot's blinds store, and on homedepot.com: merchantDetails.merchantId
325
+ // TZOPBKED with terminal 12, which OrangePay's frame hands the Secure Data
326
+ // Capture submit, read card-free from Blinds.com's payment step on 2026-09-30,
327
+ // 2026-10-06 and 2026-10-11, and the id env.js names for both stores.
328
+ merchantIds: ["TZOPBKED"],
329
+ // Blinds.com's checkout, where those three captures were read. homedepot.com
330
+ // runs the same OrangePay page and its env.js names the same merchant, but no
331
+ // capture was read there, so it is not listed and its approval names no host.
332
+ checkoutOrigins: ["https://www.blinds.com"],
329
333
  keyId: "d51d5faeb0261f664c99aac27cd131db",
330
334
  spki: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArobUom7fqw1aWE03z0OFQhnwzXPmh/rw0GxxEEdDsV07ysH/xtEAggNO46sWeWh8UEBat/mleL6wart8XEDkP1z67mSBhvu9Y3MqrbTMilxkb7NxdsMW26ukYx7Q9QGq+llTuapwBhLeU3uVsu0keUcs1kqc32fisCMv6hd7Nvh9niVp6dxPpqWnlmrB3PqQI/Y9wIOKlLtQ8n/1b5V3UJUjEXgJlDBzBn13ATj3vrl2aJxey6A99DL75md8RBF/rd2x/OxPKHkqWQWbRIgd++dtjxLVUYe5vRnq8OtqvrWdY0SCRoG61PgHqIPvoaokovJNi8utp95WuCww5jX8MQIDAQAB",
331
335
  spkiSha256: "49c3ac40078bfb4d5a08ebf1f6009d0b4b25b9cac58a4046fe64f1fa917a0e84",
@@ -341,6 +345,8 @@ var FISERV_KEY_PINS = /* @__PURE__ */ deepFreeze([
341
345
  // The sandbox merchant the key was generated under, which every Wave 0
342
346
  // capture named.
343
347
  merchantIds: ["100008000003683"],
348
+ // No merchant's checkout: the sandbox proof pays on Agentcard's own fixture page.
349
+ checkoutOrigins: [],
344
350
  keyId: "77eba5e58f007456ce2c35527bec2877",
345
351
  spki: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqbsaa0wpsHUuNIAr5LVYAbID77PB4d0qYnaIsTFGa0JZMIj/egbfdkl4DCaEhV3dzRDMivr+EB5643Xut00lT+SoPonc0Hps+Z5ejcVIZrtIvx/kSxJEqYK70HZHxJewh6P5qFUb+2ZPLSWQQpMqNMpDQhlISt3XtnO1JWmTvCUrjs4PKyVbexIq7+Di36HzwAnatSRP+mfgBnRsXnFmF/oOZKijsCYswqfomIom8eb9kpU1mVuSCPXmxivPOXPdFARFGd23PhdHpLhx0E7OIXpm2xEDmizeit56KVrP4PUHT08tZ4x26cSj7Po1oFpJ4XtsjSrXxmJCdE/LQFIZUwIDAQAB",
346
352
  spkiSha256: "7a62942573e251eeffa856ce6f307d8684aad39eec43e9eb48badb450619296a",
@@ -371,6 +377,15 @@ function fiservKeyRefusal(keyId, environment, nowMs) {
371
377
  function fiservPinServesMerchant(entry, merchantId) {
372
378
  return !!entry && Array.isArray(entry.merchantIds) && typeof merchantId === "string" && entry.merchantIds.includes(merchantId);
373
379
  }
380
+ function fiservMerchantPins(merchantId, environment) {
381
+ return FISERV_KEY_PINS.filter((entry) => (environment === null || entry.environment === environment) && fiservPinServesMerchant(entry, merchantId));
382
+ }
383
+ function fiservCheckoutHost(pinId, origin) {
384
+ const entry = typeof pinId === "string" ? FISERV_KEY_PINS.find((pin2) => pin2.id === pinId) : null;
385
+ if (!entry || typeof origin !== "string" || !entry.checkoutOrigins.includes(origin))
386
+ return null;
387
+ return new URL(origin).host;
388
+ }
374
389
  function pinnedSpki(entry) {
375
390
  const der = entry && typeof entry.spki === "string" ? decodeBase64(entry.spki) : null;
376
391
  return der && sha256Hex(der) === entry.spkiSha256 ? der : null;
@@ -512,6 +527,7 @@ var decodedLength = (value, min, max) => {
512
527
  };
513
528
  var IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
514
529
  var KEY_ID = /^[A-Za-z0-9][A-Za-z0-9-]{7,63}$/;
530
+ var SESSION_KEY_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
515
531
  var BODY_LIMIT = 65536;
516
532
  var TOP_LEVEL = ["source", "merchantDetails", "transactionDetails", "billingAddress"];
517
533
  var ENVELOPE = ["keyId", "encryptedKey", "encryptionBlock", "aesIv", "aesTagLength", "encryptionType", "encryptionTarget"];
@@ -696,10 +712,6 @@ function isNonCardCapture(body) {
696
712
  function isFiservPausableBody(body) {
697
713
  return !isNonCardCapture(body);
698
714
  }
699
- function fiservTemplateKeyId(body) {
700
- const parsed = readCaptureBody(body);
701
- return parsed && captureBodyRefusal(parsed) === null ? parsed.source.encryptionData.keyId : null;
702
- }
703
715
  function fiservCaptureMerchant(body) {
704
716
  const parsed = readCaptureBody(body);
705
717
  if (!parsed || captureBodyRefusal(parsed) !== null)
@@ -719,12 +731,24 @@ function capturePin(capture) {
719
731
  const refusal = captureBodyRefusal(body);
720
732
  if (refusal)
721
733
  return { refusal };
722
- const pin2 = fiservKeyPin(body.source.encryptionData.keyId, environment);
723
- if (!pin2)
734
+ const { keyId } = body.source.encryptionData;
735
+ const { merchantId } = body.merchantDetails;
736
+ const pinned = fiservKeyPin(keyId, environment);
737
+ if (pinned)
738
+ return fiservPinServesMerchant(pinned, merchantId) ? { pin: pinned, environment } : { refusal: "fiserv_key_merchant_unlisted" };
739
+ if (!SESSION_KEY_ID.test(keyId))
724
740
  return { refusal: "fiserv_key_unpinned" };
725
- if (!fiservPinServesMerchant(pin2, body.merchantDetails.merchantId))
726
- return { refusal: "fiserv_key_merchant_unlisted" };
727
- return { pin: pin2, environment };
741
+ const pins = fiservMerchantPins(merchantId, environment);
742
+ return pins.length === 1 ? { pin: pins[0], environment } : { refusal: "fiserv_key_merchant_unlisted" };
743
+ }
744
+ function sealingKeyId(body, environment) {
745
+ const { keyId } = body.source.encryptionData;
746
+ if (isFiservPinnedKeyId(keyId) || !SESSION_KEY_ID.test(keyId))
747
+ return keyId;
748
+ if (!environment)
749
+ return null;
750
+ const pins = fiservMerchantPins(body.merchantDetails.merchantId, environment);
751
+ return pins.length === 1 ? pins[0].keyId : null;
728
752
  }
729
753
  function fiservCapturePin(capture) {
730
754
  return capturePin(capture).pin ?? null;
@@ -808,7 +832,7 @@ function fiservSubstitutions(envelope) {
808
832
  return { encoding: "json", at: FISERV_SUBSTITUTION.at, fields };
809
833
  }
810
834
  var SUBSTITUTION_MEMBERS = ["encoding", "at", "fields", "remove"];
811
- function substituteFiservEnvelope(body, sub) {
835
+ function substituteFiservEnvelope(body, sub, url = null) {
812
836
  if (!isPlainObject(sub) || !onlyKeys(sub, SUBSTITUTION_MEMBERS) || sub.encoding !== "json" || sub.at !== FISERV_SUBSTITUTION.at || own3(sub, "remove") && !(Array.isArray(sub.remove) && sub.remove.length === 0)) {
813
837
  throw new Error("Refusing to substitute: not a Fiserv envelope substitution.");
814
838
  }
@@ -820,11 +844,17 @@ function substituteFiservEnvelope(body, sub) {
820
844
  const bodyRefusal = parsed ? captureBodyRefusal(parsed) : "fiserv_body_invalid";
821
845
  if (bodyRefusal)
822
846
  throw new Error(`Refusing to substitute: ${bodyRefusal}.`);
823
- const envelope = parsed.source.encryptionData;
824
- if (envelope.keyId !== fields.keyId)
847
+ const environment = url === null ? null : fiservEnvironment(url) ?? null;
848
+ if (url !== null && !environment)
849
+ throw new Error("Refusing to substitute: the paused capture is not on a Fiserv card-capture endpoint.");
850
+ if (!environment && SESSION_KEY_ID.test(parsed.source.encryptionData.keyId)) {
851
+ throw new Error("Refusing to substitute: a credentials session's key is written over only with the capture's own URL.");
852
+ }
853
+ if (sealingKeyId(parsed, environment) !== fields.keyId)
825
854
  throw new Error("Refusing to substitute: the envelope names another key than the paused capture.");
855
+ const envelope = parsed.source.encryptionData;
826
856
  for (const name of FISERV_SUBSTITUTION.fields)
827
857
  envelope[name] = fields[name];
828
858
  return JSON.stringify(parsed);
829
859
  }
830
- export { FISERV_CARD_CAPTURE_ENDPOINTS, FISERV_ENVELOPE_CONSTANTS, FISERV_KEY_PINS, FISERV_SIMULATOR_PANS, FISERV_SUBSTITUTION, encryptFiservCard, fiservApprovedEnvelopeRefusal, fiservCaptureCardMismatch, fiservCaptureKeyRefusal, fiservCaptureMerchant, fiservCapturePin, fiservCardCaptureRefusal, fiservEnvironment, fiservErrorIdentifiers, fiservKeyPin, fiservKeyRefusal, fiservPreparationUrl, fiservPublishedKeyRefusal, fiservSubstitutionFieldsRefusal, fiservSubstitutions, fiservTemplateKeyId, fiservTimestampMs, isFiservCardCaptureRequest, isFiservCardCaptureUrl, isFiservPausableBody, isFiservPinnedKeyId, isPreparedFiservRequest, parseFiservPublishedKey, sanitizeFiservCaptureResponse, substituteFiservEnvelope };
860
+ export { FISERV_CARD_CAPTURE_ENDPOINTS, FISERV_ENVELOPE_CONSTANTS, FISERV_KEY_PINS, FISERV_SIMULATOR_PANS, FISERV_SUBSTITUTION, encryptFiservCard, fiservApprovedEnvelopeRefusal, fiservCaptureCardMismatch, fiservCaptureKeyRefusal, fiservCaptureMerchant, fiservCapturePin, fiservCardCaptureRefusal, fiservCheckoutHost, fiservEnvironment, fiservErrorIdentifiers, fiservKeyPin, fiservKeyRefusal, fiservPreparationUrl, fiservPublishedKeyRefusal, fiservSubstitutionFieldsRefusal, fiservSubstitutions, fiservTimestampMs, isFiservCardCaptureRequest, isFiservCardCaptureUrl, isFiservPausableBody, isFiservPinnedKeyId, isPreparedFiservRequest, parseFiservPublishedKey, sanitizeFiservCaptureResponse, substituteFiservEnvelope };
package/dist/fiserv.js CHANGED
@@ -36,8 +36,9 @@ export function fiservKeyPinById(id) {
36
36
  return typeof id === 'string' ? core.FISERV_KEY_PINS.find((pin) => pin.id === id) ?? null : null;
37
37
  }
38
38
  /**
39
- * The id of the pin a paused capture's card may be sealed under (its envelope's key,
40
- * pinned for its endpoint's environment and for the merchant its body names), or null.
39
+ * The id of the pin a paused capture's card may be sealed under (its envelope's key or,
40
+ * for a credentials session's key, the key pinned for the merchant its body names; for its
41
+ * endpoint's environment and that merchant), or null.
41
42
  */
42
43
  export function fiservCapturePinId(url, body) {
43
44
  return core.fiservCapturePin({ url, body })?.id ?? null;
@@ -96,9 +97,12 @@ export function isFiservSubstitution(sub) {
96
97
  * The paused Fiserv capture with the approval's envelope written over the four members
97
98
  * its source.encryptionData already carries, and every other byte the frame's own. It
98
99
  * throws, writing nothing, for a body that is not the capture Agentcard covers, for any
99
- * substitution but Fiserv's envelope, and for an envelope under another key than the one
100
- * the paused capture names. Run this on a Fiserv approval, never substituteEncryptedFields.
100
+ * substitution but Fiserv's envelope, and for an envelope under another key than the
101
+ * paused capture's card goes under: the key it names or, when it names a credentials
102
+ * session's key, the key pinned for its merchant on the endpoint `url` names (the paused
103
+ * capture's own URL, which a session's key requires), whose id then replaces the session's.
104
+ * Run this on a Fiserv approval, never substituteEncryptedFields.
101
105
  */
102
- export function substituteFiservEnvelope(body, sub) {
103
- return core.substituteFiservEnvelope(body, sub);
106
+ export function substituteFiservEnvelope(body, sub, url) {
107
+ return core.substituteFiservEnvelope(body, sub, url ?? null);
104
108
  }
@@ -106,7 +106,14 @@ export interface CheckoutController {
106
106
  resume(result: ResumeResult): Promise<void>;
107
107
  /** Await device consent before the caller starts the first native Pay action. One use per attachment. */
108
108
  prepare(options: PrepareCheckoutOptions): Promise<PreparedCheckout>;
109
- /** Stop locally and best-effort retire an unbound preparation. Does not cancel a processor payment. */
109
+ /**
110
+ * Stop locally and best-effort retire an unbound preparation. Does not cancel a processor payment.
111
+ * A checkout that already ended keeps its state and reason (`completed`, `declined`, `failed`,
112
+ * `timed_out`, `unsupported`, `cancelled`, `outcome_unknown`) once no card request is in flight, or
113
+ * once the request in flight has its own outcome recorded (a cancel from onStateChange or the `failed` event).
114
+ * Any other reads `outcome_unknown` once a card request is in flight or an authorization exists,
115
+ * and `cancelled` before, with reason `local_cancel`.
116
+ */
110
117
  cancel(): void;
111
118
  /** Ask the application's merchant resolver. A rejection records unknown; never automatically retries payment. */
112
119
  reconcile(): Promise<Readonly<CheckoutState>>;
@@ -131,6 +138,7 @@ export declare class CheckoutLifecycle implements CheckoutController {
131
138
  private state;
132
139
  private held;
133
140
  private active;
141
+ private settled;
134
142
  private cancelled;
135
143
  private merchantAborted;
136
144
  private unboundStripeToken;
package/dist/lifecycle.js CHANGED
@@ -2,6 +2,20 @@ import { AdyenTestPlatformRefusedError, ApprovalDeclinedError, ApprovalTimeoutEr
2
2
  import { CardEndpointRefusedError } from './card-endpoint.js';
3
3
  import { parseOwnedShopReceipt, OWNED_SHOP_SKU, OWNED_SHOP_AMOUNT_CENTS, OWNED_SHOP_CURRENCY } from './owned-shop.generated.js';
4
4
  import { isWspayChallenge } from './wspay.js';
5
+ /**
6
+ * The statuses that end an attempt: its outcome is known (`completed`, `declined`, `failed`,
7
+ * `timed_out`, `unsupported`, `cancelled`), or the SDK already reported it unknown with its own
8
+ * reason (`outcome_unknown`). cancel() stops and holds the attachment but leaves these as they are,
9
+ * so an application that cancels at cleanup still reads the outcome, once no card request is in
10
+ * flight or the request in flight has its own outcome recorded. A checkout not started (`idle`) or
11
+ * still under way (`awaiting_approval`, `ready_to_submit`, `awaiting_merchant`, `requires_user_action`,
12
+ * or any status while a request whose outcome is not recorded is in flight) has none:
13
+ * cancelling it reads `outcome_unknown` once a request is in flight or an authorization exists,
14
+ * since a card or a token may still move, and `cancelled` before.
15
+ */
16
+ const FINAL_STATUSES = new Set([
17
+ 'completed', 'declined', 'failed', 'timed_out', 'unsupported', 'cancelled', 'outcome_unknown',
18
+ ]);
5
19
  /** How many times one approval's card-free Stripe confirmation may be checked before it is simply held. */
6
20
  export const MAX_STRIPE_CONTINUATION_CHECKS = 3;
7
21
  /**
@@ -29,6 +43,10 @@ export class CheckoutLifecycle {
29
43
  state = { status: 'idle', authorizationId: null };
30
44
  held = false;
31
45
  active = false;
46
+ // The card request in flight has its own outcome recorded (failed(), handedOff(), merchantNeverRetried()), and
47
+ // the adapter has yet to call end(): it still answers the page and tidies up. An application that cancels
48
+ // from onStateChange or the `failed` event runs in that window, and the request waits on nothing there.
49
+ settled = false;
32
50
  cancelled = false;
33
51
  merchantAborted = false;
34
52
  unboundStripeToken = false;
@@ -113,6 +131,7 @@ export class CheckoutLifecycle {
113
131
  }
114
132
  begin() {
115
133
  this.active = true;
134
+ this.settled = false;
116
135
  this.set({ status: 'awaiting_approval', authorizationId: null, ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
117
136
  }
118
137
  end() { this.active = false; }
@@ -137,9 +156,20 @@ export class CheckoutLifecycle {
137
156
  catch { /* observer only */ }
138
157
  }
139
158
  cancel() {
159
+ // A checkout that already ended keeps its outcome: a refusal the processor gave, a payment the
160
+ // merchant confirmed, an unknown the SDK already named. Stopping the attachment afterwards
161
+ // changes nothing that happened, so only an attempt still under way reads as stopped here.
162
+ // A card request still in flight is under way whatever the state says: its authorization can
163
+ // still be approved on the cardholder's device, so cancelling it reads outcome_unknown as before.
164
+ // One whose own outcome is recorded (settled) is not: the adapter only has to finish answering
165
+ // the page, so a cancel from onStateChange or the `failed` event keeps that outcome.
166
+ // Read before the abort, so the state the application saw decides.
167
+ const ended = FINAL_STATUSES.has(this.state.status) && (!this.active || this.settled);
140
168
  this.cancelled = true;
141
169
  this.held = true;
142
170
  this.abort.abort();
171
+ if (ended)
172
+ return;
143
173
  this.set({ ...this.state, status: this.active || this.state.authorizationId ? 'outcome_unknown' : 'cancelled', reason: 'local_cancel' });
144
174
  }
145
175
  /** The exact browser request is gone; a late approval cannot reopen it. */
@@ -180,6 +210,7 @@ export class CheckoutLifecycle {
180
210
  merchantNeverRetried(authorizationId) {
181
211
  if (this.cancelled)
182
212
  return;
213
+ this.settled = true;
183
214
  this.set({ ...this.state, authorizationId, status: 'declined', reason: 'merchant_never_retried' });
184
215
  }
185
216
  /**
@@ -324,6 +355,7 @@ export class CheckoutLifecycle {
324
355
  handedOff(replay) {
325
356
  if (this.merchantAborted)
326
357
  return;
358
+ this.settled = true;
327
359
  if (this.ownedShopOrderId) {
328
360
  this.held = true;
329
361
  this.set({ status: 'completed', authorizationId: replay.authorizationId, mode: 'token', orderId: this.ownedShopOrderId });
@@ -359,6 +391,8 @@ export class CheckoutLifecycle {
359
391
  failed(error, handoffStarted = false) {
360
392
  if (this.cancelled)
361
393
  return;
394
+ // Before any state is set, so an observer that cancels from onStateChange sees the outcome as recorded.
395
+ this.settled = true;
362
396
  if (this.merchantAborted) {
363
397
  this.merchantRequestAborted();
364
398
  return;
@@ -3,24 +3,30 @@ export { PAYRAILS_CAPABILITY, PAYRAILS_PSP };
3
3
  /**
4
4
  * How the pre-claim gate reads a POST to a Payrails host: `card` for a card save on a save URL
5
5
  * (data.encryptedData present, payment-core's pause predicate), `card_payment` for a payment that
6
- * carries an encrypted card inside paymentComposition, and null for anything else, which continues.
6
+ * carries an encrypted card inside paymentComposition, the card form's included, and null for
7
+ * anything else, which continues.
7
8
  */
8
9
  export declare function payrailsRequestKind(url: string, method: string, body: string): 'card' | 'card_payment' | null;
10
+ /** Whether `url` is exactly a payment-acceptance execution's authorize link on a Payrails API host, where a card-form payment may pause. */
11
+ export declare function isPayrailsPaymentUrl(url: string): boolean;
9
12
  /**
10
13
  * Why a Payrails request was aborted, reported as the `reason` of the `unsupported_checkout` event:
11
14
  * unsupported_payrails_card_payment a payment that sends an encrypted card inside
12
- * paymentComposition; the attachment holds
15
+ * paymentComposition in a shape this build does not take
16
+ * over; the attachment holds
13
17
  * payrails_request_malformed, payrails_not_card_save, payrails_jwe_malformed,
14
18
  * payrails_future_usage_refused, payrails_card_on_file_consent_required
15
- * a card save payment-core's guard refuses; the attachment holds
16
- * payrails_session_unavailable no captured session matches the save; that request only
19
+ * a card save or card-form payment payment-core's guard
20
+ * refuses; the attachment holds
21
+ * payrails_session_unavailable no captured session matches the save or payment; that
22
+ * request only
17
23
  * payrails_key_invalid, payrails_key_mismatch
18
24
  * the captured key cannot be the frame's; that request only
19
25
  */
20
26
  export type PayrailsUnsupportedReason = 'unsupported_payrails_card_payment' | 'payrails_request_malformed' | 'payrails_not_card_save' | 'payrails_jwe_malformed' | 'payrails_future_usage_refused' | 'payrails_card_on_file_consent_required' | 'payrails_session_unavailable' | 'payrails_key_invalid' | 'payrails_key_mismatch';
21
- /** Refusals of the save's own shape: the page's next save would be the same, so the attachment holds. */
27
+ /** Refusals of the request's own shape: the page's next one would be the same, so the attachment holds. */
22
28
  export declare const PAYRAILS_HOLDING_REASONS: readonly PayrailsUnsupportedReason[];
23
- /** What the agent reads when a save's session cannot be bound. Not shown to the cardholder. */
29
+ /** What the agent reads when a card request's session cannot be bound. Not shown to the cardholder. */
24
30
  export declare const PAYRAILS_SESSION_MESSAGE = "Payrails session expired, reload the checkout";
25
31
  /** Captured sessions kept per attachment; the oldest is dropped first. */
26
32
  export declare const PAYRAILS_CAPTURE_LIMIT = 8;
@@ -34,29 +40,50 @@ export declare const PAYRAILS_INIT_BODY_LIMIT: number;
34
40
  export declare const PAYRAILS_PENDING_READ_MS = 1000;
35
41
  /** Fetch interception patterns for every Payrails host, armed only for an org the API served Payrails to. */
36
42
  export declare const PAYRAILS_GUARD_PATTERNS: readonly string[];
37
- /** One captured Payrails session: the init payload's binding fields and its key. Never logged. */
43
+ /**
44
+ * One captured Payrails session: the init payload's binding fields and its key. Never logged.
45
+ * `executionId` is the payment-acceptance execution the payload carries (data.execution.id), or null
46
+ * when it carries none, and then no card-form payment binds to the session.
47
+ */
38
48
  export interface PayrailsSession {
39
49
  readonly token: string;
40
50
  readonly holderReference: string;
41
51
  readonly providerConfigId: string;
42
52
  readonly saveInstrumentHref: string;
53
+ readonly executionId: string | null;
43
54
  readonly encryptionPublicKey: string;
44
55
  readonly seenAt: number;
45
56
  }
46
57
  type SessionFields = Omit<PayrailsSession, 'seenAt'>;
47
- /** What the create carries beside a Payrails save: the session key its page encrypted under. */
58
+ /**
59
+ * What the create carries beside a Payrails card request: the session key its page encrypted under,
60
+ * and for a card-form payment the session's holderReference, which the payment's JWE carries.
61
+ */
48
62
  export interface PayrailsSidecar {
49
63
  readonly encryption_public_key: string;
64
+ readonly holder_reference?: string;
50
65
  }
66
+ /** How deep inside an answer an init payload is looked for, and how many of its objects at most. */
67
+ export declare const PAYRAILS_INIT_SEARCH_DEPTH = 8;
68
+ export declare const PAYRAILS_INIT_SEARCH_NODES = 4096;
51
69
  /**
52
- * Every Payrails init payload one JSON answer body carries: the body itself, or its members one
53
- * level down (an object's values or an array's items). An init payload is `{version, data}` whose
54
- * `data` decodes to JSON with a token, a holderReference and a vaultConfiguration naming its
55
- * providerConfigId, its encryptionPublicKey and a saveInstrument href on a Payrails save URL.
70
+ * Every Payrails init payload one JSON answer body carries: the body itself, or any object or array
71
+ * item inside it at most PAYRAILS_INIT_SEARCH_DEPTH levels down, read breadth first over at most
72
+ * PAYRAILS_INIT_SEARCH_NODES of them. Blacklane's session answer is the payload itself; FlixBus's
73
+ * /payment/configuration/list carries it six levels down
74
+ * (options.initialState.selection.paymentMethods[i].paymentToken). An init payload is
75
+ * `{version, data}` whose `data` decodes to JSON with a token, a holderReference and a
76
+ * vaultConfiguration naming its providerConfigId, its encryptionPublicKey and a saveInstrument href
77
+ * on a Payrails save URL; nothing inside one is read further.
56
78
  */
57
79
  export declare function payrailsInitSessions(body: unknown): SessionFields[];
58
80
  /** Whether an answer may carry an init payload worth reading: a 2xx XHR or fetch with a JSON type. */
59
81
  export declare function payrailsInitCandidate(resourceType: unknown, status: unknown, contentType: unknown): boolean;
82
+ /** The key and holderReference a paused Payrails card request binds to. */
83
+ export interface PayrailsBinding {
84
+ readonly key: string;
85
+ readonly holderReference: string;
86
+ }
60
87
  /**
61
88
  * The Payrails sessions one attachment has seen, in memory only: at most PAYRAILS_CAPTURE_LIMIT,
62
89
  * each dropped PAYRAILS_CAPTURE_TTL_MS after it was first seen. Nothing here is ever logged or
@@ -83,13 +110,27 @@ export declare class PayrailsSessionCapture {
83
110
  * The session key for a paused card save: the key of the captured session whose token is the
84
111
  * save's bearer token, whose saveInstrument href is the save's URL, and whose holderReference and
85
112
  * providerConfigId are the body's, first seen at most PAYRAILS_SESSION_MAX_AGE_MS before `now`.
86
- * Null when none matches, or when matching sessions disagree on the key.
113
+ * Null when none matches, or when matching sessions disagree on the key. bind() reads a card-form
114
+ * payment too.
87
115
  */
88
116
  keyFor(request: {
89
117
  url: string;
90
118
  headers?: Record<string, string>;
91
119
  body: string | null;
92
120
  }, now?: number): string | null;
121
+ /**
122
+ * The session a paused card request binds to: its key and holderReference. A save binds as keyFor
123
+ * says. A card-form payment binds to the captured session whose token is the payment's bearer
124
+ * token, whose execution is the one the authorize link names, whose saveInstrument href is on the
125
+ * link's host, and whose providerConfigId is the payment's vaultProviderConfigId, first seen at most
126
+ * PAYRAILS_SESSION_MAX_AGE_MS before `now`. Null when none matches, or when matching sessions
127
+ * disagree on the key or the holderReference.
128
+ */
129
+ bind(request: {
130
+ url: string;
131
+ headers?: Record<string, string>;
132
+ body: string | null;
133
+ }, now?: number): PayrailsBinding | null;
93
134
  /** How many sessions are held; for tests. */
94
135
  get size(): number;
95
136
  private prune;
@@ -116,18 +157,37 @@ export declare function isPayrailsStaticHost(rawUrl: string): boolean;
116
157
  * to, else the refusal code. A guard that throws refuses.
117
158
  */
118
159
  export declare function payrailsSaveRefusal(body: string): PayrailsUnsupportedReason | null;
160
+ /**
161
+ * payment-core's judgement of a card-form payment's own shape, before any session is looked up:
162
+ * null when it is the SDK's own payment that pays once, else the reason. A payment that stores the
163
+ * card at the merchant reads as payrails_card_on_file_consent_required, one that sets up a
164
+ * subscription as payrails_future_usage_refused, and a ciphertext of another shape as
165
+ * payrails_jwe_malformed; every other shape (a stored card's CVC, a split or installment payment, a
166
+ * preferred scheme) is one this build does not take over, unsupported_payrails_card_payment. A guard
167
+ * that throws refuses.
168
+ */
169
+ export declare function payrailsPaymentRefusal(body: string): PayrailsUnsupportedReason | null;
119
170
  /**
120
171
  * The key check for a bound session: null when the captured key is an RSA-2048, 3072 or 4096 key as
121
- * long as the key the page's frame wrapped for this save, else the refusal code.
172
+ * long as the key the page's frame wrapped for this save or payment, else the refusal code.
122
173
  */
123
174
  export declare function payrailsKeyRefusal(key: string, body: string): PayrailsUnsupportedReason | null;
124
175
  /**
125
- * The only answer the page may receive for an approved Payrails save: Payrails' 201 for a card
126
- * instrument, rebuilt from payment-core's allow-list (the BIN cut to six digits), under a JSON
127
- * content type. Null for anything else.
176
+ * The only answer the page may receive for an approved Payrails card request, by the paused `url`: for
177
+ * a save, Payrails' 201 for a card instrument, rebuilt from payment-core's allow-list (the BIN cut to
178
+ * six digits); for a card-form payment, Payrails' 2xx for the authorize action it accepted, rebuilt to
179
+ * its name, id, time and execution link, which the page's SDK polls for the issuer's answer. That link
180
+ * must be the paused payment's own execution on its own host, or the page would poll another payment's
181
+ * result. Under a JSON content type; null for anything else.
128
182
  */
183
+ export declare function payrailsAnswer(status: number, body: string, url: string): {
184
+ status: number;
185
+ headers: Record<string, string>;
186
+ body: string;
187
+ } | null;
188
+ /** payrailsAnswer for a save. */
129
189
  export declare function payrailsSaveAnswer(status: number, body: string): {
130
- status: 201;
190
+ status: number;
131
191
  headers: Record<string, string>;
132
192
  body: string;
133
193
  } | null;
@@ -3,6 +3,8 @@ declare var PAYRAILS_CAPABILITY: string;
3
3
  declare var PAYRAILS_OPERATION: string;
4
4
  declare var PAYRAILS_SAVE_PATH: string;
5
5
  declare var PAYRAILS_SAVE_HOSTS: readonly string[];
6
+ declare var PAYRAILS_MERCHANT_API_HOST: RegExp;
7
+ declare var PAYRAILS_AUTHORIZE_WORKFLOW: string;
6
8
  declare var PAYRAILS_JWE_HEADER: string;
7
9
  declare var PAYRAILS_JWE_HEADER_B64: string;
8
10
  declare var PAYRAILS_KEY_BITS: readonly number[];
@@ -25,9 +27,14 @@ declare var PAYRAILS_REFUSALS: Readonly<{
25
27
  declare function payrailsRefusalMessage(code: any): any;
26
28
  declare function base64UrlEncode(bytes: any): string;
27
29
  declare function base64UrlDecode(text: any, max: any): Uint8Array<ArrayBuffer> | null;
30
+ declare function isPayrailsApiHost(hostname: any): boolean;
28
31
  declare function isPayrailsSaveUrl(url: any, { exact: exact2 }?: {
29
32
  exact?: boolean | undefined;
30
33
  }): boolean;
34
+ declare function isPayrailsAuthorizeUrl(url: any, { exact: exact2 }?: {
35
+ exact?: boolean | undefined;
36
+ }): boolean;
37
+ declare function payrailsExecutionId(url: any): string | null;
31
38
  declare function payrailsJweParts(jwe: any): {
32
39
  wrappedKey: Uint8Array<ArrayBuffer>;
33
40
  iv: Uint8Array<ArrayBuffer>;
@@ -38,6 +45,8 @@ declare function payrailsTemplateRefusal(body: any): any;
38
45
  declare function parsePayrailsSave(body: any): any;
39
46
  declare function payrailsKeepsCardFor(save: any): any;
40
47
  declare function payrailsCardOnFileRefusal(body: any): any;
48
+ declare function payrailsAuthorizeRefusal(body: any): any;
49
+ declare function parsePayrailsAuthorize(body: any): any;
41
50
  declare function classifyPayrailsRequest(url: any, method: any, body: any): "card" | "continue" | null;
42
51
  declare function payrailsAuthorizeCarriesCard(url: any, method: any, body: any): boolean;
43
52
  declare function payrailsSessionKeyBytes(spki: any): any;
@@ -54,7 +63,8 @@ declare function payrailsFacts(text: any): {
54
63
  refusalCode: any;
55
64
  };
56
65
  declare function sanitizePayrailsResponse(text: any): string;
57
- declare function payrailsResponseOutcome(status: any, text: any, { card }?: {}): {
66
+ declare function sanitizePayrailsAuthorizeResponse(text: any, url: any): string;
67
+ declare function payrailsResponseOutcome(status: any, text: any, { card, url }?: {}): {
58
68
  outcome: string;
59
69
  reason: string;
60
70
  code?: undefined;
@@ -70,4 +80,4 @@ declare function payrailsResponseOutcome(status: any, text: any, { card }?: {}):
70
80
  reason?: undefined;
71
81
  code?: undefined;
72
82
  };
73
- export { PAYRAILS_CAPABILITY, PAYRAILS_CARD_ON_FILE_CONSENTED, PAYRAILS_FUTURE_USAGES, PAYRAILS_JWE_HEADER, PAYRAILS_JWE_HEADER_B64, PAYRAILS_KEY_BITS, PAYRAILS_OPERATION, PAYRAILS_PSP, PAYRAILS_REFUSALS, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, PAYRAILS_SERVED_CARD_FACTS, PAYRAILS_SESSION_MIN_SECONDS, PAYRAILS_UNUSABLE_STATUSES, PAYRAILS_USABLE_STATUSES, base64UrlDecode, base64UrlEncode, classifyPayrailsRequest, isPayrailsSaveUrl, parsePayrailsSave, payrailsAuthorizeCarriesCard, payrailsCardOnFileRefusal, payrailsFacts, payrailsJweParts, payrailsKeepsCardFor, payrailsRefusalMessage, payrailsResponseOutcome, payrailsSessionKeyBytes, payrailsSessionKeyRefusal, payrailsTemplateRefusal, payrailsTokenClaims, payrailsWorkspacePinRefusal, sanitizePayrailsResponse };
83
+ export { PAYRAILS_AUTHORIZE_WORKFLOW, PAYRAILS_CAPABILITY, PAYRAILS_CARD_ON_FILE_CONSENTED, PAYRAILS_FUTURE_USAGES, PAYRAILS_JWE_HEADER, PAYRAILS_JWE_HEADER_B64, PAYRAILS_KEY_BITS, PAYRAILS_MERCHANT_API_HOST, PAYRAILS_OPERATION, PAYRAILS_PSP, PAYRAILS_REFUSALS, PAYRAILS_SAVE_HOSTS, PAYRAILS_SAVE_PATH, PAYRAILS_SERVED_CARD_FACTS, PAYRAILS_SESSION_MIN_SECONDS, PAYRAILS_UNUSABLE_STATUSES, PAYRAILS_USABLE_STATUSES, base64UrlDecode, base64UrlEncode, classifyPayrailsRequest, isPayrailsApiHost, isPayrailsAuthorizeUrl, isPayrailsSaveUrl, parsePayrailsAuthorize, parsePayrailsSave, payrailsAuthorizeCarriesCard, payrailsAuthorizeRefusal, payrailsCardOnFileRefusal, payrailsExecutionId, payrailsFacts, payrailsJweParts, payrailsKeepsCardFor, payrailsRefusalMessage, payrailsResponseOutcome, payrailsSessionKeyBytes, payrailsSessionKeyRefusal, payrailsTemplateRefusal, payrailsTokenClaims, payrailsWorkspacePinRefusal, sanitizePayrailsAuthorizeResponse, sanitizePayrailsResponse };