@agent-cards/sdk 0.21.0 → 0.23.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 +87 -2
- package/PREFLIGHT.md +32 -0
- package/README.md +1177 -23
- 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 +302 -56
- 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-facts.d.ts +96 -0
- package/dist/card-endpoint-facts.js +275 -0
- package/dist/card-endpoint-page.d.ts +54 -0
- package/dist/card-endpoint-page.js +69 -0
- package/dist/card-endpoint.d.ts +298 -0
- package/dist/card-endpoint.js +438 -0
- package/dist/card-fields.generated.d.ts +5 -3
- package/dist/card-fields.generated.js +284 -7
- package/dist/cdp.d.ts +5 -1
- package/dist/cdp.js +1155 -141
- package/dist/client.d.ts +456 -20
- package/dist/client.js +1072 -104
- 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 +30 -0
- package/dist/cybersource-flex.generated.d.ts +27 -1
- package/dist/cybersource-flex.generated.js +42 -2
- package/dist/cybersource-flex.js +46 -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/ekashu.d.ts +35 -0
- package/dist/ekashu.js +55 -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/hosted-form.d.ts +3 -2
- package/dist/hosted-form.js +3 -2
- package/dist/index.d.ts +17 -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 +60 -6
- package/dist/lifecycle.js +122 -8
- package/dist/merchant-card-endpoints.generated.d.ts +122 -0
- package/dist/merchant-card-endpoints.generated.js +4261 -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 +81 -2
- package/dist/pre-claim.js +1023 -5
- package/dist/preflight-catalog.json +2267 -288
- package/dist/preflight-schemas.json +183 -6
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +243 -4
- package/dist/preparation.d.ts +12 -3
- package/dist/preparation.js +63 -12
- package/dist/prepared-processor.d.ts +84 -5
- package/dist/prepared-processor.js +147 -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 +100 -8
- 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 +764 -9
- 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 +1413 -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/ekashu.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NMI's Hosted Payment Page (formerly eKashu), as this SDK reads it.
|
|
3
|
+
*
|
|
4
|
+
* The merchant's page posts an entry form, top-level, to https://live.ekashu.com/ (test:
|
|
5
|
+
* test.ekashu.com), and NMI answers with its own card page. That entry carries no card, and it
|
|
6
|
+
* is the one request this SDK pauses: the cardholder's device repeats it over the end-to-end TLS
|
|
7
|
+
* relay and fills the card into the page NMI returns to it (payment-core processors/ekashu.js).
|
|
8
|
+
* Everything else the agent's browser posts to NMI carries a card the agent typed: the card page's
|
|
9
|
+
* own post (/index.php) and its 3-D Secure calls (ajax_3d_secure_v2_*.php). This SDK aborts every
|
|
10
|
+
* one of them, so a placeholder card never reaches NMI.
|
|
11
|
+
*
|
|
12
|
+
* The hosts are payment-core's EKASHU_HOSTS; payment-core.test.mjs pins the two lists together.
|
|
13
|
+
*/
|
|
14
|
+
export const EKASHU_PSP = 'ekashu';
|
|
15
|
+
export const EKASHU_HOSTS = Object.freeze(['live.ekashu.com', 'test.ekashu.com']);
|
|
16
|
+
/** Whether this URL is on one of NMI's hosted payment page hosts (https, standard port, no credentials). */
|
|
17
|
+
export function isEkashuHost(url) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(url);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return parsed.protocol === 'https:' && !parsed.port && !parsed.username && !parsed.password
|
|
26
|
+
&& EKASHU_HOSTS.includes(parsed.hostname.toLowerCase());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The only request on NMI's hosts that is the merchant's entry: a POST to the host's root, with no
|
|
30
|
+
* query. The entry the SDK pauses; every other POST there carries a card and is aborted.
|
|
31
|
+
*/
|
|
32
|
+
export function isEkashuEntry(url, method) {
|
|
33
|
+
if (String(method).toUpperCase() !== 'POST' || !isEkashuHost(url))
|
|
34
|
+
return false;
|
|
35
|
+
const parsed = new URL(url);
|
|
36
|
+
return parsed.pathname === '/' && !parsed.search && !parsed.hash;
|
|
37
|
+
}
|
|
38
|
+
/** The entry URL on the same NMI host as this URL, the one the registry recognizes when this build is armed for NMI. */
|
|
39
|
+
export function ekashuEntryUrl(url) {
|
|
40
|
+
if (!isEkashuHost(url))
|
|
41
|
+
return null;
|
|
42
|
+
return `https://${new URL(url).hostname.toLowerCase()}/`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Whether an adapter must hand this request to the pre-claim gates: it is on one of NMI's hosts
|
|
46
|
+
* and this build's registry serves that host's entry. The card page's own post and its 3-D Secure
|
|
47
|
+
* calls are not the entry, so the registry's card-request test (anchored to the entry) does not
|
|
48
|
+
* name them; the CDP adapter still pauses them through the recognizer's host-wide URL patterns,
|
|
49
|
+
* and attachToPlaywright's route predicate asks this, so both adapters show the gates every
|
|
50
|
+
* request to an armed NMI host.
|
|
51
|
+
*/
|
|
52
|
+
export function isArmedEkashuUrl(vault, url) {
|
|
53
|
+
const entry = ekashuEntryUrl(url);
|
|
54
|
+
return entry !== null && vault.isCardRequest(entry, 'POST');
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const CONVERGE_PSP = "elavon_converge";
|
|
2
|
+
/** The dark-launch capability the API serves the elavon_converge recognizer behind. */
|
|
3
|
+
export declare const CONVERGE_CAPABILITY = "converge_hpp_process";
|
|
4
|
+
/**
|
|
5
|
+
* Why the SDK refuses an Elavon Converge payment before anything is claimed, created or prompted:
|
|
6
|
+
* payment-core's own codes (CONVERGE_REFUSAL_CODES), the ones the API answers a create with and
|
|
7
|
+
* the device refuses with, so an integrator reads one code whichever of them refused.
|
|
8
|
+
* - A shape the Vault never pays: `converge_wallet_refused`, `converge_debit_card_refused`,
|
|
9
|
+
* `converge_push_to_card_refused`, `converge_gift_card_refused`, `converge_payment_type_refused`,
|
|
10
|
+
* `converge_captcha_refused`, `converge_service_fee_refused`, `converge_recurring_refused`,
|
|
11
|
+
* `converge_card_saving_refused` (ssl_get_token or ssl_add_token), `converge_amount_add_on_refused`
|
|
12
|
+
* (a tip or cash back) and `converge_currency_refused` (anything but US dollars).
|
|
13
|
+
* - A follow-up after a step the Vault cannot complete: `converge_3ds_follow_up_refused` (3-D
|
|
14
|
+
* Secure or Pay Defense) and `converge_surcharge_refused`.
|
|
15
|
+
* - A body the cardholder has nothing to approve on: `converge_amount_missing` and
|
|
16
|
+
* `converge_context_missing`.
|
|
17
|
+
* - Not the page's own card request, which the device could never fill: `converge_body_malformed`,
|
|
18
|
+
* `converge_credential_refused`, `converge_card_missing`, `converge_card_malformed`,
|
|
19
|
+
* `converge_url_refused`, `converge_method_refused` and `converge_content_type_refused`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CONVERGE_REFUSAL_REASONS: readonly ["converge_body_malformed", "converge_payment_type_refused", "converge_debit_card_refused", "converge_push_to_card_refused", "converge_gift_card_refused", "converge_credential_refused", "converge_wallet_refused", "converge_captcha_refused", "converge_service_fee_refused", "converge_3ds_follow_up_refused", "converge_surcharge_refused", "converge_amount_add_on_refused", "converge_card_saving_refused", "converge_recurring_refused", "converge_currency_refused", "converge_amount_missing", "converge_context_missing", "converge_card_missing", "converge_card_malformed", "converge_url_refused", "converge_method_refused", "converge_content_type_refused"];
|
|
22
|
+
export type ConvergeRefusalReason = typeof CONVERGE_REFUSAL_REASONS[number];
|
|
23
|
+
/** True when the URL's host is one of Converge's two payment page hosts. */
|
|
24
|
+
export declare function isConvergeHost(url: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The pause decision for a card-carrying POST the registry recognized on a Converge host. Null
|
|
27
|
+
* when it is the page's own first credit card sale, which pauses for approval; otherwise the reason
|
|
28
|
+
* it is refused. It judges the whole request (the exact process URL, the page's JSON content type,
|
|
29
|
+
* and a body exactly as the page serializes it) with payment-core's convergeRequestRefusal, the
|
|
30
|
+
* check the API runs at create and the device runs before it writes a card. A code this build does
|
|
31
|
+
* not list reads as not the page's request, so a newer core can only refuse more.
|
|
32
|
+
*/
|
|
33
|
+
export declare function convergeRefusal(request: {
|
|
34
|
+
url: string;
|
|
35
|
+
headers: Record<string, string>;
|
|
36
|
+
body: string | null;
|
|
37
|
+
}): ConvergeRefusalReason | null;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
declare var CONVERGE_HOSTS: readonly string[];
|
|
2
|
+
declare var CONVERGE_DEMO_HOST: string;
|
|
3
|
+
declare var CONVERGE_PROCESS_PATH: string;
|
|
4
|
+
declare var CONVERGE_PROCESS_URLS: readonly string[];
|
|
5
|
+
declare var CONVERGE_REFUSAL_CODES: readonly string[];
|
|
6
|
+
declare function parseConvergeBody(body: any): any;
|
|
7
|
+
declare function convergeAmountCents(value: any): number | null;
|
|
8
|
+
declare function convergeBodyVerdict(body: any): Readonly<{
|
|
9
|
+
verdict: "refuse";
|
|
10
|
+
code: any;
|
|
11
|
+
}> | Readonly<{
|
|
12
|
+
verdict: "continue";
|
|
13
|
+
}> | Readonly<{
|
|
14
|
+
verdict: "card";
|
|
15
|
+
}>;
|
|
16
|
+
declare function convergeBodyRefusal(body: any): any;
|
|
17
|
+
declare function isConvergeProcessUrl(url: any): boolean;
|
|
18
|
+
declare function convergeRequestRefusal(request: any): any;
|
|
19
|
+
declare function convergeRefusalMessage(code: any): any;
|
|
20
|
+
declare function injectConvergeHppCard(templateBody: any, card: any): {
|
|
21
|
+
body: string;
|
|
22
|
+
where: string[];
|
|
23
|
+
};
|
|
24
|
+
declare function convergeAnswerKind(text: any): "unknown" | "approved" | "declined" | "step" | "session_timeout";
|
|
25
|
+
declare function convergeFacts(text: any, template: any): {
|
|
26
|
+
kind: string;
|
|
27
|
+
final: boolean;
|
|
28
|
+
refusalCode: any;
|
|
29
|
+
txnId: string | null;
|
|
30
|
+
approvalCode: string | null;
|
|
31
|
+
amountCents: number | null;
|
|
32
|
+
tokenIssued: boolean;
|
|
33
|
+
chargedAboveRequest: boolean;
|
|
34
|
+
};
|
|
35
|
+
declare function convergeResponseOutcome(status: any, text: any): {
|
|
36
|
+
outcome: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
code?: undefined;
|
|
39
|
+
} | {
|
|
40
|
+
outcome: string;
|
|
41
|
+
reason?: undefined;
|
|
42
|
+
code?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
outcome: string;
|
|
45
|
+
code: any;
|
|
46
|
+
reason?: undefined;
|
|
47
|
+
};
|
|
48
|
+
declare function sanitizeConvergeResponse(text: any): string;
|
|
49
|
+
export { CONVERGE_DEMO_HOST, CONVERGE_HOSTS, CONVERGE_PROCESS_PATH, CONVERGE_PROCESS_URLS, CONVERGE_REFUSAL_CODES, convergeAmountCents, convergeAnswerKind, convergeBodyRefusal, convergeBodyVerdict, convergeFacts, convergeRefusalMessage, convergeRequestRefusal, convergeResponseOutcome, injectConvergeHppCard, isConvergeProcessUrl, parseConvergeBody, sanitizeConvergeResponse };
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: dc4269c2533c43ddcc4f7be0f3a4645beea09bce6854c98e38701f29cf037e2b
|
|
4
|
+
// src/elavon-converge.js
|
|
5
|
+
var CONVERGE_HOSTS = /* @__PURE__ */ Object.freeze(["api.convergepay.com", "api.demo.convergepay.com"]);
|
|
6
|
+
var CONVERGE_DEMO_HOST = "api.demo.convergepay.com";
|
|
7
|
+
var CONVERGE_PROCESS_PATH = "/hosted-payments/service/payment/hpp/process";
|
|
8
|
+
var CONVERGE_PROCESS_URLS = /* @__PURE__ */ Object.freeze([
|
|
9
|
+
"https://api.convergepay.com/hosted-payments/service/payment/hpp/process",
|
|
10
|
+
"https://api.demo.convergepay.com/hosted-payments/service/payment/hpp/process"
|
|
11
|
+
]);
|
|
12
|
+
var CONVERGE_REFUSAL_CODES = /* @__PURE__ */ Object.freeze([
|
|
13
|
+
"converge_body_malformed",
|
|
14
|
+
"converge_payment_type_refused",
|
|
15
|
+
"converge_debit_card_refused",
|
|
16
|
+
"converge_push_to_card_refused",
|
|
17
|
+
"converge_gift_card_refused",
|
|
18
|
+
"converge_credential_refused",
|
|
19
|
+
"converge_wallet_refused",
|
|
20
|
+
"converge_captcha_refused",
|
|
21
|
+
"converge_service_fee_refused",
|
|
22
|
+
"converge_3ds_follow_up_refused",
|
|
23
|
+
"converge_surcharge_refused",
|
|
24
|
+
"converge_amount_add_on_refused",
|
|
25
|
+
"converge_card_saving_refused",
|
|
26
|
+
"converge_recurring_refused",
|
|
27
|
+
"converge_currency_refused",
|
|
28
|
+
"converge_amount_missing",
|
|
29
|
+
"converge_context_missing",
|
|
30
|
+
"converge_card_missing",
|
|
31
|
+
"converge_card_malformed",
|
|
32
|
+
"converge_url_refused",
|
|
33
|
+
"converge_method_refused",
|
|
34
|
+
"converge_content_type_refused"
|
|
35
|
+
]);
|
|
36
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ Object.freeze(["paymentType", "paymentData", "context", "captchaToken", "shippingSameAsBilling"]);
|
|
37
|
+
var WALLET_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
38
|
+
"ssl_ewallet",
|
|
39
|
+
"googlepay_authmethod",
|
|
40
|
+
"ssl_google_pay",
|
|
41
|
+
"ssl_applepay_web",
|
|
42
|
+
"ssl_dsrp_cryptogram",
|
|
43
|
+
"ssl_visapayload",
|
|
44
|
+
"ssl_ewallet_shipping"
|
|
45
|
+
]);
|
|
46
|
+
var SERVICE_FEE_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_service_fee_agree", "ssl_service_fee_amount", "ssl_service_fee_percent"]);
|
|
47
|
+
var THREE_DS_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
48
|
+
"ssl_transaction_type",
|
|
49
|
+
"ssl_eci_ind",
|
|
50
|
+
"ssl_3dsecure_value",
|
|
51
|
+
"ssl_dir_server_tran_id",
|
|
52
|
+
"ssl_xid",
|
|
53
|
+
"ssl_program_protocol"
|
|
54
|
+
]);
|
|
55
|
+
var THREE_DS_PREFIXES = /* @__PURE__ */ Object.freeze(["ssl_3ds_", "ssl_fsg_"]);
|
|
56
|
+
var SURCHARGE_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
57
|
+
"ssl_credit_surcharge_agree",
|
|
58
|
+
"ssl_credit_surcharge_amount",
|
|
59
|
+
"bypass_credit_surcharge",
|
|
60
|
+
"ssl_amount_subtotal",
|
|
61
|
+
"ssl_total_amount",
|
|
62
|
+
"ssl_surcharge_fee_total"
|
|
63
|
+
]);
|
|
64
|
+
var RECURRING_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
65
|
+
"recurring",
|
|
66
|
+
"installment",
|
|
67
|
+
"ssl_billing_cycle",
|
|
68
|
+
"ssl_next_payment_date",
|
|
69
|
+
"ssl_total_installments",
|
|
70
|
+
"ssl_skip_payment",
|
|
71
|
+
"ssl_recurring_id",
|
|
72
|
+
"ssl_recurring_batch_count",
|
|
73
|
+
"ssl_installment_id"
|
|
74
|
+
]);
|
|
75
|
+
var CARD_SAVING_FLAGS = /* @__PURE__ */ Object.freeze(["ssl_get_token", "ssl_add_token"]);
|
|
76
|
+
var CREDENTIAL_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_account_id", "ssl_merchant_id", "ssl_user_id", "ssl_pin", "ssl_vendor_id"]);
|
|
77
|
+
var AMOUNT_ADD_ON_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_tip_amount", "ssl_cashback_amount"]);
|
|
78
|
+
var CARD_NUMBER = /^\d{12,19}$/;
|
|
79
|
+
var EXPIRY_MMYY = /^(0[1-9]|1[0-2])\d{2}$/;
|
|
80
|
+
var CVC = /^\d{3,4}$/;
|
|
81
|
+
var AMOUNT = /^(\d{1,13})(?:\.(\d{1,2}))?$/;
|
|
82
|
+
var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
83
|
+
var owns = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
84
|
+
var refuse = (code) => Object.freeze({ verdict: "refuse", code });
|
|
85
|
+
var CARD = /* @__PURE__ */ Object.freeze({ verdict: "card" });
|
|
86
|
+
var CONTINUE = /* @__PURE__ */ Object.freeze({ verdict: "continue" });
|
|
87
|
+
function parseConvergeBody(body) {
|
|
88
|
+
if (typeof body !== "string" || body === "" || body.length > 131072)
|
|
89
|
+
return null;
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(body);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (!isRecord(parsed) || JSON.stringify(parsed) !== body)
|
|
98
|
+
return null;
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
function convergeAmountCents(value) {
|
|
102
|
+
const text = typeof value === "number" && Number.isFinite(value) ? String(value) : value;
|
|
103
|
+
const m = typeof text === "string" ? AMOUNT.exec(text) : null;
|
|
104
|
+
if (!m)
|
|
105
|
+
return null;
|
|
106
|
+
const cents = Number(m[1]) * 100 + Number(((m[2] ?? "") + "00").slice(0, 2));
|
|
107
|
+
return Number.isSafeInteger(cents) && cents > 0 ? cents : null;
|
|
108
|
+
}
|
|
109
|
+
function convergeBodyVerdict(body) {
|
|
110
|
+
const parsed = parseConvergeBody(body);
|
|
111
|
+
if (parsed === null || !Object.keys(parsed).every((key) => TOP_LEVEL_KEYS.includes(key)))
|
|
112
|
+
return refuse("converge_body_malformed");
|
|
113
|
+
const data = parsed.paymentData;
|
|
114
|
+
if (!isRecord(data))
|
|
115
|
+
return refuse("converge_body_malformed");
|
|
116
|
+
const type = parsed.paymentType;
|
|
117
|
+
if (type === "ELECTRONICCHECK" || type === "FINANCING") {
|
|
118
|
+
return owns(data, "ssl_card_number") ? refuse("converge_payment_type_refused") : CONTINUE;
|
|
119
|
+
}
|
|
120
|
+
if (type === "DEBITCARD") {
|
|
121
|
+
return refuse(typeof data.ssl_transaction_type === "string" && /DISBURSEMENT/i.test(data.ssl_transaction_type) ? "converge_push_to_card_refused" : "converge_debit_card_refused");
|
|
122
|
+
}
|
|
123
|
+
if (type === "GIFTCARD")
|
|
124
|
+
return refuse("converge_gift_card_refused");
|
|
125
|
+
if (type !== "CREDITCARD")
|
|
126
|
+
return refuse("converge_payment_type_refused");
|
|
127
|
+
const keys = Object.keys(data);
|
|
128
|
+
if (CREDENTIAL_FIELDS.some((key) => owns(data, key)))
|
|
129
|
+
return refuse("converge_credential_refused");
|
|
130
|
+
if (WALLET_FIELDS.some((key) => owns(data, key)))
|
|
131
|
+
return refuse("converge_wallet_refused");
|
|
132
|
+
if (owns(parsed, "captchaToken"))
|
|
133
|
+
return refuse("converge_captcha_refused");
|
|
134
|
+
if (SERVICE_FEE_FIELDS.some((key) => owns(data, key)))
|
|
135
|
+
return refuse("converge_service_fee_refused");
|
|
136
|
+
if (THREE_DS_FIELDS.some((key) => owns(data, key)) || keys.some((key) => THREE_DS_PREFIXES.some((prefix) => key.startsWith(prefix))))
|
|
137
|
+
return refuse("converge_3ds_follow_up_refused");
|
|
138
|
+
if (SURCHARGE_FIELDS.some((key) => owns(data, key)))
|
|
139
|
+
return refuse("converge_surcharge_refused");
|
|
140
|
+
if (AMOUNT_ADD_ON_FIELDS.some((key) => owns(data, key)))
|
|
141
|
+
return refuse("converge_amount_add_on_refused");
|
|
142
|
+
if (owns(data, "ssl_token") || CARD_SAVING_FLAGS.some((key) => owns(data, key) && !(typeof data[key] === "string" && /^n$/i.test(data[key])))) {
|
|
143
|
+
return refuse("converge_card_saving_refused");
|
|
144
|
+
}
|
|
145
|
+
if (RECURRING_FIELDS.some((key) => owns(data, key)))
|
|
146
|
+
return refuse("converge_recurring_refused");
|
|
147
|
+
if (owns(data, "ssl_transaction_currency") && !(typeof data.ssl_transaction_currency === "string" && /^usd$/i.test(data.ssl_transaction_currency))) {
|
|
148
|
+
return refuse("converge_currency_refused");
|
|
149
|
+
}
|
|
150
|
+
if (convergeAmountCents(data.ssl_amount) === null)
|
|
151
|
+
return refuse("converge_amount_missing");
|
|
152
|
+
if (typeof parsed.context !== "string" || parsed.context === "")
|
|
153
|
+
return refuse("converge_context_missing");
|
|
154
|
+
if (owns(parsed, "shippingSameAsBilling") && typeof parsed.shippingSameAsBilling !== "boolean")
|
|
155
|
+
return refuse("converge_body_malformed");
|
|
156
|
+
if (!owns(data, "ssl_card_number") || data.ssl_card_number === "" || data.ssl_card_number === null)
|
|
157
|
+
return refuse("converge_card_missing");
|
|
158
|
+
if (typeof data.ssl_card_number !== "string" || !CARD_NUMBER.test(data.ssl_card_number) || typeof data.ssl_exp_date !== "string" || !EXPIRY_MMYY.test(data.ssl_exp_date) || typeof data.ssl_cvv2cvc2 !== "string" || !CVC.test(data.ssl_cvv2cvc2))
|
|
159
|
+
return refuse("converge_card_malformed");
|
|
160
|
+
return CARD;
|
|
161
|
+
}
|
|
162
|
+
function convergeBodyRefusal(body) {
|
|
163
|
+
const verdict = convergeBodyVerdict(body);
|
|
164
|
+
return verdict.verdict === "refuse" ? verdict.code : null;
|
|
165
|
+
}
|
|
166
|
+
function isConvergeProcessUrl(url) {
|
|
167
|
+
if (typeof url !== "string")
|
|
168
|
+
return false;
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
parsed = new URL(url);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return parsed.href === url && CONVERGE_PROCESS_URLS.includes(parsed.href);
|
|
177
|
+
}
|
|
178
|
+
function convergeRequestRefusal(request) {
|
|
179
|
+
if (!isRecord(request))
|
|
180
|
+
return "converge_body_malformed";
|
|
181
|
+
if (request.method !== "POST")
|
|
182
|
+
return "converge_method_refused";
|
|
183
|
+
if (!isConvergeProcessUrl(request.url))
|
|
184
|
+
return "converge_url_refused";
|
|
185
|
+
const headers = request.headers === void 0 ? {} : request.headers;
|
|
186
|
+
if (!isRecord(headers))
|
|
187
|
+
return "converge_content_type_refused";
|
|
188
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
189
|
+
if (/^content-type$/i.test(name) && !(typeof value === "string" && /^application\/json\s*(?:;\s*charset=utf-8\s*)?$/i.test(value)))
|
|
190
|
+
return "converge_content_type_refused";
|
|
191
|
+
}
|
|
192
|
+
const verdict = convergeBodyVerdict(request.body);
|
|
193
|
+
if (verdict.verdict === "refuse")
|
|
194
|
+
return verdict.code;
|
|
195
|
+
return verdict.verdict === "card" ? null : "converge_payment_type_refused";
|
|
196
|
+
}
|
|
197
|
+
var EXTRA_STEP = "Refusing to pay: this payment needs an extra step the Vault can't complete here. Nothing was sent.";
|
|
198
|
+
var NOT_THE_PAGE = "Refusing to pay: this is not the card request of Elavon Converge's payment page. Nothing was sent.";
|
|
199
|
+
var REFUSAL_MESSAGES = /* @__PURE__ */ Object.freeze({
|
|
200
|
+
converge_body_malformed: NOT_THE_PAGE,
|
|
201
|
+
converge_payment_type_refused: "Refusing to pay: only a credit card sale on Elavon Converge's payment page is supported. Nothing was sent.",
|
|
202
|
+
converge_debit_card_refused: "Refusing to pay: a debit card payment on Elavon Converge's payment page is not supported. Nothing was sent.",
|
|
203
|
+
converge_push_to_card_refused: "Refusing to pay: this payment would send money to the card, which the Vault never does. Nothing was sent.",
|
|
204
|
+
converge_gift_card_refused: "Refusing to pay: a gift card payment on Elavon Converge's payment page is not supported. Nothing was sent.",
|
|
205
|
+
converge_credential_refused: NOT_THE_PAGE,
|
|
206
|
+
converge_wallet_refused: "Refusing to pay: this payment uses a wallet, not the card you approve here. Nothing was sent.",
|
|
207
|
+
converge_captcha_refused: "Refusing to pay: this payment page asks for a CAPTCHA, which the Vault can't complete. Nothing was sent.",
|
|
208
|
+
converge_service_fee_refused: "Refusing to pay: this merchant adds a separate service fee, which a one-time card can't pay. Nothing was sent.",
|
|
209
|
+
converge_3ds_follow_up_refused: EXTRA_STEP,
|
|
210
|
+
converge_surcharge_refused: EXTRA_STEP,
|
|
211
|
+
converge_amount_add_on_refused: "Refusing to pay: this payment adds a tip or cash back to its amount. Nothing was sent.",
|
|
212
|
+
converge_card_saving_refused: "Refusing to pay: this payment would save the card with the merchant. Nothing was sent.",
|
|
213
|
+
converge_recurring_refused: "Refusing to pay: this payment would set up recurring or installment charges. Nothing was sent.",
|
|
214
|
+
converge_currency_refused: "Refusing to pay: this payment is not in US dollars. Nothing was sent.",
|
|
215
|
+
converge_amount_missing: "Refusing to pay: this payment names no amount. Nothing was sent.",
|
|
216
|
+
converge_context_missing: "Refusing to pay: this payment carries no payment page session. Nothing was sent.",
|
|
217
|
+
converge_card_missing: NOT_THE_PAGE,
|
|
218
|
+
converge_card_malformed: NOT_THE_PAGE,
|
|
219
|
+
converge_url_refused: NOT_THE_PAGE,
|
|
220
|
+
converge_method_refused: NOT_THE_PAGE,
|
|
221
|
+
converge_content_type_refused: NOT_THE_PAGE
|
|
222
|
+
});
|
|
223
|
+
function convergeRefusalMessage(code) {
|
|
224
|
+
return typeof code === "string" && owns(REFUSAL_MESSAGES, code) ? REFUSAL_MESSAGES[code] : NOT_THE_PAGE;
|
|
225
|
+
}
|
|
226
|
+
var CONVERGE_CARD_PATHS = /* @__PURE__ */ Object.freeze(["paymentData.ssl_card_number", "paymentData.ssl_exp_date", "paymentData.ssl_cvv2cvc2"]);
|
|
227
|
+
function injectConvergeHppCard(templateBody, card) {
|
|
228
|
+
const verdict = convergeBodyVerdict(templateBody);
|
|
229
|
+
if (verdict.verdict !== "card") {
|
|
230
|
+
throw new Error(convergeRefusalMessage(verdict.verdict === "refuse" ? verdict.code : "converge_payment_type_refused"));
|
|
231
|
+
}
|
|
232
|
+
const number = card ? String(card.number) : "";
|
|
233
|
+
const month = card ? String(card.month).padStart(2, "0") : "";
|
|
234
|
+
const year = card ? String(card.year) : "";
|
|
235
|
+
const cvc = card ? String(card.cvc) : "";
|
|
236
|
+
if (!CARD_NUMBER.test(number) || !/^(0[1-9]|1[0-2])$/.test(month) || !/^(?:\d{2}|\d{4})$/.test(year) || !CVC.test(cvc)) {
|
|
237
|
+
throw new Error("Could not fill the card fields for Elavon Converge. Nothing was sent.");
|
|
238
|
+
}
|
|
239
|
+
const parsed = JSON.parse(templateBody);
|
|
240
|
+
parsed.paymentData.ssl_card_number = number;
|
|
241
|
+
parsed.paymentData.ssl_exp_date = month + year.slice(-2);
|
|
242
|
+
parsed.paymentData.ssl_cvv2cvc2 = cvc;
|
|
243
|
+
return { body: JSON.stringify(parsed), where: [...CONVERGE_CARD_PATHS] };
|
|
244
|
+
}
|
|
245
|
+
var IDENTIFIER = /^[a-z0-9_.:-]{1,64}$/i;
|
|
246
|
+
var identifier = (value) => typeof value === "string" && IDENTIFIER.exec(value)?.[0] === value ? value : null;
|
|
247
|
+
function parseAnswer(text) {
|
|
248
|
+
if (typeof text !== "string" || text === "")
|
|
249
|
+
return null;
|
|
250
|
+
try {
|
|
251
|
+
const parsed = JSON.parse(text);
|
|
252
|
+
return isRecord(parsed) ? parsed : null;
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
var SURCHARGE_QUOTE_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_credit_surcharge_amount", "ssl_total_amount"]);
|
|
259
|
+
function convergeAnswerKind(text) {
|
|
260
|
+
const answer = parseAnswer(text);
|
|
261
|
+
if (answer === null)
|
|
262
|
+
return "unknown";
|
|
263
|
+
const fields = isRecord(answer.responseFields) ? answer.responseFields : null;
|
|
264
|
+
const step = !!(answer.efsToken || answer.threeDSecure || answer.dcc || fields && (fields.surchargeAllowed === "Y" || fields.ssl_result_message === "INVLD SURCHARGE") || fields && answer.approved === false && SURCHARGE_QUOTE_FIELDS.some((key) => owns(fields, key) && fields[key] !== null && fields[key] !== ""));
|
|
265
|
+
const sessionTimeout = isRecord(answer.errorModel) && answer.errorModel.errorType === "SESSION_TIMEOUT";
|
|
266
|
+
if ((step || sessionTimeout) && answer.approved === true)
|
|
267
|
+
return "unknown";
|
|
268
|
+
if (step)
|
|
269
|
+
return "step";
|
|
270
|
+
if (sessionTimeout)
|
|
271
|
+
return "session_timeout";
|
|
272
|
+
if (answer.approved === true) {
|
|
273
|
+
if (!fields || identifier(fields.ssl_txn_id) === null)
|
|
274
|
+
return "unknown";
|
|
275
|
+
if (owns(fields, "ssl_result") && String(fields.ssl_result) !== "0")
|
|
276
|
+
return "unknown";
|
|
277
|
+
return "approved";
|
|
278
|
+
}
|
|
279
|
+
if (answer.approved === false && fields && !(owns(fields, "ssl_result") && String(fields.ssl_result) === "0"))
|
|
280
|
+
return "declined";
|
|
281
|
+
return "unknown";
|
|
282
|
+
}
|
|
283
|
+
var KIND_REFUSAL = /* @__PURE__ */ Object.freeze({
|
|
284
|
+
declined: "converge_declined",
|
|
285
|
+
session_timeout: "converge_session_timeout",
|
|
286
|
+
step: "converge_step_unsupported"
|
|
287
|
+
});
|
|
288
|
+
function convergeFacts(text, template) {
|
|
289
|
+
const kind = convergeAnswerKind(text);
|
|
290
|
+
const answer = parseAnswer(text);
|
|
291
|
+
const fields = answer && isRecord(answer.responseFields) ? answer.responseFields : {};
|
|
292
|
+
const amountCents = kind === "approved" ? convergeAmountCents(fields.ssl_amount) : null;
|
|
293
|
+
const requested = template && typeof template.body === "string" ? parseConvergeBody(template.body) : null;
|
|
294
|
+
const requestAmountCents = requested && isRecord(requested.paymentData) ? convergeAmountCents(requested.paymentData.ssl_amount) : null;
|
|
295
|
+
return {
|
|
296
|
+
kind,
|
|
297
|
+
final: kind === "approved" || kind === "declined" || kind === "session_timeout",
|
|
298
|
+
refusalCode: KIND_REFUSAL[kind] ?? null,
|
|
299
|
+
txnId: kind === "approved" ? identifier(fields.ssl_txn_id) : null,
|
|
300
|
+
approvalCode: kind === "approved" ? identifier(fields.ssl_approval_code) : null,
|
|
301
|
+
amountCents,
|
|
302
|
+
tokenIssued: kind === "approved" && typeof fields.ssl_token === "string" && fields.ssl_token !== "",
|
|
303
|
+
chargedAboveRequest: amountCents !== null && requestAmountCents !== null && amountCents > requestAmountCents
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function convergeResponseOutcome(status, text) {
|
|
307
|
+
if (!(status >= 200 && status < 300))
|
|
308
|
+
return { outcome: "unknown", reason: "converge_unconfirmed" };
|
|
309
|
+
const kind = convergeAnswerKind(text);
|
|
310
|
+
if (kind === "approved")
|
|
311
|
+
return { outcome: "approved" };
|
|
312
|
+
if (KIND_REFUSAL[kind])
|
|
313
|
+
return { outcome: "refused", code: KIND_REFUSAL[kind] };
|
|
314
|
+
return { outcome: "unknown", reason: "converge_unconfirmed" };
|
|
315
|
+
}
|
|
316
|
+
var RESPONSE_FIELDS = /* @__PURE__ */ Object.freeze([
|
|
317
|
+
"ssl_result",
|
|
318
|
+
"ssl_result_message",
|
|
319
|
+
"ssl_txn_id",
|
|
320
|
+
"ssl_approval_code",
|
|
321
|
+
"ssl_amount",
|
|
322
|
+
"ssl_txn_time",
|
|
323
|
+
"ssl_invoice_number",
|
|
324
|
+
"ssl_description",
|
|
325
|
+
"ssl_card_short_description",
|
|
326
|
+
"ssl_card_type",
|
|
327
|
+
"ssl_card_number",
|
|
328
|
+
"ssl_cvv2_response",
|
|
329
|
+
"ssl_avs_response",
|
|
330
|
+
"ssl_transaction_type",
|
|
331
|
+
"ssl_merchant_txn_id",
|
|
332
|
+
"ssl_reference_number",
|
|
333
|
+
"ssl_transaction_reference_number",
|
|
334
|
+
"ssl_salestax",
|
|
335
|
+
"ssl_customer_code"
|
|
336
|
+
]);
|
|
337
|
+
var MASKED_CARD = /^(?=.{12,19}$)\d{0,6}[*Xx#]+\d{0,4}$/;
|
|
338
|
+
var FIELD_NAME = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
339
|
+
function luhn(digits) {
|
|
340
|
+
let sum = 0;
|
|
341
|
+
for (let i = 0; i < digits.length; i += 1) {
|
|
342
|
+
let d = Number(digits[digits.length - 1 - i]);
|
|
343
|
+
if (i % 2 === 1) {
|
|
344
|
+
d *= 2;
|
|
345
|
+
if (d > 9)
|
|
346
|
+
d -= 9;
|
|
347
|
+
}
|
|
348
|
+
sum += d;
|
|
349
|
+
}
|
|
350
|
+
return sum % 10 === 0;
|
|
351
|
+
}
|
|
352
|
+
var SEPARATORS = /[\s.\u00b7\u2010-\u2015\u2212-]+/g;
|
|
353
|
+
var DIGIT_GROUPS = /\d+(?:[\s.\u00b7\u2010-\u2015\u2212-]+\d+)*/g;
|
|
354
|
+
function holdsCardNumber(text) {
|
|
355
|
+
const whole = text.replace(SEPARATORS, "");
|
|
356
|
+
if (/^\d{12,19}$/.test(whole) && luhn(whole))
|
|
357
|
+
return true;
|
|
358
|
+
for (const run of text.match(DIGIT_GROUPS) ?? []) {
|
|
359
|
+
const groups = run.split(SEPARATORS);
|
|
360
|
+
for (let start = 0; start < groups.length; start += 1) {
|
|
361
|
+
if (groups[start][0] === "0")
|
|
362
|
+
continue;
|
|
363
|
+
let digits = "";
|
|
364
|
+
for (let end = start; end < groups.length && digits.length + groups[end].length <= 19; end += 1) {
|
|
365
|
+
digits += groups[end];
|
|
366
|
+
if (digits.length >= 12 && luhn(digits))
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
function safeScalar(value) {
|
|
374
|
+
if (typeof value === "boolean")
|
|
375
|
+
return true;
|
|
376
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
377
|
+
return false;
|
|
378
|
+
if (typeof value !== "number" && (typeof value !== "string" || value.length > 1024 || /[\u0000-\u001f\u007f]/.test(value)))
|
|
379
|
+
return false;
|
|
380
|
+
return !holdsCardNumber(String(value));
|
|
381
|
+
}
|
|
382
|
+
function keepReturnField(name, value) {
|
|
383
|
+
if (typeof name !== "string" || !FIELD_NAME.test(name))
|
|
384
|
+
return false;
|
|
385
|
+
const lower = name.toLowerCase();
|
|
386
|
+
if (lower.startsWith("ssl_") && !RESPONSE_FIELDS.includes(lower))
|
|
387
|
+
return false;
|
|
388
|
+
if (lower === "ssl_card_number")
|
|
389
|
+
return typeof value === "string" && MASKED_CARD.test(value);
|
|
390
|
+
return safeScalar(value);
|
|
391
|
+
}
|
|
392
|
+
function sanitizeReturnList(list) {
|
|
393
|
+
if (Array.isArray(list)) {
|
|
394
|
+
return list.filter((entry) => isRecord(entry) && keepReturnField(entry.name, entry.value)).slice(0, 128).map((entry) => ({ name: entry.name, value: entry.value }));
|
|
395
|
+
}
|
|
396
|
+
if (isRecord(list)) {
|
|
397
|
+
const out = {};
|
|
398
|
+
for (const [name, value] of Object.entries(list).slice(0, 128))
|
|
399
|
+
if (keepReturnField(name, value))
|
|
400
|
+
out[name] = value;
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
function sanitizeConvergeResponse(text) {
|
|
406
|
+
if (convergeAnswerKind(text) !== "approved")
|
|
407
|
+
throw new Error("Refusing to store an Elavon Converge answer that is not an approval.");
|
|
408
|
+
const answer = parseAnswer(text);
|
|
409
|
+
const out = { approved: true };
|
|
410
|
+
for (const key of ["amount", "currency", "paymentType", "transactionType"]) {
|
|
411
|
+
if (owns(answer, key) && (typeof answer[key] === "string" || typeof answer[key] === "number") && safeScalar(answer[key]) && String(answer[key]).length <= 64)
|
|
412
|
+
out[key] = answer[key];
|
|
413
|
+
}
|
|
414
|
+
for (const key of ["debitAsCredit", "dcc", "threeDSecure"])
|
|
415
|
+
if (answer[key] === false)
|
|
416
|
+
out[key] = false;
|
|
417
|
+
const fields = {};
|
|
418
|
+
for (const name of RESPONSE_FIELDS) {
|
|
419
|
+
if (!owns(answer.responseFields, name))
|
|
420
|
+
continue;
|
|
421
|
+
const value = answer.responseFields[name];
|
|
422
|
+
if (name === "ssl_card_number" ? typeof value === "string" && MASKED_CARD.test(value) : safeScalar(value))
|
|
423
|
+
fields[name] = value;
|
|
424
|
+
}
|
|
425
|
+
out.responseFields = fields;
|
|
426
|
+
if (isRecord(answer.paymentReturnFields)) {
|
|
427
|
+
const returns = {};
|
|
428
|
+
for (const key of ["approveFields", "declineFields"]) {
|
|
429
|
+
const kept = sanitizeReturnList(answer.paymentReturnFields[key]);
|
|
430
|
+
if (kept !== void 0)
|
|
431
|
+
returns[key] = kept;
|
|
432
|
+
}
|
|
433
|
+
out.paymentReturnFields = returns;
|
|
434
|
+
}
|
|
435
|
+
if (typeof answer.context === "string" && answer.context.length <= 65536)
|
|
436
|
+
out.context = answer.context;
|
|
437
|
+
return JSON.stringify(out);
|
|
438
|
+
}
|
|
439
|
+
export { CONVERGE_DEMO_HOST, CONVERGE_HOSTS, CONVERGE_PROCESS_PATH, CONVERGE_PROCESS_URLS, CONVERGE_REFUSAL_CODES, convergeAmountCents, convergeAnswerKind, convergeBodyRefusal, convergeBodyVerdict, convergeFacts, convergeRefusalMessage, convergeRequestRefusal, convergeResponseOutcome, injectConvergeHppCard, isConvergeProcessUrl, parseConvergeBody, sanitizeConvergeResponse };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Elavon Converge Hosted Payment Page (psp `elavon_converge`): the page NY DMV's payment apps
|
|
2
|
+
// redirect to, api.convergepay.com/hosted-payments/?ssl_txn_auth_token=<one-time token> (the
|
|
3
|
+
// demo environment serves the same page on api.demo.convergepay.com). Converge's own AngularJS
|
|
4
|
+
// app posts the card in one same-origin JSON XHR, POST /hosted-payments/service/payment/hpp/process,
|
|
5
|
+
// with the card at paymentData.ssl_card_number, ssl_exp_date (MMYY) and ssl_cvv2cvc2, the amount
|
|
6
|
+
// at paymentData.ssl_amount, and the page's rolling session in `context`. The same URL also takes
|
|
7
|
+
// electronic check and financing payments with no card, wallet payments with a card number the
|
|
8
|
+
// server supplied, the debit, gift card and push-to-card tabs, and the follow-ups after a 3-D
|
|
9
|
+
// Secure, Pay Defense or surcharge step. This module holds what the SDK decides about that request.
|
|
10
|
+
// The rules themselves are payment-core's (elavon-converge.generated.ts): the API applies the same
|
|
11
|
+
// ones at create, and the cardholder's device applies them again before it writes a card.
|
|
12
|
+
//
|
|
13
|
+
// The processor ships dark. The API serves the elavon_converge recognizer only to a build that
|
|
14
|
+
// declares the `converge_hpp_process` capability (client.ts SUPPORTED_CAPABILITIES), and only for
|
|
15
|
+
// an org Agentcard enabled for Converge, so an SDK that has not synced, or that runs for any other
|
|
16
|
+
// org, never recognizes a Converge host and leaves its requests alone.
|
|
17
|
+
import { CONVERGE_HOSTS, convergeRequestRefusal } from './elavon-converge.generated.js';
|
|
18
|
+
export const CONVERGE_PSP = 'elavon_converge';
|
|
19
|
+
/** The dark-launch capability the API serves the elavon_converge recognizer behind. */
|
|
20
|
+
export const CONVERGE_CAPABILITY = 'converge_hpp_process';
|
|
21
|
+
/**
|
|
22
|
+
* Why the SDK refuses an Elavon Converge payment before anything is claimed, created or prompted:
|
|
23
|
+
* payment-core's own codes (CONVERGE_REFUSAL_CODES), the ones the API answers a create with and
|
|
24
|
+
* the device refuses with, so an integrator reads one code whichever of them refused.
|
|
25
|
+
* - A shape the Vault never pays: `converge_wallet_refused`, `converge_debit_card_refused`,
|
|
26
|
+
* `converge_push_to_card_refused`, `converge_gift_card_refused`, `converge_payment_type_refused`,
|
|
27
|
+
* `converge_captcha_refused`, `converge_service_fee_refused`, `converge_recurring_refused`,
|
|
28
|
+
* `converge_card_saving_refused` (ssl_get_token or ssl_add_token), `converge_amount_add_on_refused`
|
|
29
|
+
* (a tip or cash back) and `converge_currency_refused` (anything but US dollars).
|
|
30
|
+
* - A follow-up after a step the Vault cannot complete: `converge_3ds_follow_up_refused` (3-D
|
|
31
|
+
* Secure or Pay Defense) and `converge_surcharge_refused`.
|
|
32
|
+
* - A body the cardholder has nothing to approve on: `converge_amount_missing` and
|
|
33
|
+
* `converge_context_missing`.
|
|
34
|
+
* - Not the page's own card request, which the device could never fill: `converge_body_malformed`,
|
|
35
|
+
* `converge_credential_refused`, `converge_card_missing`, `converge_card_malformed`,
|
|
36
|
+
* `converge_url_refused`, `converge_method_refused` and `converge_content_type_refused`.
|
|
37
|
+
*/
|
|
38
|
+
export const CONVERGE_REFUSAL_REASONS = [
|
|
39
|
+
'converge_body_malformed',
|
|
40
|
+
'converge_payment_type_refused',
|
|
41
|
+
'converge_debit_card_refused',
|
|
42
|
+
'converge_push_to_card_refused',
|
|
43
|
+
'converge_gift_card_refused',
|
|
44
|
+
'converge_credential_refused',
|
|
45
|
+
'converge_wallet_refused',
|
|
46
|
+
'converge_captcha_refused',
|
|
47
|
+
'converge_service_fee_refused',
|
|
48
|
+
'converge_3ds_follow_up_refused',
|
|
49
|
+
'converge_surcharge_refused',
|
|
50
|
+
'converge_amount_add_on_refused',
|
|
51
|
+
'converge_card_saving_refused',
|
|
52
|
+
'converge_recurring_refused',
|
|
53
|
+
'converge_currency_refused',
|
|
54
|
+
'converge_amount_missing',
|
|
55
|
+
'converge_context_missing',
|
|
56
|
+
'converge_card_missing',
|
|
57
|
+
'converge_card_malformed',
|
|
58
|
+
'converge_url_refused',
|
|
59
|
+
'converge_method_refused',
|
|
60
|
+
'converge_content_type_refused',
|
|
61
|
+
];
|
|
62
|
+
/** True when the URL's host is one of Converge's two payment page hosts. */
|
|
63
|
+
export function isConvergeHost(url) {
|
|
64
|
+
try {
|
|
65
|
+
return CONVERGE_HOSTS.includes(new URL(url).hostname);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The pause decision for a card-carrying POST the registry recognized on a Converge host. Null
|
|
73
|
+
* when it is the page's own first credit card sale, which pauses for approval; otherwise the reason
|
|
74
|
+
* it is refused. It judges the whole request (the exact process URL, the page's JSON content type,
|
|
75
|
+
* and a body exactly as the page serializes it) with payment-core's convergeRequestRefusal, the
|
|
76
|
+
* check the API runs at create and the device runs before it writes a card. A code this build does
|
|
77
|
+
* not list reads as not the page's request, so a newer core can only refuse more.
|
|
78
|
+
*/
|
|
79
|
+
export function convergeRefusal(request) {
|
|
80
|
+
const code = convergeRequestRefusal({ url: request.url, method: 'POST', headers: request.headers ?? {}, body: request.body ?? '' });
|
|
81
|
+
if (code === null)
|
|
82
|
+
return null;
|
|
83
|
+
return CONVERGE_REFUSAL_REASONS.includes(code) ? code : 'converge_body_malformed';
|
|
84
|
+
}
|