@agent-cards/sdk 0.23.23 → 0.23.25
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 +18 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +13 -1
- package/dist/agent-amount.generated.js +1 -1
- package/dist/amadeus.d.ts +5 -2
- package/dist/amadeus.js +18 -3
- package/dist/amazon-payment-services.d.ts +25 -0
- package/dist/amazon-payment-services.generated.d.ts +76 -0
- package/dist/amazon-payment-services.generated.js +883 -0
- package/dist/amazon-payment-services.js +53 -0
- package/dist/barion.d.ts +44 -0
- package/dist/barion.js +84 -0
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +5 -4
- package/dist/cdp.js +70 -4
- package/dist/client.d.ts +55 -3
- package/dist/client.js +119 -12
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/openpay.generated.d.ts +67 -0
- package/dist/openpay.generated.js +446 -0
- package/dist/paddle-checkout-watch.d.ts +70 -0
- package/dist/paddle-checkout-watch.js +239 -0
- package/dist/paddle-tokenex.generated.d.ts +29 -1
- package/dist/paddle-tokenex.generated.js +175 -26
- package/dist/pre-claim.d.ts +1 -0
- package/dist/pre-claim.js +18 -1
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +31 -5
- package/dist/primer.generated.js +5 -4
- package/dist/stripe-checkout.generated.js +5 -4
- package/dist/tap-payments.d.ts +43 -0
- package/dist/tap-payments.generated.d.ts +59 -0
- package/dist/tap-payments.generated.js +990 -0
- package/dist/tap-payments.js +72 -0
- package/dist/tokenex-iframe.generated.d.ts +18 -6
- package/dist/tokenex-iframe.generated.js +3 -3
- package/package.json +2 -2
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: 9664676e236436f6a52fd0f968ffa6e403c52ee156408ab9a428cc5fd749175b
|
|
4
|
+
// src/openpay.js
|
|
5
|
+
var OPENPAY_PSP = "openpay_bbva";
|
|
6
|
+
var OPENPAY_CAPABILITY = "openpay_tokenizer";
|
|
7
|
+
var OPENPAY_OPERATION = "openpay_bbva.card_request";
|
|
8
|
+
var OPENPAY_API_HOSTS = /* @__PURE__ */ Object.freeze({
|
|
9
|
+
"api.openpay.mx": "production",
|
|
10
|
+
"api.openpay.co": "production",
|
|
11
|
+
"api.openpay.pe": "production",
|
|
12
|
+
"sandbox-api.openpay.mx": "sandbox",
|
|
13
|
+
"sandbox-api.openpay.co": "sandbox",
|
|
14
|
+
"sandbox-api.openpay.pe": "sandbox"
|
|
15
|
+
});
|
|
16
|
+
var OPENPAY_CARD_KEYS = /* @__PURE__ */ Object.freeze(["card_number", "holder_name", "cvv2", "expiration_month", "expiration_year", "address"]);
|
|
17
|
+
var OPENPAY_ADDRESS_KEYS = /* @__PURE__ */ Object.freeze(["line1", "line2", "line3", "state", "city", "postal_code", "country_code"]);
|
|
18
|
+
var OPENPAY_PREPARATION_URLS = /* @__PURE__ */ Object.freeze({
|
|
19
|
+
production: "https://api.openpay.mx/v1/prepared/tokens",
|
|
20
|
+
sandbox: "https://sandbox-api.openpay.mx/v1/prepared/tokens"
|
|
21
|
+
});
|
|
22
|
+
var OPENPAY_REFUSAL_CODES = /* @__PURE__ */ Object.freeze([
|
|
23
|
+
"openpay_url_refused",
|
|
24
|
+
"openpay_headers_refused",
|
|
25
|
+
"openpay_body_unsupported",
|
|
26
|
+
"openpay_merchant_unreviewed",
|
|
27
|
+
"openpay_account_unreviewed",
|
|
28
|
+
"openpay_live_row_required"
|
|
29
|
+
]);
|
|
30
|
+
var BODY_LIMIT = 4096;
|
|
31
|
+
var URL_LIMIT = 96;
|
|
32
|
+
var TOKEN_URL = /^https:\/\/((?:sandbox-)?api\.openpay\.(?:mx|co|pe))\/v1\/([a-z0-9]{10,40})\/tokens$/;
|
|
33
|
+
var PUBLIC_KEY = /^pk_[a-z0-9]{16,64}$/;
|
|
34
|
+
var BASIC = /^Basic ([A-Za-z0-9+/]{4,128}={0,2})$/;
|
|
35
|
+
var JSON_TYPE = /^application\/json(?:; ?charset=utf-8)?$/i;
|
|
36
|
+
var ACCEPT = "application/json";
|
|
37
|
+
var NUMBER = /^[0-9]{12,19}$/;
|
|
38
|
+
var SECURITY_CODE = /^[0-9]{3,4}$/;
|
|
39
|
+
var MONTH_TEXT = /^(?:0?[1-9]|1[0-2])$/;
|
|
40
|
+
var YEAR = /^[0-9]{2}$/;
|
|
41
|
+
var COUNTRY = /^[A-Z]{2}$/;
|
|
42
|
+
var LETTERS = "A-Za-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u017F";
|
|
43
|
+
var PAGE_NAME = new RegExp(`^[${LETTERS} .'-]{1,64}$`);
|
|
44
|
+
var HAS_LETTER = new RegExp(`[${LETTERS}]`);
|
|
45
|
+
var ADDRESS_VALUE = new RegExp(`^[\\x20-\\x7e\\u00a0-\\u017f]{0,128}$`);
|
|
46
|
+
var PAN_LIKE = new RegExp(`(?:[0-9][^0-9${LETTERS}]*){12,}`);
|
|
47
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
48
|
+
var hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
|
49
|
+
var refuse = (code) => Object.freeze({ verdict: "refuse", code });
|
|
50
|
+
var CARD = /* @__PURE__ */ Object.freeze({ verdict: "card" });
|
|
51
|
+
function openpayTokenTarget(url) {
|
|
52
|
+
if (typeof url !== "string" || url.length > URL_LIMIT)
|
|
53
|
+
return null;
|
|
54
|
+
const match = TOKEN_URL.exec(url);
|
|
55
|
+
if (!match)
|
|
56
|
+
return null;
|
|
57
|
+
return Object.freeze({ environment: OPENPAY_API_HOSTS[match[1]], host: match[1], merchantId: match[2] });
|
|
58
|
+
}
|
|
59
|
+
function isOpenpayTokenUrl(url) {
|
|
60
|
+
return openpayTokenTarget(url) !== null;
|
|
61
|
+
}
|
|
62
|
+
function openpayTokenEnvironment(url) {
|
|
63
|
+
const target = openpayTokenTarget(url);
|
|
64
|
+
return target === null ? null : target.environment;
|
|
65
|
+
}
|
|
66
|
+
function readJson(body) {
|
|
67
|
+
if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT)
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
const value = JSON.parse(body);
|
|
71
|
+
return record(value) && JSON.stringify(value) === body ? value : null;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
var soundMonth = (value) => Number.isInteger(value) && value >= 1 && value <= 12 || typeof value === "string" && MONTH_TEXT.test(value);
|
|
78
|
+
function soundAddress(address) {
|
|
79
|
+
if (!record(address))
|
|
80
|
+
return false;
|
|
81
|
+
const keys = Object.keys(address);
|
|
82
|
+
return keys.length > 0 && keys.every((key) => OPENPAY_ADDRESS_KEYS.includes(key) && typeof address[key] === "string" && ADDRESS_VALUE.test(address[key]) && !PAN_LIKE.test(address[key])) && keys.some((key) => address[key] !== "") && (!hasOwn(address, "country_code") || COUNTRY.test(address.country_code));
|
|
83
|
+
}
|
|
84
|
+
function openpayBodyVerdict(body, url) {
|
|
85
|
+
if (url !== void 0 && openpayTokenTarget(url) === null)
|
|
86
|
+
return refuse("openpay_url_refused");
|
|
87
|
+
const value = readJson(body);
|
|
88
|
+
if (value === null)
|
|
89
|
+
return refuse("openpay_body_unsupported");
|
|
90
|
+
const keys = Object.keys(value);
|
|
91
|
+
if (!keys.every((key) => OPENPAY_CARD_KEYS.includes(key)) || !["card_number", "holder_name", "cvv2", "expiration_month", "expiration_year"].every((key) => keys.includes(key))) {
|
|
92
|
+
return refuse("openpay_body_unsupported");
|
|
93
|
+
}
|
|
94
|
+
if (typeof value.card_number !== "string" || !NUMBER.test(value.card_number) || typeof value.cvv2 !== "string" || !SECURITY_CODE.test(value.cvv2) || !soundMonth(value.expiration_month) || typeof value.expiration_year !== "string" || !YEAR.test(value.expiration_year) || typeof value.holder_name !== "string" || !PAGE_NAME.test(value.holder_name) || !HAS_LETTER.test(value.holder_name) || keys.includes("address") && !soundAddress(value.address)) {
|
|
95
|
+
return refuse("openpay_body_unsupported");
|
|
96
|
+
}
|
|
97
|
+
return CARD;
|
|
98
|
+
}
|
|
99
|
+
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
100
|
+
function base64Text(input) {
|
|
101
|
+
if (input.length % 4 !== 0)
|
|
102
|
+
return null;
|
|
103
|
+
const stripped = input.replace(/={1,2}$/, "");
|
|
104
|
+
if (stripped.includes("="))
|
|
105
|
+
return null;
|
|
106
|
+
let out = "";
|
|
107
|
+
let bits = 0;
|
|
108
|
+
let value = 0;
|
|
109
|
+
for (const character of stripped) {
|
|
110
|
+
const sextet = BASE64.indexOf(character);
|
|
111
|
+
if (sextet < 0)
|
|
112
|
+
return null;
|
|
113
|
+
value = value << 6 | sextet;
|
|
114
|
+
bits += 6;
|
|
115
|
+
if (bits >= 8) {
|
|
116
|
+
bits -= 8;
|
|
117
|
+
out += String.fromCharCode(value >> bits & 255);
|
|
118
|
+
value &= (1 << bits) - 1;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
function base64Of(text) {
|
|
124
|
+
let out = "";
|
|
125
|
+
for (let at = 0; at < text.length; at += 3) {
|
|
126
|
+
const a = text.charCodeAt(at);
|
|
127
|
+
const b = at + 1 < text.length ? text.charCodeAt(at + 1) : NaN;
|
|
128
|
+
const c = at + 2 < text.length ? text.charCodeAt(at + 2) : NaN;
|
|
129
|
+
out += BASE64[a >> 2] + BASE64[(a & 3) << 4 | (Number.isNaN(b) ? 0 : b >> 4)] + (Number.isNaN(b) ? "=" : BASE64[(b & 15) << 2 | (Number.isNaN(c) ? 0 : c >> 6)]) + (Number.isNaN(c) ? "=" : BASE64[c & 63]);
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function headerMap(headers) {
|
|
134
|
+
const out = /* @__PURE__ */ new Map();
|
|
135
|
+
for (const [name, value] of Object.entries(record(headers) ? headers : {})) {
|
|
136
|
+
const lower = name.toLowerCase();
|
|
137
|
+
if (out.has(lower) || typeof value !== "string")
|
|
138
|
+
return null;
|
|
139
|
+
out.set(lower, value);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
function openpayPublicKey(headers) {
|
|
144
|
+
const map = headerMap(headers);
|
|
145
|
+
const auth = map === null ? null : BASIC.exec(map.get("authorization") ?? "");
|
|
146
|
+
const text = auth ? base64Text(auth[1]) : null;
|
|
147
|
+
if (text === null || !text.endsWith(":"))
|
|
148
|
+
return null;
|
|
149
|
+
const key = text.slice(0, -1);
|
|
150
|
+
return PUBLIC_KEY.test(key) && base64Of(text) === auth[1] ? key : null;
|
|
151
|
+
}
|
|
152
|
+
function openpayRequestRefusal(request) {
|
|
153
|
+
if (!record(request) || request.method !== "POST" || openpayTokenTarget(request.url) === null)
|
|
154
|
+
return "openpay_url_refused";
|
|
155
|
+
const map = headerMap(request.headers);
|
|
156
|
+
if (map === null || !JSON_TYPE.test(map.get("content-type") ?? "") || map.get("accept") !== ACCEPT || openpayPublicKey(request.headers) === null)
|
|
157
|
+
return "openpay_headers_refused";
|
|
158
|
+
const verdict = openpayBodyVerdict(request.body, request.url);
|
|
159
|
+
return verdict.verdict === "card" ? null : verdict.code;
|
|
160
|
+
}
|
|
161
|
+
function isPreparedOpenpayRequest(url, method, body, environment, headers) {
|
|
162
|
+
return (environment === "production" || environment === "sandbox") && openpayTokenEnvironment(url) === environment && openpayRequestRefusal({ url, method, headers, body }) === null;
|
|
163
|
+
}
|
|
164
|
+
var ACCOUNT_UNREVIEWED = "Refusing to pay: this Openpay payment goes to a merchant account Agentcard has not reviewed. Nothing was sent.";
|
|
165
|
+
var REFUSAL_MESSAGES = /* @__PURE__ */ Object.freeze({
|
|
166
|
+
openpay_url_refused: "Refusing to pay: this Openpay card request does not go to Openpay's token address exactly as openpay.js sends it. Nothing was sent.",
|
|
167
|
+
openpay_headers_refused: "Refusing to pay: this Openpay card request does not carry the headers openpay.js sends. Nothing was sent.",
|
|
168
|
+
openpay_body_unsupported: "Refusing to pay: this is not the card request of openpay.js. Nothing was sent.",
|
|
169
|
+
openpay_merchant_unreviewed: ACCOUNT_UNREVIEWED,
|
|
170
|
+
openpay_account_unreviewed: ACCOUNT_UNREVIEWED,
|
|
171
|
+
openpay_live_row_required: "Refusing to pay: Agentcard sends a card to a live Openpay checkout only for a live purchase, and this one is not live. Nothing was sent."
|
|
172
|
+
});
|
|
173
|
+
function openpayRefusalMessage(code) {
|
|
174
|
+
return hasOwn(REFUSAL_MESSAGES, code) ? REFUSAL_MESSAGES[code] : REFUSAL_MESSAGES.openpay_body_unsupported;
|
|
175
|
+
}
|
|
176
|
+
var PIN_ORIGIN = /^https:\/\/([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)$/;
|
|
177
|
+
var OPENPAY_DOMAIN = /(?:^|\.)openpay\.(?:mx|co|pe)$/;
|
|
178
|
+
var MERCHANT_ID = /^[a-z0-9]{10,40}$/;
|
|
179
|
+
function openpayPinOriginUsable(origin) {
|
|
180
|
+
const host = typeof origin === "string" ? PIN_ORIGIN.exec(origin) : null;
|
|
181
|
+
return !!host && !OPENPAY_DOMAIN.test(host[1]);
|
|
182
|
+
}
|
|
183
|
+
var pin = (fields) => Object.freeze({ ...fields, origins: Object.freeze([...fields.origins]), publicKeys: Object.freeze([...fields.publicKeys]) });
|
|
184
|
+
var OPENPAY_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
|
|
185
|
+
pin({
|
|
186
|
+
id: "diario_de_yucatan",
|
|
187
|
+
merchant: "Diario de Yucatán",
|
|
188
|
+
origins: ["https://www.yucatan.com.mx"],
|
|
189
|
+
host: "api.openpay.mx",
|
|
190
|
+
merchantId: "mqyloyrm8tsvtsgcrj0d",
|
|
191
|
+
publicKeys: ["pk_260aa43616a445bb98472e6ca51da968"]
|
|
192
|
+
})
|
|
193
|
+
]);
|
|
194
|
+
var usablePin = (entry) => record(entry) && Array.isArray(entry.origins) && entry.origins.length > 0 && entry.origins.every(openpayPinOriginUsable) && OPENPAY_API_HOSTS[entry.host] === "production" && typeof entry.merchantId === "string" && MERCHANT_ID.test(entry.merchantId) && Array.isArray(entry.publicKeys) && entry.publicKeys.length > 0 && entry.publicKeys.every((key) => typeof key === "string" && PUBLIC_KEY.test(key));
|
|
195
|
+
function openpayOriginPin(merchantOrigin, pins = OPENPAY_MERCHANT_PINS) {
|
|
196
|
+
if (typeof merchantOrigin !== "string" || !Array.isArray(pins) || !openpayPinOriginUsable(merchantOrigin))
|
|
197
|
+
return null;
|
|
198
|
+
const found = pins.filter((entry) => usablePin(entry) && entry.origins.includes(merchantOrigin));
|
|
199
|
+
return found.length === 1 ? found[0] : null;
|
|
200
|
+
}
|
|
201
|
+
function openpayPreparationRefusal(environment, merchantOrigin, pins = OPENPAY_MERCHANT_PINS) {
|
|
202
|
+
if (environment === "sandbox")
|
|
203
|
+
return null;
|
|
204
|
+
if (environment !== "production")
|
|
205
|
+
return "unsupported_environment";
|
|
206
|
+
return openpayOriginPin(merchantOrigin, pins) ? null : "openpay_merchant_unreviewed";
|
|
207
|
+
}
|
|
208
|
+
function openpayMerchantPin(merchantOrigin, request, pins = OPENPAY_MERCHANT_PINS) {
|
|
209
|
+
const target = record(request) ? openpayTokenTarget(request.url) : null;
|
|
210
|
+
if (target === null || target.environment !== "production")
|
|
211
|
+
return null;
|
|
212
|
+
const found = openpayOriginPin(merchantOrigin, pins);
|
|
213
|
+
return found && found.host === target.host && found.merchantId === target.merchantId && found.publicKeys.includes(openpayPublicKey(request.headers)) ? found : null;
|
|
214
|
+
}
|
|
215
|
+
function openpayMerchantRefusal(merchantOrigin, request, pins = OPENPAY_MERCHANT_PINS) {
|
|
216
|
+
const target = record(request) ? openpayTokenTarget(request.url) : null;
|
|
217
|
+
if (target === null)
|
|
218
|
+
return "openpay_url_refused";
|
|
219
|
+
if (target.environment === "sandbox")
|
|
220
|
+
return null;
|
|
221
|
+
if (openpayOriginPin(merchantOrigin, pins) === null)
|
|
222
|
+
return "openpay_merchant_unreviewed";
|
|
223
|
+
return openpayMerchantPin(merchantOrigin, request, pins) ? null : "openpay_account_unreviewed";
|
|
224
|
+
}
|
|
225
|
+
function openpayDeviceRefusal(request, options) {
|
|
226
|
+
const refusal = openpayRequestRefusal(request);
|
|
227
|
+
if (refusal)
|
|
228
|
+
return refusal;
|
|
229
|
+
if (openpayTokenEnvironment(request.url) === "sandbox")
|
|
230
|
+
return null;
|
|
231
|
+
const settings = record(options) ? options : {};
|
|
232
|
+
if (typeof settings.merchantOrigin !== "string")
|
|
233
|
+
return "openpay_merchant_unreviewed";
|
|
234
|
+
if (settings.sandbox !== false)
|
|
235
|
+
return "openpay_live_row_required";
|
|
236
|
+
return openpayMerchantRefusal(settings.merchantOrigin, request);
|
|
237
|
+
}
|
|
238
|
+
var OPENPAY_TEST_PANS = /* @__PURE__ */ Object.freeze([
|
|
239
|
+
"4111111111111111",
|
|
240
|
+
"5555555555554444",
|
|
241
|
+
"5105105105105100",
|
|
242
|
+
"345678000000007",
|
|
243
|
+
"341111111111111",
|
|
244
|
+
"343434343434343",
|
|
245
|
+
"5062541600005232",
|
|
246
|
+
"5064050100000063",
|
|
247
|
+
"5064510000300020",
|
|
248
|
+
"4222222222222220",
|
|
249
|
+
"4000000000000069",
|
|
250
|
+
"4444444444444448",
|
|
251
|
+
"4000000000000119",
|
|
252
|
+
"4000000000000044",
|
|
253
|
+
"5454545454545454",
|
|
254
|
+
"340000000000009",
|
|
255
|
+
"373737373737374",
|
|
256
|
+
"370000000000002"
|
|
257
|
+
]);
|
|
258
|
+
var digits = (value, min, max) => typeof value === "string" && value.length >= min && value.length <= max && !/[^0-9]/.test(value);
|
|
259
|
+
var whole = (value, min, max) => {
|
|
260
|
+
if (Number.isSafeInteger(value) && value >= 0)
|
|
261
|
+
return value;
|
|
262
|
+
return digits(value, min, max) ? Number(value) : NaN;
|
|
263
|
+
};
|
|
264
|
+
var cardYear = (year) => year >= 0 && year <= 99 || year >= 2e3 && year <= 2099;
|
|
265
|
+
var AMEX = /^3[47]/;
|
|
266
|
+
var FILL_REFUSAL = "Could not fill the card fields for Openpay. Nothing was sent.";
|
|
267
|
+
var WHERE = /* @__PURE__ */ Object.freeze(["card_number", "cvv2", "expiration_month", "expiration_year"]);
|
|
268
|
+
function written(page, card) {
|
|
269
|
+
const value = JSON.parse(page);
|
|
270
|
+
value.card_number = card.number;
|
|
271
|
+
value.cvv2 = card.cvc;
|
|
272
|
+
value.expiration_month = typeof value.expiration_month === "number" ? card.month : value.expiration_month.length === 2 ? String(card.month).padStart(2, "0") : String(card.month);
|
|
273
|
+
value.expiration_year = String(card.year % 100).padStart(2, "0");
|
|
274
|
+
return JSON.stringify(value);
|
|
275
|
+
}
|
|
276
|
+
function fillCard(card) {
|
|
277
|
+
if (!card || typeof card !== "object")
|
|
278
|
+
return null;
|
|
279
|
+
const month = whole(card.month, 1, 2);
|
|
280
|
+
const year = whole(card.year, 2, 4);
|
|
281
|
+
const amex = typeof card.number === "string" && AMEX.test(card.number);
|
|
282
|
+
if (!digits(card.number, 12, 19) || !(month >= 1 && month <= 12) || !cardYear(year) || !digits(card.cvc, amex ? 4 : 3, amex ? 4 : 3))
|
|
283
|
+
return null;
|
|
284
|
+
return { number: card.number, cvc: card.cvc, month, year };
|
|
285
|
+
}
|
|
286
|
+
function injectOpenpayCard(body, card) {
|
|
287
|
+
const verdict = openpayBodyVerdict(body);
|
|
288
|
+
if (verdict.verdict !== "card")
|
|
289
|
+
throw new Error(openpayRefusalMessage(verdict.code));
|
|
290
|
+
const values = fillCard(card);
|
|
291
|
+
if (!values)
|
|
292
|
+
throw new Error(FILL_REFUSAL);
|
|
293
|
+
return { body: written(body, values), where: [...WHERE] };
|
|
294
|
+
}
|
|
295
|
+
function isOpenpayDeviceRequest(templateBody, deviceBody) {
|
|
296
|
+
if (openpayBodyVerdict(templateBody).verdict !== "card" || openpayBodyVerdict(deviceBody).verdict !== "card")
|
|
297
|
+
return false;
|
|
298
|
+
const template = JSON.parse(templateBody);
|
|
299
|
+
const device = JSON.parse(deviceBody);
|
|
300
|
+
if (device.card_number === template.card_number || typeof device.expiration_month !== typeof template.expiration_month)
|
|
301
|
+
return false;
|
|
302
|
+
const values = fillCard({ number: device.card_number, cvc: device.cvv2, month: device.expiration_month, year: device.expiration_year });
|
|
303
|
+
return values !== null && written(templateBody, values) === deviceBody;
|
|
304
|
+
}
|
|
305
|
+
var ANSWER_LIMIT = 16384;
|
|
306
|
+
var TOKEN_ID = /^[a-z0-9]{16,32}$/;
|
|
307
|
+
var MASKED = /^([0-9]{6})X{2,9}([0-9]{4})$/;
|
|
308
|
+
var BRAND = /^[a-z][a-z_ ]{1,31}$/;
|
|
309
|
+
var ERROR_CODE = /^[1-9][0-9]{3}$/;
|
|
310
|
+
var SERVER_FAILURES = /* @__PURE__ */ Object.freeze(["1000", "1004", "1015", "1017"]);
|
|
311
|
+
function parseAnswer(text) {
|
|
312
|
+
if (typeof text !== "string" || text === "" || text.length > ANSWER_LIMIT)
|
|
313
|
+
return null;
|
|
314
|
+
try {
|
|
315
|
+
const value = JSON.parse(text);
|
|
316
|
+
return record(value) ? value : null;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function errorCodeOf(value) {
|
|
323
|
+
const code = value.error_code;
|
|
324
|
+
const text = Number.isInteger(code) ? String(code) : typeof code === "string" ? code : null;
|
|
325
|
+
return text !== null && ERROR_CODE.test(text) ? text : null;
|
|
326
|
+
}
|
|
327
|
+
function isOpenpayRefusalCode(code) {
|
|
328
|
+
if (typeof code !== "string" || !code.startsWith("openpay_"))
|
|
329
|
+
return false;
|
|
330
|
+
const digits2 = code.slice("openpay_".length);
|
|
331
|
+
return ERROR_CODE.test(digits2) && !SERVER_FAILURES.includes(digits2);
|
|
332
|
+
}
|
|
333
|
+
function readAnswer(status, text) {
|
|
334
|
+
if (!Number.isInteger(status))
|
|
335
|
+
return { kind: "unknown", reason: "openpay_unconfirmed" };
|
|
336
|
+
if (status >= 500)
|
|
337
|
+
return { kind: "unknown", reason: "server_error" };
|
|
338
|
+
const value = parseAnswer(text);
|
|
339
|
+
if (value === null)
|
|
340
|
+
return { kind: "unknown", reason: "openpay_answer_unreadable" };
|
|
341
|
+
const code = errorCodeOf(value);
|
|
342
|
+
const ok = status === 200 || status === 201;
|
|
343
|
+
const noToken = typeof value.id !== "string" || !TOKEN_ID.test(value.id);
|
|
344
|
+
if (code !== null && (status >= 400 && status < 500 || ok && noToken)) {
|
|
345
|
+
return SERVER_FAILURES.includes(code) ? { kind: "unknown", reason: "server_error" } : { kind: "refused", code: `openpay_${code}`, value };
|
|
346
|
+
}
|
|
347
|
+
if (!ok)
|
|
348
|
+
return { kind: "unknown", reason: "openpay_answer_status" };
|
|
349
|
+
if (code !== null || typeof value.id !== "string" || !TOKEN_ID.test(value.id) || !record(value.card) || typeof value.card.card_number !== "string" || !MASKED.test(value.card.card_number) || value.card.brand !== void 0 && value.card.brand !== null && (typeof value.card.brand !== "string" || !BRAND.test(value.card.brand))) {
|
|
350
|
+
return { kind: "unknown", reason: "openpay_token_unconfirmed" };
|
|
351
|
+
}
|
|
352
|
+
return { kind: "token", token: value, value };
|
|
353
|
+
}
|
|
354
|
+
function cardDigits(card) {
|
|
355
|
+
const number = card && typeof card.number === "string" ? card.number.replace(/[^0-9]/g, "") : "";
|
|
356
|
+
return /^[0-9]{12,19}$/.test(number) ? number : "";
|
|
357
|
+
}
|
|
358
|
+
function unescapePercent(value) {
|
|
359
|
+
let out = value;
|
|
360
|
+
for (let round = 0; round < 3 && /%[0-9A-Fa-f]{2}/.test(out); round += 1) {
|
|
361
|
+
out = out.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
function decodedTexts(value) {
|
|
366
|
+
const texts = [];
|
|
367
|
+
const walk = (node, depth) => {
|
|
368
|
+
if (depth > 16 || texts.length > 4096)
|
|
369
|
+
return;
|
|
370
|
+
if (typeof node === "string")
|
|
371
|
+
texts.push(node);
|
|
372
|
+
else if (typeof node === "number")
|
|
373
|
+
texts.push(String(node));
|
|
374
|
+
else if (Array.isArray(node))
|
|
375
|
+
for (const item of node)
|
|
376
|
+
walk(item, depth + 1);
|
|
377
|
+
else if (record(node)) {
|
|
378
|
+
for (const [key, item] of Object.entries(node)) {
|
|
379
|
+
texts.push(key);
|
|
380
|
+
walk(item, depth + 1);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
walk(value, 0);
|
|
385
|
+
return texts;
|
|
386
|
+
}
|
|
387
|
+
function holdsCardNumber(value, number) {
|
|
388
|
+
if (number === "")
|
|
389
|
+
return false;
|
|
390
|
+
const spaced = new RegExp(number.split("").join("[^0-9]{0,3}"));
|
|
391
|
+
const json = JSON.stringify(value);
|
|
392
|
+
if (spaced.test(json) || spaced.test(unescapePercent(json)))
|
|
393
|
+
return true;
|
|
394
|
+
return decodedTexts(value).some((text) => [text, unescapePercent(text)].some((form) => form.replace(/[^0-9]/g, "").includes(number)));
|
|
395
|
+
}
|
|
396
|
+
function numberNetwork(number) {
|
|
397
|
+
const two = Number(number.slice(0, 2));
|
|
398
|
+
const four = Number(number.slice(0, 4));
|
|
399
|
+
if (number.startsWith("4"))
|
|
400
|
+
return "visa";
|
|
401
|
+
if (two >= 51 && two <= 55 || four >= 2221 && four <= 2720)
|
|
402
|
+
return "mastercard";
|
|
403
|
+
return two === 34 || two === 37 ? "american_express" : null;
|
|
404
|
+
}
|
|
405
|
+
var brandNetwork = (brand) => {
|
|
406
|
+
const name = typeof brand === "string" ? brand.toLowerCase().replace(/[^a-z]/g, "") : "";
|
|
407
|
+
if (name === "visa")
|
|
408
|
+
return "visa";
|
|
409
|
+
if (name === "mastercard")
|
|
410
|
+
return "mastercard";
|
|
411
|
+
return name === "americanexpress" || name === "amex" ? "american_express" : null;
|
|
412
|
+
};
|
|
413
|
+
var rebuild = (token) => JSON.stringify({ id: token.id, card: {
|
|
414
|
+
card_number: token.card.card_number,
|
|
415
|
+
...typeof token.card.brand === "string" ? { brand: token.card.brand } : {},
|
|
416
|
+
...typeof token.card.points_card === "boolean" ? { points_card: token.card.points_card } : {}
|
|
417
|
+
} });
|
|
418
|
+
function openpayAnswerOutcome(status, text, card) {
|
|
419
|
+
const read = readAnswer(status, text);
|
|
420
|
+
if (read.kind === "unknown")
|
|
421
|
+
return { outcome: "unknown", reason: read.reason };
|
|
422
|
+
const number = cardDigits(card);
|
|
423
|
+
if (holdsCardNumber(read.value, number))
|
|
424
|
+
return { outcome: "unknown", reason: "openpay_unsafe_response" };
|
|
425
|
+
if (read.kind === "refused")
|
|
426
|
+
return { outcome: "refused", code: read.code };
|
|
427
|
+
if (number !== "") {
|
|
428
|
+
const masked = MASKED.exec(read.token.card.card_number);
|
|
429
|
+
const brand = brandNetwork(read.token.card.brand);
|
|
430
|
+
const network = numberNetwork(number);
|
|
431
|
+
if (masked[1] !== number.slice(0, 6) || masked[2] !== number.slice(-4) || masked[0].length !== number.length || brand !== null && network !== null && brand !== network) {
|
|
432
|
+
return { outcome: "unknown", reason: "openpay_token_mismatch" };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return { outcome: "approved", body: rebuild(read.token) };
|
|
436
|
+
}
|
|
437
|
+
function openpayRefusalCode(text, status) {
|
|
438
|
+
if (status !== void 0 && status !== null) {
|
|
439
|
+
const read = readAnswer(status, text);
|
|
440
|
+
return read.kind === "refused" ? read.code : null;
|
|
441
|
+
}
|
|
442
|
+
const value = parseAnswer(text);
|
|
443
|
+
const code = value === null ? null : errorCodeOf(value);
|
|
444
|
+
return code === null || SERVER_FAILURES.includes(code) ? null : `openpay_${code}`;
|
|
445
|
+
}
|
|
446
|
+
export { OPENPAY_ADDRESS_KEYS, OPENPAY_API_HOSTS, OPENPAY_CAPABILITY, OPENPAY_CARD_KEYS, OPENPAY_MERCHANT_PINS, OPENPAY_OPERATION, OPENPAY_PREPARATION_URLS, OPENPAY_PSP, OPENPAY_REFUSAL_CODES, OPENPAY_TEST_PANS, injectOpenpayCard, isOpenpayDeviceRequest, isOpenpayRefusalCode, isOpenpayTokenUrl, isPreparedOpenpayRequest, openpayAnswerOutcome, openpayBodyVerdict, openpayDeviceRefusal, openpayMerchantPin, openpayMerchantRefusal, openpayOriginPin, openpayPinOriginUsable, openpayPreparationRefusal, openpayPublicKey, openpayRefusalCode, openpayRefusalMessage, openpayRequestRefusal, openpayTokenEnvironment, openpayTokenTarget };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** One recorded answer, as the create sends it: the request's method and URL, the status, when it finished, and its projection. */
|
|
2
|
+
export interface PaddleCheckoutAnswer {
|
|
3
|
+
method: string;
|
|
4
|
+
url: string;
|
|
5
|
+
status: number;
|
|
6
|
+
completedAt: number;
|
|
7
|
+
body: string;
|
|
8
|
+
}
|
|
9
|
+
/** What a TokenEx pause hands the client: when the /pay paused, and the answers recorded before it, newest last. */
|
|
10
|
+
export interface PaddleCheckoutCapture {
|
|
11
|
+
pausedAt: number;
|
|
12
|
+
answers: PaddleCheckoutAnswer[];
|
|
13
|
+
}
|
|
14
|
+
/** How long a /pay's pause waits for the texts of Paddle's answers that finished before it. */
|
|
15
|
+
export declare const PADDLE_CHECKOUT_READ_WAIT_MS = 1000;
|
|
16
|
+
export declare class PaddleCheckoutWatch {
|
|
17
|
+
private readonly exchanges;
|
|
18
|
+
/** Each checkout's request keys, oldest sent first; the checkout asked about least recently first. */
|
|
19
|
+
private readonly checkouts;
|
|
20
|
+
/** Each checkout's dropped answers, by `${environment}:${checkoutId}`. */
|
|
21
|
+
private readonly lost;
|
|
22
|
+
/**
|
|
23
|
+
* By environment, the latest finish of a create dropped after it finished and before its text was read: it may have
|
|
24
|
+
* been any checkout's on that host, and a create is its checkout's oldest answer.
|
|
25
|
+
*/
|
|
26
|
+
private readonly lostCreateFinishedAt;
|
|
27
|
+
private readonly waiters;
|
|
28
|
+
private readonly now;
|
|
29
|
+
private readonly readWaitMs;
|
|
30
|
+
constructor(options?: {
|
|
31
|
+
now?: () => number;
|
|
32
|
+
readWaitMs?: number;
|
|
33
|
+
});
|
|
34
|
+
/** Whether the request under `key` is one of Paddle's checkout answers whose text is still to be read. */
|
|
35
|
+
tracks(key: string): boolean;
|
|
36
|
+
/** A request left the page: kept when it asks Paddle's checkout service about the checkout. */
|
|
37
|
+
sent(key: string, request: {
|
|
38
|
+
method: string;
|
|
39
|
+
url: string;
|
|
40
|
+
}): void;
|
|
41
|
+
/** Drops a kept request, noting what it was about (forget). */
|
|
42
|
+
private drop;
|
|
43
|
+
/** Takes a kept request out without a trace, and hands it back. */
|
|
44
|
+
private remove;
|
|
45
|
+
/** Notes what a dropped answer was about, so a pause on its checkout knows what it can no longer see. */
|
|
46
|
+
private forget;
|
|
47
|
+
/** Its answer's status arrived. */
|
|
48
|
+
answered(key: string, status: unknown): void;
|
|
49
|
+
/** It finished loading: stamped the moment the browser says so, before its text is read, so a pause waits for that read. */
|
|
50
|
+
finished(key: string, completedAt: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Its text was read (`text`, or null when it could not be). Kept cut to the terms; a body that is not a checkout
|
|
53
|
+
* answer, or whose projection is longer than the API takes, is kept as an empty one, so the reader knows a newer
|
|
54
|
+
* answer it cannot read came in.
|
|
55
|
+
*/
|
|
56
|
+
read(key: string, text: string | null, completedAt: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* The request failed or was given up. One that never finished gave the page no answer and is forgotten; one that
|
|
59
|
+
* finished gave the page an answer this watch could not read, and is kept as one that cannot be read.
|
|
60
|
+
*/
|
|
61
|
+
failed(key: string): void;
|
|
62
|
+
private settle;
|
|
63
|
+
/**
|
|
64
|
+
* The answers for a Billing /pay paused at `pausedAt`, newest last, or undefined when there are none to send: the
|
|
65
|
+
* card request is not a Billing /pay, nothing was recorded for its checkout, or the terms as of the pause are not
|
|
66
|
+
* known (see the header). Waits, up to the read bound or until `signal` aborts, for the texts of the checkout's
|
|
67
|
+
* answers that finished before the pause.
|
|
68
|
+
*/
|
|
69
|
+
capture(pausedAt: number, cardUrl: string, signal?: AbortSignal): Promise<PaddleCheckoutCapture | undefined>;
|
|
70
|
+
}
|