@agent-cards/sdk 0.23.16 → 0.23.17
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 +4 -0
- package/dist/card-fields.generated.js +101 -1
- package/dist/cdp.js +19 -2
- package/dist/client.d.ts +1 -0
- package/dist/client.js +14 -0
- package/dist/primer.generated.js +120 -20
- package/dist/stripe-checkout.generated.js +29 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Finish a Worldpay eProtect checkout that your code resumes itself. With `attachToCdp(…, { pauseOnly: true })`, you create the authorization, read it, and call `controller.resume(read)`. The read carries the card registration as Agentcard stored it, bare JSON, and earlier releases handed exactly that to the merchant's paused registration script. Worldpay's script reads its answer only through the callback the request named, so it never received the registration: the page waited until the merchant's timeout, and the checkout read `outcome_unknown` with reason `merchant_request_aborted` after the cardholder had approved. The SDK now answers the resumed registration as it answers one it created itself, with the registration inside the request's own callback under Worldpay's script type. The SDK never hands the page a read that is not this request's registration, such as one already inside a callback or another request's answer: `resume` rejects with `PaymentOutcomeUnknownError`, reason `eprotect_registration_unconfirmed`, and the paused request fails. A checkout whose authorization the SDK creates was already answered this way and does not change. No API or Vault release is needed.
|
|
6
|
+
|
|
5
7
|
- Refuse a sandbox declaration whose endpoint is on agentcard.com, Agentcard's own domain, as one on agentcard.sh is already refused. A `sandboxMerchants` entry on `VaultClient` whose endpoint is agentcard.com or any host under it makes the constructor throw:
|
|
6
8
|
|
|
7
9
|
```text
|
|
@@ -141,6 +143,8 @@
|
|
|
141
143
|
|
|
142
144
|
- Pay a Wix checkout whose card goes to Wix's own tokenizer, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it checks that the site instance Wix's card frame sends belongs to the reviewed store. Every Wix checkout mounts Wix Cashier's card frame, and for Wix Payments and classic Stripe on Wix that frame posts the card to `https://cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens`. Wix's payment widget fails the payment 30 seconds after it asks the frame for a token, too soon for an approval after Pay, so the SDK pays a Wix checkout only through `prepare({ psp: 'wix_payments', environment: 'production' })`, at a store Agentcard reviewed (ParaHouse Magazine, Psalms & Potions and Alexandra Hagel today), with `amount` and `currency`, the total the cardholder approves: the request names none. Wix has no test environment, so a test client's `prepare()` fails with `live_host_on_sandbox_row`. The sync now also asks for `capabilities=wix_payments_tokenize`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. The frame sends its card request once more under a fresh site instance after Wix answers 401 or 403: the SDK refuses that second request in the browser and never answers it from the first approval. The SDK fails a paused request the cardholder's device has not sent 20 seconds after the pause, of the 30 seconds Wix waits, with reason `wix_payments_send_window_closed`, as it fails a prepared Secure Acceptance token create, and the device sends nothing past its own earlier boundary.
|
|
143
145
|
|
|
146
|
+
- Keep a GoPay card form's token out of your events and error messages. GoPay's card frame sends the card to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>?lang=..&ver=..&SG=<signature>`, and that token, with the signature in the query, opens that payment's card form to anyone who holds it. `redactUrl`, every event and every error message now show it as `{token}` on GoPay's card frame hosts, as in `https://secure.gopay.com/gp-card-comm/h/{token}`, and drop the query as before. The SDK does not pause a GoPay payment yet. No API or Vault release is needed.
|
|
147
|
+
|
|
144
148
|
## 0.23.0
|
|
145
149
|
|
|
146
150
|
### Upgrading from 0.22.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 0fcc0e758acec92fba9d21092bcb3704b8a73ec864647ea194ca44eba57cb96d
|
|
4
4
|
// src/card-number-key.js
|
|
5
5
|
var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
|
|
6
6
|
// src/json-path.js
|
|
@@ -1442,6 +1442,104 @@ function paymongoBodyVerdict(body, session) {
|
|
|
1442
1442
|
const code = judgeCardAttach(parsed, session);
|
|
1443
1443
|
return code === null ? CARD9 : refuse10(code);
|
|
1444
1444
|
}
|
|
1445
|
+
// src/gopay.js
|
|
1446
|
+
var GOPAY_PSP = "gopay";
|
|
1447
|
+
var GOPAY_CARD_FORM_HOSTS = /* @__PURE__ */ Object.freeze({ "secure.gopay.com": "production", "secure.sandbox.gopay.com": "test" });
|
|
1448
|
+
var GOPAY_CARD_FORM_PATH = "/gp-card-comm/h/";
|
|
1449
|
+
var BODY_LIMIT7 = 1024;
|
|
1450
|
+
var ROOT_KEYS4 = /* @__PURE__ */ Object.freeze(["SG", "cardData", "type"]);
|
|
1451
|
+
var CARD_KEYS6 = /* @__PURE__ */ Object.freeze(["cardNumber", "cvc", "expiration"]);
|
|
1452
|
+
var TOKEN2 = /^[0-9A-Fa-f]{16,1024}$/;
|
|
1453
|
+
var SIGNATURE = /^[0-9A-Fa-f]{64}$/;
|
|
1454
|
+
var LANGUAGE = /^[a-z]{2}(?:-[A-Za-z]{2})?$/;
|
|
1455
|
+
var VERSION = /^[A-Za-z0-9]{1,16}$/;
|
|
1456
|
+
var QUERY_NAMES = /* @__PURE__ */ Object.freeze(["lang", "ver", "SG"]);
|
|
1457
|
+
var CARD_NUMBER7 = /^[0-9]{16}$/;
|
|
1458
|
+
var CODE = /^[0-9]{3}$/;
|
|
1459
|
+
var EXPIRATION = /^[0-9]{2}(?:0[1-9]|1[0-2])$/;
|
|
1460
|
+
var CARD_PAYMENT = "PAYMENT_CARD";
|
|
1461
|
+
var record8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1462
|
+
var sameKeys4 = (value, keys) => {
|
|
1463
|
+
const own2 = Object.keys(value);
|
|
1464
|
+
return own2.length === keys.length && own2.every((key, at) => key === keys[at]);
|
|
1465
|
+
};
|
|
1466
|
+
var refuse11 = (code) => Object.freeze({ verdict: "refuse", code });
|
|
1467
|
+
var CARD10 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
|
|
1468
|
+
function luhn(digits) {
|
|
1469
|
+
let sum = 0;
|
|
1470
|
+
for (let at = 0; at < digits.length; at += 1) {
|
|
1471
|
+
let digit = Number(digits[digits.length - 1 - at]);
|
|
1472
|
+
if (at % 2 === 1) {
|
|
1473
|
+
digit *= 2;
|
|
1474
|
+
if (digit > 9)
|
|
1475
|
+
digit -= 9;
|
|
1476
|
+
}
|
|
1477
|
+
sum += digit;
|
|
1478
|
+
}
|
|
1479
|
+
return sum % 10 === 0;
|
|
1480
|
+
}
|
|
1481
|
+
function gopayCardFormUrl(url) {
|
|
1482
|
+
if (typeof url !== "string" || url.length > 2048 || url.includes("#"))
|
|
1483
|
+
return null;
|
|
1484
|
+
let parsed;
|
|
1485
|
+
try {
|
|
1486
|
+
parsed = new URL(url);
|
|
1487
|
+
}
|
|
1488
|
+
catch {
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password)
|
|
1492
|
+
return null;
|
|
1493
|
+
const environment = Object.prototype.hasOwnProperty.call(GOPAY_CARD_FORM_HOSTS, parsed.hostname) ? GOPAY_CARD_FORM_HOSTS[parsed.hostname] : null;
|
|
1494
|
+
if (environment === null || !parsed.pathname.startsWith(GOPAY_CARD_FORM_PATH))
|
|
1495
|
+
return null;
|
|
1496
|
+
if (!TOKEN2.test(parsed.pathname.slice(GOPAY_CARD_FORM_PATH.length)))
|
|
1497
|
+
return null;
|
|
1498
|
+
const names = [...parsed.searchParams.keys()];
|
|
1499
|
+
if (names.some((name) => !QUERY_NAMES.includes(name)) || new Set(names).size !== names.length)
|
|
1500
|
+
return null;
|
|
1501
|
+
const signature = parsed.searchParams.get("SG");
|
|
1502
|
+
const language = parsed.searchParams.get("lang");
|
|
1503
|
+
const version = parsed.searchParams.get("ver");
|
|
1504
|
+
if (signature === null || !SIGNATURE.test(signature) || language !== null && !LANGUAGE.test(language) || version !== null && !VERSION.test(version))
|
|
1505
|
+
return null;
|
|
1506
|
+
return Object.freeze({ host: parsed.hostname, environment, signature });
|
|
1507
|
+
}
|
|
1508
|
+
function readBody4(body) {
|
|
1509
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT7)
|
|
1510
|
+
return null;
|
|
1511
|
+
let value;
|
|
1512
|
+
try {
|
|
1513
|
+
value = JSON.parse(body);
|
|
1514
|
+
if (!record8(value) || JSON.stringify(value) !== body)
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
catch {
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return value;
|
|
1521
|
+
}
|
|
1522
|
+
function gopayBodyVerdict(body, url) {
|
|
1523
|
+
let target;
|
|
1524
|
+
if (url !== void 0) {
|
|
1525
|
+
target = gopayCardFormUrl(url);
|
|
1526
|
+
if (target === null)
|
|
1527
|
+
return refuse11("gopay_url_refused");
|
|
1528
|
+
}
|
|
1529
|
+
const value = readBody4(body);
|
|
1530
|
+
if (value === null || !sameKeys4(value, ROOT_KEYS4) || !record8(value.cardData) || !sameKeys4(value.cardData, CARD_KEYS6)) {
|
|
1531
|
+
return refuse11("gopay_body_unsupported");
|
|
1532
|
+
}
|
|
1533
|
+
const card = value.cardData;
|
|
1534
|
+
if (typeof value.SG !== "string" || !SIGNATURE.test(value.SG) || typeof card.cardNumber !== "string" || !CARD_NUMBER7.test(card.cardNumber) || !luhn(card.cardNumber) || typeof card.cvc !== "string" || !CODE.test(card.cvc) || typeof card.expiration !== "string" || !EXPIRATION.test(card.expiration) || typeof value.type !== "string") {
|
|
1535
|
+
return refuse11("gopay_body_unsupported");
|
|
1536
|
+
}
|
|
1537
|
+
if (value.type !== CARD_PAYMENT)
|
|
1538
|
+
return refuse11("gopay_card_saving_refused");
|
|
1539
|
+
if (target !== void 0 && value.SG !== target.signature)
|
|
1540
|
+
return refuse11("gopay_signature_mismatch");
|
|
1541
|
+
return CARD10;
|
|
1542
|
+
}
|
|
1445
1543
|
// src/card-fields.js
|
|
1446
1544
|
function processorGate(recognizer, url, body) {
|
|
1447
1545
|
if (recognizer && recognizer.psp === LYRA_PSP)
|
|
@@ -1473,6 +1571,8 @@ function processorGate(recognizer, url, body) {
|
|
|
1473
1571
|
return { verdict: "refuse", code: "paymongo_url_refused" };
|
|
1474
1572
|
return paymongoBodyVerdict(body, session);
|
|
1475
1573
|
}
|
|
1574
|
+
if (recognizer && recognizer.psp === GOPAY_PSP)
|
|
1575
|
+
return gopayBodyVerdict(body, url);
|
|
1476
1576
|
if (recognizer && recognizer.psp === GRAVY_PSP)
|
|
1477
1577
|
return gravyBodyVerdict(body);
|
|
1478
1578
|
if (recognizer && recognizer.psp === MONERIS_PSP) {
|
package/dist/cdp.js
CHANGED
|
@@ -7,7 +7,7 @@ import { MercadoCheckoutGate } from './mercado-checkout.js';
|
|
|
7
7
|
import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
|
|
8
8
|
import { JuspayCheckoutGate } from './juspay-gate.js';
|
|
9
9
|
import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from './aci.js';
|
|
10
|
-
import { EprotectGate } from './eprotect.js';
|
|
10
|
+
import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './eprotect.js';
|
|
11
11
|
import { dispatchPreClaim, eventUrl, isArmedCreditguardCall } from './pre-claim.js';
|
|
12
12
|
import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
|
|
13
13
|
import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
|
|
@@ -1549,6 +1549,23 @@ function replayOf(result, timeoutMs) {
|
|
|
1549
1549
|
return { mode: 'token', authorizationId, status: response.status, headers: { ...response.headers }, body: response.body, amountVerified: null,
|
|
1550
1550
|
...(result.processor_context !== undefined ? { processorContext: result.processor_context } : {}), ...execution };
|
|
1551
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Worldpay eProtect's registration, resumed by the business. The read carries the registration as the API
|
|
1554
|
+
* stored it, bare JSON, and the paused request is the vendor's JSONP <script>, which runs its answer and reads
|
|
1555
|
+
* the registration only through the callback the paused URL named. So it gets the answer authorize() gives on
|
|
1556
|
+
* the create path (client.ts): the registration inside that callback, under Worldpay's script type, and only a
|
|
1557
|
+
* registration payment-core's judge accepts for that URL. A read in any other mode never answers it either. The
|
|
1558
|
+
* paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and a
|
|
1559
|
+
* create's replay never comes here, so no answer is wrapped twice.
|
|
1560
|
+
*/
|
|
1561
|
+
function eprotectResumedReplay(replay, request) {
|
|
1562
|
+
if (classifyEprotectRequest(request.url, request.method)?.kind !== 'registration')
|
|
1563
|
+
return replay;
|
|
1564
|
+
const answer = replay.mode === 'token' ? eprotectScriptAnswer(request.url, replay) : null;
|
|
1565
|
+
if (replay.mode !== 'token' || !answer)
|
|
1566
|
+
throw new PaymentOutcomeUnknownError(replay.authorizationId, 'eprotect_registration_unconfirmed');
|
|
1567
|
+
return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
|
|
1568
|
+
}
|
|
1552
1569
|
/** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
|
|
1553
1570
|
function recognizerNameOf(opts, url, method) {
|
|
1554
1571
|
const read = opts.vault.recognizerOf;
|
|
@@ -2763,7 +2780,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
2763
2780
|
// through controller.resume; the replay it hands back goes down the same delivery path as a create's.
|
|
2764
2781
|
const pause = opts.pauseOnly ? pauseForBusiness(opts, lifecycle, attempt.signal, { url: request.url, method: request.method, headers: request.headers ?? {}, body }, pageOrigin) : null;
|
|
2765
2782
|
businessPause = pause;
|
|
2766
|
-
const replay = pause ? await pause.replay : await opts.vault.authorize({
|
|
2783
|
+
const replay = pause ? eprotectResumedReplay(await pause.replay, request) : await opts.vault.authorize({
|
|
2767
2784
|
user: opts.user,
|
|
2768
2785
|
merchant: opts.merchant,
|
|
2769
2786
|
amount: opts.amount,
|
package/dist/client.d.ts
CHANGED
|
@@ -1175,6 +1175,7 @@ export declare class CheckoutAuthError extends Error {
|
|
|
1175
1175
|
* On PayPal's hosts an order id reads `{order}`:
|
|
1176
1176
|
* https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
|
|
1177
1177
|
* On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
|
|
1178
|
+
* On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1178
1179
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1179
1180
|
* same segments masked.
|
|
1180
1181
|
*/
|
package/dist/client.js
CHANGED
|
@@ -1115,6 +1115,18 @@ const PAYMONGO_SESSION = /(?:cs_)?(?:[A-Za-z0-9]|%[0-9A-Fa-f]{2}){24,}/gi;
|
|
|
1115
1115
|
function maskPaymongoSession(path) {
|
|
1116
1116
|
return path.replace(PAYMONGO_SESSION, '{session}');
|
|
1117
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* GoPay's card frame posts the card to its own URL,
|
|
1120
|
+
* https://secure.gopay.com/gp-card-comm/h/<hex token>?lang=..&ver=..&SG=<64 hex> (payment-core gopay.js). The
|
|
1121
|
+
* token names that payment's card form, and with the SG, which redactUrl drops with the query, anyone who holds
|
|
1122
|
+
* the URL can open the form and post a card to it. On GoPay's two card frame hosts the segment after /h/, in any
|
|
1123
|
+
* case or after an escaped slash, reads {token}: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1124
|
+
*/
|
|
1125
|
+
const GOPAY_CARD_FORM_HOST = /^secure(?:\.sandbox)?\.gopay\.com\.?$/i;
|
|
1126
|
+
const GOPAY_CARD_FORM_TOKEN = /(\/h(?:\/|%2f))[^/?#]*/gi;
|
|
1127
|
+
function maskGopayToken(path) {
|
|
1128
|
+
return path.replace(GOPAY_CARD_FORM_TOKEN, '$1{token}');
|
|
1129
|
+
}
|
|
1118
1130
|
/**
|
|
1119
1131
|
* The processors whose card request carries its own credential in the URL path, with the hosts
|
|
1120
1132
|
* that send it, the text that names those hosts in a string that is not a URL, and the mask. The
|
|
@@ -1155,6 +1167,7 @@ const PATH_CREDENTIALS = [
|
|
|
1155
1167
|
{ host: LYRA_HOST, named: /(?:payzen\.eu|systempay\.fr|societegenerale\.eu|lyra\.com)/i, mask: maskLyraSession },
|
|
1156
1168
|
{ host: PAYPAL_HOST, named: /paypal\.com/i, mask: maskPaypalOrder },
|
|
1157
1169
|
{ host: PAYMONGO_CHECKOUT_HOST, named: /checkout\.paymongo\.com/i, mask: maskPaymongoSession },
|
|
1170
|
+
{ host: GOPAY_CARD_FORM_HOST, named: /secure(?:\.sandbox)?\.gopay\.com/i, mask: maskGopayToken },
|
|
1158
1171
|
];
|
|
1159
1172
|
/**
|
|
1160
1173
|
* A URL as it may appear in an error message: origin + path only. A paused
|
|
@@ -1178,6 +1191,7 @@ const PATH_CREDENTIALS = [
|
|
|
1178
1191
|
* On PayPal's hosts an order id reads `{order}`:
|
|
1179
1192
|
* https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source.
|
|
1180
1193
|
* On PayMongo's checkout host the session id reads `{session}`: https://checkout.paymongo.com/{session}.
|
|
1194
|
+
* On GoPay's card frame hosts the card form's token reads `{token}`: https://secure.gopay.com/gp-card-comm/h/{token}.
|
|
1181
1195
|
* A string that is not a URL keeps the part before any `?` or `#`, with the
|
|
1182
1196
|
* same segments masked.
|
|
1183
1197
|
*/
|
package/dist/primer.generated.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: c3b720efaa20837c1bf50cc511610282f324867e9fe5538ac155b2ff79f2d9b4
|
|
4
4
|
// src/card-number-key.js
|
|
5
5
|
var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
|
|
6
6
|
// src/json-path.js
|
|
@@ -1450,6 +1450,104 @@ function paymongoBodyVerdict(body, session) {
|
|
|
1450
1450
|
const code = judgeCardAttach(parsed, session);
|
|
1451
1451
|
return code === null ? CARD9 : refuse10(code);
|
|
1452
1452
|
}
|
|
1453
|
+
// src/gopay.js
|
|
1454
|
+
var GOPAY_PSP = "gopay";
|
|
1455
|
+
var GOPAY_CARD_FORM_HOSTS = /* @__PURE__ */ Object.freeze({ "secure.gopay.com": "production", "secure.sandbox.gopay.com": "test" });
|
|
1456
|
+
var GOPAY_CARD_FORM_PATH = "/gp-card-comm/h/";
|
|
1457
|
+
var BODY_LIMIT7 = 1024;
|
|
1458
|
+
var ROOT_KEYS4 = /* @__PURE__ */ Object.freeze(["SG", "cardData", "type"]);
|
|
1459
|
+
var CARD_KEYS6 = /* @__PURE__ */ Object.freeze(["cardNumber", "cvc", "expiration"]);
|
|
1460
|
+
var TOKEN2 = /^[0-9A-Fa-f]{16,1024}$/;
|
|
1461
|
+
var SIGNATURE = /^[0-9A-Fa-f]{64}$/;
|
|
1462
|
+
var LANGUAGE = /^[a-z]{2}(?:-[A-Za-z]{2})?$/;
|
|
1463
|
+
var VERSION = /^[A-Za-z0-9]{1,16}$/;
|
|
1464
|
+
var QUERY_NAMES = /* @__PURE__ */ Object.freeze(["lang", "ver", "SG"]);
|
|
1465
|
+
var CARD_NUMBER7 = /^[0-9]{16}$/;
|
|
1466
|
+
var CODE = /^[0-9]{3}$/;
|
|
1467
|
+
var EXPIRATION = /^[0-9]{2}(?:0[1-9]|1[0-2])$/;
|
|
1468
|
+
var CARD_PAYMENT = "PAYMENT_CARD";
|
|
1469
|
+
var record8 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1470
|
+
var sameKeys4 = (value, keys) => {
|
|
1471
|
+
const own2 = Object.keys(value);
|
|
1472
|
+
return own2.length === keys.length && own2.every((key, at) => key === keys[at]);
|
|
1473
|
+
};
|
|
1474
|
+
var refuse11 = (code) => Object.freeze({ verdict: "refuse", code });
|
|
1475
|
+
var CARD10 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
|
|
1476
|
+
function luhn(digits2) {
|
|
1477
|
+
let sum = 0;
|
|
1478
|
+
for (let at = 0; at < digits2.length; at += 1) {
|
|
1479
|
+
let digit = Number(digits2[digits2.length - 1 - at]);
|
|
1480
|
+
if (at % 2 === 1) {
|
|
1481
|
+
digit *= 2;
|
|
1482
|
+
if (digit > 9)
|
|
1483
|
+
digit -= 9;
|
|
1484
|
+
}
|
|
1485
|
+
sum += digit;
|
|
1486
|
+
}
|
|
1487
|
+
return sum % 10 === 0;
|
|
1488
|
+
}
|
|
1489
|
+
function gopayCardFormUrl(url) {
|
|
1490
|
+
if (typeof url !== "string" || url.length > 2048 || url.includes("#"))
|
|
1491
|
+
return null;
|
|
1492
|
+
let parsed;
|
|
1493
|
+
try {
|
|
1494
|
+
parsed = new URL(url);
|
|
1495
|
+
}
|
|
1496
|
+
catch {
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
if (parsed.href !== url || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password)
|
|
1500
|
+
return null;
|
|
1501
|
+
const environment = Object.prototype.hasOwnProperty.call(GOPAY_CARD_FORM_HOSTS, parsed.hostname) ? GOPAY_CARD_FORM_HOSTS[parsed.hostname] : null;
|
|
1502
|
+
if (environment === null || !parsed.pathname.startsWith(GOPAY_CARD_FORM_PATH))
|
|
1503
|
+
return null;
|
|
1504
|
+
if (!TOKEN2.test(parsed.pathname.slice(GOPAY_CARD_FORM_PATH.length)))
|
|
1505
|
+
return null;
|
|
1506
|
+
const names = [...parsed.searchParams.keys()];
|
|
1507
|
+
if (names.some((name) => !QUERY_NAMES.includes(name)) || new Set(names).size !== names.length)
|
|
1508
|
+
return null;
|
|
1509
|
+
const signature = parsed.searchParams.get("SG");
|
|
1510
|
+
const language = parsed.searchParams.get("lang");
|
|
1511
|
+
const version = parsed.searchParams.get("ver");
|
|
1512
|
+
if (signature === null || !SIGNATURE.test(signature) || language !== null && !LANGUAGE.test(language) || version !== null && !VERSION.test(version))
|
|
1513
|
+
return null;
|
|
1514
|
+
return Object.freeze({ host: parsed.hostname, environment, signature });
|
|
1515
|
+
}
|
|
1516
|
+
function readBody4(body) {
|
|
1517
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT7)
|
|
1518
|
+
return null;
|
|
1519
|
+
let value;
|
|
1520
|
+
try {
|
|
1521
|
+
value = JSON.parse(body);
|
|
1522
|
+
if (!record8(value) || JSON.stringify(value) !== body)
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
catch {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
return value;
|
|
1529
|
+
}
|
|
1530
|
+
function gopayBodyVerdict(body, url) {
|
|
1531
|
+
let target;
|
|
1532
|
+
if (url !== void 0) {
|
|
1533
|
+
target = gopayCardFormUrl(url);
|
|
1534
|
+
if (target === null)
|
|
1535
|
+
return refuse11("gopay_url_refused");
|
|
1536
|
+
}
|
|
1537
|
+
const value = readBody4(body);
|
|
1538
|
+
if (value === null || !sameKeys4(value, ROOT_KEYS4) || !record8(value.cardData) || !sameKeys4(value.cardData, CARD_KEYS6)) {
|
|
1539
|
+
return refuse11("gopay_body_unsupported");
|
|
1540
|
+
}
|
|
1541
|
+
const card = value.cardData;
|
|
1542
|
+
if (typeof value.SG !== "string" || !SIGNATURE.test(value.SG) || typeof card.cardNumber !== "string" || !CARD_NUMBER7.test(card.cardNumber) || !luhn(card.cardNumber) || typeof card.cvc !== "string" || !CODE.test(card.cvc) || typeof card.expiration !== "string" || !EXPIRATION.test(card.expiration) || typeof value.type !== "string") {
|
|
1543
|
+
return refuse11("gopay_body_unsupported");
|
|
1544
|
+
}
|
|
1545
|
+
if (value.type !== CARD_PAYMENT)
|
|
1546
|
+
return refuse11("gopay_card_saving_refused");
|
|
1547
|
+
if (target !== void 0 && value.SG !== target.signature)
|
|
1548
|
+
return refuse11("gopay_signature_mismatch");
|
|
1549
|
+
return CARD10;
|
|
1550
|
+
}
|
|
1453
1551
|
// src/card-fields.js
|
|
1454
1552
|
function processorGate(recognizer, url, body) {
|
|
1455
1553
|
if (recognizer && recognizer.psp === LYRA_PSP)
|
|
@@ -1481,6 +1579,8 @@ function processorGate(recognizer, url, body) {
|
|
|
1481
1579
|
return { verdict: "refuse", code: "paymongo_url_refused" };
|
|
1482
1580
|
return paymongoBodyVerdict(body, session);
|
|
1483
1581
|
}
|
|
1582
|
+
if (recognizer && recognizer.psp === GOPAY_PSP)
|
|
1583
|
+
return gopayBodyVerdict(body, url);
|
|
1484
1584
|
if (recognizer && recognizer.psp === GRAVY_PSP)
|
|
1485
1585
|
return gravyBodyVerdict(body);
|
|
1486
1586
|
if (recognizer && recognizer.psp === MONERIS_PSP) {
|
|
@@ -1601,12 +1701,12 @@ var KEEP_ON_FILE_REFUSAL = "Refusing to pay: this Primer payment would keep the
|
|
|
1601
1701
|
var UNSUPPORTED_SDK_REFUSAL = "Refusing to pay: this checkout uses a Primer SDK Agentcard does not support yet. Nothing was sent.";
|
|
1602
1702
|
var NETWORK_MISMATCH_REFUSAL = "Refusing to pay: this Primer payment names a card network that is not your card's. Nothing was sent.";
|
|
1603
1703
|
var FILL_REFUSAL = "Could not fill the card fields for Primer. Nothing was sent.";
|
|
1604
|
-
var
|
|
1704
|
+
var BODY_LIMIT8 = 16384;
|
|
1605
1705
|
var RESPONSE_LIMIT = 32768;
|
|
1606
|
-
var
|
|
1706
|
+
var ROOT_KEYS5 = ["paymentInstrument", "userDescription", "tokenType", "paymentFlow"];
|
|
1607
1707
|
var INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear", "cardholderName", "preferredNetwork"];
|
|
1608
1708
|
var REQUIRED_INSTRUMENT_KEYS = ["number", "cvv", "expirationMonth", "expirationYear"];
|
|
1609
|
-
var
|
|
1709
|
+
var record9 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1610
1710
|
var owns5 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
1611
1711
|
var exact = (pattern, value) => typeof value === "string" && pattern.exec(value)?.[0] === value;
|
|
1612
1712
|
var text2 = (value, max) => typeof value === "string" && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
|
|
@@ -1629,7 +1729,7 @@ function isPrimerTokenizeUrl(url, method = "POST") {
|
|
|
1629
1729
|
return parsed.href === url && parsed.protocol === "https:" && !parsed.port && !parsed.username && !parsed.password && !parsed.search && !parsed.hash && PRIMER_PCI_HOSTS.includes(parsed.hostname) && parsed.pathname === PRIMER_TOKENIZE_PATH;
|
|
1630
1730
|
}
|
|
1631
1731
|
function judgeBody2(body) {
|
|
1632
|
-
if (typeof body !== "string" || body === "" || body.length >
|
|
1732
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT8)
|
|
1633
1733
|
return null;
|
|
1634
1734
|
let value;
|
|
1635
1735
|
try {
|
|
@@ -1638,12 +1738,12 @@ function judgeBody2(body) {
|
|
|
1638
1738
|
catch {
|
|
1639
1739
|
return null;
|
|
1640
1740
|
}
|
|
1641
|
-
if (!
|
|
1741
|
+
if (!record9(value) || !roundTrips(value, body))
|
|
1642
1742
|
return null;
|
|
1643
|
-
if (!Object.keys(value).every((key) =>
|
|
1743
|
+
if (!Object.keys(value).every((key) => ROOT_KEYS5.includes(key)) || !owns5(value, "paymentInstrument"))
|
|
1644
1744
|
return null;
|
|
1645
1745
|
const instrument = value.paymentInstrument;
|
|
1646
|
-
if (!
|
|
1746
|
+
if (!record9(instrument) || !Object.keys(instrument).every((key) => INSTRUMENT_KEYS.includes(key)) || !REQUIRED_INSTRUMENT_KEYS.every((key) => owns5(instrument, key)))
|
|
1647
1747
|
return null;
|
|
1648
1748
|
if (!digits(instrument.number, 12, 19) || !digits(instrument.cvv, 3, 4) || !exact(MONTH3, instrument.expirationMonth) || !exact(YEAR3, instrument.expirationYear))
|
|
1649
1749
|
return null;
|
|
@@ -1671,7 +1771,7 @@ var CLIENT_TOKEN = /^[A-Za-z0-9._~+/=-]{16,4096}$/;
|
|
|
1671
1771
|
var REQUEST_ID = /^[A-Za-z0-9-]{1,64}$/;
|
|
1672
1772
|
var API_VERSION = /^2\.[0-9]{1,2}$/;
|
|
1673
1773
|
function readHeaders(headers) {
|
|
1674
|
-
if (!
|
|
1774
|
+
if (!record9(headers))
|
|
1675
1775
|
return null;
|
|
1676
1776
|
const folded = /* @__PURE__ */ new Map();
|
|
1677
1777
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -1718,7 +1818,7 @@ function onTokenizePath(url) {
|
|
|
1718
1818
|
}
|
|
1719
1819
|
}
|
|
1720
1820
|
function primerTokenizeRefusal(request) {
|
|
1721
|
-
if (!
|
|
1821
|
+
if (!record9(request) || request.method !== "POST" || !isPrimerTokenizeUrl(request.url))
|
|
1722
1822
|
return "unsupported";
|
|
1723
1823
|
return headersRefusal(request.headers) ?? primerBodyRefusal(request.body);
|
|
1724
1824
|
}
|
|
@@ -1733,7 +1833,7 @@ function primerCardOnFileRefusal(url, options, policy = PRIMER_SESSION_CARD_ON_F
|
|
|
1733
1833
|
return "unsupported";
|
|
1734
1834
|
if (new URL(url).hostname !== LIVE_PCI_HOST)
|
|
1735
1835
|
return null;
|
|
1736
|
-
return primerCardOnFileDisclosure(url, policy) &&
|
|
1836
|
+
return primerCardOnFileDisclosure(url, policy) && record9(options) && options.keepOnFile === true ? null : "keep_on_file";
|
|
1737
1837
|
}
|
|
1738
1838
|
function primerRefusalMessage(reason) {
|
|
1739
1839
|
if (reason === "keep_on_file")
|
|
@@ -1792,9 +1892,9 @@ function injectPrimerCard(body, card) {
|
|
|
1792
1892
|
}
|
|
1793
1893
|
return { body: JSON.stringify(judged.value), where };
|
|
1794
1894
|
}
|
|
1795
|
-
var
|
|
1895
|
+
var TOKEN3 = /^[A-Za-z0-9_-]{8,256}$/;
|
|
1796
1896
|
var ERROR_ID = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
|
|
1797
|
-
var
|
|
1897
|
+
var CODE2 = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
1798
1898
|
var NETWORK_NAME = /^[A-Za-z][A-Za-z0-9 _-]{0,31}$/;
|
|
1799
1899
|
var RESPONSE_MONTH = /^(?:0?[1-9]|1[0-2])$/;
|
|
1800
1900
|
var RESPONSE_YEAR = /^(?:[0-9]{2}|20[0-9]{2})$/;
|
|
@@ -1817,25 +1917,25 @@ function readAnswer(body) {
|
|
|
1817
1917
|
return null;
|
|
1818
1918
|
try {
|
|
1819
1919
|
const value = JSON.parse(body);
|
|
1820
|
-
return
|
|
1920
|
+
return record9(value) ? value : null;
|
|
1821
1921
|
}
|
|
1822
1922
|
catch {
|
|
1823
1923
|
return null;
|
|
1824
1924
|
}
|
|
1825
1925
|
}
|
|
1826
|
-
var tokenValue = (value) => exact(
|
|
1926
|
+
var tokenValue = (value) => exact(TOKEN3, value) && !panLike(value);
|
|
1827
1927
|
var optional = (value, key, check) => !owns5(value, key) || value[key] === null || check(value[key]);
|
|
1828
1928
|
function cardDetails(data) {
|
|
1829
|
-
if (!
|
|
1929
|
+
if (!record9(data) || !exact(/^[0-9]{4}$/, data.last4Digits))
|
|
1830
1930
|
return null;
|
|
1831
|
-
if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData",
|
|
1931
|
+
if (!optional(data, "first6Digits", (v) => exact(/^[0-9]{6}$/, v)) || !optional(data, "expirationMonth", (v) => exact(RESPONSE_MONTH, v)) || !optional(data, "expirationYear", (v) => exact(RESPONSE_YEAR, v)) || !optional(data, "cardholderName", (v) => plain(v, 256)) || !optional(data, "network", (v) => exact(NETWORK_NAME, v)) || !optional(data, "isNetworkTokenized", (v) => typeof v === "boolean") || !optional(data, "binData", record9))
|
|
1832
1932
|
return null;
|
|
1833
1933
|
const safe = { last4Digits: data.last4Digits };
|
|
1834
1934
|
for (const key of ["first6Digits", "expirationMonth", "expirationYear", "cardholderName", "network", "isNetworkTokenized"]) {
|
|
1835
1935
|
if (owns5(data, key) && data[key] !== null)
|
|
1836
1936
|
safe[key] = data[key];
|
|
1837
1937
|
}
|
|
1838
|
-
if (
|
|
1938
|
+
if (record9(data.binData)) {
|
|
1839
1939
|
const bin = {};
|
|
1840
1940
|
for (const key of BIN_KEYS) {
|
|
1841
1941
|
if (!owns5(data.binData, key) || data.binData[key] === null)
|
|
@@ -1851,7 +1951,7 @@ function cardDetails(data) {
|
|
|
1851
1951
|
function threeDsSummary(value) {
|
|
1852
1952
|
if (value === void 0 || value === null)
|
|
1853
1953
|
return null;
|
|
1854
|
-
if (!
|
|
1954
|
+
if (!record9(value) || !exact(CODE2, value.responseCode) || !optional(value, "reasonCode", (v) => exact(CODE2, v)) || !optional(value, "reasonText", (v) => plain(v, 256)) || !optional(value, "protocolVersion", (v) => v === "" || exact(PROTOCOL, v)) || !optional(value, "challengeIssued", (v) => typeof v === "boolean"))
|
|
1855
1955
|
return void 0;
|
|
1856
1956
|
const safe = { responseCode: value.responseCode };
|
|
1857
1957
|
for (const key of ["reasonCode", "reasonText", "protocolVersion", "challengeIssued"]) {
|
|
@@ -1879,7 +1979,7 @@ function rebuildToken(value) {
|
|
|
1879
1979
|
}
|
|
1880
1980
|
function primerErrorId(body) {
|
|
1881
1981
|
const value = readAnswer(body);
|
|
1882
|
-
const id = value &&
|
|
1982
|
+
const id = value && record9(value.error) ? value.error.errorId : null;
|
|
1883
1983
|
return exact(ERROR_ID, id) && !panLike(id) ? id : null;
|
|
1884
1984
|
}
|
|
1885
1985
|
function matchesCard(details, card) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 7af6b8dae5c779ea1901a4deac135effd97b00ea0925db7a269c82330e110cc9
|
|
4
4
|
// src/deep-freeze.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -1715,6 +1715,34 @@ var PROCESSOR_DEFINITIONS = deepFreeze([
|
|
|
1715
1715
|
passthroughHeaders: ["^next-action$", "^next-router-state-tree$", "^accept$"],
|
|
1716
1716
|
jsonCardFields: [[1, "data", "attributes", "details", "card_number"]],
|
|
1717
1717
|
passWithoutCard: "checkout\\.paymongo\\.com/(?:cs_)?[A-Za-z0-9]{24}"
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
psp: "gopay",
|
|
1721
|
+
// GoPay's payment gateway (Czech Republic and Slovakia), which a shop opens as a page or as an overlay frame
|
|
1722
|
+
// (embed.js). The gateway loads GoPay's card frame from https://secure.gopay.com/gp-card-comm/h/<hex
|
|
1723
|
+
// token>?lang=..&ver=..&SG=<64 hex>, and on Pay the frame posts the card as one JSON body to its own URL:
|
|
1724
|
+
// {"SG":<the URL's SG>,"cardData":{"cardNumber":<16 digits>,"cvc":<3 digits>,"expiration":<YYMM>},
|
|
1725
|
+
// "type":"PAYMENT_CARD"}, unencrypted. gopay.js holds the request rule the pause decision, the API's create
|
|
1726
|
+
// and the device all read: exactly a card frame URL, a JSON Content-Type, and the frame's body byte for byte,
|
|
1727
|
+
// with a body that names Click to Pay (the frame's "save the card" box) refused. The body names no amount
|
|
1728
|
+
// and no shop: the payment GoPay keeps on its side holds both, so a live request is taken only for a
|
|
1729
|
+
// checkout origin a reviewed pin lists. The frame's host answers no browser origin, so the card leaves the
|
|
1730
|
+
// cardholder's device only through the checkout relay. The answer tells the gateway only whether GoPay took
|
|
1731
|
+
// the card; the gateway then reads the payment's end from its own API and runs 3-D Secure by redirect.
|
|
1732
|
+
// secure.sandbox.gopay.com is GoPay's sandbox: pinned (S7) to test rows, which send only test cards.
|
|
1733
|
+
// Ships dark: served only to a client that declares `gopay_card_form`, gated off for every org until an
|
|
1734
|
+
// operator lists one (`gopay:<org>`), and autopilot never sends this request (autopilot.js
|
|
1735
|
+
// DEVICE_REPLAY_REFUSED_OPERATIONS). `(?![\w/.%-])` ends the path for a consumer that tests `match`
|
|
1736
|
+
// unanchored.
|
|
1737
|
+
capability: "gopay_card_form",
|
|
1738
|
+
match: "secure(?:\\.sandbox)?\\.gopay\\.com/gp-card-comm/h/[0-9A-Fa-f]{16,1024}(?![\\w/.%-])",
|
|
1739
|
+
hosts: ["^secure\\.gopay\\.com$", "^secure\\.sandbox\\.gopay\\.com$"],
|
|
1740
|
+
sandboxHosts: ["^secure\\.sandbox\\.gopay\\.com$"],
|
|
1741
|
+
hostEnvironmentPinned: true,
|
|
1742
|
+
encoding: "json",
|
|
1743
|
+
// The frame's own Accept rides as it sent it; origin, referer and cookies never do.
|
|
1744
|
+
passthroughHeaders: ["^accept$"],
|
|
1745
|
+
jsonCardFields: [["cardData", "cardNumber"]]
|
|
1718
1746
|
}
|
|
1719
1747
|
]);
|
|
1720
1748
|
var PSP_RECOGNIZERS = deepFreeze(PROCESSOR_DEFINITIONS.map(({ compose, encryptedCardFields, deviceSteps, deviceEncrypt, ...definition }) => definition));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-cards/sdk",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.17",
|
|
4
4
|
"description": "Agentcard's SDK: one client, Agentcard, with two modules. checkout pauses a browser purchase and pays it with the person's own card; voice connects a person to your product once and approves a purchase by voice inside it. Card data never enters your process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|