@agent-cards/checkout 0.18.0 → 0.19.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/PREFLIGHT.md +4 -0
  3. package/README.md +91 -7
  4. package/dist/adyen-merchant-hosted.generated.d.ts +277 -0
  5. package/dist/adyen-merchant-hosted.generated.js +1902 -0
  6. package/dist/builtin-registry.generated.js +1 -1
  7. package/dist/cdp.d.ts +4 -1
  8. package/dist/cdp.js +416 -217
  9. package/dist/client.d.ts +236 -5
  10. package/dist/client.js +514 -11
  11. package/dist/cse-body.d.ts +25 -0
  12. package/dist/cse-body.js +41 -0
  13. package/dist/fiserv.d.ts +65 -0
  14. package/dist/fiserv.generated.d.ts +73 -0
  15. package/dist/fiserv.generated.js +830 -0
  16. package/dist/fiserv.js +104 -0
  17. package/dist/index.d.ts +7 -2
  18. package/dist/index.js +5 -1
  19. package/dist/lifecycle.d.ts +15 -1
  20. package/dist/lifecycle.js +28 -3
  21. package/dist/merchant-handoff.d.ts +54 -0
  22. package/dist/merchant-handoff.js +100 -0
  23. package/dist/merchant-hosted.d.ts +140 -0
  24. package/dist/merchant-hosted.js +170 -0
  25. package/dist/merchant-total-watch.d.ts +115 -0
  26. package/dist/merchant-total-watch.js +268 -0
  27. package/dist/merchant-total.d.ts +257 -0
  28. package/dist/merchant-total.js +383 -0
  29. package/dist/pre-claim.d.ts +123 -0
  30. package/dist/pre-claim.js +386 -0
  31. package/dist/preflight-catalog.json +132 -0
  32. package/dist/preflight-schemas.json +14 -2
  33. package/dist/preflight.generated.js +15 -1
  34. package/dist/preparation.d.ts +7 -0
  35. package/dist/preparation.js +33 -6
  36. package/dist/prepared-processor.d.ts +36 -3
  37. package/dist/prepared-processor.js +53 -3
  38. package/dist/registry.d.ts +45 -0
  39. package/dist/registry.js +14 -0
  40. package/dist/stripe-checkout.generated.js +96 -7
  41. package/dist/substitutions.generated.d.ts +2 -1
  42. package/dist/substitutions.generated.js +758 -6
  43. package/examples/preflight/kernel-native/inventory.json +1 -1
  44. package/package.json +3 -3
package/dist/client.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { type CheckoutMode, type Recognizer } from './registry.js';
2
2
  import type { Substitutions } from './substitute.js';
3
+ import { type MerchantHostedSubstitutions, type MerchantProfile, type SandboxMerchantDeclaration } from './merchant-hosted.js';
4
+ import { type MerchantTotalApproved, type MerchantTotalCapture } from './merchant-total.js';
3
5
  import { type PreparationMode, type PreparationProcessor } from './prepared-processor.js';
4
6
  export interface PausedRequest {
5
7
  url: string;
@@ -25,6 +27,17 @@ export declare const SUPPORTED_MODES: readonly CheckoutMode[];
25
27
  * confirm, which hosted Checkout sends after an approval.
26
28
  */
27
29
  export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
30
+ /**
31
+ * Dark-launch capabilities this build can finish. Sent on syncRegistry as
32
+ * ?capabilities= so the API also serves the recognizers gated behind one of
33
+ * these. `fiserv_card_capture`: Fiserv Commerce Hub's Secure Data Capture card
34
+ * capture, which this build pauses and pays only through a prepared checkout
35
+ * (pre-claim.ts, fiserv.ts). The API serves Fiserv's recognizer only to a build
36
+ * that lists it and only for a company Agentcard turned Fiserv on for, so an older
37
+ * SDK never pauses a capture it cannot finish and no SDK pauses one for a company
38
+ * Fiserv is off for; the API also refuses a Fiserv payment for every such company.
39
+ */
40
+ export declare const SUPPORTED_CAPABILITIES: readonly string[];
28
41
  /**
29
42
  * What the amount on an authorization IS: held to a Stripe PaymentIntent
30
43
  * (read back at create and before the replay), the parked form's own sum
@@ -94,14 +107,52 @@ export interface TokenReplay extends ExecutionMetadata {
94
107
  * (substituteEncryptedFields) and let the request CONTINUE from the browser
95
108
  * that paused it, so its session data, risk data and cookies stay its own.
96
109
  * The processor's answer then reaches the page as it normally would; the
97
- * merchant's order state is where the outcome shows up.
110
+ * merchant's order state is where the outcome shows up. A Fiserv card capture
111
+ * (a prepared checkout only) carries its envelope at `source.encryptionData`:
112
+ * write it with substituteFiservEnvelope, never substituteEncryptedFields.
98
113
  */
99
114
  export interface CseReplay extends ExecutionMetadata {
100
115
  mode: 'cse';
116
+ /** Absent on a processor-hosted cse approval; see MerchantHostedReplay. */
117
+ kind?: undefined;
101
118
  authorizationId: string;
102
119
  substitutions: Substitutions;
103
120
  amountAuthority?: AmountAuthority;
104
121
  }
122
+ /**
123
+ * The cse flow at an Adyen merchant's OWN endpoint (a reviewed merchant
124
+ * profile, see merchant-hosted.ts): the cardholder's device encrypted the card
125
+ * under the key Agentcard reviewed for that merchant, and nothing was sent. The
126
+ * paused request continues from this browser with the ciphertext written where
127
+ * the profile holds it (substituteMerchantHostedBody), and only when the live
128
+ * body still hashes to `bodySha256`, the body the API checked at create. The
129
+ * merchant's server charges the card and answers its own page; its order state is
130
+ * the outcome. Only a prepared checkout pays one, never Autopilot.
131
+ */
132
+ export interface MerchantHostedReplay extends ExecutionMetadata {
133
+ mode: 'cse';
134
+ kind: 'merchant_hosted';
135
+ authorizationId: string;
136
+ /** The reviewed merchant profile the approval pays. */
137
+ profile: string;
138
+ substitutions: MerchantHostedSubstitutions;
139
+ /** SHA-256 (lowercase hex) of the paused body the API checked at create. */
140
+ bodySha256: string;
141
+ /** When the API stops serving this ciphertext, if it said. */
142
+ substitutionsExpiresAt: string | null;
143
+ /** Present when the approval pays this client's own sandbox declaration (VaultClientOptions.sandboxMerchants). */
144
+ sandboxDeclaration?: Readonly<{
145
+ endpoint: string;
146
+ clientKey: string;
147
+ }>;
148
+ /**
149
+ * The merchant's own total this approval was created at, when its profile is priced by
150
+ * one: the adapters read it again at release (merchantTotalAtRelease) and hold the card
151
+ * request unless it is still this amount, then report what the merchant says it charged.
152
+ */
153
+ merchantTotal?: MerchantTotalApproved;
154
+ amountAuthority?: AmountAuthority;
155
+ }
105
156
  /**
106
157
  * The hosted_form flow (Tranzila): the cardholder's device rebuilt the
107
158
  * processor's own form with the real card and submitted it itself, top-level,
@@ -131,11 +182,25 @@ export interface HostedFormReplay extends ExecutionMetadata {
131
182
  amountAuthority?: AmountAuthority;
132
183
  }
133
184
  /** What authorize() resolves with; branch on `mode` (absent means token). */
134
- export type ReplayResponse = TokenReplay | CseReplay | HostedFormReplay;
185
+ export type ReplayResponse = TokenReplay | CseReplay | MerchantHostedReplay | HostedFormReplay;
135
186
  export interface PrepareCheckoutOptions {
136
187
  psp: PreparationProcessor;
137
188
  /** The processor environment, independent of your Agentcard client's mode. */
138
189
  environment: 'production' | 'sandbox' | 'shared';
190
+ /**
191
+ * An Adyen merchant that takes the card on its own server: the id of the
192
+ * profile Agentcard reviewed for it (psp 'adyen'; the environment is the
193
+ * profile's). The profile must be enabled on this client (syncRegistry); its
194
+ * card request is then the one request this preparation pays.
195
+ *
196
+ * A Fiserv checkout (psp 'fiserv'): the id of the key Agentcard pinned for the
197
+ * merchant, `agentcard_sandbox` in the sandbox environment. Required for Fiserv,
198
+ * and refused for every processor but Adyen and Fiserv. The cardholder is asked to
199
+ * approve a payment to the merchant that key belongs to, so the checkout's `merchant`
200
+ * must be that merchant's name (`Agentcard sandbox` for `agentcard_sandbox`), and the
201
+ * one card capture this preparation pays must carry an envelope under that key.
202
+ */
203
+ merchantProfile?: string;
139
204
  signal?: AbortSignal;
140
205
  }
141
206
  export interface PrepareCheckoutInput extends PrepareCheckoutOptions {
@@ -171,6 +236,13 @@ export interface PreparedCheckout {
171
236
  readonly checkoutKey: string;
172
237
  readonly paymentStatus: 'not_started';
173
238
  readonly amountAuthority: 'agent';
239
+ /** The reviewed merchant profile this preparation pays, or the Fiserv key pin it pays under; absent for a processor-hosted checkout. */
240
+ readonly merchantProfile?: string;
241
+ /** The declared test endpoint and key this preparation pays instead of the profile's (VaultClientOptions.sandboxMerchants). */
242
+ readonly sandboxDeclaration?: Readonly<{
243
+ endpoint: string;
244
+ clientKey: string;
245
+ }>;
174
246
  }
175
247
  export declare class CheckoutPreparationError extends Error {
176
248
  preparationId: string | null;
@@ -240,6 +312,13 @@ export interface AuthorizeInput extends ExecutionMetadata {
240
312
  onApprovalUrl?: (url: string) => void;
241
313
  /** One-use preparation returned by this client. Never resumes an older request. */
242
314
  preparation?: PreparedCheckout;
315
+ /**
316
+ * For a reviewed merchant profile priced by the merchant's own total: when the card
317
+ * request paused and the merchant responses this browser recorded (see
318
+ * merchant-total.ts). The adapters pass it; a runtime that intercepts on its own builds
319
+ * one from its network events, or the payment is refused before any create.
320
+ */
321
+ merchantTotal?: MerchantTotalCapture;
243
322
  }
244
323
  export declare class CardEncryptedError extends Error {
245
324
  psp: string;
@@ -254,6 +333,18 @@ export declare class UnsupportedModeError extends Error {
254
333
  mode: string;
255
334
  constructor(mode: string);
256
335
  }
336
+ /**
337
+ * The recognizer says this processor is preparation-required, and no preparation
338
+ * was passed. Its card may be sent only after the cardholder approved a
339
+ * prepare(): call prepareCheckout() (or the adapters' preparation gate) before
340
+ * this request is intercepted. Thrown locally before any create or prompt, and
341
+ * terminal (retrying the same paused request without preparing fails the same
342
+ * way). The API answers 409 preparation_required for the same case.
343
+ */
344
+ export declare class PreparationRequiredError extends Error {
345
+ psp: string;
346
+ constructor(psp: string);
347
+ }
257
348
  export declare class ApprovalTimeoutError extends Error {
258
349
  constructor(ms: number);
259
350
  }
@@ -288,13 +379,20 @@ export declare class ApprovalDeclinedError extends Error {
288
379
  * and quiet the page's retry exactly as for a person's "no"), so it extends
289
380
  * ApprovalDeclinedError: code that already handles declines keeps working,
290
381
  * and code that wants the numbers reads them here or branches on `code`.
382
+ *
383
+ * A merchant-hosted checkout (an Adyen merchant whose own server charges the card)
384
+ * has no processor read-back: its agent amount is held to the merchant's own
385
+ * checkout total, as the agent's browser read it, at stage 'create'. The SDK refuses
386
+ * that before any create, and the API refuses the same with 409 `amount_mismatch`, so a
387
+ * caller catches this one class either way; `amountSource` says whose number
388
+ * `actualCents` is, and the message names it.
291
389
  */
292
390
  export declare class AmountMismatchError extends ApprovalDeclinedError {
293
391
  /** The declined authorization, or null for a create-time refusal (no row exists). */
294
392
  authorizationId: string | null;
295
393
  /** What the user was asked to approve, smallest currency unit. */
296
394
  expectedCents: number;
297
- /** What the processor reported at the last check. */
395
+ /** What the processor (or the merchant, see `amountSource`) reported at the last check. */
298
396
  actualCents: number;
299
397
  /** ISO 4217 of the approved amount. */
300
398
  currency: string;
@@ -302,20 +400,32 @@ export declare class AmountMismatchError extends ApprovalDeclinedError {
302
400
  actualCurrency: string;
303
401
  /** Which check refused it. */
304
402
  stage: 'create' | 'pre_replay';
403
+ /**
404
+ * Whose number `actualCents` is: the processor's ('processor'), a merchant-hosted
405
+ * merchant's own checkout total ('merchant_total'), or the amount its card request
406
+ * names ('merchant_request').
407
+ */
408
+ readonly amountSource: 'processor' | 'merchant_total' | 'merchant_request';
305
409
  readonly code: "amount_mismatch";
306
410
  constructor(
307
411
  /** The declined authorization, or null for a create-time refusal (no row exists). */
308
412
  authorizationId: string | null,
309
413
  /** What the user was asked to approve, smallest currency unit. */
310
414
  expectedCents: number,
311
- /** What the processor reported at the last check. */
415
+ /** What the processor (or the merchant, see `amountSource`) reported at the last check. */
312
416
  actualCents: number,
313
417
  /** ISO 4217 of the approved amount. */
314
418
  currency: string,
315
419
  /** ISO 4217 the processor reported (differs only on a currency change). */
316
420
  actualCurrency?: string,
317
421
  /** Which check refused it. */
318
- stage?: 'create' | 'pre_replay');
422
+ stage?: 'create' | 'pre_replay',
423
+ /**
424
+ * Whose number `actualCents` is: the processor's ('processor'), a merchant-hosted
425
+ * merchant's own checkout total ('merchant_total'), or the amount its card request
426
+ * names ('merchant_request').
427
+ */
428
+ amountSource?: 'processor' | 'merchant_total' | 'merchant_request');
319
429
  }
320
430
  /**
321
431
  * The PaymentIntent behind this checkout can no longer be confirmed: it was
@@ -353,6 +463,55 @@ export declare class ProcessorRefusedError extends ApprovalDeclinedError {
353
463
  readonly processorError: RazorpayProcessorError | null;
354
464
  constructor(authorizationId: string, pspErrorCode: string | null, processorError?: RazorpayProcessorError | null);
355
465
  }
466
+ /** Why Agentcard refused a payment on Adyen's TEST platform; see AdyenTestPlatformRefusedError. */
467
+ export type AdyenTestPlatformRefusal = 'adyen_test_environment_refused' | 'adyen_test_platform_requires_documented_test_card';
468
+ /**
469
+ * Agentcard refused a payment on Adyen's TEST platform, where a test account can
470
+ * read whatever is encrypted under its key. Nothing was encrypted and nothing was
471
+ * charged. `code` says which rule:
472
+ * - 'adyen_test_environment_refused': a live checkout names Adyen's test host or
473
+ * a `test_` client key. Stage 'create': the API refused it before anyone was
474
+ * asked (`authorizationId` is null) and the page's next request is refused the
475
+ * same way, so the adapters stop intercepting. Stage 'pre_replay': the
476
+ * authorization was declined right before the card would have been encrypted.
477
+ * - 'adyen_test_platform_requires_documented_test_card': a test-mode checkout on
478
+ * Adyen's test platform, where the approval page encrypts only Adyen's
479
+ * documented test cards and the cardholder's card is not one.
480
+ * A decline in every structural sense, so it extends ApprovalDeclinedError.
481
+ */
482
+ export declare class AdyenTestPlatformRefusedError extends ApprovalDeclinedError {
483
+ readonly authorizationId: string | null;
484
+ readonly code: AdyenTestPlatformRefusal;
485
+ readonly stage: 'create' | 'pre_replay';
486
+ constructor(authorizationId: string | null, code: AdyenTestPlatformRefusal, stage: 'create' | 'pre_replay');
487
+ }
488
+ /** Why a merchant-hosted payment priced by the merchant's own total was refused; see MerchantTotalError. */
489
+ export type MerchantTotalRefusal = 'merchant_total_required' | 'merchant_total_refused' | 'merchant_total_changed' | 'merchant_total_stale';
490
+ /**
491
+ * A reviewed merchant's own checkout total could not stand behind this payment, so the
492
+ * card request was held and nothing was charged. The merchant's server picks what it
493
+ * charges, so a merchant-hosted payment is priced by the total the merchant's own
494
+ * checkout responses named in this browser (the profile's amount source), never by the
495
+ * agent's number alone. `code`:
496
+ * - 'merchant_total_required': no total was sent (stage 'sdk': this runtime recorded no
497
+ * merchant responses; stage 'create': the API got none);
498
+ * - 'merchant_total_refused': the responses do not confirm one total for this order
499
+ * (`reasonCode`: no_source when none was seen, stale, refused, unreadable,
500
+ * mismatch, unbound, invalid); stage 'sdk' before any create, 'create' by the API;
501
+ * - 'merchant_total_changed': stage 'release': between the approval and the moment the
502
+ * card would have gone out, the merchant's total moved, or a request that could move
503
+ * it had not answered. The approval is retired; the card never left this browser;
504
+ * - 'merchant_total_stale': stage 'runtime': the approval came after the total was too
505
+ * old for its profile, so the API withheld the card.
506
+ * A decline in every structural sense, so it extends ApprovalDeclinedError.
507
+ */
508
+ export declare class MerchantTotalError extends ApprovalDeclinedError {
509
+ readonly authorizationId: string | null;
510
+ readonly code: MerchantTotalRefusal;
511
+ readonly stage: 'sdk' | 'create' | 'release' | 'runtime';
512
+ readonly reasonCode: string | null;
513
+ constructor(authorizationId: string | null, code: MerchantTotalRefusal, stage: 'sdk' | 'create' | 'release' | 'runtime', reasonCode: string | null, reason: string);
514
+ }
356
515
  /**
357
516
  * A non-2xx from the Agentcard API, carrying the status so callers can tell a
358
517
  * misconfiguration from a blip. The adapters use this to decide whether
@@ -484,6 +643,25 @@ export interface VaultClientOptions {
484
643
  * the error is thrown. Default [500, 1500]; [] disables retries.
485
644
  */
486
645
  unverifiableRetryDelaysMs?: number[];
646
+ /**
647
+ * Test mode only: run a merchant-hosted checkout against your own Adyen TEST
648
+ * account. Each entry arms your own endpoint as taking the card request of a
649
+ * profile Agentcard reviewed (its body rules), encrypted under your own Adyen
650
+ * TEST client key; prepare({ psp: 'adyen', environment: 'sandbox',
651
+ * merchantProfile: <that profile> }) then pays it. Declare them here, before any
652
+ * attach, so every adapter pauses the endpoint. The API accepts a declaration
653
+ * only from a test-mode client of an org Agentcard turned declarations on for,
654
+ * and the Vault encrypts only Adyen's documented test cards under a test_ key.
655
+ * Throws a TypeError at construction for a profile this build did not review, an
656
+ * endpoint the API would refuse (see declareSandboxMerchant) or a non-test key.
657
+ *
658
+ * On this client a declared profile id takes the place of the reviewed profile
659
+ * with that id: merchantProfile(id) and prepare({ merchantProfile: id }) name
660
+ * the declaration, so this client never prepares the reviewed merchant's own
661
+ * checkout. Nothing is lost on a test-mode client, which can never pay a
662
+ * reviewed profile on Adyen's live platform; use a separate client for that.
663
+ */
664
+ sandboxMerchants?: SandboxMerchantDeclaration[];
487
665
  }
488
666
  export declare class VaultClient {
489
667
  private readonly opts;
@@ -492,11 +670,37 @@ export declare class VaultClient {
492
670
  private readonly pollIntervalMs;
493
671
  private readonly unverifiableRetryDelaysMs;
494
672
  private registry;
673
+ /**
674
+ * The reviewed merchant profiles this client arms: every one this build reviewed,
675
+ * in 'observe' until a sync reads that the API enabled it (armServedProfiles).
676
+ */
677
+ private merchantProfiles;
678
+ /** This client's own sandbox declarations (sandboxMerchants), armed at construction, one per profile. */
679
+ private readonly declaredProfiles;
495
680
  private readonly preparations;
496
681
  private readonly usedPreparations;
497
682
  constructor(opts: VaultClientOptions);
498
683
  /** Refresh recognizers from the API so new PSPs work without a redeploy. */
499
684
  syncRegistry(): Promise<void>;
685
+ /**
686
+ * The reviewed Adyen merchant profile whose card endpoint (or a sibling of it,
687
+ * the same path under another query) this URL is, with its status on this
688
+ * client; null for any other URL. Every profile this build reviewed is armed,
689
+ * in 'observe' until a sync reads that the API enabled it. The adapters pause
690
+ * these requests and judge them with classifyMerchantRequest. A raw runtime
691
+ * that pauses one must never continue a card body there unless authorize()
692
+ * paid it.
693
+ */
694
+ merchantProfileOf(url: string): MerchantProfile | null;
695
+ /** The armed profile with this id, or null. A profile id this client declared (sandboxMerchants) names its declaration, in place of the reviewed profile. */
696
+ merchantProfile(id: string): MerchantProfile | null;
697
+ /**
698
+ * Fetch.enable globs for every armed merchant profile's endpoint and its
699
+ * siblings, for a raw CDP runtime to arm beside cardUrlPatterns(). Every profile
700
+ * this build reviewed is armed from the start, so these are the same before and
701
+ * after syncRegistry; a sync changes only a profile's status.
702
+ */
703
+ merchantProfileUrlPatterns(): string[];
500
704
  /** True when this request is a card tokenization we can take over. */
501
705
  isCardRequest(url: string, method?: string): boolean;
502
706
  /**
@@ -588,6 +792,33 @@ export declare class VaultClient {
588
792
  amount: number;
589
793
  currency: string;
590
794
  }>;
795
+ /**
796
+ * After a merchant-hosted payment priced by the merchant's own total: what the merchant's
797
+ * confirmation says it charged (merchant-total.ts merchantChargeReport builds `report`
798
+ * from the responses this browser recorded after the card request). The API compares it
799
+ * with the approved amount and alerts Agentcard once when the merchant charged more, or
800
+ * in another currency. The adapters send it on their own; the verdict is 'equal',
801
+ * 'lower', 'higher', 'currency_mismatch', or 'unread' when no response read.
802
+ */
803
+ reportMerchantCharge(authorizationId: string, report: {
804
+ request: {
805
+ url: string;
806
+ method: string;
807
+ body: string;
808
+ };
809
+ responses: unknown[];
810
+ }): Promise<{
811
+ verdict: 'equal' | 'lower' | 'higher' | 'currency_mismatch' | 'unread';
812
+ alerted: boolean;
813
+ }>;
814
+ /**
815
+ * The merchant's own total for a paused card request whose reviewed profile is priced by
816
+ * one: the exchanges the adapter recorded (once every request that can move the total has
817
+ * answered), cut to what the profile's rules read, and the same reading the API makes.
818
+ * Refused with a MerchantTotalError before any create when nothing recorded them, one is
819
+ * still unanswered, or they do not confirm one total for this order.
820
+ */
821
+ private readMerchantTotal;
591
822
  /**
592
823
  * POST the create, with two typed twists: a 502 `amount_unverifiable`
593
824
  * (Stripe did not answer the read-back) is retried on a short backoff