@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
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: cf655438763f54e5d5bfea2555f76941ef80dbfd5aa7bc6342a2a75d1ac9cd39
|
|
4
|
+
// src/deep-freeze.js
|
|
5
|
+
function deepFreeze(value) {
|
|
6
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
7
|
+
for (const child of Object.values(value))
|
|
8
|
+
deepFreeze(child);
|
|
9
|
+
Object.freeze(value);
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
// src/json-path.js
|
|
14
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
15
|
+
function isPlainObject(v) {
|
|
16
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
var MAX_PATH_STEPS = 16;
|
|
19
|
+
function isJsonPath(path) {
|
|
20
|
+
return Array.isArray(path) && path.length <= MAX_PATH_STEPS && path.every((step) => typeof step === "string" ? step.length > 0 && !FORBIDDEN_KEYS.has(step) : Number.isSafeInteger(step) && step >= 0);
|
|
21
|
+
}
|
|
22
|
+
function jsonAt(root, path) {
|
|
23
|
+
let node = root;
|
|
24
|
+
for (const step of path) {
|
|
25
|
+
if (typeof step === "string") {
|
|
26
|
+
if (!isPlainObject(node) || FORBIDDEN_KEYS.has(step) || !Object.prototype.hasOwnProperty.call(node, step))
|
|
27
|
+
return { found: false };
|
|
28
|
+
}
|
|
29
|
+
else if (!Array.isArray(node) || !Number.isSafeInteger(step) || step < 0 || step >= node.length) {
|
|
30
|
+
return { found: false };
|
|
31
|
+
}
|
|
32
|
+
node = node[step];
|
|
33
|
+
}
|
|
34
|
+
return { found: true, value: node };
|
|
35
|
+
}
|
|
36
|
+
function roundTrips(parsed, body) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(parsed) === body;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function sameKeyIgnoringCase(key, step) {
|
|
45
|
+
return !FORBIDDEN_KEYS.has(key) && (key.toLowerCase() === step.toLowerCase() || key.toUpperCase() === step.toUpperCase());
|
|
46
|
+
}
|
|
47
|
+
function valuesIgnoringCase(root, path) {
|
|
48
|
+
let nodes = [root];
|
|
49
|
+
for (const step of path) {
|
|
50
|
+
const next = [];
|
|
51
|
+
for (const node of nodes) {
|
|
52
|
+
if (typeof step === "number") {
|
|
53
|
+
if (Array.isArray(node) && Number.isSafeInteger(step) && step >= 0 && step < node.length)
|
|
54
|
+
next.push(node[step]);
|
|
55
|
+
}
|
|
56
|
+
else if (isPlainObject(node)) {
|
|
57
|
+
for (const key of Object.keys(node))
|
|
58
|
+
if (sameKeyIgnoringCase(key, step))
|
|
59
|
+
next.push(node[key]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
nodes = next;
|
|
63
|
+
}
|
|
64
|
+
return nodes;
|
|
65
|
+
}
|
|
66
|
+
// src/sha256.js
|
|
67
|
+
var K = /* @__PURE__ */ new Uint32Array([
|
|
68
|
+
1116352408,
|
|
69
|
+
1899447441,
|
|
70
|
+
3049323471,
|
|
71
|
+
3921009573,
|
|
72
|
+
961987163,
|
|
73
|
+
1508970993,
|
|
74
|
+
2453635748,
|
|
75
|
+
2870763221,
|
|
76
|
+
3624381080,
|
|
77
|
+
310598401,
|
|
78
|
+
607225278,
|
|
79
|
+
1426881987,
|
|
80
|
+
1925078388,
|
|
81
|
+
2162078206,
|
|
82
|
+
2614888103,
|
|
83
|
+
3248222580,
|
|
84
|
+
3835390401,
|
|
85
|
+
4022224774,
|
|
86
|
+
264347078,
|
|
87
|
+
604807628,
|
|
88
|
+
770255983,
|
|
89
|
+
1249150122,
|
|
90
|
+
1555081692,
|
|
91
|
+
1996064986,
|
|
92
|
+
2554220882,
|
|
93
|
+
2821834349,
|
|
94
|
+
2952996808,
|
|
95
|
+
3210313671,
|
|
96
|
+
3336571891,
|
|
97
|
+
3584528711,
|
|
98
|
+
113926993,
|
|
99
|
+
338241895,
|
|
100
|
+
666307205,
|
|
101
|
+
773529912,
|
|
102
|
+
1294757372,
|
|
103
|
+
1396182291,
|
|
104
|
+
1695183700,
|
|
105
|
+
1986661051,
|
|
106
|
+
2177026350,
|
|
107
|
+
2456956037,
|
|
108
|
+
2730485921,
|
|
109
|
+
2820302411,
|
|
110
|
+
3259730800,
|
|
111
|
+
3345764771,
|
|
112
|
+
3516065817,
|
|
113
|
+
3600352804,
|
|
114
|
+
4094571909,
|
|
115
|
+
275423344,
|
|
116
|
+
430227734,
|
|
117
|
+
506948616,
|
|
118
|
+
659060556,
|
|
119
|
+
883997877,
|
|
120
|
+
958139571,
|
|
121
|
+
1322822218,
|
|
122
|
+
1537002063,
|
|
123
|
+
1747873779,
|
|
124
|
+
1955562222,
|
|
125
|
+
2024104815,
|
|
126
|
+
2227730452,
|
|
127
|
+
2361852424,
|
|
128
|
+
2428436474,
|
|
129
|
+
2756734187,
|
|
130
|
+
3204031479,
|
|
131
|
+
3329325298
|
|
132
|
+
]);
|
|
133
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
134
|
+
function utf8(text) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const char of text) {
|
|
137
|
+
let cp = char.codePointAt(0);
|
|
138
|
+
if (cp >= 55296 && cp <= 57343)
|
|
139
|
+
cp = 65533;
|
|
140
|
+
if (cp < 128)
|
|
141
|
+
out.push(cp);
|
|
142
|
+
else if (cp < 2048)
|
|
143
|
+
out.push(192 | cp >> 6, 128 | cp & 63);
|
|
144
|
+
else if (cp < 65536)
|
|
145
|
+
out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
146
|
+
else
|
|
147
|
+
out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
148
|
+
}
|
|
149
|
+
return Uint8Array.from(out);
|
|
150
|
+
}
|
|
151
|
+
function sha256Hex(input) {
|
|
152
|
+
const isBytes = Object.prototype.toString.call(input) === "[object Uint8Array]";
|
|
153
|
+
if (typeof input !== "string" && !isBytes)
|
|
154
|
+
throw new TypeError("sha256Hex takes a string or a Uint8Array.");
|
|
155
|
+
const bytes = typeof input === "string" ? utf8(input) : input;
|
|
156
|
+
const length = bytes.length;
|
|
157
|
+
const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
|
|
158
|
+
padded.set(bytes);
|
|
159
|
+
padded[length] = 128;
|
|
160
|
+
const view = new DataView(padded.buffer);
|
|
161
|
+
view.setUint32(padded.length - 8, Math.floor(length / 536870912));
|
|
162
|
+
view.setUint32(padded.length - 4, length << 3 >>> 0);
|
|
163
|
+
const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
|
|
164
|
+
const W = new Uint32Array(64);
|
|
165
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
166
|
+
for (let t = 0; t < 16; t++)
|
|
167
|
+
W[t] = view.getUint32(offset + t * 4);
|
|
168
|
+
for (let t = 16; t < 64; t++) {
|
|
169
|
+
const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
|
|
170
|
+
const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
|
|
171
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
172
|
+
}
|
|
173
|
+
let a = H[0], b = H[1], c = H[2], d = H[3], e = H[4], f = H[5], g = H[6], h = H[7];
|
|
174
|
+
for (let t = 0; t < 64; t++) {
|
|
175
|
+
const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
|
|
176
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
177
|
+
h = g;
|
|
178
|
+
g = f;
|
|
179
|
+
f = e;
|
|
180
|
+
e = d + t1 >>> 0;
|
|
181
|
+
d = c;
|
|
182
|
+
c = b;
|
|
183
|
+
b = a;
|
|
184
|
+
a = t1 + t2 >>> 0;
|
|
185
|
+
}
|
|
186
|
+
H[0] += a;
|
|
187
|
+
H[1] += b;
|
|
188
|
+
H[2] += c;
|
|
189
|
+
H[3] += d;
|
|
190
|
+
H[4] += e;
|
|
191
|
+
H[5] += f;
|
|
192
|
+
H[6] += g;
|
|
193
|
+
H[7] += h;
|
|
194
|
+
}
|
|
195
|
+
return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
|
|
196
|
+
}
|
|
197
|
+
// src/verifone-keys.js
|
|
198
|
+
var VERIFONE_KEY_PINS = /* @__PURE__ */ deepFreeze([]);
|
|
199
|
+
var HEX40 = /^[0-9a-f]{40}$/;
|
|
200
|
+
var HEX16 = /^[0-9a-f]{16}$/;
|
|
201
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
202
|
+
var PIN_ID = /^[a-z0-9_]{1,64}$/;
|
|
203
|
+
var PIN_MEMBERS = "alias,encryptionSubkeyId,environment,id,merchant,observedAt,primaryFingerprint,publicKeyBase64,publicKeyBase64Sha256,source";
|
|
204
|
+
function verifoneKeyPinShapeRefusal(entry) {
|
|
205
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
206
|
+
return "verifone_key_pin_invalid";
|
|
207
|
+
if (Object.keys(entry).sort().join(",") !== PIN_MEMBERS)
|
|
208
|
+
return "verifone_key_pin_invalid";
|
|
209
|
+
if (typeof entry.id !== "string" || !PIN_ID.test(entry.id))
|
|
210
|
+
return "verifone_key_pin_invalid";
|
|
211
|
+
if (typeof entry.merchant !== "string" || entry.merchant.length === 0 || entry.merchant.length > 128)
|
|
212
|
+
return "verifone_key_pin_invalid";
|
|
213
|
+
if (entry.environment !== "production" && entry.environment !== "sandbox")
|
|
214
|
+
return "verifone_key_pin_invalid";
|
|
215
|
+
if (entry.alias !== null && (typeof entry.alias !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(entry.alias)))
|
|
216
|
+
return "verifone_key_pin_invalid";
|
|
217
|
+
if (typeof entry.publicKeyBase64 !== "string" || !/^[A-Za-z0-9+/]+={0,2}$/.test(entry.publicKeyBase64))
|
|
218
|
+
return "verifone_key_pin_invalid";
|
|
219
|
+
if (!HEX64.test(String(entry.publicKeyBase64Sha256)) || !HEX40.test(String(entry.primaryFingerprint)) || !HEX16.test(String(entry.encryptionSubkeyId)))
|
|
220
|
+
return "verifone_key_pin_invalid";
|
|
221
|
+
if (sha256Hex(entry.publicKeyBase64) !== entry.publicKeyBase64Sha256)
|
|
222
|
+
return "verifone_key_pin_mismatch";
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
function verifoneKeyPinById(id, pins = VERIFONE_KEY_PINS) {
|
|
226
|
+
return typeof id === "string" ? pins.find((entry) => entry.id === id) ?? null : null;
|
|
227
|
+
}
|
|
228
|
+
// src/url-template.js
|
|
229
|
+
var PLACEHOLDER_VALUE = /^[A-Za-z0-9._~@+-]{1,256}$/;
|
|
230
|
+
var PLACEHOLDER_NAME = /^[A-Za-z][A-Za-z0-9_]{0,31}$/;
|
|
231
|
+
var LITERAL_SEGMENT = /^[A-Za-z0-9._~@+-]{1,256}$/;
|
|
232
|
+
var QUERY_KEY = /^[A-Za-z0-9._~-]{1,64}$/;
|
|
233
|
+
var QUERY_LITERAL = /^[A-Za-z0-9._~,-]{0,256}$/;
|
|
234
|
+
var QUERY_PREFIX = /^(?:[A-Za-z0-9._~,-]|%[0-9A-F]{2}){1,64}$/;
|
|
235
|
+
var MAX_QUERIES = 8;
|
|
236
|
+
var MAX_PAIRS = 16;
|
|
237
|
+
var placeholderValue = (value) => PLACEHOLDER_VALUE.test(value) && value !== "." && !value.includes("..");
|
|
238
|
+
function queryPlaceholder(value) {
|
|
239
|
+
const mark = value.lastIndexOf(":");
|
|
240
|
+
return mark === -1 ? null : { prefix: value.slice(0, mark), name: value.slice(mark + 1) };
|
|
241
|
+
}
|
|
242
|
+
function pathPlaceholders(template, pathname) {
|
|
243
|
+
if (typeof template !== "string" || typeof pathname !== "string")
|
|
244
|
+
return null;
|
|
245
|
+
if (!template.startsWith("/") || !pathname.startsWith("/"))
|
|
246
|
+
return null;
|
|
247
|
+
const want = template.split("/");
|
|
248
|
+
const got = pathname.split("/");
|
|
249
|
+
if (want.length !== got.length)
|
|
250
|
+
return null;
|
|
251
|
+
const values = {};
|
|
252
|
+
for (const [i, segment] of want.entries()) {
|
|
253
|
+
if (segment.startsWith(":")) {
|
|
254
|
+
if (!placeholderValue(got[i]))
|
|
255
|
+
return null;
|
|
256
|
+
values[segment.slice(1)] = got[i];
|
|
257
|
+
}
|
|
258
|
+
else if (segment !== got[i]) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return values;
|
|
263
|
+
}
|
|
264
|
+
function queryPlaceholders(query, text) {
|
|
265
|
+
if (!query || typeof query !== "object" || Array.isArray(query))
|
|
266
|
+
return null;
|
|
267
|
+
const pairs = Object.entries(query);
|
|
268
|
+
if (pairs.length === 0)
|
|
269
|
+
return text === null ? {} : null;
|
|
270
|
+
if (typeof text !== "string")
|
|
271
|
+
return null;
|
|
272
|
+
const got = text.split("&");
|
|
273
|
+
if (got.length !== pairs.length)
|
|
274
|
+
return null;
|
|
275
|
+
const values = {};
|
|
276
|
+
for (const [i, [key, value]] of pairs.entries()) {
|
|
277
|
+
const mark = got[i].indexOf("=");
|
|
278
|
+
if (mark === -1 || got[i].slice(0, mark) !== key)
|
|
279
|
+
return null;
|
|
280
|
+
const written = got[i].slice(mark + 1);
|
|
281
|
+
if (typeof value !== "string")
|
|
282
|
+
return null;
|
|
283
|
+
const placeholder = queryPlaceholder(value);
|
|
284
|
+
if (placeholder) {
|
|
285
|
+
if (!written.startsWith(placeholder.prefix))
|
|
286
|
+
return null;
|
|
287
|
+
const rest = written.slice(placeholder.prefix.length);
|
|
288
|
+
if (!placeholderValue(rest))
|
|
289
|
+
return null;
|
|
290
|
+
values[placeholder.name] = rest;
|
|
291
|
+
}
|
|
292
|
+
else if (written !== value) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return values;
|
|
297
|
+
}
|
|
298
|
+
function strictUrl(rawUrl) {
|
|
299
|
+
if (typeof rawUrl !== "string" || rawUrl.includes("#"))
|
|
300
|
+
return null;
|
|
301
|
+
let url;
|
|
302
|
+
try {
|
|
303
|
+
url = new URL(rawUrl);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (url.href !== rawUrl || url.protocol !== "https:" || url.port || url.username || url.password)
|
|
309
|
+
return null;
|
|
310
|
+
return url;
|
|
311
|
+
}
|
|
312
|
+
function urlTemplateMatch(template, rawUrl) {
|
|
313
|
+
const url = strictUrl(rawUrl);
|
|
314
|
+
if (!url || !template || typeof template !== "object")
|
|
315
|
+
return null;
|
|
316
|
+
const fromPath = pathPlaceholders(template.path, url.pathname);
|
|
317
|
+
if (!fromPath)
|
|
318
|
+
return null;
|
|
319
|
+
const mark = rawUrl.indexOf("?");
|
|
320
|
+
const text = mark === -1 ? null : rawUrl.slice(mark + 1);
|
|
321
|
+
const queries = Array.isArray(template.queries) ? template.queries : [template.query];
|
|
322
|
+
for (const query of queries) {
|
|
323
|
+
const fromQuery = queryPlaceholders(query, text);
|
|
324
|
+
if (fromQuery)
|
|
325
|
+
return { origin: url.origin, placeholders: { ...fromPath, ...fromQuery } };
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function templatePlaceholders(template) {
|
|
330
|
+
const names = [];
|
|
331
|
+
if (typeof template?.path === "string") {
|
|
332
|
+
for (const segment of template.path.split("/"))
|
|
333
|
+
if (segment.startsWith(":"))
|
|
334
|
+
names.push(segment.slice(1));
|
|
335
|
+
}
|
|
336
|
+
const queries = Array.isArray(template?.queries) ? template.queries : [template?.query];
|
|
337
|
+
for (const query of queries) {
|
|
338
|
+
if (query && typeof query === "object") {
|
|
339
|
+
for (const value of Object.values(query)) {
|
|
340
|
+
const placeholder = typeof value === "string" ? queryPlaceholder(value) : null;
|
|
341
|
+
if (placeholder)
|
|
342
|
+
names.push(placeholder.name);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return names;
|
|
347
|
+
}
|
|
348
|
+
function pathTemplateRefusal(template) {
|
|
349
|
+
if (typeof template !== "string" || !template.startsWith("/") || template.length > 1024)
|
|
350
|
+
return "the path is not a template starting with /";
|
|
351
|
+
const names = [];
|
|
352
|
+
for (const segment of template.split("/").slice(1)) {
|
|
353
|
+
if (segment.startsWith(":")) {
|
|
354
|
+
if (!PLACEHOLDER_NAME.test(segment.slice(1)))
|
|
355
|
+
return `the path placeholder ${segment} is not a name`;
|
|
356
|
+
names.push(segment.slice(1));
|
|
357
|
+
}
|
|
358
|
+
else if (!LITERAL_SEGMENT.test(segment) || segment === "." || segment === "..") {
|
|
359
|
+
return `the path segment "${segment}" is not one literal segment`;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (new Set(names).size !== names.length)
|
|
363
|
+
return "a path placeholder is named twice";
|
|
364
|
+
const literal = template.split("/").slice(1).map((segment) => segment.startsWith(":") ? "x" : segment).join("/");
|
|
365
|
+
return new URL(`https://template.invalid/${literal}`).pathname === `/${literal}` ? null : "the path is not written the way a URL writes it";
|
|
366
|
+
}
|
|
367
|
+
function queryTemplateRefusal(query) {
|
|
368
|
+
if (!query || typeof query !== "object" || Array.isArray(query))
|
|
369
|
+
return "the query is not an object of key=value pairs";
|
|
370
|
+
const pairs = Object.entries(query);
|
|
371
|
+
if (pairs.length > MAX_PAIRS)
|
|
372
|
+
return `the query has more than ${MAX_PAIRS} pairs`;
|
|
373
|
+
for (const [key, value] of pairs) {
|
|
374
|
+
if (!QUERY_KEY.test(key))
|
|
375
|
+
return `the query key "${key}" is not unreserved characters`;
|
|
376
|
+
if (typeof value !== "string")
|
|
377
|
+
return `the query value of ${key} is not text`;
|
|
378
|
+
const placeholder = queryPlaceholder(value);
|
|
379
|
+
if (placeholder ? !PLACEHOLDER_NAME.test(placeholder.name) || placeholder.prefix !== "" && !QUERY_PREFIX.test(placeholder.prefix) : !QUERY_LITERAL.test(value)) {
|
|
380
|
+
return `the query value of ${key} is neither a literal nor a :name placeholder`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
function urlTemplateRefusal(template) {
|
|
386
|
+
if (!template || typeof template !== "object")
|
|
387
|
+
return "the template is not an object";
|
|
388
|
+
const pathRefusal = pathTemplateRefusal(template.path);
|
|
389
|
+
if (pathRefusal)
|
|
390
|
+
return pathRefusal;
|
|
391
|
+
const hasQuery = Object.prototype.hasOwnProperty.call(template, "query");
|
|
392
|
+
const hasQueries = Object.prototype.hasOwnProperty.call(template, "queries");
|
|
393
|
+
if (hasQuery === hasQueries)
|
|
394
|
+
return "the template names exactly one of query and queries";
|
|
395
|
+
const queries = hasQueries ? template.queries : [template.query];
|
|
396
|
+
if (!Array.isArray(queries) || queries.length === 0 || queries.length > MAX_QUERIES)
|
|
397
|
+
return `queries is not a list of 1 to ${MAX_QUERIES}`;
|
|
398
|
+
const pathNames = templatePlaceholders({ path: template.path, query: {} });
|
|
399
|
+
for (const query of queries) {
|
|
400
|
+
const refusal = queryTemplateRefusal(query);
|
|
401
|
+
if (refusal)
|
|
402
|
+
return refusal;
|
|
403
|
+
const names = [...pathNames, ...templatePlaceholders({ query })];
|
|
404
|
+
if (new Set(names).size !== names.length)
|
|
405
|
+
return "a placeholder is named twice in one request shape";
|
|
406
|
+
}
|
|
407
|
+
const texts = queries.map((query) => JSON.stringify(Object.entries(query)));
|
|
408
|
+
return new Set(texts).size === texts.length ? null : "a query is listed twice";
|
|
409
|
+
}
|
|
410
|
+
// src/verifone-profiles.js
|
|
411
|
+
var VERIFONE_MERCHANT_PROFILES = /* @__PURE__ */ deepFreeze([
|
|
412
|
+
{
|
|
413
|
+
// The Container Store (research/processors-wave2/items/verifone-ecommerce/DESIGN.md). TCS's own
|
|
414
|
+
// page config turns Verifone on for credit cards, its Synchrony private-label card and both
|
|
415
|
+
// employee variants (featureConfig verifoneCCEnabled, verifonePLCCEnabled,
|
|
416
|
+
// verifoneEmployeeCCEnabled, verifoneEmployeePLCCEnabled; live from late 2023, still on
|
|
417
|
+
// 2026-09-19). Checkout is www.containerstore.com/checkout?fulfillmentMethod=STANDARD|PICKUP,
|
|
418
|
+
// on the legacy app behind PerimeterX. Its card request has never been observed: the
|
|
419
|
+
// CheckoutV2 bundle was never archived and the page is session-gated. So no leg and no key
|
|
420
|
+
// are pinned, and every blob-bearing request to www.containerstore.com is aborted, until one
|
|
421
|
+
// person-run checkout captured as a HAR pins the endpoint, the member holding the blob, the
|
|
422
|
+
// key and its alias, and whether the blob is sent once or twice.
|
|
423
|
+
id: "containerstore_checkout",
|
|
424
|
+
merchant: "The Container Store",
|
|
425
|
+
processor: "verifone",
|
|
426
|
+
status: "observe",
|
|
427
|
+
environment: "production",
|
|
428
|
+
origins: [{ checkout: "https://www.containerstore.com", endpoint: "https://www.containerstore.com" }],
|
|
429
|
+
legs: [],
|
|
430
|
+
keyPin: null,
|
|
431
|
+
amount: null
|
|
432
|
+
}
|
|
433
|
+
]);
|
|
434
|
+
var PROFILE_ID = /^[a-z0-9_]{1,64}$/;
|
|
435
|
+
var LEG_NAME = /^[a-z0-9_]{1,32}$/;
|
|
436
|
+
var MAX_RULES = 16;
|
|
437
|
+
var PROFILE_MEMBERS = "amount,environment,id,keyPin,legs,merchant,origins,processor,status";
|
|
438
|
+
var LEG_MEMBERS = "field,holder,method,name,path,query,refuse,requires";
|
|
439
|
+
function isHttpsOrigin(value) {
|
|
440
|
+
try {
|
|
441
|
+
const url = new URL(value);
|
|
442
|
+
return url.protocol === "https:" && url.origin === value && !url.port && !url.username && !url.password;
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function ruleRefusal(rule, kind) {
|
|
449
|
+
if (!isPlainObject(rule) || !isJsonPath(rule.path) || rule.path.length === 0)
|
|
450
|
+
return `a ${kind} rule's path is not a JSON path`;
|
|
451
|
+
if (kind === "requires") {
|
|
452
|
+
const members = Object.keys(rule).sort().join(",");
|
|
453
|
+
if (members !== "equals,path")
|
|
454
|
+
return "a requires rule is not {path, equals}";
|
|
455
|
+
const value = rule.equals;
|
|
456
|
+
return typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? null : "a requires rule does not compare with a string, a number or a boolean";
|
|
457
|
+
}
|
|
458
|
+
if (Object.keys(rule).sort().join(",") !== "path,when" || rule.when !== "true" && rule.when !== "present") {
|
|
459
|
+
return "a refuse rule is not {path, when: 'true' | 'present'}";
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
function legRefusal(leg) {
|
|
464
|
+
if (!isPlainObject(leg) || Object.keys(leg).sort().join(",") !== LEG_MEMBERS)
|
|
465
|
+
return "a leg is not {name, method, path, query, requires, refuse, holder, field}";
|
|
466
|
+
if (typeof leg.name !== "string" || !LEG_NAME.test(leg.name))
|
|
467
|
+
return "a leg has no name";
|
|
468
|
+
if (leg.method !== "POST")
|
|
469
|
+
return `leg ${leg.name} is not a POST`;
|
|
470
|
+
const template = urlTemplateRefusal({ path: leg.path, query: leg.query });
|
|
471
|
+
if (template)
|
|
472
|
+
return `leg ${leg.name}: ${template}`;
|
|
473
|
+
for (const [kind, list] of [["requires", leg.requires], ["refuse", leg.refuse]]) {
|
|
474
|
+
if (!Array.isArray(list) || list.length > MAX_RULES)
|
|
475
|
+
return `leg ${leg.name}: ${kind} is not a list of at most ${MAX_RULES}`;
|
|
476
|
+
for (const rule of list) {
|
|
477
|
+
const refusal = ruleRefusal(rule, kind);
|
|
478
|
+
if (refusal)
|
|
479
|
+
return `leg ${leg.name}: ${refusal}`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (!isJsonPath(leg.holder))
|
|
483
|
+
return `leg ${leg.name}: the holder is not a JSON path`;
|
|
484
|
+
if (typeof leg.field !== "string" || leg.field.length === 0 || leg.field.length > 64 || ["__proto__", "constructor", "prototype"].includes(leg.field)) {
|
|
485
|
+
return `leg ${leg.name}: the field is not a member name`;
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
function verifoneProfileRefusal(profile, pins = VERIFONE_KEY_PINS) {
|
|
490
|
+
if (!isPlainObject(profile) || Object.keys(profile).sort().join(",") !== PROFILE_MEMBERS)
|
|
491
|
+
return "the profile does not carry exactly the reviewed members";
|
|
492
|
+
if (typeof profile.id !== "string" || !PROFILE_ID.test(profile.id))
|
|
493
|
+
return "the profile id is not a name";
|
|
494
|
+
if (typeof profile.merchant !== "string" || profile.merchant.length === 0 || profile.merchant.length > 128)
|
|
495
|
+
return "the profile names no merchant";
|
|
496
|
+
if (profile.processor !== "verifone")
|
|
497
|
+
return "the profile is not a Verifone profile";
|
|
498
|
+
if (profile.status !== "observe" && profile.status !== "enabled")
|
|
499
|
+
return "the status is neither observe nor enabled";
|
|
500
|
+
if (profile.environment !== "production" && profile.environment !== "sandbox")
|
|
501
|
+
return "the environment is neither production nor sandbox";
|
|
502
|
+
if (!Array.isArray(profile.origins) || profile.origins.length === 0 || profile.origins.length > 8 || !profile.origins.every((pair) => isPlainObject(pair) && Object.keys(pair).sort().join(",") === "checkout,endpoint" && isHttpsOrigin(pair.checkout) && isHttpsOrigin(pair.endpoint)))
|
|
503
|
+
return "the origins are not exact HTTPS origin pairs";
|
|
504
|
+
if (!Array.isArray(profile.legs) || profile.legs.length > 2)
|
|
505
|
+
return "legs is not a list of at most two";
|
|
506
|
+
for (const leg of profile.legs) {
|
|
507
|
+
const refusal = legRefusal(leg);
|
|
508
|
+
if (refusal)
|
|
509
|
+
return refusal;
|
|
510
|
+
}
|
|
511
|
+
if (new Set(profile.legs.map((leg) => leg.name)).size !== profile.legs.length)
|
|
512
|
+
return "two legs share a name";
|
|
513
|
+
if (profile.amount !== null && (!isJsonPath(profile.amount) || profile.amount.length === 0))
|
|
514
|
+
return "the amount is neither null nor a JSON path";
|
|
515
|
+
if (profile.keyPin !== null) {
|
|
516
|
+
const pin = verifoneKeyPinById(profile.keyPin, pins);
|
|
517
|
+
if (!pin)
|
|
518
|
+
return "the key pin is not in the table";
|
|
519
|
+
if (pin.environment !== profile.environment)
|
|
520
|
+
return "the key pin is for another environment than the profile's";
|
|
521
|
+
if (pin.merchant !== profile.merchant)
|
|
522
|
+
return "the key pin is for another merchant than the profile's";
|
|
523
|
+
}
|
|
524
|
+
if (profile.status === "enabled") {
|
|
525
|
+
if (profile.legs.length !== 1)
|
|
526
|
+
return "an enabled profile pays exactly one leg";
|
|
527
|
+
if (profile.keyPin === null)
|
|
528
|
+
return "an enabled profile names a key pin";
|
|
529
|
+
if (profile.amount === null)
|
|
530
|
+
return "an enabled profile names where its card request carries the amount";
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
function verifoneProfilePin(profile, pins = VERIFONE_KEY_PINS) {
|
|
535
|
+
if (!isPlainObject(profile) || profile.keyPin === null)
|
|
536
|
+
return null;
|
|
537
|
+
const pin = verifoneKeyPinById(profile.keyPin, pins);
|
|
538
|
+
return pin && pin.merchant === profile.merchant && pin.environment === profile.environment ? pin : null;
|
|
539
|
+
}
|
|
540
|
+
function verifoneProfileById(id, profiles = VERIFONE_MERCHANT_PROFILES) {
|
|
541
|
+
const key = typeof id === "string" ? id : id?.id;
|
|
542
|
+
return typeof key === "string" ? profiles.find((profile) => profile.id === key) ?? null : null;
|
|
543
|
+
}
|
|
544
|
+
function trustedVerifoneProfile(idOrEntry, profiles = VERIFONE_MERCHANT_PROFILES) {
|
|
545
|
+
const profile = verifoneProfileById(idOrEntry, profiles);
|
|
546
|
+
if (!profile)
|
|
547
|
+
throw new Error("Refusing to pay: unknown Verifone merchant profile.");
|
|
548
|
+
return profile;
|
|
549
|
+
}
|
|
550
|
+
function isVerifoneProfileOrigin(profile, rawUrl) {
|
|
551
|
+
let url;
|
|
552
|
+
try {
|
|
553
|
+
url = new URL(rawUrl);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
return url.protocol === "https:" && !url.username && !url.password && profile.origins.some((pair) => pair.endpoint === url.origin);
|
|
559
|
+
}
|
|
560
|
+
function verifoneLegFor(profile, rawUrl, method, opts) {
|
|
561
|
+
if (typeof rawUrl !== "string" || typeof method !== "string")
|
|
562
|
+
return null;
|
|
563
|
+
for (const leg of profile.legs) {
|
|
564
|
+
if (method.toUpperCase() !== leg.method)
|
|
565
|
+
continue;
|
|
566
|
+
const match = urlTemplateMatch({ path: leg.path, query: leg.query }, rawUrl);
|
|
567
|
+
if (!match)
|
|
568
|
+
continue;
|
|
569
|
+
const checkoutOrigin = opts?.checkoutOrigin;
|
|
570
|
+
if (profile.origins.some((pair) => pair.endpoint === match.origin && (checkoutOrigin === void 0 || pair.checkout === checkoutOrigin)))
|
|
571
|
+
return leg;
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
function verifoneLegBodyRefusal(profile, leg, body) {
|
|
576
|
+
let parsed;
|
|
577
|
+
try {
|
|
578
|
+
parsed = typeof body === "string" ? JSON.parse(body) : void 0;
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return "the paused body is not JSON";
|
|
582
|
+
}
|
|
583
|
+
if (!isPlainObject(parsed))
|
|
584
|
+
return "the paused body is not a JSON object";
|
|
585
|
+
if (!roundTrips(parsed, body))
|
|
586
|
+
return "the paused body does not round-trip through JSON, so its rules cannot be read";
|
|
587
|
+
for (const rule of leg.requires) {
|
|
588
|
+
const at = jsonAt(parsed, rule.path);
|
|
589
|
+
if (!at.found || at.value !== rule.equals || valuesIgnoringCase(parsed, rule.path).some((value) => value !== rule.equals)) {
|
|
590
|
+
return `the paused body is not ${profile.merchant}'s card request`;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const rule of leg.refuse) {
|
|
594
|
+
const values = valuesIgnoringCase(parsed, rule.path);
|
|
595
|
+
const fires = rule.when === "true" ? values.some((value) => value !== false && value !== null) : values.length > 0;
|
|
596
|
+
if (fires)
|
|
597
|
+
return `the paused body asks ${profile.merchant} for more than this purchase`;
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function wireProjection(profile, pins) {
|
|
602
|
+
const pin = verifoneProfilePin(profile, pins);
|
|
603
|
+
const { keyPin, ...rest } = profile;
|
|
604
|
+
return deepFreeze({ ...rest, keyPin, encryptionSubkeyId: pin ? pin.encryptionSubkeyId : null });
|
|
605
|
+
}
|
|
606
|
+
var VERIFONE_PROFILE_RECOGNIZERS = /* @__PURE__ */ deepFreeze(VERIFONE_MERCHANT_PROFILES.map((profile) => wireProjection(profile, VERIFONE_KEY_PINS)));
|
|
607
|
+
// src/fiserv-envelope.js
|
|
608
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
609
|
+
var BASE64_VALUE = /* @__PURE__ */ (() => {
|
|
610
|
+
const table = /* @__PURE__ */ new Map();
|
|
611
|
+
for (let i = 0; i < BASE64_ALPHABET.length; i++)
|
|
612
|
+
table.set(BASE64_ALPHABET[i], i);
|
|
613
|
+
return table;
|
|
614
|
+
})();
|
|
615
|
+
var FISERV_ENVELOPE_CONSTANTS = Object.freeze({ aesTagLength: 128, encryptionType: "AES-GCM", encryptionTarget: "MANUAL" });
|
|
616
|
+
function encodeBase64(bytes) {
|
|
617
|
+
let out = "";
|
|
618
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
619
|
+
const a = bytes[i], b = i + 1 < bytes.length ? bytes[i + 1] : 0, c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
620
|
+
const triple = a << 16 | b << 8 | c;
|
|
621
|
+
out += BASE64_ALPHABET[triple >> 18 & 63] + BASE64_ALPHABET[triple >> 12 & 63] + (i + 1 < bytes.length ? BASE64_ALPHABET[triple >> 6 & 63] : "=") + (i + 2 < bytes.length ? BASE64_ALPHABET[triple & 63] : "=");
|
|
622
|
+
}
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
625
|
+
function decodeBase64(text) {
|
|
626
|
+
if (typeof text !== "string" || text.length === 0 || text.length % 4 !== 0 || text.length > 65536)
|
|
627
|
+
return null;
|
|
628
|
+
const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
629
|
+
const body = text.length - padding;
|
|
630
|
+
const out = new Uint8Array(text.length / 4 * 3 - padding);
|
|
631
|
+
let at = 0;
|
|
632
|
+
for (let i = 0; i < text.length; i += 4) {
|
|
633
|
+
const quad = [0, 0, 0, 0];
|
|
634
|
+
for (let j = 0; j < 4; j++) {
|
|
635
|
+
const index = i + j;
|
|
636
|
+
if (index >= body) {
|
|
637
|
+
quad[j] = 0;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
const value = BASE64_VALUE.get(text[index]);
|
|
641
|
+
if (value === void 0)
|
|
642
|
+
return null;
|
|
643
|
+
quad[j] = value;
|
|
644
|
+
}
|
|
645
|
+
const triple = quad[0] << 18 | quad[1] << 12 | quad[2] << 6 | quad[3];
|
|
646
|
+
if (at < out.length)
|
|
647
|
+
out[at++] = triple >> 16 & 255;
|
|
648
|
+
if (at < out.length)
|
|
649
|
+
out[at++] = triple >> 8 & 255;
|
|
650
|
+
if (at < out.length)
|
|
651
|
+
out[at++] = triple & 255;
|
|
652
|
+
}
|
|
653
|
+
return encodeBase64(out) === text ? out : null;
|
|
654
|
+
}
|
|
655
|
+
// src/verifone-pgp.js
|
|
656
|
+
var P256_OID = Object.freeze([42, 134, 72, 206, 61, 3, 1, 7]);
|
|
657
|
+
var ALGORITHM_ECDH = 18;
|
|
658
|
+
var ENCRYPTING_ALGORITHMS = Object.freeze([1, 2, 16, 18]);
|
|
659
|
+
var KNOWN_SYMMETRIC = Object.freeze([2, 3, 4, 7, 8, 9, 10]);
|
|
660
|
+
var AES = Object.freeze({ 7: 16, 8: 24, 9: 32 });
|
|
661
|
+
var KDF_HASHES = Object.freeze({ 8: "SHA-256", 9: "SHA-384", 10: "SHA-512" });
|
|
662
|
+
var DEFAULT_SESSION_CIPHER = 7;
|
|
663
|
+
var MESSAGE_BEGIN = "-----BEGIN PGP MESSAGE-----";
|
|
664
|
+
var MESSAGE_END = "-----END PGP MESSAGE-----";
|
|
665
|
+
var KEY_BEGIN = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
|
|
666
|
+
var KEY_END = "-----END PGP PUBLIC KEY BLOCK-----";
|
|
667
|
+
var ARMOR_HEADERS = Object.freeze(["Version: OpenPGP.js v4.10.9", "Comment: https://openpgpjs.org"]);
|
|
668
|
+
var VERIFONE_BLOB_PREFIX = "LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0t";
|
|
669
|
+
var PGP_MESSAGE_ARMOR = MESSAGE_BEGIN;
|
|
670
|
+
var MAX_BASE64 = 16384;
|
|
671
|
+
var concat = (...parts) => {
|
|
672
|
+
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
673
|
+
let at = 0;
|
|
674
|
+
for (const part of parts) {
|
|
675
|
+
out.set(part, at);
|
|
676
|
+
at += part.length;
|
|
677
|
+
}
|
|
678
|
+
return out;
|
|
679
|
+
};
|
|
680
|
+
var u16 = (n) => Uint8Array.of(n >> 8 & 255, n & 255);
|
|
681
|
+
var u32 = (n) => Uint8Array.of(n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
682
|
+
var readU32 = (bytes, at) => (bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0;
|
|
683
|
+
var hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
684
|
+
var sameBytes = (a, b) => a.length === b.length && a.every((byte, i) => byte === b[i]);
|
|
685
|
+
var asciiBytes = (text) => Uint8Array.from(text, (char) => char.charCodeAt(0));
|
|
686
|
+
function asciiText(bytes) {
|
|
687
|
+
let out = "";
|
|
688
|
+
for (const byte of bytes) {
|
|
689
|
+
if (byte !== 9 && byte !== 10 && byte !== 13 && (byte < 32 || byte > 126))
|
|
690
|
+
return null;
|
|
691
|
+
out += String.fromCharCode(byte);
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
function crc24(bytes) {
|
|
696
|
+
let crc = 11994318;
|
|
697
|
+
for (const byte of bytes) {
|
|
698
|
+
crc ^= byte << 16;
|
|
699
|
+
for (let i = 0; i < 8; i++) {
|
|
700
|
+
crc <<= 1;
|
|
701
|
+
if (crc & 16777216)
|
|
702
|
+
crc ^= 25578747;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return crc & 16777215;
|
|
706
|
+
}
|
|
707
|
+
function dearmor(text, begin, end) {
|
|
708
|
+
const lines = text.split(/\r?\n/);
|
|
709
|
+
while (lines.length > 0 && lines[lines.length - 1] === "")
|
|
710
|
+
lines.pop();
|
|
711
|
+
if (lines.length < 4 || lines[0] !== begin || lines[lines.length - 1] !== end)
|
|
712
|
+
return null;
|
|
713
|
+
let at = 1;
|
|
714
|
+
const headers = [];
|
|
715
|
+
while (at < lines.length - 1 && lines[at] !== "") {
|
|
716
|
+
if (!/^[A-Za-z][A-Za-z0-9-]{0,63}: [ -~]{0,256}$/.test(lines[at]))
|
|
717
|
+
return null;
|
|
718
|
+
headers.push(lines[at]);
|
|
719
|
+
at += 1;
|
|
720
|
+
}
|
|
721
|
+
if (lines[at] !== "")
|
|
722
|
+
return null;
|
|
723
|
+
const body = lines.slice(at + 1, lines.length - 1);
|
|
724
|
+
let checksum = null;
|
|
725
|
+
if (body.length > 0 && body[body.length - 1].startsWith("="))
|
|
726
|
+
checksum = body.pop().slice(1);
|
|
727
|
+
if (body.length === 0 || !body.every((line) => /^[A-Za-z0-9+/=]{1,76}$/.test(line)))
|
|
728
|
+
return null;
|
|
729
|
+
const bytes = decodeBase64(body.join(""));
|
|
730
|
+
if (!bytes)
|
|
731
|
+
return null;
|
|
732
|
+
if (checksum !== null) {
|
|
733
|
+
const expected = decodeBase64(checksum);
|
|
734
|
+
if (!expected || expected.length !== 3 || (expected[0] << 16 | expected[1] << 8 | expected[2]) !== crc24(bytes))
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
return { bytes, headers };
|
|
738
|
+
}
|
|
739
|
+
function readPackets(bytes) {
|
|
740
|
+
const out = [];
|
|
741
|
+
let at = 0;
|
|
742
|
+
while (at < bytes.length) {
|
|
743
|
+
const head = bytes[at];
|
|
744
|
+
if (!(head & 128))
|
|
745
|
+
return null;
|
|
746
|
+
let tag;
|
|
747
|
+
let length;
|
|
748
|
+
let headerLength;
|
|
749
|
+
if (head & 64) {
|
|
750
|
+
tag = head & 63;
|
|
751
|
+
const first = bytes[at + 1];
|
|
752
|
+
if (first === void 0)
|
|
753
|
+
return null;
|
|
754
|
+
if (first < 192) {
|
|
755
|
+
length = first;
|
|
756
|
+
headerLength = 2;
|
|
757
|
+
}
|
|
758
|
+
else if (first < 224) {
|
|
759
|
+
if (at + 2 >= bytes.length)
|
|
760
|
+
return null;
|
|
761
|
+
length = (first - 192 << 8) + bytes[at + 2] + 192;
|
|
762
|
+
headerLength = 3;
|
|
763
|
+
}
|
|
764
|
+
else if (first === 255) {
|
|
765
|
+
if (at + 5 >= bytes.length)
|
|
766
|
+
return null;
|
|
767
|
+
length = readU32(bytes, at + 2);
|
|
768
|
+
headerLength = 6;
|
|
769
|
+
}
|
|
770
|
+
else
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
tag = head >> 2 & 15;
|
|
775
|
+
const size = [1, 2, 4][head & 3];
|
|
776
|
+
if (!size || at + size >= bytes.length)
|
|
777
|
+
return null;
|
|
778
|
+
length = 0;
|
|
779
|
+
for (let i = 0; i < size; i++)
|
|
780
|
+
length = length * 256 + bytes[at + 1 + i];
|
|
781
|
+
headerLength = 1 + size;
|
|
782
|
+
}
|
|
783
|
+
if (at + headerLength + length > bytes.length)
|
|
784
|
+
return null;
|
|
785
|
+
out.push({ tag, body: bytes.subarray(at + headerLength, at + headerLength + length) });
|
|
786
|
+
at += headerLength + length;
|
|
787
|
+
}
|
|
788
|
+
return out.length > 0 ? out : null;
|
|
789
|
+
}
|
|
790
|
+
function packetHeader(tag, length) {
|
|
791
|
+
if (length < 192)
|
|
792
|
+
return Uint8Array.of(192 | tag, length);
|
|
793
|
+
if (length < 8384)
|
|
794
|
+
return Uint8Array.of(192 | tag, (length - 192 >> 8) + 192, length - 192 & 255);
|
|
795
|
+
return concat(Uint8Array.of(192 | tag, 255), u32(length));
|
|
796
|
+
}
|
|
797
|
+
function verifoneBlobInfo(value) {
|
|
798
|
+
if (typeof value !== "string" || !value.startsWith(VERIFONE_BLOB_PREFIX) || value.length > MAX_BASE64)
|
|
799
|
+
return null;
|
|
800
|
+
const decoded = decodeBase64(value);
|
|
801
|
+
const text = decoded ? asciiText(decoded) : null;
|
|
802
|
+
const armor = text === null ? null : dearmor(text, MESSAGE_BEGIN, MESSAGE_END);
|
|
803
|
+
const packets = armor ? readPackets(armor.bytes) : null;
|
|
804
|
+
if (!packets || packets.length !== 2)
|
|
805
|
+
return null;
|
|
806
|
+
const [pkesk, seipd] = packets;
|
|
807
|
+
if (pkesk.tag !== 1 || seipd.tag !== 18)
|
|
808
|
+
return null;
|
|
809
|
+
const b = pkesk.body;
|
|
810
|
+
if (b.length < 12 || b[0] !== 3 || b[9] !== ALGORITHM_ECDH || (b[10] << 8 | b[11]) !== 515 || b[12] !== 4)
|
|
811
|
+
return null;
|
|
812
|
+
const wrappedAt = 12 + 65;
|
|
813
|
+
const wrappedLength = b[wrappedAt];
|
|
814
|
+
if (![32, 40, 48].includes(wrappedLength) || b.length !== wrappedAt + 1 + wrappedLength)
|
|
815
|
+
return null;
|
|
816
|
+
if (seipd.body[0] !== 1 || seipd.body.length < 1 + 18 + 2 + 22)
|
|
817
|
+
return null;
|
|
818
|
+
return Object.freeze({
|
|
819
|
+
recipientKeyId: hex(b.subarray(1, 9)),
|
|
820
|
+
wrappedKeyLength: wrappedLength,
|
|
821
|
+
armorHeaders: Object.freeze([...armor.headers])
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function isVerifoneBlob(value) {
|
|
825
|
+
return verifoneBlobInfo(value) !== null;
|
|
826
|
+
}
|
|
827
|
+
function hashedSubpackets(body) {
|
|
828
|
+
if (body.length < 6 || body[0] !== 4)
|
|
829
|
+
return null;
|
|
830
|
+
const hashedLength = body[4] << 8 | body[5];
|
|
831
|
+
if (6 + hashedLength > body.length)
|
|
832
|
+
return null;
|
|
833
|
+
const area = body.subarray(6, 6 + hashedLength);
|
|
834
|
+
const out = [];
|
|
835
|
+
let at = 0;
|
|
836
|
+
while (at < area.length) {
|
|
837
|
+
let length = area[at];
|
|
838
|
+
let size = 1;
|
|
839
|
+
if (length >= 192 && length < 255) {
|
|
840
|
+
if (at + 1 >= area.length)
|
|
841
|
+
return null;
|
|
842
|
+
length = (length - 192 << 8) + area[at + 1] + 192;
|
|
843
|
+
size = 2;
|
|
844
|
+
}
|
|
845
|
+
else if (length === 255) {
|
|
846
|
+
if (at + 4 >= area.length)
|
|
847
|
+
return null;
|
|
848
|
+
length = readU32(area, at + 1);
|
|
849
|
+
size = 5;
|
|
850
|
+
}
|
|
851
|
+
if (length < 1 || at + size + length > area.length)
|
|
852
|
+
return null;
|
|
853
|
+
out.push({ type: area[at + size] & 127, data: area.subarray(at + size + 1, at + size + length) });
|
|
854
|
+
at += size + length;
|
|
855
|
+
}
|
|
856
|
+
return out;
|
|
857
|
+
}
|
|
858
|
+
function keyPacket(body) {
|
|
859
|
+
if (body.length < 6 || body[0] !== 4)
|
|
860
|
+
return null;
|
|
861
|
+
return { created: readU32(body, 1), algorithm: body[5], fields: body.subarray(6) };
|
|
862
|
+
}
|
|
863
|
+
function ecdhFields(fields) {
|
|
864
|
+
const oidLength = fields[0];
|
|
865
|
+
const oid = fields.subarray(1, 1 + oidLength);
|
|
866
|
+
if (oidLength !== P256_OID.length || !sameBytes(oid, P256_OID))
|
|
867
|
+
return { refusal: "verifone_key_curve_unsupported" };
|
|
868
|
+
let at = 1 + oidLength;
|
|
869
|
+
const bits = fields[at] << 8 | fields[at + 1];
|
|
870
|
+
const point = fields.subarray(at + 2, at + 2 + (bits + 7 >> 3));
|
|
871
|
+
at += 2 + point.length;
|
|
872
|
+
if (bits !== 515 || point.length !== 65 || point[0] !== 4)
|
|
873
|
+
return { refusal: "verifone_key_malformed" };
|
|
874
|
+
const kdf = fields.subarray(at, at + 1 + fields[at]);
|
|
875
|
+
if (kdf.length !== 4 || kdf[0] !== 3 || kdf[1] !== 1 || at + 4 !== fields.length)
|
|
876
|
+
return { refusal: "verifone_key_malformed" };
|
|
877
|
+
if (!KDF_HASHES[kdf[2]] || !AES[kdf[3]])
|
|
878
|
+
return { refusal: "verifone_key_kdf_unsupported" };
|
|
879
|
+
return { point: Uint8Array.from(point), kdfHash: kdf[2], kekAlgorithm: kdf[3] };
|
|
880
|
+
}
|
|
881
|
+
async function fingerprintOf(body, subtle) {
|
|
882
|
+
return new Uint8Array(await subtle.digest("SHA-1", concat(Uint8Array.of(153), u16(body.length), body)));
|
|
883
|
+
}
|
|
884
|
+
var REVOCATIONS = Object.freeze([32, 40]);
|
|
885
|
+
var CERTIFICATIONS = Object.freeze([16, 17, 18, 19]);
|
|
886
|
+
async function readVerifonePublicKey(publicKeyBase64, facilities) {
|
|
887
|
+
if (typeof publicKeyBase64 !== "string" || publicKeyBase64.length === 0 || publicKeyBase64.length > MAX_BASE64)
|
|
888
|
+
return { refusal: "verifone_key_malformed" };
|
|
889
|
+
const decoded = decodeBase64(publicKeyBase64);
|
|
890
|
+
const text = decoded ? asciiText(decoded) : null;
|
|
891
|
+
const armor = text === null ? null : dearmor(text, KEY_BEGIN, KEY_END);
|
|
892
|
+
const packets = armor ? readPackets(armor.bytes) : null;
|
|
893
|
+
if (!packets || packets[0].tag !== 6)
|
|
894
|
+
return { refusal: "verifone_key_malformed" };
|
|
895
|
+
const primary = keyPacket(packets[0].body);
|
|
896
|
+
if (!primary)
|
|
897
|
+
return { refusal: "verifone_key_malformed" };
|
|
898
|
+
const subkeys = [];
|
|
899
|
+
const userIds = [];
|
|
900
|
+
const certifications = [];
|
|
901
|
+
for (const packet of packets.slice(1)) {
|
|
902
|
+
if (packet.tag === 13)
|
|
903
|
+
userIds.push(packet.body);
|
|
904
|
+
else if (packet.tag === 14) {
|
|
905
|
+
const subkey = keyPacket(packet.body);
|
|
906
|
+
if (!subkey)
|
|
907
|
+
return { refusal: "verifone_key_malformed" };
|
|
908
|
+
subkeys.push({ ...subkey, body: packet.body, bindings: [] });
|
|
909
|
+
}
|
|
910
|
+
else if (packet.tag === 2) {
|
|
911
|
+
const subpackets = hashedSubpackets(packet.body);
|
|
912
|
+
if (!subpackets)
|
|
913
|
+
return { refusal: "verifone_key_malformed" };
|
|
914
|
+
const type = packet.body[1];
|
|
915
|
+
if (REVOCATIONS.includes(type))
|
|
916
|
+
return { refusal: "verifone_key_revoked" };
|
|
917
|
+
const signature = { type, subpackets };
|
|
918
|
+
if (type === 24 && subkeys.length > 0)
|
|
919
|
+
subkeys[subkeys.length - 1].bindings.push(signature);
|
|
920
|
+
else if (CERTIFICATIONS.includes(type) && subkeys.length === 0)
|
|
921
|
+
certifications.push(signature);
|
|
922
|
+
}
|
|
923
|
+
else if (packet.tag !== 12 && packet.tag !== 17)
|
|
924
|
+
return { refusal: "verifone_key_malformed" };
|
|
925
|
+
}
|
|
926
|
+
if (userIds.length !== 1 || certifications.length === 0)
|
|
927
|
+
return { refusal: "verifone_key_malformed" };
|
|
928
|
+
const flagsOf = (signatures) => signatures?.flatMap((signature) => signature.subpackets).find((sub) => sub.type === 27)?.data[0];
|
|
929
|
+
const canEncrypt = (algorithm, flags) => ENCRYPTING_ALGORITHMS.includes(algorithm) && (flags === void 0 || (flags & 12) !== 0);
|
|
930
|
+
const encrypting = [
|
|
931
|
+
...canEncrypt(primary.algorithm, flagsOf(certifications)) ? [{ ...primary, body: packets[0].body, bindings: null }] : [],
|
|
932
|
+
...subkeys.filter((subkey) => canEncrypt(subkey.algorithm, flagsOf(subkey.bindings)))
|
|
933
|
+
];
|
|
934
|
+
if (encrypting.length !== 1)
|
|
935
|
+
return { refusal: encrypting.length === 0 ? "verifone_key_not_encrypting" : "verifone_key_ambiguous" };
|
|
936
|
+
const key = encrypting[0];
|
|
937
|
+
if (key.algorithm !== ALGORITHM_ECDH)
|
|
938
|
+
return { refusal: "verifone_key_algorithm_unsupported" };
|
|
939
|
+
const ecdh = ecdhFields(key.fields);
|
|
940
|
+
if (ecdh.refusal)
|
|
941
|
+
return ecdh;
|
|
942
|
+
const now = facilities?.nowSeconds;
|
|
943
|
+
if (!Number.isSafeInteger(now))
|
|
944
|
+
return { refusal: "verifone_key_malformed" };
|
|
945
|
+
const expired = (created, signatures) => signatures.some((signature) => signature.subpackets.some((sub) => sub.type === 9 && sub.data.length === 4 && readU32(sub.data, 0) !== 0 && now >= created + readU32(sub.data, 0)));
|
|
946
|
+
if (expired(primary.created, certifications))
|
|
947
|
+
return { refusal: "verifone_key_expired" };
|
|
948
|
+
if (key.bindings !== null && (key.bindings.length === 0 || expired(key.created, key.bindings))) {
|
|
949
|
+
return { refusal: key.bindings.length === 0 ? "verifone_key_malformed" : "verifone_key_expired" };
|
|
950
|
+
}
|
|
951
|
+
const preferences = certifications[0].subpackets.find((sub) => sub.type === 11)?.data;
|
|
952
|
+
const first = preferences ? Array.from(preferences).find((algorithm) => KNOWN_SYMMETRIC.includes(algorithm)) : void 0;
|
|
953
|
+
const cipher = first ?? DEFAULT_SESSION_CIPHER;
|
|
954
|
+
if (!AES[cipher])
|
|
955
|
+
return { refusal: "verifone_key_cipher_unsupported" };
|
|
956
|
+
const subtle = facilities?.subtle;
|
|
957
|
+
if (!subtle)
|
|
958
|
+
return { refusal: "verifone_key_malformed" };
|
|
959
|
+
const fingerprint = await fingerprintOf(key.body, subtle);
|
|
960
|
+
const primaryFingerprint = await fingerprintOf(packets[0].body, subtle);
|
|
961
|
+
return Object.freeze({
|
|
962
|
+
primaryFingerprint: hex(primaryFingerprint),
|
|
963
|
+
encryptionSubkeyId: hex(fingerprint.subarray(12, 20)),
|
|
964
|
+
encryptionFingerprint: fingerprint,
|
|
965
|
+
point: ecdh.point,
|
|
966
|
+
kdfHash: ecdh.kdfHash,
|
|
967
|
+
kekAlgorithm: ecdh.kekAlgorithm,
|
|
968
|
+
sessionCipher: cipher
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
async function aesBlock(subtle, key, block) {
|
|
972
|
+
return new Uint8Array(await subtle.encrypt({ name: "AES-CBC", iv: new Uint8Array(16) }, key, block)).subarray(0, 16);
|
|
973
|
+
}
|
|
974
|
+
async function openpgpCfb(subtle, rawKey, plaintext) {
|
|
975
|
+
const key = await subtle.importKey("raw", rawKey, "AES-CBC", false, ["encrypt"]);
|
|
976
|
+
const out = new Uint8Array(plaintext.length);
|
|
977
|
+
let register = new Uint8Array(16);
|
|
978
|
+
for (let at = 0; at < plaintext.length; at += 16) {
|
|
979
|
+
const mask = await aesBlock(subtle, key, register);
|
|
980
|
+
const n = Math.min(16, plaintext.length - at);
|
|
981
|
+
for (let i = 0; i < n; i++)
|
|
982
|
+
out[at + i] = plaintext[at + i] ^ mask[i];
|
|
983
|
+
if (n < 16)
|
|
984
|
+
break;
|
|
985
|
+
register = out.slice(at, at + 16);
|
|
986
|
+
}
|
|
987
|
+
return out;
|
|
988
|
+
}
|
|
989
|
+
function armorMessage(message) {
|
|
990
|
+
const body = encodeBase64(message).match(/.{1,60}/g).join("\r\n");
|
|
991
|
+
const crc = crc24(message);
|
|
992
|
+
const checksum = encodeBase64(Uint8Array.of(crc >> 16, crc >> 8 & 255, crc & 255));
|
|
993
|
+
const armored = `${MESSAGE_BEGIN}\r
|
|
994
|
+
${ARMOR_HEADERS.join("\r\n")}\r
|
|
995
|
+
\r
|
|
996
|
+
${body}\r
|
|
997
|
+
=${checksum}\r
|
|
998
|
+
${MESSAGE_END}\r
|
|
999
|
+
`;
|
|
1000
|
+
return encodeBase64(asciiBytes(armored));
|
|
1001
|
+
}
|
|
1002
|
+
async function verifoneEncryptText(key, text, facilities) {
|
|
1003
|
+
const crypto = facilities?.crypto;
|
|
1004
|
+
const subtle = crypto?.subtle;
|
|
1005
|
+
if (!subtle || typeof crypto.getRandomValues !== "function")
|
|
1006
|
+
throw new Error("Refusing to encrypt: no WebCrypto.");
|
|
1007
|
+
if (!key || key.refusal || !(key.point instanceof Uint8Array) || !AES[key.sessionCipher] || !AES[key.kekAlgorithm] || !KDF_HASHES[key.kdfHash]) {
|
|
1008
|
+
throw new Error("Refusing to encrypt: the Verifone key is not one this codec reads.");
|
|
1009
|
+
}
|
|
1010
|
+
if (!Number.isSafeInteger(facilities.nowMs) || facilities.nowMs < 0)
|
|
1011
|
+
throw new Error("Refusing to encrypt: no clock.");
|
|
1012
|
+
const ephemeral = await subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
|
|
1013
|
+
const ephemeralPoint = new Uint8Array(await subtle.exportKey("raw", ephemeral.publicKey));
|
|
1014
|
+
const recipient = await subtle.importKey("raw", key.point, { name: "ECDH", namedCurve: "P-256" }, false, []);
|
|
1015
|
+
const shared = new Uint8Array(await subtle.deriveBits({ name: "ECDH", public: recipient }, ephemeral.privateKey, 256));
|
|
1016
|
+
const param = concat(Uint8Array.of(P256_OID.length), Uint8Array.from(P256_OID), Uint8Array.of(ALGORITHM_ECDH, 3, 1, key.kdfHash, key.kekAlgorithm), asciiBytes("Anonymous Sender "), key.encryptionFingerprint);
|
|
1017
|
+
const derived = new Uint8Array(await subtle.digest(KDF_HASHES[key.kdfHash], concat(u32(1), shared, param)));
|
|
1018
|
+
const kek = await subtle.importKey("raw", derived.subarray(0, AES[key.kekAlgorithm]), "AES-KW", false, ["wrapKey"]);
|
|
1019
|
+
const sessionKey = crypto.getRandomValues(new Uint8Array(AES[key.sessionCipher]));
|
|
1020
|
+
const checksum = sessionKey.reduce((sum, byte) => sum + byte & 65535, 0);
|
|
1021
|
+
const framed = concat(Uint8Array.of(key.sessionCipher), sessionKey, u16(checksum));
|
|
1022
|
+
const pad = 8 - framed.length % 8;
|
|
1023
|
+
const padded = concat(framed, new Uint8Array(pad).fill(pad));
|
|
1024
|
+
const toWrap = await subtle.importKey("raw", padded, { name: "HMAC", hash: "SHA-256", length: padded.length * 8 }, true, ["sign"]);
|
|
1025
|
+
const wrapped = new Uint8Array(await subtle.wrapKey("raw", toWrap, kek, "AES-KW"));
|
|
1026
|
+
const keyId = key.encryptionFingerprint.subarray(12, 20);
|
|
1027
|
+
const pkesk = concat(Uint8Array.of(3), keyId, Uint8Array.of(ALGORITHM_ECDH), u16(515), ephemeralPoint, Uint8Array.of(wrapped.length), wrapped);
|
|
1028
|
+
const data = new TextEncoder().encode(text);
|
|
1029
|
+
const literalBody = concat(Uint8Array.of(117, 7), asciiBytes("msg.txt"), u32(Math.floor(facilities.nowMs / 1e3)), data);
|
|
1030
|
+
const literal = concat(packetHeader(11, literalBody.length), literalBody);
|
|
1031
|
+
const prefix = crypto.getRandomValues(new Uint8Array(16));
|
|
1032
|
+
const plain = concat(prefix, prefix.subarray(14, 16), literal, Uint8Array.of(211, 20));
|
|
1033
|
+
const mdc = new Uint8Array(await subtle.digest("SHA-1", plain));
|
|
1034
|
+
const sealed = await openpgpCfb(subtle, sessionKey, concat(plain, mdc));
|
|
1035
|
+
const seipd = concat(Uint8Array.of(1), sealed);
|
|
1036
|
+
return armorMessage(concat(packetHeader(1, pkesk.length), pkesk, packetHeader(18, seipd.length), seipd));
|
|
1037
|
+
}
|
|
1038
|
+
// src/verifone-core.js
|
|
1039
|
+
var VERIFONE_TEST_PANS = Object.freeze([
|
|
1040
|
+
"4000000000002701",
|
|
1041
|
+
"5200000000002235",
|
|
1042
|
+
"340000000002708",
|
|
1043
|
+
"4000000000001000",
|
|
1044
|
+
"5200000000001005",
|
|
1045
|
+
"340000000001007",
|
|
1046
|
+
"4000000000002503",
|
|
1047
|
+
"5200000000002151",
|
|
1048
|
+
"4000000000001091"
|
|
1049
|
+
]);
|
|
1050
|
+
var VERIFONE_CLOCK_SKEW_MS = 12e4;
|
|
1051
|
+
var VERIFONE_BLOB_MAX = 4096;
|
|
1052
|
+
var PASS = Object.freeze({ verdict: "pass" });
|
|
1053
|
+
var abort = (reason) => Object.freeze({ verdict: "abort", reason });
|
|
1054
|
+
function textCarriesBlob(text) {
|
|
1055
|
+
return text.includes(VERIFONE_BLOB_PREFIX) || text.includes(PGP_MESSAGE_ARMOR);
|
|
1056
|
+
}
|
|
1057
|
+
function jsonStrings(root) {
|
|
1058
|
+
const out = [];
|
|
1059
|
+
const stack = [root];
|
|
1060
|
+
while (stack.length > 0) {
|
|
1061
|
+
const value = stack.pop();
|
|
1062
|
+
if (typeof value === "string")
|
|
1063
|
+
out.push(value);
|
|
1064
|
+
else if (Array.isArray(value))
|
|
1065
|
+
for (const item of value)
|
|
1066
|
+
stack.push(item);
|
|
1067
|
+
else if (value !== null && typeof value === "object") {
|
|
1068
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1069
|
+
out.push(key);
|
|
1070
|
+
stack.push(child);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return out;
|
|
1075
|
+
}
|
|
1076
|
+
function decodedTexts(text) {
|
|
1077
|
+
let parsed;
|
|
1078
|
+
if (/^\s*[[{"]/.test(text)) {
|
|
1079
|
+
try {
|
|
1080
|
+
parsed = JSON.parse(text);
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
parsed = void 0;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const out = [];
|
|
1087
|
+
if (parsed !== void 0)
|
|
1088
|
+
out.push(...jsonStrings(parsed));
|
|
1089
|
+
else if (/[%+]/.test(text)) {
|
|
1090
|
+
try {
|
|
1091
|
+
for (const [name, value] of new URLSearchParams(text))
|
|
1092
|
+
out.push(name, value);
|
|
1093
|
+
}
|
|
1094
|
+
catch {
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return out.filter((decoded) => decoded !== text);
|
|
1098
|
+
}
|
|
1099
|
+
var DECODE_DEPTH = 6;
|
|
1100
|
+
function carriesAt(text, depth) {
|
|
1101
|
+
if (textCarriesBlob(text))
|
|
1102
|
+
return true;
|
|
1103
|
+
const decoded = decodedTexts(text);
|
|
1104
|
+
if (decoded.length === 0)
|
|
1105
|
+
return false;
|
|
1106
|
+
if (depth >= DECODE_DEPTH)
|
|
1107
|
+
return true;
|
|
1108
|
+
return decoded.some((inner) => carriesAt(inner, depth + 1));
|
|
1109
|
+
}
|
|
1110
|
+
function carriesVerifoneBlob(body) {
|
|
1111
|
+
if (typeof body !== "string" || body === "")
|
|
1112
|
+
return false;
|
|
1113
|
+
return carriesAt(body, 0);
|
|
1114
|
+
}
|
|
1115
|
+
function blobStrings(parsed) {
|
|
1116
|
+
return jsonStrings(parsed).filter((text) => carriesAt(text, 0)).length;
|
|
1117
|
+
}
|
|
1118
|
+
var digitsOf = (value) => typeof value === "string" || typeof value === "number" ? String(value).replace(/[ -]/g, "") : "";
|
|
1119
|
+
function verifoneCardPlaintext(card, nowMs) {
|
|
1120
|
+
const number = digitsOf(card?.number);
|
|
1121
|
+
if (!/^\d{12,19}$/.test(number))
|
|
1122
|
+
throw new Error("Refusing to encrypt: the card number is not 12 to 19 digits.");
|
|
1123
|
+
const monthText = String(card?.month ?? "");
|
|
1124
|
+
if (!/^\d{1,2}$/.test(monthText) || Number(monthText) < 1 || Number(monthText) > 12)
|
|
1125
|
+
throw new Error("Refusing to encrypt: the expiry month is not 1 to 12.");
|
|
1126
|
+
const yearText = String(card?.year ?? "");
|
|
1127
|
+
if (!/^(?:\d{2}|\d{4})$/.test(yearText))
|
|
1128
|
+
throw new Error("Refusing to encrypt: the expiry year is not two or four digits.");
|
|
1129
|
+
const year = yearText.length === 2 ? 2e3 + Number(yearText) : Number(yearText);
|
|
1130
|
+
const cvv = typeof card?.cvc === "string" ? card.cvc : "";
|
|
1131
|
+
if (!/^\d{3,4}$/.test(cvv))
|
|
1132
|
+
throw new Error("Refusing to encrypt: the security code is not three or four digits.");
|
|
1133
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0)
|
|
1134
|
+
throw new Error("Refusing to encrypt: no clock.");
|
|
1135
|
+
const captureTime = `${new Date(nowMs).toISOString().split(".")[0]}Z`;
|
|
1136
|
+
return JSON.stringify({ cardNumber: number, expiryMonth: Number(monthText), expiryYear: year, cvv, captureTime });
|
|
1137
|
+
}
|
|
1138
|
+
function verifoneClockSkewRefusal(deviceNowMs, serverNowMs) {
|
|
1139
|
+
if (!Number.isSafeInteger(deviceNowMs) || !Number.isSafeInteger(serverNowMs))
|
|
1140
|
+
return "verifone_clock_unknown";
|
|
1141
|
+
return Math.abs(deviceNowMs - serverNowMs) > VERIFONE_CLOCK_SKEW_MS ? "verifone_clock_skew" : null;
|
|
1142
|
+
}
|
|
1143
|
+
var KEY_REFUSALS = Object.freeze({
|
|
1144
|
+
verifone_key_unpinned: "Refusing to encrypt: Agentcard has not pinned this Verifone key.",
|
|
1145
|
+
verifone_key_curve_unsupported: "Refusing to encrypt: the Verifone key is not on NIST P-256.",
|
|
1146
|
+
verifone_key_expired: "Refusing to encrypt: this Verifone key has expired.",
|
|
1147
|
+
verifone_key_revoked: "Refusing to encrypt: this Verifone key was revoked."
|
|
1148
|
+
});
|
|
1149
|
+
var KEY_UNUSABLE = "Refusing to encrypt: the Verifone key is not one Agentcard can use.";
|
|
1150
|
+
function makeVerifone(tables) {
|
|
1151
|
+
const profiles = tables.profiles;
|
|
1152
|
+
const pins = tables.pins;
|
|
1153
|
+
const trusted = (id) => trustedVerifoneProfile(id, profiles);
|
|
1154
|
+
const pinOf = (profile) => verifoneProfilePin(profile, pins);
|
|
1155
|
+
function amountRefusal(profile, parsed) {
|
|
1156
|
+
if (profile.amount === null)
|
|
1157
|
+
return null;
|
|
1158
|
+
const at = jsonAt(parsed, profile.amount);
|
|
1159
|
+
const amount = at.found ? at.value : void 0;
|
|
1160
|
+
const value = isPlainObject(amount) ? amount.value : void 0;
|
|
1161
|
+
const currency = isPlainObject(amount) ? amount.currency : void 0;
|
|
1162
|
+
const valueOk = typeof value === "number" ? Number.isSafeInteger(value) && value >= 0 : typeof value === "string" && /^\d{1,15}$/.test(value);
|
|
1163
|
+
const currencyOk = typeof currency === "string" && /^[A-Za-z]{3}$/.test(currency);
|
|
1164
|
+
return valueOk && currencyOk ? null : `the amount where ${profile.merchant}'s card request holds it is not the {value, currency} Agentcard reviewed, so it cannot be checked`;
|
|
1165
|
+
}
|
|
1166
|
+
function legBlob(leg, parsed) {
|
|
1167
|
+
const holder = jsonAt(parsed, leg.holder);
|
|
1168
|
+
if (!holder.found || !isPlainObject(holder.value) || !Object.prototype.hasOwnProperty.call(holder.value, leg.field))
|
|
1169
|
+
return null;
|
|
1170
|
+
const value = holder.value[leg.field];
|
|
1171
|
+
return typeof value === "string" && value.length <= VERIFONE_BLOB_MAX && verifoneBlobInfo(value) ? value : null;
|
|
1172
|
+
}
|
|
1173
|
+
function classifyRequest(profileId, rawUrl, method, body) {
|
|
1174
|
+
const profile = trusted(profileId);
|
|
1175
|
+
if (typeof rawUrl !== "string" || !isVerifoneProfileOrigin(profile, rawUrl) || !carriesVerifoneBlob(body))
|
|
1176
|
+
return PASS;
|
|
1177
|
+
const leg = verifoneLegFor(profile, rawUrl, method);
|
|
1178
|
+
if (!leg)
|
|
1179
|
+
return abort(`the paused request sends a Verifone-encrypted card to ${profile.merchant} somewhere other than a card request Agentcard reviewed`);
|
|
1180
|
+
const refusal = verifoneLegBodyRefusal(profile, leg, body);
|
|
1181
|
+
if (refusal)
|
|
1182
|
+
return abort(refusal);
|
|
1183
|
+
const parsed = JSON.parse(body);
|
|
1184
|
+
const placeholder = legBlob(leg, parsed);
|
|
1185
|
+
if (!placeholder)
|
|
1186
|
+
return abort(`the paused body does not carry the encrypted card where ${profile.merchant}'s card request holds it`);
|
|
1187
|
+
if (blobStrings(parsed) !== 1)
|
|
1188
|
+
return abort("the paused body carries more than one encrypted card");
|
|
1189
|
+
const pin = pinOf(profile);
|
|
1190
|
+
if (!pin)
|
|
1191
|
+
return abort(`Agentcard has not reviewed ${profile.merchant}'s Verifone key`);
|
|
1192
|
+
if (verifoneBlobInfo(placeholder).recipientKeyId !== pin.encryptionSubkeyId) {
|
|
1193
|
+
return abort(`the page encrypted the card under a Verifone key Agentcard has not reviewed for ${profile.merchant}`);
|
|
1194
|
+
}
|
|
1195
|
+
const amount = amountRefusal(profile, parsed);
|
|
1196
|
+
return amount ? abort(amount) : Object.freeze({ verdict: "pause", leg: leg.name });
|
|
1197
|
+
}
|
|
1198
|
+
function legName(profileId, rawUrl, method, checkoutOrigin) {
|
|
1199
|
+
const profile = trusted(profileId);
|
|
1200
|
+
return verifoneLegFor(profile, rawUrl, method, { checkoutOrigin })?.name ?? null;
|
|
1201
|
+
}
|
|
1202
|
+
function placeholderBlob(profileId, request) {
|
|
1203
|
+
const verdict = classifyRequest(profileId, request?.url, request?.method, request?.body);
|
|
1204
|
+
if (verdict.verdict !== "pause")
|
|
1205
|
+
return null;
|
|
1206
|
+
const profile = trusted(profileId);
|
|
1207
|
+
return legBlob(profile.legs.find((leg) => leg.name === verdict.leg), JSON.parse(request.body));
|
|
1208
|
+
}
|
|
1209
|
+
async function encryptCard(card, request, facilities) {
|
|
1210
|
+
const profile = verifoneProfileById(request?.profile, profiles);
|
|
1211
|
+
if (!profile)
|
|
1212
|
+
throw new Error("Refusing to encrypt: unknown Verifone merchant profile.");
|
|
1213
|
+
if (verifoneProfileRefusal(profile, pins) !== null)
|
|
1214
|
+
throw new Error("Refusing to encrypt: the Verifone merchant profile is not a reviewed one.");
|
|
1215
|
+
if (profile.status !== "enabled")
|
|
1216
|
+
throw new Error(`Refusing to encrypt: Agentcard has not turned on payments to ${profile.merchant} yet.`);
|
|
1217
|
+
if (!verifoneLegFor(profile, request.url, request.method))
|
|
1218
|
+
throw new Error("Refusing to encrypt: unsupported Verifone card request.");
|
|
1219
|
+
const pin = pinOf(profile);
|
|
1220
|
+
if (!pin)
|
|
1221
|
+
throw new Error(KEY_REFUSALS.verifone_key_unpinned);
|
|
1222
|
+
if (verifoneKeyPinShapeRefusal(pin) !== null)
|
|
1223
|
+
throw new Error(KEY_UNUSABLE);
|
|
1224
|
+
if (typeof request.sandbox !== "boolean" || pin.environment === "sandbox" !== request.sandbox) {
|
|
1225
|
+
throw new Error("Refusing to encrypt: this Verifone key is for another environment than this payment's.");
|
|
1226
|
+
}
|
|
1227
|
+
if (pin.environment === "sandbox" && !VERIFONE_TEST_PANS.includes(digitsOf(card?.number))) {
|
|
1228
|
+
throw new Error("Refusing to encrypt: the Verifone sandbox key encrypts only Verifone's published test cards.");
|
|
1229
|
+
}
|
|
1230
|
+
const nowMs = facilities?.nowMs;
|
|
1231
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0)
|
|
1232
|
+
throw new Error("Refusing to encrypt: no clock.");
|
|
1233
|
+
const key = await readVerifonePublicKey(pin.publicKeyBase64, { subtle: facilities?.crypto?.subtle, nowSeconds: Math.floor(nowMs / 1e3) });
|
|
1234
|
+
if (key.refusal)
|
|
1235
|
+
throw new Error(KEY_REFUSALS[key.refusal] ?? KEY_UNUSABLE);
|
|
1236
|
+
if (key.primaryFingerprint !== pin.primaryFingerprint || key.encryptionSubkeyId !== pin.encryptionSubkeyId) {
|
|
1237
|
+
throw new Error("Refusing to encrypt: the Verifone key is not the one Agentcard pinned.");
|
|
1238
|
+
}
|
|
1239
|
+
return verifoneEncryptText(key, verifoneCardPlaintext(card, nowMs), facilities);
|
|
1240
|
+
}
|
|
1241
|
+
function substitution(profileId, blob) {
|
|
1242
|
+
const profile = trusted(profileId);
|
|
1243
|
+
if (typeof blob !== "string" || blob.length > VERIFONE_BLOB_MAX || !verifoneBlobInfo(blob))
|
|
1244
|
+
throw new Error("Refusing the Verifone blob: it is not one verifone.js writes.");
|
|
1245
|
+
return { encoding: "json", kind: "verifone", profile: profile.id, blob };
|
|
1246
|
+
}
|
|
1247
|
+
function substitute(request, sub) {
|
|
1248
|
+
if (!isPlainObject(sub) || Object.keys(sub).sort().join(",") !== "blob,encoding,kind,profile" || sub.encoding !== "json" || sub.kind !== "verifone") {
|
|
1249
|
+
throw new Error("Refusing to substitute: not a Verifone substitution.");
|
|
1250
|
+
}
|
|
1251
|
+
const profile = trusted(sub.profile);
|
|
1252
|
+
if (profile.status !== "enabled")
|
|
1253
|
+
throw new Error(`Refusing to substitute: Agentcard has not turned on payments to ${profile.merchant} yet.`);
|
|
1254
|
+
const verdict = classifyRequest(profile.id, request?.url, request?.method, request?.body);
|
|
1255
|
+
if (verdict.verdict !== "pause")
|
|
1256
|
+
throw new Error("Refusing to substitute: the paused request is not the card request Agentcard reviewed.");
|
|
1257
|
+
const info = typeof sub.blob === "string" && sub.blob.length <= VERIFONE_BLOB_MAX ? verifoneBlobInfo(sub.blob) : null;
|
|
1258
|
+
const pin = pinOf(profile);
|
|
1259
|
+
if (!info || !pin || info.recipientKeyId !== pin.encryptionSubkeyId)
|
|
1260
|
+
throw new Error("Refusing to substitute: the blob is not under the key Agentcard pinned.");
|
|
1261
|
+
const parsed = JSON.parse(request.body);
|
|
1262
|
+
const leg = profile.legs.find((candidate) => candidate.name === verdict.leg);
|
|
1263
|
+
const holder = jsonAt(parsed, leg.holder).value;
|
|
1264
|
+
if (holder[leg.field] === sub.blob)
|
|
1265
|
+
throw new Error("Refusing to substitute: the blob is the page's own.");
|
|
1266
|
+
holder[leg.field] = sub.blob;
|
|
1267
|
+
return JSON.stringify(parsed);
|
|
1268
|
+
}
|
|
1269
|
+
function approvedBlobRefusal(blob, context) {
|
|
1270
|
+
if (typeof blob !== "string" || blob.length > VERIFONE_BLOB_MAX)
|
|
1271
|
+
return "verifone_substitution_invalid";
|
|
1272
|
+
const info = verifoneBlobInfo(blob);
|
|
1273
|
+
if (!info)
|
|
1274
|
+
return "verifone_substitution_invalid";
|
|
1275
|
+
if (!isPlainObject(context) || typeof context.sandbox !== "boolean" || typeof context.placeholderSha256 !== "string" || !/^[0-9a-f]{64}$/.test(context.placeholderSha256))
|
|
1276
|
+
return "verifone_approval_context_invalid";
|
|
1277
|
+
const profile = verifoneProfileById(context.profile, profiles);
|
|
1278
|
+
if (!profile)
|
|
1279
|
+
return "verifone_profile_unknown";
|
|
1280
|
+
if (!verifoneLegFor(profile, context.url, context.method))
|
|
1281
|
+
return "verifone_request_invalid";
|
|
1282
|
+
const pin = pinOf(profile);
|
|
1283
|
+
if (!pin)
|
|
1284
|
+
return "verifone_key_unpinned";
|
|
1285
|
+
if (pin.environment === "sandbox" !== context.sandbox)
|
|
1286
|
+
return "verifone_environment_mismatch";
|
|
1287
|
+
if (info.recipientKeyId !== pin.encryptionSubkeyId)
|
|
1288
|
+
return "verifone_key_mismatch";
|
|
1289
|
+
if (sha256Hex(blob) === context.placeholderSha256)
|
|
1290
|
+
return "verifone_blob_unchanged";
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
function isPreparedRequest(url, method, body, profileId) {
|
|
1294
|
+
const profile = verifoneProfileById(profileId, profiles);
|
|
1295
|
+
return !!profile && profile.status === "enabled" && verifoneProfileRefusal(profile, pins) === null && classifyRequest(profile.id, url, method, body).verdict === "pause";
|
|
1296
|
+
}
|
|
1297
|
+
function endpointFor(profileId, checkoutOrigin) {
|
|
1298
|
+
const profile = trusted(profileId);
|
|
1299
|
+
const pair = profile.origins.find((candidate) => candidate.checkout === checkoutOrigin);
|
|
1300
|
+
if (!pair || profile.legs.length !== 1)
|
|
1301
|
+
return null;
|
|
1302
|
+
const leg = profile.legs[0];
|
|
1303
|
+
if (leg.path.split("/").some((segment) => segment.startsWith(":")) || Object.values(leg.query).some((value) => value.startsWith(":")))
|
|
1304
|
+
return null;
|
|
1305
|
+
const query = Object.entries(leg.query).map(([key, value]) => `${key}=${value}`).join("&");
|
|
1306
|
+
const url = `${pair.endpoint}${leg.path}${query ? `?${query}` : ""}`;
|
|
1307
|
+
return urlTemplateMatch({ path: leg.path, query: leg.query }, url) ? url : null;
|
|
1308
|
+
}
|
|
1309
|
+
function templatedUrl(profileId, rawUrl) {
|
|
1310
|
+
const profile = trusted(profileId);
|
|
1311
|
+
let origin = null;
|
|
1312
|
+
try {
|
|
1313
|
+
origin = new URL(rawUrl).origin;
|
|
1314
|
+
}
|
|
1315
|
+
catch {
|
|
1316
|
+
origin = null;
|
|
1317
|
+
}
|
|
1318
|
+
const pair = profile.origins.find((candidate) => candidate.endpoint === origin) ?? profile.origins[0];
|
|
1319
|
+
const leg = typeof rawUrl === "string" && typeof origin === "string" ? verifoneLegFor(profile, rawUrl, "POST") : null;
|
|
1320
|
+
return leg ? `${pair.endpoint}${leg.path}` : `${pair.endpoint}/`;
|
|
1321
|
+
}
|
|
1322
|
+
function urlGlobs(profileId) {
|
|
1323
|
+
const profile = trusted(profileId);
|
|
1324
|
+
return [...new Set(profile.origins.map((pair) => `${pair.endpoint}/*`))];
|
|
1325
|
+
}
|
|
1326
|
+
return Object.freeze({
|
|
1327
|
+
classifyRequest,
|
|
1328
|
+
legName,
|
|
1329
|
+
placeholderBlob,
|
|
1330
|
+
encryptCard,
|
|
1331
|
+
substitution,
|
|
1332
|
+
substitute,
|
|
1333
|
+
approvedBlobRefusal,
|
|
1334
|
+
isPreparedRequest,
|
|
1335
|
+
endpointFor,
|
|
1336
|
+
templatedUrl,
|
|
1337
|
+
urlGlobs,
|
|
1338
|
+
amountPath: (profileId) => {
|
|
1339
|
+
const path = trusted(profileId).amount;
|
|
1340
|
+
return path === null ? null : [...path];
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
// src/verifone.js
|
|
1345
|
+
var core = /* @__PURE__ */ makeVerifone({ profiles: VERIFONE_MERCHANT_PROFILES, pins: VERIFONE_KEY_PINS });
|
|
1346
|
+
var VERIFONE_SUBSTITUTION = /* @__PURE__ */ Object.freeze({ at: "merchant_profile", fields: Object.freeze(["encryptedCard"]) });
|
|
1347
|
+
function classifyVerifoneRequest(profileId, url, method, body) {
|
|
1348
|
+
return core.classifyRequest(profileId, url, method, body);
|
|
1349
|
+
}
|
|
1350
|
+
function verifoneLegName(profileId, url, method, checkoutOrigin) {
|
|
1351
|
+
return core.legName(profileId, url, method, checkoutOrigin);
|
|
1352
|
+
}
|
|
1353
|
+
function verifonePlaceholderBlob(profileId, request) {
|
|
1354
|
+
return core.placeholderBlob(profileId, request);
|
|
1355
|
+
}
|
|
1356
|
+
function encryptVerifoneCard(card, request, facilities) {
|
|
1357
|
+
return core.encryptCard(card, request, facilities);
|
|
1358
|
+
}
|
|
1359
|
+
function verifoneSubstitution(profileId, blob) {
|
|
1360
|
+
return core.substitution(profileId, blob);
|
|
1361
|
+
}
|
|
1362
|
+
function substituteVerifoneBlob(request, sub) {
|
|
1363
|
+
return core.substitute(request, sub);
|
|
1364
|
+
}
|
|
1365
|
+
function verifoneApprovedBlobRefusal(blob, context) {
|
|
1366
|
+
return core.approvedBlobRefusal(blob, context);
|
|
1367
|
+
}
|
|
1368
|
+
function isPreparedVerifoneRequest(url, method, body, profileId) {
|
|
1369
|
+
return core.isPreparedRequest(url, method, body, profileId);
|
|
1370
|
+
}
|
|
1371
|
+
function verifoneProfileEndpointFor(profileId, checkoutOrigin) {
|
|
1372
|
+
return core.endpointFor(profileId, checkoutOrigin);
|
|
1373
|
+
}
|
|
1374
|
+
function verifoneProfileTemplatedUrl(profileId, url) {
|
|
1375
|
+
return core.templatedUrl(profileId, url);
|
|
1376
|
+
}
|
|
1377
|
+
function verifoneProfileUrlGlobs(profileId) {
|
|
1378
|
+
return core.urlGlobs(profileId);
|
|
1379
|
+
}
|
|
1380
|
+
function verifoneProfileAmountPath(profileId) {
|
|
1381
|
+
return core.amountPath(profileId);
|
|
1382
|
+
}
|
|
1383
|
+
function verifoneProfileEnvironment(profileId) {
|
|
1384
|
+
const profile = verifoneProfileById(profileId);
|
|
1385
|
+
if (!profile)
|
|
1386
|
+
throw new Error("Refusing to pay: unknown Verifone merchant profile.");
|
|
1387
|
+
return profile.environment;
|
|
1388
|
+
}
|
|
1389
|
+
function verifoneMerchantProfile(id) {
|
|
1390
|
+
const profile = verifoneProfileById(id);
|
|
1391
|
+
return profile ? deepFreeze({ id: profile.id, merchant: profile.merchant, status: profile.status, environment: profile.environment }) : null;
|
|
1392
|
+
}
|
|
1393
|
+
function canonical(value) {
|
|
1394
|
+
if (Array.isArray(value))
|
|
1395
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
1396
|
+
if (isPlainObject(value))
|
|
1397
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
1398
|
+
return JSON.stringify(value) ?? "null";
|
|
1399
|
+
}
|
|
1400
|
+
function verifoneProfileRulesMatch(profileId, served) {
|
|
1401
|
+
const built = VERIFONE_PROFILE_RECOGNIZERS.find((entry) => entry.id === profileId);
|
|
1402
|
+
if (!built || !isPlainObject(served) || served.id !== built.id)
|
|
1403
|
+
return false;
|
|
1404
|
+
const { status: _built, ...mine } = built;
|
|
1405
|
+
const { status: _served, ...theirs } = served;
|
|
1406
|
+
try {
|
|
1407
|
+
return canonical(mine) === canonical(theirs);
|
|
1408
|
+
}
|
|
1409
|
+
catch {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
export { VERIFONE_BLOB_MAX, VERIFONE_BLOB_PREFIX, VERIFONE_CLOCK_SKEW_MS, VERIFONE_KEY_PINS, VERIFONE_MERCHANT_PROFILES, VERIFONE_PROFILE_RECOGNIZERS, VERIFONE_SUBSTITUTION, VERIFONE_TEST_PANS, carriesVerifoneBlob, classifyVerifoneRequest, encryptVerifoneCard, isPreparedVerifoneRequest, isVerifoneBlob, substituteVerifoneBlob, verifoneApprovedBlobRefusal, verifoneBlobInfo, verifoneCardPlaintext, verifoneClockSkewRefusal, verifoneKeyPinShapeRefusal, verifoneLegName, verifoneMerchantProfile, verifonePlaceholderBlob, verifoneProfileAmountPath, verifoneProfileEndpointFor, verifoneProfileEnvironment, verifoneProfileRefusal, verifoneProfileRulesMatch, verifoneProfileTemplatedUrl, verifoneProfileUrlGlobs, verifoneSubstitution };
|