@agent-cards/sdk 0.21.0 → 0.22.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 (103) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/PREFLIGHT.md +30 -0
  3. package/README.md +1070 -21
  4. package/dist/aci.d.ts +173 -0
  5. package/dist/aci.generated.d.ts +87 -0
  6. package/dist/aci.generated.js +306 -0
  7. package/dist/aci.js +371 -0
  8. package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
  9. package/dist/adyen-merchant-hosted.generated.js +283 -49
  10. package/dist/agentcard.d.ts +9 -2
  11. package/dist/agentcard.js +4 -2
  12. package/dist/amadeus-checkout.generated.d.ts +57 -0
  13. package/dist/amadeus-checkout.generated.js +549 -0
  14. package/dist/amadeus.d.ts +41 -0
  15. package/dist/amadeus.js +81 -0
  16. package/dist/builtin-registry.generated.js +1 -1
  17. package/dist/card-endpoint.d.ts +177 -0
  18. package/dist/card-endpoint.js +286 -0
  19. package/dist/card-fields.generated.d.ts +5 -3
  20. package/dist/card-fields.generated.js +284 -7
  21. package/dist/cdp.d.ts +5 -1
  22. package/dist/cdp.js +841 -135
  23. package/dist/client.d.ts +403 -19
  24. package/dist/client.js +949 -98
  25. package/dist/creditguard.generated.d.ts +79 -0
  26. package/dist/creditguard.generated.js +662 -0
  27. package/dist/cse-body.js +11 -1
  28. package/dist/datatrans.d.ts +96 -0
  29. package/dist/datatrans.generated.d.ts +64 -0
  30. package/dist/datatrans.generated.js +426 -0
  31. package/dist/datatrans.js +178 -0
  32. package/dist/elavon-converge.d.ts +37 -0
  33. package/dist/elavon-converge.generated.d.ts +49 -0
  34. package/dist/elavon-converge.generated.js +439 -0
  35. package/dist/elavon-converge.js +84 -0
  36. package/dist/eprotect.d.ts +152 -0
  37. package/dist/eprotect.generated.d.ts +77 -0
  38. package/dist/eprotect.generated.js +498 -0
  39. package/dist/eprotect.js +222 -0
  40. package/dist/hosted-form.d.ts +3 -2
  41. package/dist/hosted-form.js +3 -2
  42. package/dist/index.d.ts +17 -3
  43. package/dist/index.js +6 -1
  44. package/dist/juspay-gate.d.ts +82 -0
  45. package/dist/juspay-gate.js +126 -0
  46. package/dist/juspay.generated.d.ts +89 -0
  47. package/dist/juspay.generated.js +705 -0
  48. package/dist/lifecycle.d.ts +57 -6
  49. package/dist/lifecycle.js +119 -8
  50. package/dist/merchant-card-endpoints.generated.d.ts +63 -0
  51. package/dist/merchant-card-endpoints.generated.js +1103 -0
  52. package/dist/merchant-hosted.d.ts +4 -1
  53. package/dist/merchant-hosted.js +5 -2
  54. package/dist/merchant-session-probe.d.ts +48 -0
  55. package/dist/merchant-session-probe.js +53 -0
  56. package/dist/merchant-session.generated.d.ts +69 -0
  57. package/dist/merchant-session.generated.js +919 -0
  58. package/dist/payrails.d.ts +133 -0
  59. package/dist/payrails.generated.d.ts +73 -0
  60. package/dist/payrails.generated.js +596 -0
  61. package/dist/payrails.js +305 -0
  62. package/dist/pelecard.d.ts +32 -0
  63. package/dist/pelecard.generated.d.ts +32 -0
  64. package/dist/pelecard.generated.js +283 -0
  65. package/dist/pelecard.js +42 -0
  66. package/dist/pre-claim.d.ts +72 -2
  67. package/dist/pre-claim.js +857 -5
  68. package/dist/preflight-catalog.json +2218 -287
  69. package/dist/preflight-schemas.json +179 -6
  70. package/dist/preflight.generated.d.ts +1 -1
  71. package/dist/preflight.generated.js +233 -4
  72. package/dist/preparation.d.ts +5 -2
  73. package/dist/preparation.js +36 -10
  74. package/dist/prepared-processor.d.ts +84 -5
  75. package/dist/prepared-processor.js +147 -6
  76. package/dist/primer.d.ts +53 -0
  77. package/dist/primer.generated.d.ts +38 -0
  78. package/dist/primer.generated.js +624 -0
  79. package/dist/primer.js +82 -0
  80. package/dist/registry.d.ts +100 -8
  81. package/dist/registry.js +85 -11
  82. package/dist/shiji-sts.d.ts +144 -0
  83. package/dist/shiji-sts.generated.d.ts +72 -0
  84. package/dist/shiji-sts.generated.js +913 -0
  85. package/dist/shiji-sts.js +222 -0
  86. package/dist/stripe-checkout.generated.d.ts +1 -1
  87. package/dist/stripe-checkout.generated.js +699 -9
  88. package/dist/stripe-setup.d.ts +16 -0
  89. package/dist/stripe-setup.js +61 -0
  90. package/dist/substitutions.generated.js +187 -29
  91. package/dist/verifone.d.ts +68 -0
  92. package/dist/verifone.generated.d.ts +51 -0
  93. package/dist/verifone.generated.js +1401 -0
  94. package/dist/verifone.js +119 -0
  95. package/dist/voice-types.d.ts +27 -0
  96. package/dist/voice.d.ts +6 -1
  97. package/dist/voice.js +6 -1
  98. package/dist/wspay.d.ts +41 -0
  99. package/dist/wspay.generated.d.ts +41 -0
  100. package/dist/wspay.generated.js +370 -0
  101. package/dist/wspay.js +65 -0
  102. package/examples/preflight/kernel-native/inventory.json +1 -1
  103. package/package.json +3 -3
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Verifone eCommerce merchants, as the agent's browser sees them.
3
+ *
4
+ * A Verifone merchant (The Container Store) keeps its own card inputs and has
5
+ * verifone.js encrypt the card in the page under the merchant's own Verifone key,
6
+ * then posts the blob to ITS OWN server. No processor host is involved, so the
7
+ * processor registry can never recognize that request. Agentcard reviews each such
8
+ * merchant instead and pins its card request (a leg: endpoint, body rules and where
9
+ * the blob sits) and its key in a profile (payment-core verifone-profiles.js). This
10
+ * build carries those profiles as a generated projection (verifone.generated.ts) and
11
+ * arms one only when the API serves it (VaultClient.syncRegistry asks with the
12
+ * verifone_card_capture capability), which it does only for a company Agentcard
13
+ * turned Verifone on for. Every other company's checkout at a Verifone merchant is
14
+ * never paused, as before.
15
+ *
16
+ * A served profile's `status` keeps it dark: 'observe' pauses every request to the
17
+ * merchant's origin that carries a verifone.js blob, reports its shape (a hash of its
18
+ * key paths, never a value) and aborts it, so the agent's placeholder card never
19
+ * reaches the merchant and nothing is charged; 'enabled' lets a prepared checkout
20
+ * pay the reviewed leg. A profile is 'enabled' only when the API and this build both
21
+ * say so. The Container Store ships in 'observe', with no leg and no key reviewed.
22
+ */
23
+ import * as generated from './verifone.generated.js';
24
+ import { sha256Hex as sharedSha256Hex } from './adyen-merchant-hosted.generated.js';
25
+ import { SubstitutionError } from './substitutions.generated.js';
26
+ const sha256Hex = sharedSha256Hex;
27
+ const core = generated;
28
+ const BUILT = core.VERIFONE_PROFILE_RECOGNIZERS;
29
+ /** Whether an entry the API served is a Verifone profile (`{ verifone_profile }`), never a recognizer. */
30
+ export function isVerifoneProfileEntry(entry) {
31
+ return !!entry && typeof entry === 'object' && !Array.isArray(entry) && 'verifone_profile' in entry;
32
+ }
33
+ /**
34
+ * The profiles this client arms: only the ones the API served, each once, in this
35
+ * build's order. A served entry is `{ verifone_profile: <the profile's wire
36
+ * projection> }`, and the first one that names a profile with exactly this build's
37
+ * rules (origins, legs, body rules, key subkey: everything but `status`) and a status
38
+ * this build knows decides it: 'enabled' only when both that entry and this build say
39
+ * so. A profile the API did not serve, or served with other rules, is not armed.
40
+ */
41
+ export function armServedVerifoneProfiles(entries = []) {
42
+ const decided = new Map();
43
+ for (const entry of entries) {
44
+ if (!isVerifoneProfileEntry(entry))
45
+ continue;
46
+ const served = entry.verifone_profile;
47
+ if (!served || typeof served !== 'object' || Array.isArray(served))
48
+ continue;
49
+ const id = served.id;
50
+ const status = served.status;
51
+ const built = typeof id === 'string' ? BUILT.find((profile) => profile.id === id) : undefined;
52
+ if (!built || decided.has(built.id) || (status !== 'observe' && status !== 'enabled') || !core.verifoneProfileRulesMatch(built.id, served))
53
+ continue;
54
+ decided.set(built.id, status === 'enabled' && built.status === 'enabled' ? 'enabled' : 'observe');
55
+ }
56
+ return BUILT.filter((built) => decided.has(built.id))
57
+ .map((built) => Object.freeze({ id: built.id, merchant: built.merchant, environment: built.environment, status: decided.get(built.id) }));
58
+ }
59
+ /** The armed profile whose endpoint origin a URL is on, or null. */
60
+ export function verifoneProfileFor(profiles, url) {
61
+ if (typeof url !== 'string')
62
+ return null;
63
+ let origin;
64
+ try {
65
+ origin = new URL(url).origin;
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ return profiles.find((profile) => core.verifoneProfileUrlGlobs(profile.id).some((glob) => glob === `${origin}/*`)) ?? null;
71
+ }
72
+ /** The Fetch.enable globs that pause every request to every armed profile's endpoint origins. */
73
+ export function verifoneProfileUrlPatterns(profiles) {
74
+ return [...new Set(profiles.flatMap((profile) => core.verifoneProfileUrlGlobs(profile.id)))];
75
+ }
76
+ /** The verdict payment-core gives a request at an armed profile: pause (its reviewed leg), abort (a blob it cannot pay, with the reason) or pass. */
77
+ export function classifyVerifoneProfileRequest(profile, url, method, body) {
78
+ return core.classifyVerifoneRequest(profile.id, url, method, body);
79
+ }
80
+ /** The URL an event names for a request at an armed profile: its origin and, for a leg, the leg's path template; never a query or an id. */
81
+ export function verifoneTemplatedUrl(profileId, url) {
82
+ return core.verifoneProfileTemplatedUrl(profileId, url);
83
+ }
84
+ /** The endpoint a Verifone preparation pays from a checkout page: the profile's leg URL, or null. */
85
+ export function verifoneProfileEndpointFor(profileId, checkoutOrigin) {
86
+ return core.verifoneProfileEndpointFor(profileId, checkoutOrigin);
87
+ }
88
+ /** Whether a paused request is the one a Verifone preparation for this profile consents to. */
89
+ export function isPreparedVerifoneRequest(url, method, body, profileId) {
90
+ return core.isPreparedVerifoneRequest(url, method, body, profileId);
91
+ }
92
+ /** Whether an approval's substitution is exactly a Verifone blob for this profile. */
93
+ export function isVerifoneSubstitution(sub, profileId) {
94
+ if (!sub || typeof sub !== 'object' || Array.isArray(sub))
95
+ return false;
96
+ const value = sub;
97
+ return Object.keys(value).sort().join(',') === 'blob,encoding,kind,profile' && value.encoding === 'json' && value.kind === 'verifone'
98
+ && value.profile === profileId && core.isVerifoneBlob(value.blob);
99
+ }
100
+ /**
101
+ * The continuation of a Verifone approval: the live paused body with the device's blob in place
102
+ * of the page's placeholder blob. The API keeps no merchant body, so the live body must first
103
+ * hash to the one the API checked when the authorization was created (`bodySha256`); a page that
104
+ * rewrote its body while the cardholder decided is refused rather than paid. payment-core's
105
+ * substituteVerifoneBlob then reads the leg's rules on the live body again, writes only the blob,
106
+ * and refuses a profile that is not enabled or a blob under any other key. Throws
107
+ * SubstitutionError; nothing leaves the browser when it does.
108
+ */
109
+ export function substituteVerifoneBody(request, replay) {
110
+ if (typeof request.body !== 'string' || sha256Hex(request.body) !== replay.bodySha256) {
111
+ throw new SubstitutionError('the paused body is not the one Agentcard checked when this checkout was created');
112
+ }
113
+ try {
114
+ return core.substituteVerifoneBlob(request, replay.substitutions);
115
+ }
116
+ catch (error) {
117
+ throw new SubstitutionError(error instanceof Error ? error.message : String(error));
118
+ }
119
+ }
@@ -592,6 +592,27 @@ export interface AppVoiceKeyDescription {
592
592
  proof_version: 1;
593
593
  lang?: Language;
594
594
  device_label?: string;
595
+ /**
596
+ * The phone's hardware key, attested for this app voice key (WP199): `VoiceHost` 1.9.8 puts it on the description it
597
+ * hands your `register` when the phone attested one, and `voice.appKeys.register` sends it on as `hardware`. Absent
598
+ * from a phone that registered a software key.
599
+ */
600
+ hardware?: HardwareAttestation;
601
+ }
602
+ /**
603
+ * The hardware key the phone binds to its app voice key at registration (WP199): the key's id, the platform's
604
+ * attestation of it (Android's certificate chain as base64url DER, one string a certificate, or iOS App Attest's
605
+ * attestation object as one base64url string), the challenge the attestation answers (`SHA-256(credential_id + "." +
606
+ * public_key)`, as the phone computed it; Agentcard recomputes and compares), and on iOS the App ID the build runs
607
+ * under (`<team id>.<bundle id>`), which Agentcard checks the attestation's `rpIdHash` against. Opaque to the SDK: it
608
+ * is sent as `hardware` on the registration and Agentcard checks it.
609
+ */
610
+ export interface HardwareAttestation {
611
+ platform: 'android' | 'ios';
612
+ key_id: string;
613
+ attestation: string[] | string;
614
+ challenge: string;
615
+ app_id?: string;
595
616
  }
596
617
  /** The registered app voice key, with the phone's scoped credential (one hour; register again for a fresh one). */
597
618
  export interface RegisteredAppVoiceKey {
@@ -610,6 +631,12 @@ export interface RegisteredAppVoiceKey {
610
631
  token_type: 'Bearer';
611
632
  expires_in: number;
612
633
  };
634
+ /** Whether Agentcard verified a hardware key bound to this app voice key (WP199); absent from an API from before it. */
635
+ hardware_proven?: boolean;
636
+ /** Where the proven hardware key lives: Android StrongBox or TEE, or Apple's Secure Enclave. */
637
+ hardware_level?: 'strongbox' | 'tee' | 'secure_enclave';
638
+ /** Which hardware key is proven, by its id, so the phone compares it with the key it holds (WP199). Present only when proven. */
639
+ hardware_key_id?: string;
613
640
  }
614
641
  /** `voice.appKeys.revoke`: the key is revoked and nothing else is. */
615
642
  export interface RevokedAppVoiceKey {
package/dist/voice.d.ts CHANGED
@@ -14,7 +14,7 @@ import { ApiModule, type ApiTransport } from './api-module.js';
14
14
  import { type StreamFormat, type StreamHandle, type ChannelKind } from './voice-stream.generated.js';
15
15
  import { type PkceCrypto } from './voice-pkce.js';
16
16
  import type { Language, LanguageInfo } from './languages.js';
17
- import { type Action, type ActionState, type AppVoiceKeyDescription, type ApprovalPosture, type Authorization, type AuthorizationResult, type Cancelled, type CancelReason, type Connected, type ConnectInput, type ConnectResult, type CreateAuthorizationInput, type Done, type Exchanged, type ExchangeInput, type HostApprovalBundle, type Person, type Proposed, type RegisteredAppVoiceKey, type RevokedAppVoiceKey, type Revoked, type SessionState, type VaultSessionStep, type VoiceChallenge, type VoiceResetInput } from './voice-types.js';
17
+ import { type Action, type ActionState, type AppVoiceKeyDescription, type ApprovalPosture, type Authorization, type AuthorizationResult, type Cancelled, type CancelReason, type Connected, type ConnectInput, type ConnectResult, type CreateAuthorizationInput, type Done, type Exchanged, type ExchangeInput, type HardwareAttestation, type HostApprovalBundle, type Person, type Proposed, type RegisteredAppVoiceKey, type RevokedAppVoiceKey, type Revoked, type SessionState, type VaultSessionStep, type VoiceChallenge, type VoiceResetInput } from './voice-types.js';
18
18
  export { stripePaymentIntentConfirm, type MintedIntent } from './requests.js';
19
19
  /**
20
20
  * The voice half of the client's options: named once, per business, when the
@@ -290,9 +290,14 @@ export declare class VoiceModule extends ApiModule {
290
290
  * the phone and nothing else. `user_id` is the person the connect exchange returned; required. 404
291
291
  * `connection_not_found` when the person never connected through this client; 409 `credential_exists`
292
292
  * when the credential id already names a different key. The same key again answers with a fresh credential.
293
+ * `hardware` (WP199), when the phone minted a hardware key for this app voice key, is its attestation as the
294
+ * phone handed it over: on the key itself (`key.hardware`, where `VoiceHost` 1.9.8 puts it, so a server that
295
+ * forwards the key as it came needs no change) or in `opts.hardware`, which wins when both are given. It is sent
296
+ * on as is, and the answer's `hardware_proven` and `hardware_level` say what Agentcard made of it.
293
297
  */
294
298
  register: (key: AppVoiceKeyDescription, opts: {
295
299
  user_id: string;
300
+ hardware?: HardwareAttestation;
296
301
  }) => Promise<RegisteredAppVoiceKey>;
297
302
  /** Revokes one app voice key of the person `user_id` names; the person's other keys, the card and the connection stay. Idempotent. */
298
303
  revoke: (keyId: string, opts: {
package/dist/voice.js CHANGED
@@ -657,6 +657,10 @@ export class VoiceModule extends ApiModule {
657
657
  * the phone and nothing else. `user_id` is the person the connect exchange returned; required. 404
658
658
  * `connection_not_found` when the person never connected through this client; 409 `credential_exists`
659
659
  * when the credential id already names a different key. The same key again answers with a fresh credential.
660
+ * `hardware` (WP199), when the phone minted a hardware key for this app voice key, is its attestation as the
661
+ * phone handed it over: on the key itself (`key.hardware`, where `VoiceHost` 1.9.8 puts it, so a server that
662
+ * forwards the key as it came needs no change) or in `opts.hardware`, which wins when both are given. It is sent
663
+ * on as is, and the answer's `hardware_proven` and `hardware_level` say what Agentcard made of it.
660
664
  */
661
665
  register: async (key, opts) => {
662
666
  this.posture();
@@ -666,7 +670,8 @@ export class VoiceModule extends ApiModule {
666
670
  if (!key || typeof key.credential_id !== 'string' || typeof key.public_key !== 'string')
667
671
  throw new AgentcardError('invalid_app_key', 'The app voice key needs credential_id and public_key, as VoiceHost describes it.');
668
672
  await this.t.languages.check(key.lang);
669
- const body = { credential_id: key.credential_id, public_key: key.public_key, kind: 'app_voice_key', alg: 'ES256', proof_version: 1, ...(key.lang ? { lang: key.lang } : {}), ...(key.device_label ? { device_label: key.device_label } : {}) };
673
+ const hardware = opts.hardware ?? key.hardware;
674
+ const body = { credential_id: key.credential_id, public_key: key.public_key, kind: 'app_voice_key', alg: 'ES256', proof_version: 1, ...(key.lang ? { lang: key.lang } : {}), ...(key.device_label ? { device_label: key.device_label } : {}), ...(hardware ? { hardware } : {}) };
670
675
  const reg = await this.request(`/api/v2/vault_users/${encodeURIComponent(user)}/app_voice_keys`, { method: 'POST', body });
671
676
  if (!reg || !reg.credential || typeof reg.credential.token !== 'string')
672
677
  throw new AgentcardError('register_failed', 'Agentcard registered the key without a scoped credential.');
@@ -0,0 +1,41 @@
1
+ import type { ReplayResponse } from './client.js';
2
+ export declare const WSPAY_PSP = "wspay";
3
+ /** The dark-launch capability the API serves the wspay recognizer behind. */
4
+ export declare const WSPAY_CAPABILITY = "wspay_confirm";
5
+ /**
6
+ * Why the SDK refuses a WSPay request before anything is claimed, created or prompted:
7
+ * - `unsupported_wspay_payment_method`: not the card frame's new-card confirm. That covers a
8
+ * saved_card or temp_card_id body (no new card to fill), a card-shaped object the page
9
+ * built into the transaction, another URL shape on the host, or headers the frame does not
10
+ * send. The cardholder's device could never fill it.
11
+ * - `wspay_keep_on_file_unsupported`: a sound confirm that asks WSPay to keep the card on file
12
+ * (tokenize_pan true), so the merchant could charge it later without another approval. Only
13
+ * the cardholder can agree to that, and no approval asks them yet.
14
+ * The API refuses the same requests at create (invalid_wspay_request and
15
+ * wspay_keep_on_file_unsupported), so a refusal here only saves the round trip.
16
+ */
17
+ export type WspayRefusalReason = 'unsupported_wspay_payment_method' | 'wspay_keep_on_file_unsupported';
18
+ /** True when the URL's host is one of WSPay's two Components hosts. */
19
+ export declare function isWspayHost(url: string): boolean;
20
+ /**
21
+ * The pause decision for a POST the registry recognized on a WSPay host. Null when it is the
22
+ * card frame's confirm of a new card for a one-time payment, which pauses for approval;
23
+ * otherwise the reason it is refused. It judges the whole request (the exact URL, the frame's
24
+ * content type, Accept and WP3-v2-Client authorization, and a body of the frame's own keys
25
+ * bound to the URL's client_secret) with payment-core's wspayConfirmRefusal, the check the API
26
+ * runs at create. It never passes keep-on-file consent: that waits for an approval that asks
27
+ * the cardholder.
28
+ */
29
+ export declare function wspayRefusal(request: {
30
+ url: string;
31
+ headers: Record<string, string>;
32
+ body: string | null;
33
+ }): WspayRefusalReason | null;
34
+ /**
35
+ * Whether an approved answer handed to a WSPay confirm is WSPay's 3-D Secure step: HTTP 200 with
36
+ * status `action_required`, which is how the API stores it (payment-core sanitizeWspayResponse,
37
+ * with no payment_result). The card frame then runs the bank's challenge in the agent's browser,
38
+ * and nothing is charged until the cardholder completes it there. The adapters read it after the
39
+ * handoff to report `requires_user_action` with reason `3ds` (lifecycle.ts).
40
+ */
41
+ export declare function isWspayChallenge(requestUrl: string, replay: ReplayResponse): boolean;
@@ -0,0 +1,41 @@
1
+ declare var WSPAY_CONFIRM_HOSTS: readonly string[];
2
+ declare var WSPAY_TEST_HOST: string;
3
+ declare function wspayConfirmClientSecret(url: any): string | null;
4
+ declare function isWspayConfirmUrl(url: any, method?: string): boolean;
5
+ declare function isWspayNewCardBody(body: any, clientSecret: any, options: any): boolean;
6
+ declare function classifyWspayRequest(url: any, method: any, body: any, options: any): "card" | "unsupported" | null;
7
+ declare function isWspayConfirmRequest(request: any, options: any): any;
8
+ declare function wspayStoredCredentialRequested(body: any): boolean | null;
9
+ declare function wspayConfirmRefusal(request: any, options: any): "unsupported" | "keep_on_file" | null;
10
+ declare function wspayRefusalMessage(reason: any): string;
11
+ declare function injectWspayCard(body: any, card: any, options: any): {
12
+ body: string;
13
+ where: string[];
14
+ };
15
+ declare function wspayResponseOutcome(status: any, text2: any): {
16
+ outcome: string;
17
+ reason: string;
18
+ code?: undefined;
19
+ pending?: undefined;
20
+ } | {
21
+ outcome: string;
22
+ code: any;
23
+ reason?: undefined;
24
+ pending?: undefined;
25
+ } | {
26
+ outcome: string;
27
+ reason?: undefined;
28
+ code?: undefined;
29
+ pending?: undefined;
30
+ } | {
31
+ outcome: string;
32
+ pending: string;
33
+ reason?: undefined;
34
+ code?: undefined;
35
+ };
36
+ declare function sanitizeWspayResponse(text2: any, options: any): string;
37
+ declare function wspayFacts(status: any, text2: any): {
38
+ outcome: string;
39
+ refusalCode: any;
40
+ };
41
+ export { WSPAY_CONFIRM_HOSTS, WSPAY_TEST_HOST, classifyWspayRequest, injectWspayCard, isWspayConfirmRequest, isWspayConfirmUrl, isWspayNewCardBody, sanitizeWspayResponse, wspayConfirmClientSecret, wspayConfirmRefusal, wspayFacts, wspayRefusalMessage, wspayResponseOutcome, wspayStoredCredentialRequested };
@@ -0,0 +1,370 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: 560fa6d54ff04e65a21dc08d3046495f67499274b35c83053f8b25370f13a17a
4
+ // src/wspay.js
5
+ var WSPAY_CONFIRM_HOSTS = Object.freeze(["proxy.wspay.biz", "proxy-test.wspay.biz"]);
6
+ var WSPAY_TEST_HOST = "proxy-test.wspay.biz";
7
+ var CONFIRM_PATH = /^\/v2\/payment\/([A-Za-z0-9_-]{1,128})\/confirm$/;
8
+ var AUTHORIZATION = /^WP3-v2-Client [A-Za-z0-9_-]{1,256}$/;
9
+ var CONTENT_TYPE = /^application\/json(?:;\s*charset=utf-8)?$/i;
10
+ var ACCEPT_RANGES = /* @__PURE__ */ new Set(["application/json", "text/plain", "*/*"]);
11
+ var TRANSACTION_TEXT = [
12
+ "ch_address",
13
+ "ch_city",
14
+ "ch_country",
15
+ "ch_zip",
16
+ "language",
17
+ "ch_email",
18
+ "ch_full_name",
19
+ "ch_phone",
20
+ "order_info",
21
+ "custom_params"
22
+ ];
23
+ var TRANSACTION_KEYS = [...TRANSACTION_TEXT, "number_of_installments", "meta"];
24
+ var META_KEYS = ["integration_type", "library_version", "library"];
25
+ var CARD_DATA_KEYS = ["pan", "expiration_date", "cvv", "tokenize_pan"];
26
+ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this WSPay payment would keep the card on file without your consent. Nothing was sent.";
27
+ var UNSUPPORTED_REFUSAL = "Refusing to pay: unsupported WSPay card payment request.";
28
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
29
+ var owns = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
30
+ var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
31
+ var text = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
32
+ var keysWithin = (value, allowed) => record(value) && Object.keys(value).every((key) => allowed.includes(key));
33
+ var digits = (value, min, max) => typeof value === "string" && value.length >= min && value.length <= max && !/[^0-9]/.test(value);
34
+ var yymm = (value) => digits(value, 4, 4) && Number(value.slice(2)) >= 1 && Number(value.slice(2)) <= 12;
35
+ function frameCardNumber(number) {
36
+ const mask = /^3[47]/.test(number) ? [4, 6, 5] : /^3[0689]/.test(number) ? [4, 6, 4] : [4, 4, 4, 4, 3];
37
+ const parts = [];
38
+ let at = 0;
39
+ for (const size of mask) {
40
+ if (at >= number.length)
41
+ break;
42
+ parts.push(number.slice(at, at + size));
43
+ at += size;
44
+ }
45
+ return at >= number.length ? parts.join(" ") : null;
46
+ }
47
+ var cardNumberField = (value) => {
48
+ if (typeof value !== "string")
49
+ return false;
50
+ const number = value.replace(/ /g, "");
51
+ return digits(number, 12, 19) && (value === number || value === frameCardNumber(number));
52
+ };
53
+ function readJson(body, limit = 65536) {
54
+ if (typeof body !== "string" || !body || body.length > limit)
55
+ return null;
56
+ let at = 0, nodes = 0;
57
+ const fail = () => {
58
+ throw new Error("invalid_wspay_json");
59
+ };
60
+ const space = () => {
61
+ while (" \r\n".includes(body[at] ?? "\0"))
62
+ at++;
63
+ };
64
+ const string = () => {
65
+ space();
66
+ const start = at;
67
+ if (body[at++] !== '"')
68
+ fail();
69
+ while (at < body.length) {
70
+ const char = body[at++];
71
+ if (char === "\\") {
72
+ at++;
73
+ continue;
74
+ }
75
+ if (char === '"')
76
+ return JSON.parse(body.slice(start, at));
77
+ }
78
+ return fail();
79
+ };
80
+ const value = (depth) => {
81
+ if (depth > 8 || ++nodes > 512)
82
+ fail();
83
+ space();
84
+ const char = body[at];
85
+ if (char === '"')
86
+ return string();
87
+ if (char === "{") {
88
+ at++;
89
+ const result = /* @__PURE__ */ Object.create(null), seen = /* @__PURE__ */ new Set();
90
+ space();
91
+ if (body[at] === "}") {
92
+ at++;
93
+ return result;
94
+ }
95
+ while (true) {
96
+ const key = string();
97
+ if (seen.has(key) || ["__proto__", "prototype", "constructor"].includes(key))
98
+ fail();
99
+ seen.add(key);
100
+ space();
101
+ if (body[at++] !== ":")
102
+ fail();
103
+ result[key] = value(depth + 1);
104
+ space();
105
+ const next = body[at++];
106
+ if (next === "}")
107
+ return result;
108
+ if (next !== ",")
109
+ fail();
110
+ }
111
+ }
112
+ if (char === "[") {
113
+ at++;
114
+ const result = [];
115
+ space();
116
+ if (body[at] === "]") {
117
+ at++;
118
+ return result;
119
+ }
120
+ while (true) {
121
+ result.push(value(depth + 1));
122
+ space();
123
+ const next = body[at++];
124
+ if (next === "]")
125
+ return result;
126
+ if (next !== ",")
127
+ fail();
128
+ }
129
+ }
130
+ for (const literal of ["true", "false", "null"]) {
131
+ if (body.startsWith(literal, at)) {
132
+ at += literal.length;
133
+ return JSON.parse(literal);
134
+ }
135
+ }
136
+ const number = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(body.slice(at, at + 32));
137
+ if (!number)
138
+ fail();
139
+ at += number[0].length;
140
+ const parsed = Number(number[0]);
141
+ if (!Number.isFinite(parsed))
142
+ fail();
143
+ return parsed;
144
+ };
145
+ try {
146
+ const parsed = value(0);
147
+ space();
148
+ return at === body.length && record(parsed) ? parsed : null;
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ function wspayConfirmClientSecret(url) {
155
+ if (typeof url !== "string" || url.length > 512 || url.includes("?") || url.includes("#"))
156
+ return null;
157
+ let parsed;
158
+ try {
159
+ parsed = new URL(url);
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password || parsed.search || parsed.hash || !WSPAY_CONFIRM_HOSTS.includes(parsed.hostname))
165
+ return null;
166
+ return CONFIRM_PATH.exec(parsed.pathname)?.[1] ?? null;
167
+ }
168
+ function isWspayConfirmUrl(url, method = "POST") {
169
+ return method === "POST" && wspayConfirmClientSecret(url) !== null;
170
+ }
171
+ var PARES_REDIRECT = /^\/v2\/js\/3ds-pares-iframe\.html\?client_secret=([A-Za-z0-9_-]{1,128})&three_ds_controller=iframe$/;
172
+ function confirmBody(body, clientSecret) {
173
+ const value = readJson(body);
174
+ if (!keysWithin(value, ["payment_method", "transaction", "pares_redirect_url"]) || !owns(value, "payment_method") || !owns(value, "transaction") || !owns(value, "pares_redirect_url"))
175
+ return null;
176
+ const pares = PARES_REDIRECT.exec(typeof value.pares_redirect_url === "string" ? value.pares_redirect_url : "");
177
+ if (!pares || pares[0] !== value.pares_redirect_url || clientSecret !== void 0 && pares[1] !== clientSecret)
178
+ return null;
179
+ const pm = value.payment_method, data = pm?.data;
180
+ if (!keysWithin(pm, ["type", "data"]) || pm.type !== "card" || !keysWithin(data, CARD_DATA_KEYS) || !CARD_DATA_KEYS.every((key) => owns(data, key)) || !cardNumberField(data.pan) || !yymm(data.expiration_date) || !digits(data.cvv, 3, 4) || typeof data.tokenize_pan !== "boolean")
181
+ return null;
182
+ const trx = value.transaction;
183
+ if (!keysWithin(trx, TRANSACTION_KEYS) || !owns(trx, "meta") || TRANSACTION_TEXT.some((key) => owns(trx, key) && !text(trx[key], key === "custom_params" ? 2048 : 256)))
184
+ return null;
185
+ const installments = trx.number_of_installments;
186
+ if (owns(trx, "number_of_installments") && !(Number.isSafeInteger(installments) && installments >= 1 && installments <= 99) && !(digits(installments, 1, 2) && Number(installments) >= 1))
187
+ return null;
188
+ if (!keysWithin(trx.meta, META_KEYS) || !META_KEYS.every((key) => owns(trx.meta, key)) || META_KEYS.some((key) => !exact(/^[A-Za-z0-9][A-Za-z0-9._ -]{0,63}$/, trx.meta[key])))
189
+ return null;
190
+ return value;
191
+ }
192
+ var payable = (value, options) => value.payment_method.data.tokenize_pan === false || record(options) && options.keepOnFile === true;
193
+ function isWspayNewCardBody(body, clientSecret, options) {
194
+ const value = confirmBody(body, clientSecret);
195
+ return value !== null && payable(value, options);
196
+ }
197
+ function classifyWspayRequest(url, method, body, options) {
198
+ if (method !== "POST")
199
+ return null;
200
+ const clientSecret = wspayConfirmClientSecret(url);
201
+ if (clientSecret === null)
202
+ return null;
203
+ return isWspayNewCardBody(typeof body === "string" ? body : "", clientSecret, options) ? "card" : "unsupported";
204
+ }
205
+ function isWspayConfirmRequest(request, options) {
206
+ if (!record(request) || request.method !== "POST" || !record(request.headers))
207
+ return false;
208
+ const clientSecret = wspayConfirmClientSecret(request.url);
209
+ if (clientSecret === null || !isWspayNewCardBody(request.body, clientSecret, options))
210
+ return false;
211
+ const headers = /* @__PURE__ */ new Map();
212
+ for (const [name, value] of Object.entries(request.headers)) {
213
+ const lower = name.toLowerCase();
214
+ if (headers.has(lower) || typeof value !== "string")
215
+ return false;
216
+ headers.set(lower, value);
217
+ }
218
+ const reviewed = (name) => text(headers.get(name), 1024) ? headers.get(name) : null;
219
+ const accept = reviewed("accept");
220
+ const ranges = accept === null ? [] : accept.split(",").map((range) => range.trim().toLowerCase());
221
+ return exact(CONTENT_TYPE, reviewed("content-type")) && exact(AUTHORIZATION, reviewed("authorization")) && ranges.includes("application/json") && ranges.every((range) => ACCEPT_RANGES.has(range));
222
+ }
223
+ function wspayStoredCredentialRequested(body) {
224
+ const value = confirmBody(body);
225
+ return value === null ? null : value.payment_method.data.tokenize_pan === true;
226
+ }
227
+ function wspayConfirmRefusal(request, options) {
228
+ if (!isWspayConfirmRequest(request, { keepOnFile: true }))
229
+ return "unsupported";
230
+ return isWspayConfirmRequest(request, options) ? null : "keep_on_file";
231
+ }
232
+ function wspayRefusalMessage(reason) {
233
+ return reason === "keep_on_file" ? KEEP_ON_FILE_REFUSAL : UNSUPPORTED_REFUSAL;
234
+ }
235
+ function injectWspayCard(body, card, options) {
236
+ const value = confirmBody(body);
237
+ if (!value)
238
+ throw new Error(UNSUPPORTED_REFUSAL);
239
+ if (!payable(value, options))
240
+ throw new Error(KEEP_ON_FILE_REFUSAL);
241
+ const month = card && (Number.isInteger(card.month) || digits(card.month, 1, 2)) ? Number(card.month) : NaN;
242
+ const year = card && (Number.isInteger(card.year) || digits(card.year, 2, 4)) ? Number(card.year) : NaN;
243
+ if (!card || !digits(card.number, 12, 19) || !digits(card.cvc, 3, 4) || !(month >= 1 && month <= 12) || !(year >= 0 && year <= 99 || year >= 2e3 && year <= 2099)) {
244
+ throw new Error("Could not fill the card fields for WSPay. Nothing was sent.");
245
+ }
246
+ const data = value.payment_method.data;
247
+ data.pan = data.pan.includes(" ") ? frameCardNumber(card.number) ?? card.number : card.number;
248
+ data.expiration_date = String(year % 100).padStart(2, "0") + String(month).padStart(2, "0");
249
+ data.cvv = card.cvc;
250
+ return { body: JSON.stringify(value), where: ["pan", "expiration_date", "cvv"].map((key) => "payment_method.data." + key) };
251
+ }
252
+ var ENVELOPE_KEYS = ["id", "status", "client_secret", "payment_result", "action_required", "order", "errors"];
253
+ var RESULT_KEYS = [
254
+ "status",
255
+ "currency",
256
+ "amount",
257
+ "order_number",
258
+ "pan_token",
259
+ "created_at",
260
+ "transaction_type",
261
+ "payment_method",
262
+ "errors",
263
+ "response_message",
264
+ "success_url",
265
+ "transaction_response"
266
+ ];
267
+ var panLike = (value) => /(?:[0-9][ _.:-]*){12,}/.test(value);
268
+ var opaqueId = (value, max = 128) => exact(new RegExp(`^[A-Za-z0-9_.:-]{1,${max}}$`), value) && !panLike(value);
269
+ var isoTime = (value) => text(value, 40) && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:?\d{2})?$/.test(value);
270
+ function httpsUrl(value) {
271
+ if (!text(value, 2048) || panLike(value))
272
+ return false;
273
+ try {
274
+ const url = new URL(value);
275
+ return url.protocol === "https:" && !url.username && !url.password && url.href === value;
276
+ }
277
+ catch {
278
+ return false;
279
+ }
280
+ }
281
+ var emptyErrors = (value) => value === void 0 || value === null || Array.isArray(value) && value.length === 0;
282
+ function namesSecret(url, secret) {
283
+ if (url.includes(secret))
284
+ return true;
285
+ try {
286
+ return decodeURIComponent(url).includes(secret);
287
+ }
288
+ catch {
289
+ return true;
290
+ }
291
+ }
292
+ function approvedResult(result) {
293
+ return keysWithin(result, RESULT_KEYS) && result.status === "approved" && emptyErrors(result.errors) && Number.isSafeInteger(result.amount) && result.amount >= 0 && exact(/^[A-Z]{3}$/, result.currency) && opaqueId(result.order_number, 64) && (!owns(result, "transaction_type") || ["authorize", "purchase"].includes(result.transaction_type));
294
+ }
295
+ function responseCode(result) {
296
+ const trx = record(result) ? result.transaction_response : null;
297
+ const code = record(trx) ? trx.response_code : null;
298
+ return exact(/^[0-9]{4}$/, code) ? code : null;
299
+ }
300
+ function wspayResponseOutcome(status, text2) {
301
+ if (status !== 200)
302
+ return { outcome: "unknown", reason: "wspay_status_unconfirmed" };
303
+ const value = readJson(text2);
304
+ if (!value)
305
+ return { outcome: "unknown", reason: "wspay_unreadable" };
306
+ if (!keysWithin(value, ENVELOPE_KEYS))
307
+ return { outcome: "unknown", reason: "wspay_unexpected_response" };
308
+ if (!emptyErrors(value.errors))
309
+ return { outcome: "unknown", reason: "wspay_processor_error" };
310
+ const result = value.payment_result;
311
+ if (value.status === "declined" || value.status === "approved" && record(result) && result.status === "declined") {
312
+ return { outcome: "refused", code: responseCode(result) ?? "declined" };
313
+ }
314
+ if (value.status === "approved" && approvedResult(result))
315
+ return { outcome: "approved" };
316
+ if (value.status === "action_required" && (result === null || result === void 0) && keysWithin(value.action_required, ["acs_url", "redirect_to"]) && httpsUrl(value.action_required.acs_url) && httpsUrl(value.action_required.redirect_to))
317
+ return { outcome: "approved", pending: "three_ds" };
318
+ return { outcome: "unknown", reason: "wspay_unconfirmed" };
319
+ }
320
+ function sanitizeWspayResponse(text2, options) {
321
+ const fail = () => {
322
+ throw new Error("Invalid WSPay payment response.");
323
+ };
324
+ const keepOnFile = record(options) && options.keepOnFile === true;
325
+ const clientSecret = record(options) && typeof options.clientSecret === "string" && options.clientSecret ? options.clientSecret : null;
326
+ if (wspayResponseOutcome(200, text2).outcome !== "approved")
327
+ return fail();
328
+ const value = readJson(text2);
329
+ if (value.status === "action_required") {
330
+ return JSON.stringify({
331
+ status: "action_required",
332
+ action_required: { acs_url: value.action_required.acs_url, redirect_to: value.action_required.redirect_to },
333
+ payment_result: null
334
+ });
335
+ }
336
+ const result = value.payment_result;
337
+ const safe = {
338
+ status: "approved",
339
+ currency: result.currency,
340
+ amount: result.amount,
341
+ order_number: result.order_number,
342
+ pan_token: null,
343
+ payment_method: null
344
+ };
345
+ if (isoTime(result.created_at))
346
+ safe.created_at = result.created_at;
347
+ if (owns(result, "transaction_type"))
348
+ safe.transaction_type = result.transaction_type;
349
+ if (httpsUrl(result.success_url) && clientSecret !== null && !namesSecret(result.success_url, clientSecret))
350
+ safe.success_url = result.success_url;
351
+ if (keepOnFile) {
352
+ if (result.pan_token !== null && result.pan_token !== void 0) {
353
+ if (!opaqueId(result.pan_token))
354
+ return fail();
355
+ safe.pan_token = result.pan_token;
356
+ }
357
+ const pm = result.payment_method, data = record(pm) ? pm.data : null;
358
+ if (pm !== null && pm !== void 0) {
359
+ if (!keysWithin(pm, ["type", "data"]) || pm.type !== "card" || !keysWithin(data, ["brand", "issuer", "masked", "expiration_date", "token"]) || !opaqueId(data.token) || owns(data, "brand") && !opaqueId(data.brand, 32) || owns(data, "issuer") && !opaqueId(data.issuer, 64) || owns(data, "expiration_date") && !yymm(data.expiration_date) || owns(data, "masked") && !exact(/^[0-9]{0,6}[-* xX]{4,16}[0-9]{4}$/, data.masked))
360
+ return fail();
361
+ safe.payment_method = { type: "card", data: { ...data } };
362
+ }
363
+ }
364
+ return JSON.stringify({ status: "approved", action_required: null, payment_result: safe });
365
+ }
366
+ function wspayFacts(status, text2) {
367
+ const verdict = wspayResponseOutcome(status, text2);
368
+ return { outcome: verdict.outcome, refusalCode: verdict.outcome === "refused" ? verdict.code : null };
369
+ }
370
+ export { WSPAY_CONFIRM_HOSTS, WSPAY_TEST_HOST, classifyWspayRequest, injectWspayCard, isWspayConfirmRequest, isWspayConfirmUrl, isWspayNewCardBody, sanitizeWspayResponse, wspayConfirmClientSecret, wspayConfirmRefusal, wspayFacts, wspayRefusalMessage, wspayResponseOutcome, wspayStoredCredentialRequested };