@agent-cards/sdk 0.20.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.
- package/CHANGELOG.md +51 -0
- package/PREFLIGHT.md +34 -0
- package/README.md +1217 -15
- package/dist/aci.d.ts +173 -0
- package/dist/aci.generated.d.ts +87 -0
- package/dist/aci.generated.js +306 -0
- package/dist/aci.js +371 -0
- package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
- package/dist/adyen-merchant-hosted.generated.js +283 -49
- package/dist/agentcard.d.ts +9 -2
- package/dist/agentcard.js +4 -2
- package/dist/amadeus-checkout.generated.d.ts +57 -0
- package/dist/amadeus-checkout.generated.js +549 -0
- package/dist/amadeus.d.ts +41 -0
- package/dist/amadeus.js +81 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-endpoint.d.ts +177 -0
- package/dist/card-endpoint.js +286 -0
- package/dist/card-fields.generated.d.ts +5 -3
- package/dist/card-fields.generated.js +284 -7
- package/dist/cdp.d.ts +6 -1
- package/dist/cdp.js +983 -147
- package/dist/client.d.ts +493 -13
- package/dist/client.js +1100 -82
- package/dist/creditguard.generated.d.ts +79 -0
- package/dist/creditguard.generated.js +662 -0
- package/dist/cse-body.js +11 -1
- package/dist/cybersource-flex.d.ts +183 -0
- package/dist/cybersource-flex.generated.d.ts +92 -0
- package/dist/cybersource-flex.generated.js +689 -0
- package/dist/cybersource-flex.js +275 -0
- package/dist/datatrans.d.ts +96 -0
- package/dist/datatrans.generated.d.ts +64 -0
- package/dist/datatrans.generated.js +426 -0
- package/dist/datatrans.js +178 -0
- package/dist/elavon-converge.d.ts +37 -0
- package/dist/elavon-converge.generated.d.ts +49 -0
- package/dist/elavon-converge.generated.js +439 -0
- package/dist/elavon-converge.js +84 -0
- package/dist/eprotect.d.ts +152 -0
- package/dist/eprotect.generated.d.ts +77 -0
- package/dist/eprotect.generated.js +498 -0
- package/dist/eprotect.js +222 -0
- package/dist/gmo.d.ts +63 -0
- package/dist/gmo.generated.d.ts +60 -0
- package/dist/gmo.generated.js +586 -0
- package/dist/gmo.js +119 -0
- package/dist/hosted-form.d.ts +3 -2
- package/dist/hosted-form.js +3 -2
- package/dist/index.d.ts +19 -3
- package/dist/index.js +6 -1
- package/dist/juspay-gate.d.ts +82 -0
- package/dist/juspay-gate.js +126 -0
- package/dist/juspay.generated.d.ts +89 -0
- package/dist/juspay.generated.js +705 -0
- package/dist/lifecycle.d.ts +62 -6
- package/dist/lifecycle.js +123 -8
- package/dist/merchant-card-endpoints.generated.d.ts +63 -0
- package/dist/merchant-card-endpoints.generated.js +1103 -0
- package/dist/merchant-hosted.d.ts +4 -1
- package/dist/merchant-hosted.js +5 -2
- package/dist/merchant-session-probe.d.ts +48 -0
- package/dist/merchant-session-probe.js +53 -0
- package/dist/merchant-session.generated.d.ts +69 -0
- package/dist/merchant-session.generated.js +919 -0
- package/dist/payrails.d.ts +133 -0
- package/dist/payrails.generated.d.ts +73 -0
- package/dist/payrails.generated.js +596 -0
- package/dist/payrails.js +305 -0
- package/dist/pelecard.d.ts +32 -0
- package/dist/pelecard.generated.d.ts +32 -0
- package/dist/pelecard.generated.js +283 -0
- package/dist/pelecard.js +42 -0
- package/dist/pre-claim.d.ts +84 -3
- package/dist/pre-claim.js +987 -9
- package/dist/preflight-catalog.json +2310 -263
- package/dist/preflight-schemas.json +187 -6
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +247 -4
- package/dist/preparation.d.ts +5 -2
- package/dist/preparation.js +36 -10
- package/dist/prepared-processor.d.ts +86 -5
- package/dist/prepared-processor.js +157 -6
- package/dist/primer.d.ts +53 -0
- package/dist/primer.generated.d.ts +38 -0
- package/dist/primer.generated.js +624 -0
- package/dist/primer.js +82 -0
- package/dist/registry.d.ts +114 -12
- package/dist/registry.js +85 -11
- package/dist/shiji-sts.d.ts +144 -0
- package/dist/shiji-sts.generated.d.ts +72 -0
- package/dist/shiji-sts.generated.js +913 -0
- package/dist/shiji-sts.js +222 -0
- package/dist/stripe-checkout.generated.d.ts +1 -1
- package/dist/stripe-checkout.generated.js +735 -10
- package/dist/stripe-setup.d.ts +16 -0
- package/dist/stripe-setup.js +61 -0
- package/dist/substitutions.generated.js +187 -29
- package/dist/verifone.d.ts +68 -0
- package/dist/verifone.generated.d.ts +51 -0
- package/dist/verifone.generated.js +1401 -0
- package/dist/verifone.js +119 -0
- package/dist/voice-types.d.ts +27 -0
- package/dist/voice.d.ts +6 -1
- package/dist/voice.js +6 -1
- package/dist/wspay.d.ts +41 -0
- package/dist/wspay.generated.d.ts +41 -0
- package/dist/wspay.generated.js +370 -0
- package/dist/wspay.js +65 -0
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
package/dist/verifone.js
ADDED
|
@@ -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
|
+
}
|
package/dist/voice-types.d.ts
CHANGED
|
@@ -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
|
|
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.');
|
package/dist/wspay.d.ts
ADDED
|
@@ -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 };
|