@agent-cards/sdk 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/PREFLIGHT.md +34 -0
- package/README.md +1217 -15
- package/dist/aci.d.ts +173 -0
- package/dist/aci.generated.d.ts +87 -0
- package/dist/aci.generated.js +306 -0
- package/dist/aci.js +371 -0
- package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
- package/dist/adyen-merchant-hosted.generated.js +283 -49
- package/dist/agentcard.d.ts +9 -2
- package/dist/agentcard.js +4 -2
- package/dist/amadeus-checkout.generated.d.ts +57 -0
- package/dist/amadeus-checkout.generated.js +549 -0
- package/dist/amadeus.d.ts +41 -0
- package/dist/amadeus.js +81 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-endpoint.d.ts +177 -0
- package/dist/card-endpoint.js +286 -0
- package/dist/card-fields.generated.d.ts +5 -3
- package/dist/card-fields.generated.js +284 -7
- package/dist/cdp.d.ts +6 -1
- package/dist/cdp.js +983 -147
- package/dist/client.d.ts +493 -13
- package/dist/client.js +1100 -82
- package/dist/creditguard.generated.d.ts +79 -0
- package/dist/creditguard.generated.js +662 -0
- package/dist/cse-body.js +11 -1
- package/dist/cybersource-flex.d.ts +183 -0
- package/dist/cybersource-flex.generated.d.ts +92 -0
- package/dist/cybersource-flex.generated.js +689 -0
- package/dist/cybersource-flex.js +275 -0
- package/dist/datatrans.d.ts +96 -0
- package/dist/datatrans.generated.d.ts +64 -0
- package/dist/datatrans.generated.js +426 -0
- package/dist/datatrans.js +178 -0
- package/dist/elavon-converge.d.ts +37 -0
- package/dist/elavon-converge.generated.d.ts +49 -0
- package/dist/elavon-converge.generated.js +439 -0
- package/dist/elavon-converge.js +84 -0
- package/dist/eprotect.d.ts +152 -0
- package/dist/eprotect.generated.d.ts +77 -0
- package/dist/eprotect.generated.js +498 -0
- package/dist/eprotect.js +222 -0
- package/dist/gmo.d.ts +63 -0
- package/dist/gmo.generated.d.ts +60 -0
- package/dist/gmo.generated.js +586 -0
- package/dist/gmo.js +119 -0
- package/dist/hosted-form.d.ts +3 -2
- package/dist/hosted-form.js +3 -2
- package/dist/index.d.ts +19 -3
- package/dist/index.js +6 -1
- package/dist/juspay-gate.d.ts +82 -0
- package/dist/juspay-gate.js +126 -0
- package/dist/juspay.generated.d.ts +89 -0
- package/dist/juspay.generated.js +705 -0
- package/dist/lifecycle.d.ts +62 -6
- package/dist/lifecycle.js +123 -8
- package/dist/merchant-card-endpoints.generated.d.ts +63 -0
- package/dist/merchant-card-endpoints.generated.js +1103 -0
- package/dist/merchant-hosted.d.ts +4 -1
- package/dist/merchant-hosted.js +5 -2
- package/dist/merchant-session-probe.d.ts +48 -0
- package/dist/merchant-session-probe.js +53 -0
- package/dist/merchant-session.generated.d.ts +69 -0
- package/dist/merchant-session.generated.js +919 -0
- package/dist/payrails.d.ts +133 -0
- package/dist/payrails.generated.d.ts +73 -0
- package/dist/payrails.generated.js +596 -0
- package/dist/payrails.js +305 -0
- package/dist/pelecard.d.ts +32 -0
- package/dist/pelecard.generated.d.ts +32 -0
- package/dist/pelecard.generated.js +283 -0
- package/dist/pelecard.js +42 -0
- package/dist/pre-claim.d.ts +84 -3
- package/dist/pre-claim.js +987 -9
- package/dist/preflight-catalog.json +2310 -263
- package/dist/preflight-schemas.json +187 -6
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +247 -4
- package/dist/preparation.d.ts +5 -2
- package/dist/preparation.js +36 -10
- package/dist/prepared-processor.d.ts +86 -5
- package/dist/prepared-processor.js +157 -6
- package/dist/primer.d.ts +53 -0
- package/dist/primer.generated.d.ts +38 -0
- package/dist/primer.generated.js +624 -0
- package/dist/primer.js +82 -0
- package/dist/registry.d.ts +114 -12
- package/dist/registry.js +85 -11
- package/dist/shiji-sts.d.ts +144 -0
- package/dist/shiji-sts.generated.d.ts +72 -0
- package/dist/shiji-sts.generated.js +913 -0
- package/dist/shiji-sts.js +222 -0
- package/dist/stripe-checkout.generated.d.ts +1 -1
- package/dist/stripe-checkout.generated.js +735 -10
- package/dist/stripe-setup.d.ts +16 -0
- package/dist/stripe-setup.js +61 -0
- package/dist/substitutions.generated.js +187 -29
- package/dist/verifone.d.ts +68 -0
- package/dist/verifone.generated.d.ts +51 -0
- package/dist/verifone.generated.js +1401 -0
- package/dist/verifone.js +119 -0
- package/dist/voice-types.d.ts +27 -0
- package/dist/voice.d.ts +6 -1
- package/dist/voice.js +6 -1
- package/dist/wspay.d.ts +41 -0
- package/dist/wspay.generated.d.ts +41 -0
- package/dist/wspay.generated.js +370 -0
- package/dist/wspay.js +65 -0
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
+
// artifact-sha256: 364c3799a825b2f0a1058cadcf9aec6c6bf63d845616a9d451f048b8baa4f8de
|
|
4
|
+
// src/sha256.js
|
|
5
|
+
var K = /* @__PURE__ */ new Uint32Array([
|
|
6
|
+
1116352408,
|
|
7
|
+
1899447441,
|
|
8
|
+
3049323471,
|
|
9
|
+
3921009573,
|
|
10
|
+
961987163,
|
|
11
|
+
1508970993,
|
|
12
|
+
2453635748,
|
|
13
|
+
2870763221,
|
|
14
|
+
3624381080,
|
|
15
|
+
310598401,
|
|
16
|
+
607225278,
|
|
17
|
+
1426881987,
|
|
18
|
+
1925078388,
|
|
19
|
+
2162078206,
|
|
20
|
+
2614888103,
|
|
21
|
+
3248222580,
|
|
22
|
+
3835390401,
|
|
23
|
+
4022224774,
|
|
24
|
+
264347078,
|
|
25
|
+
604807628,
|
|
26
|
+
770255983,
|
|
27
|
+
1249150122,
|
|
28
|
+
1555081692,
|
|
29
|
+
1996064986,
|
|
30
|
+
2554220882,
|
|
31
|
+
2821834349,
|
|
32
|
+
2952996808,
|
|
33
|
+
3210313671,
|
|
34
|
+
3336571891,
|
|
35
|
+
3584528711,
|
|
36
|
+
113926993,
|
|
37
|
+
338241895,
|
|
38
|
+
666307205,
|
|
39
|
+
773529912,
|
|
40
|
+
1294757372,
|
|
41
|
+
1396182291,
|
|
42
|
+
1695183700,
|
|
43
|
+
1986661051,
|
|
44
|
+
2177026350,
|
|
45
|
+
2456956037,
|
|
46
|
+
2730485921,
|
|
47
|
+
2820302411,
|
|
48
|
+
3259730800,
|
|
49
|
+
3345764771,
|
|
50
|
+
3516065817,
|
|
51
|
+
3600352804,
|
|
52
|
+
4094571909,
|
|
53
|
+
275423344,
|
|
54
|
+
430227734,
|
|
55
|
+
506948616,
|
|
56
|
+
659060556,
|
|
57
|
+
883997877,
|
|
58
|
+
958139571,
|
|
59
|
+
1322822218,
|
|
60
|
+
1537002063,
|
|
61
|
+
1747873779,
|
|
62
|
+
1955562222,
|
|
63
|
+
2024104815,
|
|
64
|
+
2227730452,
|
|
65
|
+
2361852424,
|
|
66
|
+
2428436474,
|
|
67
|
+
2756734187,
|
|
68
|
+
3204031479,
|
|
69
|
+
3329325298
|
|
70
|
+
]);
|
|
71
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
72
|
+
function utf8(text) {
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const char of text) {
|
|
75
|
+
let cp = char.codePointAt(0);
|
|
76
|
+
if (cp >= 55296 && cp <= 57343)
|
|
77
|
+
cp = 65533;
|
|
78
|
+
if (cp < 128)
|
|
79
|
+
out.push(cp);
|
|
80
|
+
else if (cp < 2048)
|
|
81
|
+
out.push(192 | cp >> 6, 128 | cp & 63);
|
|
82
|
+
else if (cp < 65536)
|
|
83
|
+
out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
84
|
+
else
|
|
85
|
+
out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
86
|
+
}
|
|
87
|
+
return Uint8Array.from(out);
|
|
88
|
+
}
|
|
89
|
+
function sha256Hex(input) {
|
|
90
|
+
const isBytes = Object.prototype.toString.call(input) === "[object Uint8Array]";
|
|
91
|
+
if (typeof input !== "string" && !isBytes)
|
|
92
|
+
throw new TypeError("sha256Hex takes a string or a Uint8Array.");
|
|
93
|
+
const bytes = typeof input === "string" ? utf8(input) : input;
|
|
94
|
+
const length = bytes.length;
|
|
95
|
+
const padded = new Uint8Array((length + 8 >> 6) + 1 << 6);
|
|
96
|
+
padded.set(bytes);
|
|
97
|
+
padded[length] = 128;
|
|
98
|
+
const view = new DataView(padded.buffer);
|
|
99
|
+
view.setUint32(padded.length - 8, Math.floor(length / 536870912));
|
|
100
|
+
view.setUint32(padded.length - 4, length << 3 >>> 0);
|
|
101
|
+
const H = new Uint32Array([1779033703, 3144134277, 1013904242, 2773480762, 1359893119, 2600822924, 528734635, 1541459225]);
|
|
102
|
+
const W = new Uint32Array(64);
|
|
103
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
104
|
+
for (let t = 0; t < 16; t++)
|
|
105
|
+
W[t] = view.getUint32(offset + t * 4);
|
|
106
|
+
for (let t = 16; t < 64; t++) {
|
|
107
|
+
const s0 = rotr(W[t - 15], 7) ^ rotr(W[t - 15], 18) ^ W[t - 15] >>> 3;
|
|
108
|
+
const s1 = rotr(W[t - 2], 17) ^ rotr(W[t - 2], 19) ^ W[t - 2] >>> 10;
|
|
109
|
+
W[t] = W[t - 16] + s0 + W[t - 7] + s1 >>> 0;
|
|
110
|
+
}
|
|
111
|
+
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];
|
|
112
|
+
for (let t = 0; t < 64; t++) {
|
|
113
|
+
const t1 = h + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[t] + W[t] >>> 0;
|
|
114
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
115
|
+
h = g;
|
|
116
|
+
g = f;
|
|
117
|
+
f = e;
|
|
118
|
+
e = d + t1 >>> 0;
|
|
119
|
+
d = c;
|
|
120
|
+
c = b;
|
|
121
|
+
b = a;
|
|
122
|
+
a = t1 + t2 >>> 0;
|
|
123
|
+
}
|
|
124
|
+
H[0] += a;
|
|
125
|
+
H[1] += b;
|
|
126
|
+
H[2] += c;
|
|
127
|
+
H[3] += d;
|
|
128
|
+
H[4] += e;
|
|
129
|
+
H[5] += f;
|
|
130
|
+
H[6] += g;
|
|
131
|
+
H[7] += h;
|
|
132
|
+
}
|
|
133
|
+
return Array.from(H, (word) => word.toString(16).padStart(8, "0")).join("");
|
|
134
|
+
}
|
|
135
|
+
// src/rsa-pkcs1v15.js
|
|
136
|
+
var RsaPkcs1v15Error = class extends Error {
|
|
137
|
+
constructor(reason) {
|
|
138
|
+
super("RSA PKCS#1 v1.5 encryption refused.");
|
|
139
|
+
this.name = "RsaPkcs1v15Error";
|
|
140
|
+
this.code = "rsa_pkcs1v15_refused";
|
|
141
|
+
this.reason = reason;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var refuse = (reason) => new RsaPkcs1v15Error(reason);
|
|
145
|
+
var BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
146
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
147
|
+
var RSA_ALGORITHM = [48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0];
|
|
148
|
+
var MODULUS_BYTES = [128, 256, 384, 512];
|
|
149
|
+
var MIN_PADDING = 8;
|
|
150
|
+
var MAX_DRAWS = 16;
|
|
151
|
+
var TYPED_ARRAY = Object.getPrototypeOf(Uint8Array.prototype);
|
|
152
|
+
var typedArrayName = Object.getOwnPropertyDescriptor(TYPED_ARRAY, Symbol.toStringTag).get;
|
|
153
|
+
function parseRsaPublicKey(key) {
|
|
154
|
+
const spki = key && typeof key === "object" ? key.spki : void 0;
|
|
155
|
+
const pin = key && typeof key === "object" ? key.sha256 : void 0;
|
|
156
|
+
if (typeof spki !== "string" || spki.length > 1024 || spki.length % 4 !== 0 || !BASE64.test(spki))
|
|
157
|
+
throw refuse("key");
|
|
158
|
+
if (typeof pin !== "string" || !SHA256_HEX.test(pin))
|
|
159
|
+
throw refuse("key");
|
|
160
|
+
let binary;
|
|
161
|
+
try {
|
|
162
|
+
binary = atob(spki);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw refuse("key");
|
|
166
|
+
}
|
|
167
|
+
if (btoa(binary) !== spki)
|
|
168
|
+
throw refuse("key");
|
|
169
|
+
const der = Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
170
|
+
if (sha256Hex(der) !== pin)
|
|
171
|
+
throw refuse("key");
|
|
172
|
+
const outer = readElement(der, 0, 48);
|
|
173
|
+
if (outer.end !== der.length || outer.start + RSA_ALGORITHM.length > der.length)
|
|
174
|
+
throw refuse("key");
|
|
175
|
+
if (!RSA_ALGORITHM.every((byte, i) => der[outer.start + i] === byte))
|
|
176
|
+
throw refuse("key");
|
|
177
|
+
const bits = readElement(der, outer.start + RSA_ALGORITHM.length, 3);
|
|
178
|
+
if (bits.end !== outer.end || bits.start === bits.end || der[bits.start] !== 0)
|
|
179
|
+
throw refuse("key");
|
|
180
|
+
const rsa = readElement(der, bits.start + 1, 48);
|
|
181
|
+
if (rsa.end !== bits.end)
|
|
182
|
+
throw refuse("key");
|
|
183
|
+
const modulus = readPositiveInteger(der, rsa.start);
|
|
184
|
+
const exponent = readPositiveInteger(der, modulus.end);
|
|
185
|
+
if (exponent.end !== rsa.end)
|
|
186
|
+
throw refuse("key");
|
|
187
|
+
const n = modulus.bytes;
|
|
188
|
+
if (!MODULUS_BYTES.includes(n.length) || !(n[0] & 128) || !(n[n.length - 1] & 1))
|
|
189
|
+
throw refuse("key");
|
|
190
|
+
const e = exponent.bytes;
|
|
191
|
+
if (e.length !== 3 || e[0] !== 1 || e[1] !== 0 || e[2] !== 1)
|
|
192
|
+
throw refuse("key");
|
|
193
|
+
return Object.freeze({ n: toBigInt(n), e: 65537n, size: n.length });
|
|
194
|
+
}
|
|
195
|
+
function rsaesPkcs1v15Encrypt(key, message, random) {
|
|
196
|
+
const { n, e, size } = parseRsaPublicKey(key);
|
|
197
|
+
if (typedArrayName.call(message) !== "Uint8Array")
|
|
198
|
+
throw refuse("message");
|
|
199
|
+
let bytes;
|
|
200
|
+
try {
|
|
201
|
+
bytes = new Uint8Array(message);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
throw refuse("message");
|
|
205
|
+
}
|
|
206
|
+
const em = new Uint8Array(size);
|
|
207
|
+
try {
|
|
208
|
+
if (bytes.length > size - MIN_PADDING - 3)
|
|
209
|
+
throw refuse("message");
|
|
210
|
+
em[1] = 2;
|
|
211
|
+
fillNonZero(em.subarray(2, size - bytes.length - 1), random);
|
|
212
|
+
em.set(bytes, size - bytes.length);
|
|
213
|
+
return toBytes(modPow(toBigInt(em), e, n), size);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
em.fill(0);
|
|
217
|
+
bytes.fill(0);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function readElement(der, at, tag) {
|
|
221
|
+
if (at + 2 > der.length || der[at] !== tag)
|
|
222
|
+
throw refuse("key");
|
|
223
|
+
let length = der[at + 1];
|
|
224
|
+
let start = at + 2;
|
|
225
|
+
if (length & 128) {
|
|
226
|
+
const count = length & 127;
|
|
227
|
+
if (count === 0 || count > 2 || start + count > der.length || der[start] === 0)
|
|
228
|
+
throw refuse("key");
|
|
229
|
+
length = count === 1 ? der[start] : der[start] * 256 + der[start + 1];
|
|
230
|
+
if (length < 128)
|
|
231
|
+
throw refuse("key");
|
|
232
|
+
start += count;
|
|
233
|
+
}
|
|
234
|
+
if (start + length > der.length)
|
|
235
|
+
throw refuse("key");
|
|
236
|
+
return { start, end: start + length };
|
|
237
|
+
}
|
|
238
|
+
function readPositiveInteger(der, at) {
|
|
239
|
+
const { start, end } = readElement(der, at, 2);
|
|
240
|
+
if (end === start || der[start] & 128)
|
|
241
|
+
throw refuse("key");
|
|
242
|
+
if (der[start] === 0 && (end - start === 1 || !(der[start + 1] & 128)))
|
|
243
|
+
throw refuse("key");
|
|
244
|
+
return { bytes: der.subarray(der[start] === 0 ? start + 1 : start, end), end };
|
|
245
|
+
}
|
|
246
|
+
function fillNonZero(target, random) {
|
|
247
|
+
if (!random || typeof random.getRandomValues !== "function")
|
|
248
|
+
throw refuse("random");
|
|
249
|
+
const draw = new Uint8Array(target.length);
|
|
250
|
+
let filled = 0;
|
|
251
|
+
try {
|
|
252
|
+
for (let round = 0; round < MAX_DRAWS && filled < target.length; round++) {
|
|
253
|
+
draw.fill(0);
|
|
254
|
+
random.getRandomValues(draw);
|
|
255
|
+
for (let i = 0; i < draw.length && filled < target.length; i++)
|
|
256
|
+
if (draw[i] !== 0)
|
|
257
|
+
target[filled++] = draw[i];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
throw refuse("random");
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
draw.fill(0);
|
|
265
|
+
}
|
|
266
|
+
if (filled < target.length)
|
|
267
|
+
throw refuse("random");
|
|
268
|
+
}
|
|
269
|
+
function toBigInt(bytes) {
|
|
270
|
+
let hex = "";
|
|
271
|
+
for (let i = 0; i < bytes.length; i++)
|
|
272
|
+
hex += (bytes[i] < 16 ? "0" : "") + bytes[i].toString(16);
|
|
273
|
+
return BigInt(`0x${hex}`);
|
|
274
|
+
}
|
|
275
|
+
function toBytes(value, size) {
|
|
276
|
+
const hex = value.toString(16).padStart(size * 2, "0");
|
|
277
|
+
const out = new Uint8Array(size);
|
|
278
|
+
for (let i = 0; i < size; i++)
|
|
279
|
+
out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
function modPow(base, exponent, modulus) {
|
|
283
|
+
let result = 1n;
|
|
284
|
+
let square = base % modulus;
|
|
285
|
+
for (let rest = exponent; rest > 0n; rest >>= 1n) {
|
|
286
|
+
if (rest & 1n)
|
|
287
|
+
result = result * square % modulus;
|
|
288
|
+
square = square * square % modulus;
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
// src/adyen-jwe.js
|
|
293
|
+
var ADYEN_JWE_HEADER = { alg: "RSA-OAEP-256", enc: "A256GCM", version: "1" };
|
|
294
|
+
var BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
295
|
+
function isAdyenJweCompact(s) {
|
|
296
|
+
if (typeof s !== "string" || s.length > 2048)
|
|
297
|
+
return false;
|
|
298
|
+
const parts = s.split(".");
|
|
299
|
+
if (parts.length !== 5)
|
|
300
|
+
return false;
|
|
301
|
+
const [header, encryptedKey, iv, ciphertext, tag] = parts;
|
|
302
|
+
if (!parts.every((p) => p.length > 0 && BASE64URL.test(p)))
|
|
303
|
+
return false;
|
|
304
|
+
if (encryptedKey.length !== 342 || iv.length !== 16 || tag.length !== 22)
|
|
305
|
+
return false;
|
|
306
|
+
if (ciphertext.length > 1024)
|
|
307
|
+
return false;
|
|
308
|
+
return isAdyenJweHeader(header);
|
|
309
|
+
}
|
|
310
|
+
function isAdyenJweHeader(segment) {
|
|
311
|
+
if (!BASE64URL.test(segment) || segment.length > 128)
|
|
312
|
+
return false;
|
|
313
|
+
let parsed;
|
|
314
|
+
try {
|
|
315
|
+
parsed = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(segment.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
321
|
+
return false;
|
|
322
|
+
const obj = parsed;
|
|
323
|
+
const keys = Object.keys(obj).sort();
|
|
324
|
+
if (keys.join(",") !== "alg,enc,version")
|
|
325
|
+
return false;
|
|
326
|
+
return obj.alg === ADYEN_JWE_HEADER.alg && obj.enc === ADYEN_JWE_HEADER.enc && obj.version === ADYEN_JWE_HEADER.version;
|
|
327
|
+
}
|
|
328
|
+
// src/json-path.js
|
|
329
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
330
|
+
function isPlainObject(v) {
|
|
331
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
332
|
+
}
|
|
333
|
+
var MAX_PATH_STEPS = 16;
|
|
334
|
+
function isJsonPath(path) {
|
|
335
|
+
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);
|
|
336
|
+
}
|
|
337
|
+
function jsonAt(root, path) {
|
|
338
|
+
let node = root;
|
|
339
|
+
for (const step of path) {
|
|
340
|
+
if (typeof step === "string") {
|
|
341
|
+
if (!isPlainObject(node) || FORBIDDEN_KEYS.has(step) || !Object.prototype.hasOwnProperty.call(node, step))
|
|
342
|
+
return { found: false };
|
|
343
|
+
}
|
|
344
|
+
else if (!Array.isArray(node) || !Number.isSafeInteger(step) || step < 0 || step >= node.length) {
|
|
345
|
+
return { found: false };
|
|
346
|
+
}
|
|
347
|
+
node = node[step];
|
|
348
|
+
}
|
|
349
|
+
return { found: true, value: node };
|
|
350
|
+
}
|
|
351
|
+
function roundTrips(parsed, body) {
|
|
352
|
+
try {
|
|
353
|
+
return JSON.stringify(parsed) === body;
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function sameSteps(a, b) {
|
|
360
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((step, i) => step === b[i]);
|
|
361
|
+
}
|
|
362
|
+
function pathsOverlap(a, b) {
|
|
363
|
+
const n = Math.min(a.length, b.length);
|
|
364
|
+
for (let i = 0; i < n; i += 1)
|
|
365
|
+
if (a[i] !== b[i])
|
|
366
|
+
return false;
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
function describePath(path) {
|
|
370
|
+
return path.length ? path.map((step) => typeof step === "number" ? `[${step}]` : `.${step}`).join("").replace(/^\./, "") : "(root)";
|
|
371
|
+
}
|
|
372
|
+
// src/substitution-primitives.js
|
|
373
|
+
var SubstitutionError = class extends Error {
|
|
374
|
+
constructor(message) {
|
|
375
|
+
super(message);
|
|
376
|
+
this.name = "SubstitutionError";
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var SWAP_MEMBERS = /* @__PURE__ */ new Set(["path", "fields", "remove", "set"]);
|
|
380
|
+
function checkedSet(set, path, names, remove) {
|
|
381
|
+
if (!Array.isArray(set))
|
|
382
|
+
throw new SubstitutionError("substitutions.set is not a list");
|
|
383
|
+
const entries = [...set].map((raw) => {
|
|
384
|
+
const entry = isPlainObject(raw) ? Object.fromEntries(Object.entries(raw)) : null;
|
|
385
|
+
if (!entry || Object.keys(entry).sort().join(",") !== "path,value")
|
|
386
|
+
throw new SubstitutionError("a set entry is not a {path, value}");
|
|
387
|
+
const at = Array.isArray(entry.path) ? [...entry.path] : entry.path;
|
|
388
|
+
if (!isJsonPath(at) || at.length === 0 || typeof at[at.length - 1] !== "string")
|
|
389
|
+
throw new SubstitutionError("refusing set path");
|
|
390
|
+
const value = entry.value;
|
|
391
|
+
if (value !== null && !["string", "boolean"].includes(typeof value) && !(typeof value === "number" && Number.isFinite(value))) {
|
|
392
|
+
throw new SubstitutionError(`the set value at ${describePath(at)} is not a string, number, boolean or null`);
|
|
393
|
+
}
|
|
394
|
+
const key = at[at.length - 1];
|
|
395
|
+
if (sameSteps(at.slice(0, -1), path) && (names.includes(key) || remove.includes(key))) {
|
|
396
|
+
throw new SubstitutionError(`the set entry at ${describePath(at)} writes over an encrypted field or a removed key`);
|
|
397
|
+
}
|
|
398
|
+
return { path: at, value };
|
|
399
|
+
});
|
|
400
|
+
for (const [i, a] of entries.entries()) {
|
|
401
|
+
for (const b of entries.slice(i + 1)) {
|
|
402
|
+
if (pathsOverlap(a.path, b.path))
|
|
403
|
+
throw new SubstitutionError(`two set entries write at ${describePath(a.path)}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return entries;
|
|
407
|
+
}
|
|
408
|
+
var ADYEN_JWE = /* @__PURE__ */ Object.freeze({ accepts: isAdyenJweCompact, label: "an Adyen JWE" });
|
|
409
|
+
function swapMerchantHostedFields(body, spec, ciphertext = ADYEN_JWE) {
|
|
410
|
+
if (!isPlainObject(spec))
|
|
411
|
+
throw new SubstitutionError("refusing a substitution that is not an object");
|
|
412
|
+
for (const member of Object.keys(spec)) {
|
|
413
|
+
if (!SWAP_MEMBERS.has(member))
|
|
414
|
+
throw new SubstitutionError(`unexpected substitution member ${member}`);
|
|
415
|
+
}
|
|
416
|
+
const path = Array.isArray(spec.path) ? [...spec.path] : spec.path;
|
|
417
|
+
const fields = isPlainObject(spec.fields) ? Object.fromEntries(Object.entries(spec.fields)) : null;
|
|
418
|
+
const remove = Array.isArray(spec.remove) ? [...spec.remove] : spec.remove ?? [];
|
|
419
|
+
if (!isJsonPath(path))
|
|
420
|
+
throw new SubstitutionError("refusing substitution path");
|
|
421
|
+
if (!fields || Object.keys(fields).length === 0)
|
|
422
|
+
throw new SubstitutionError("substitutions carry no fields");
|
|
423
|
+
const names = Object.keys(fields);
|
|
424
|
+
const values = names.map((name) => fields[name]);
|
|
425
|
+
for (const name of names) {
|
|
426
|
+
if (FORBIDDEN_KEYS.has(name))
|
|
427
|
+
throw new SubstitutionError(`refusing substitution field ${name}`);
|
|
428
|
+
if (!ciphertext.accepts(fields[name]))
|
|
429
|
+
throw new SubstitutionError(`substitution for ${name} is not ${ciphertext.label}`);
|
|
430
|
+
}
|
|
431
|
+
if (!Array.isArray(remove))
|
|
432
|
+
throw new SubstitutionError("substitutions.remove is not a list");
|
|
433
|
+
for (const key of remove) {
|
|
434
|
+
if (typeof key !== "string" || !key || FORBIDDEN_KEYS.has(key))
|
|
435
|
+
throw new SubstitutionError(`refusing removal of ${String(key)}`);
|
|
436
|
+
if (names.includes(key))
|
|
437
|
+
throw new SubstitutionError(`substitution field ${key} is also listed for removal`);
|
|
438
|
+
}
|
|
439
|
+
if (new Set(remove).size !== remove.length)
|
|
440
|
+
throw new SubstitutionError("substitutions.remove names a key twice");
|
|
441
|
+
const set = checkedSet(spec.set ?? [], path, names, remove);
|
|
442
|
+
if (typeof body !== "string")
|
|
443
|
+
throw new SubstitutionError("the paused body is not JSON");
|
|
444
|
+
let parsed;
|
|
445
|
+
try {
|
|
446
|
+
parsed = JSON.parse(body);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
throw new SubstitutionError("the paused body is not JSON");
|
|
450
|
+
}
|
|
451
|
+
if (!isPlainObject(parsed))
|
|
452
|
+
throw new SubstitutionError("the paused body is not a JSON object");
|
|
453
|
+
const holder = jsonAt(parsed, path);
|
|
454
|
+
if (!holder.found || !isPlainObject(holder.value))
|
|
455
|
+
throw new SubstitutionError(`the paused body has no object at ${describePath(path)}`);
|
|
456
|
+
const swaps = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const name of names) {
|
|
458
|
+
const dummy = Object.prototype.hasOwnProperty.call(holder.value, name) ? holder.value[name] : void 0;
|
|
459
|
+
if (typeof dummy !== "string")
|
|
460
|
+
throw new SubstitutionError(`the paused body has no string field ${name} at ${describePath(path)}`);
|
|
461
|
+
if (!ciphertext.accepts(dummy))
|
|
462
|
+
throw new SubstitutionError(`the paused field ${name} is not ${ciphertext.label}`);
|
|
463
|
+
if (swaps.has(dummy))
|
|
464
|
+
throw new SubstitutionError("two paused fields carry the same ciphertext");
|
|
465
|
+
swaps.set(dummy, fields[name]);
|
|
466
|
+
}
|
|
467
|
+
const dummies = [...swaps.keys()];
|
|
468
|
+
if (new Set(values).size !== values.length || values.some((value) => swaps.has(value))) {
|
|
469
|
+
throw new SubstitutionError("two ciphertexts in this substitution are the same");
|
|
470
|
+
}
|
|
471
|
+
const texts = [...dummies, ...values];
|
|
472
|
+
for (const a of texts)
|
|
473
|
+
for (const b of texts)
|
|
474
|
+
if (a !== b && b.includes(a))
|
|
475
|
+
throw new SubstitutionError("one ciphertext is part of another");
|
|
476
|
+
if ((remove.length > 0 || set.length > 0) && !roundTrips(parsed, body)) {
|
|
477
|
+
throw new SubstitutionError("the paused body does not round-trip through JSON, so it cannot be edited");
|
|
478
|
+
}
|
|
479
|
+
for (const entry of set) {
|
|
480
|
+
const parent = jsonAt(parsed, entry.path.slice(0, -1));
|
|
481
|
+
if (!parent.found || !isPlainObject(parent.value)) {
|
|
482
|
+
throw new SubstitutionError(`the paused body has no object at ${describePath(entry.path.slice(0, -1))} to write a value in`);
|
|
483
|
+
}
|
|
484
|
+
const key = entry.path[entry.path.length - 1];
|
|
485
|
+
const current = Object.prototype.hasOwnProperty.call(parent.value, key) ? parent.value[key] : void 0;
|
|
486
|
+
if (current !== null && typeof current === "object") {
|
|
487
|
+
throw new SubstitutionError(`refusing to write over the object at ${describePath(entry.path)}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
491
|
+
const pattern = new RegExp(dummies.map((text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g");
|
|
492
|
+
const swapped = body.replace(pattern, (text) => {
|
|
493
|
+
seen.add(text);
|
|
494
|
+
return swaps.get(text);
|
|
495
|
+
});
|
|
496
|
+
if (seen.size !== dummies.length)
|
|
497
|
+
throw new SubstitutionError("a paused field is not written verbatim in the body");
|
|
498
|
+
let out;
|
|
499
|
+
try {
|
|
500
|
+
out = JSON.parse(swapped);
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
throw new SubstitutionError("the substituted body is not JSON");
|
|
504
|
+
}
|
|
505
|
+
const written = jsonAt(out, path);
|
|
506
|
+
if (!written.found || !isPlainObject(written.value) || names.some((name) => written.value[name] !== fields[name]) || dummies.some((dummy) => swapped.includes(dummy))) {
|
|
507
|
+
throw new SubstitutionError(`the substitution did not land at ${describePath(path)}`);
|
|
508
|
+
}
|
|
509
|
+
if (remove.length === 0 && set.length === 0)
|
|
510
|
+
return swapped;
|
|
511
|
+
for (const key of remove)
|
|
512
|
+
if (Object.prototype.hasOwnProperty.call(written.value, key))
|
|
513
|
+
delete written.value[key];
|
|
514
|
+
for (const entry of set) {
|
|
515
|
+
const parent = jsonAt(out, entry.path.slice(0, -1));
|
|
516
|
+
if (!parent.found || !isPlainObject(parent.value))
|
|
517
|
+
throw new SubstitutionError(`the set entry at ${describePath(entry.path)} did not land`);
|
|
518
|
+
parent.value[entry.path[entry.path.length - 1]] = entry.value;
|
|
519
|
+
}
|
|
520
|
+
return JSON.stringify(out);
|
|
521
|
+
}
|
|
522
|
+
// src/shiji-sts.js
|
|
523
|
+
var SHIJI_STS_PSP = "shiji_sts";
|
|
524
|
+
var SHIJI_STS_HOSTS = /* @__PURE__ */ Object.freeze(["iframe.vault.eu.prod.shijipayment.com", "iframe.vault.eu.uat.shijipayment.com"]);
|
|
525
|
+
var SHIJI_STS_SANDBOX_HOST = "iframe.vault.eu.uat.shijipayment.com";
|
|
526
|
+
var SHIJI_STS_TOKEN_PATH = "/v1/token/getTokenId";
|
|
527
|
+
var SHIJI_STS_CVV_PATH = "/v1/token/getCvvId";
|
|
528
|
+
var SHIJI_STS_KEY = /* @__PURE__ */ Object.freeze({
|
|
529
|
+
spki: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDF68IfqgqwmtmAKHNDlEyO9dYQiJHLd9ZixQ32PmzQtBH/mK+KB0q9K2kT7IjL1KdfSPSh8zXlXJOzZ8HQPewmxZgDSnKBVoeVb8HENiaMcIioddz9GJSY1z5ZvEE2DZawiSRxOiOorXQ8HjyVF+1McoWbIShrbeLcT/lC/FS5UQIDAQAB",
|
|
530
|
+
sha256: "c7846a0f1736b11b7549a364122d43b6517b87989f873ad805748b1f5a469ff7"
|
|
531
|
+
});
|
|
532
|
+
var SHIJI_STS_CIPHERTEXT_BYTES = 128;
|
|
533
|
+
var SHIJI_STS_COMPANION_WINDOW_MS = 12e4;
|
|
534
|
+
var SHIJI_STS_FIELDS = /* @__PURE__ */ Object.freeze({
|
|
535
|
+
token: Object.freeze(["pan", "expires"]),
|
|
536
|
+
cvv: Object.freeze(["cvv"])
|
|
537
|
+
});
|
|
538
|
+
var SHIJI_STS_KEEP_ON_FILE = "refuse";
|
|
539
|
+
var SHIJI_STS_REFUSAL_REASONS = /* @__PURE__ */ Object.freeze([
|
|
540
|
+
"unsupported",
|
|
541
|
+
"foreign_key",
|
|
542
|
+
"keep_on_file",
|
|
543
|
+
"companion_unbound",
|
|
544
|
+
"companion_expired",
|
|
545
|
+
"companion_used",
|
|
546
|
+
"card"
|
|
547
|
+
]);
|
|
548
|
+
var MESSAGES = /* @__PURE__ */ Object.freeze({
|
|
549
|
+
unsupported: "Refusing to pay: unsupported Shiji card request. Nothing was sent.",
|
|
550
|
+
foreign_key: "Refusing to pay: this Shiji card request is encrypted under a key Agentcard has not reviewed. Nothing was sent.",
|
|
551
|
+
keep_on_file: "Refusing to pay: this hotel keeps the card on file to guarantee the stay, and Agentcard cannot ask for your consent to that yet. Nothing was sent.",
|
|
552
|
+
companion_unbound: "Refusing to pay: this Shiji security code request does not belong to a card payment you approved. Nothing was sent.",
|
|
553
|
+
companion_expired: "Refusing to pay: this Shiji security code request came too late after the card payment you approved. Nothing was sent.",
|
|
554
|
+
companion_used: "Refusing to pay: the security code for the card payment you approved was already sent. Nothing was sent.",
|
|
555
|
+
card: "Could not encrypt this card for Shiji. Nothing was sent."
|
|
556
|
+
});
|
|
557
|
+
var ShijiStsError = class extends Error {
|
|
558
|
+
constructor(reason) {
|
|
559
|
+
const known = Object.prototype.hasOwnProperty.call(MESSAGES, reason) ? reason : "unsupported";
|
|
560
|
+
super(MESSAGES[known]);
|
|
561
|
+
this.name = "ShijiStsError";
|
|
562
|
+
this.code = "shiji_sts_refused";
|
|
563
|
+
this.reason = known;
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
function shijiStsRefusalMessage(reason) {
|
|
567
|
+
return MESSAGES[Object.prototype.hasOwnProperty.call(MESSAGES, reason) ? reason : "unsupported"];
|
|
568
|
+
}
|
|
569
|
+
function shijiStsKeepOnFileRefusal(policy = SHIJI_STS_KEEP_ON_FILE) {
|
|
570
|
+
return policy === "ask" ? null : "keep_on_file";
|
|
571
|
+
}
|
|
572
|
+
function parseUrl(url) {
|
|
573
|
+
if (typeof url !== "string" || url.length > 2048)
|
|
574
|
+
return null;
|
|
575
|
+
try {
|
|
576
|
+
return new URL(url);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function shijiStsEndpoint(url, method = "POST") {
|
|
583
|
+
if (method !== "POST")
|
|
584
|
+
return null;
|
|
585
|
+
const parsed = parseUrl(url);
|
|
586
|
+
if (!parsed || parsed.protocol !== "https:" || parsed.port || parsed.username || parsed.password || parsed.search || parsed.hash || url.includes("?") || url.includes("#"))
|
|
587
|
+
return null;
|
|
588
|
+
if (!SHIJI_STS_HOSTS.includes(parsed.hostname))
|
|
589
|
+
return null;
|
|
590
|
+
if (parsed.pathname === SHIJI_STS_TOKEN_PATH)
|
|
591
|
+
return "token";
|
|
592
|
+
if (parsed.pathname === SHIJI_STS_CVV_PATH)
|
|
593
|
+
return "cvv";
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
function shijiStsEnvironment(url, method = "POST") {
|
|
597
|
+
if (!shijiStsEndpoint(url, method))
|
|
598
|
+
return null;
|
|
599
|
+
return new URL(url).hostname === SHIJI_STS_SANDBOX_HOST ? "sandbox" : "live";
|
|
600
|
+
}
|
|
601
|
+
function isShijiStsHost(url) {
|
|
602
|
+
if (typeof url !== "string")
|
|
603
|
+
return false;
|
|
604
|
+
let parsed;
|
|
605
|
+
try {
|
|
606
|
+
parsed = new URL(url);
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
612
|
+
return SHIJI_STS_HOSTS.includes(host);
|
|
613
|
+
}
|
|
614
|
+
var CIPHERTEXT_B64 = /^[A-Za-z0-9+/]{170}[AEIMQUYcgkosw048]=$/;
|
|
615
|
+
var BASE642 = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
616
|
+
var OTHER_KEY_BYTES = [256, 384, 512];
|
|
617
|
+
function isShijiStsCiphertext(value) {
|
|
618
|
+
return typeof value === "string" && CIPHERTEXT_B64.test(value);
|
|
619
|
+
}
|
|
620
|
+
function decodedLength(value) {
|
|
621
|
+
if (typeof value !== "string" || value.length % 4 !== 0 || !BASE642.test(value))
|
|
622
|
+
return -1;
|
|
623
|
+
const pad = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
624
|
+
return value.length / 4 * 3 - pad;
|
|
625
|
+
}
|
|
626
|
+
function isOtherKeyCiphertext(value) {
|
|
627
|
+
return OTHER_KEY_BYTES.includes(decodedLength(value)) && btoa(atob(value)) === value;
|
|
628
|
+
}
|
|
629
|
+
function ciphertextInteger(value) {
|
|
630
|
+
const binary = atob(value);
|
|
631
|
+
let hex = "";
|
|
632
|
+
for (let i = 0; i < binary.length; i++)
|
|
633
|
+
hex += binary.charCodeAt(i).toString(16).padStart(2, "0");
|
|
634
|
+
return BigInt(`0x${hex}`);
|
|
635
|
+
}
|
|
636
|
+
function isShijiStsCiphertextUnderKey(value) {
|
|
637
|
+
if (!isShijiStsCiphertext(value))
|
|
638
|
+
return false;
|
|
639
|
+
const c = ciphertextInteger(value);
|
|
640
|
+
return c > 1n && c < parseRsaPublicKey(SHIJI_STS_KEY).n;
|
|
641
|
+
}
|
|
642
|
+
var BODY_LIMIT = 16384;
|
|
643
|
+
var ACCESS_TOKEN = /^[\x21-\x7e]{1,4096}$/;
|
|
644
|
+
var KEY_SEQUENCE = /^[\x21-\x7e]{0,256}$/;
|
|
645
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
646
|
+
function readShijiStsBody(kind, body) {
|
|
647
|
+
const unsupported = Object.freeze({ ok: false, reason: "unsupported" });
|
|
648
|
+
const names = kind === "token" || kind === "cvv" ? SHIJI_STS_FIELDS[kind] : null;
|
|
649
|
+
if (!names || typeof body !== "string" || body.length > BODY_LIMIT)
|
|
650
|
+
return unsupported;
|
|
651
|
+
let parsed;
|
|
652
|
+
try {
|
|
653
|
+
parsed = JSON.parse(body);
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
return unsupported;
|
|
657
|
+
}
|
|
658
|
+
if (!record(parsed) || JSON.stringify(parsed) !== body)
|
|
659
|
+
return unsupported;
|
|
660
|
+
const keys = Object.keys(parsed);
|
|
661
|
+
const expected = ["accessToken", ...names];
|
|
662
|
+
const withSequence = keys.length === expected.length + 1 && keys[expected.length] === "keySequence";
|
|
663
|
+
if (!(keys.length === expected.length || withSequence) || !expected.every((key, i) => keys[i] === key))
|
|
664
|
+
return unsupported;
|
|
665
|
+
if (typeof parsed.accessToken !== "string" || !ACCESS_TOKEN.test(parsed.accessToken))
|
|
666
|
+
return unsupported;
|
|
667
|
+
if (withSequence && (typeof parsed.keySequence !== "string" || !KEY_SEQUENCE.test(parsed.keySequence)))
|
|
668
|
+
return unsupported;
|
|
669
|
+
const fields = {};
|
|
670
|
+
for (const name of names) {
|
|
671
|
+
const value = parsed[name];
|
|
672
|
+
if (isOtherKeyCiphertext(value))
|
|
673
|
+
return (
|
|
674
|
+
/** @type {ShijiStsBodyRead} */
|
|
675
|
+
Object.freeze({ ok: false, reason: "foreign_key" }));
|
|
676
|
+
if (!isShijiStsCiphertext(value))
|
|
677
|
+
return unsupported;
|
|
678
|
+
fields[name] = value;
|
|
679
|
+
}
|
|
680
|
+
if (new Set(Object.values(fields)).size !== names.length)
|
|
681
|
+
return unsupported;
|
|
682
|
+
const read = { ok: true, accessToken: parsed.accessToken, keySequence: withSequence ? parsed.keySequence : null, fields: Object.freeze(fields) };
|
|
683
|
+
return Object.freeze(read);
|
|
684
|
+
}
|
|
685
|
+
function shijiStsBodyRefusal(url, body) {
|
|
686
|
+
const kind = shijiStsEndpoint(url);
|
|
687
|
+
if (!kind)
|
|
688
|
+
return "unsupported";
|
|
689
|
+
const read = readShijiStsBody(kind, body);
|
|
690
|
+
return read.ok ? null : read.reason;
|
|
691
|
+
}
|
|
692
|
+
function classifyShijiStsRequest(url, method, body) {
|
|
693
|
+
const kind = shijiStsEndpoint(url, method);
|
|
694
|
+
if (!kind)
|
|
695
|
+
return null;
|
|
696
|
+
const read = readShijiStsBody(kind, body);
|
|
697
|
+
if (!read.ok)
|
|
698
|
+
return read.reason;
|
|
699
|
+
if (kind === "cvv")
|
|
700
|
+
return "companion";
|
|
701
|
+
return shijiStsKeepOnFileRefusal() ?? "card";
|
|
702
|
+
}
|
|
703
|
+
function isShijiStsCardRequest(request) {
|
|
704
|
+
return record(request) && classifyShijiStsRequest(request.url, request.method, request.body) === "card";
|
|
705
|
+
}
|
|
706
|
+
var digitsOf = (value) => typeof value === "string" || typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? String(value) : null;
|
|
707
|
+
function shijiStsPlaintexts(card) {
|
|
708
|
+
if (!record(card))
|
|
709
|
+
throw new ShijiStsError("card");
|
|
710
|
+
const number = digitsOf(card.number)?.replace(/\s/g, "") ?? "";
|
|
711
|
+
const month = digitsOf(card.month) ?? "";
|
|
712
|
+
const year = digitsOf(card.year) ?? "";
|
|
713
|
+
const cvv = digitsOf(card.cvc) ?? "";
|
|
714
|
+
if (!/^\d{12,19}$/.test(number) || !/^\d{1,2}$/.test(month) || !/^(?:\d{2}|\d{4})$/.test(year) || !/^\d{3,4}$/.test(cvv)) {
|
|
715
|
+
throw new ShijiStsError("card");
|
|
716
|
+
}
|
|
717
|
+
if (Number(month) < 1 || Number(month) > 12)
|
|
718
|
+
throw new ShijiStsError("card");
|
|
719
|
+
return { pan: number, expires: `${month.padStart(2, "0")}/${year.slice(-2)}`, cvv };
|
|
720
|
+
}
|
|
721
|
+
function base64(bytes) {
|
|
722
|
+
let binary = "";
|
|
723
|
+
for (let i = 0; i < bytes.length; i++)
|
|
724
|
+
binary += String.fromCharCode(bytes[i]);
|
|
725
|
+
return btoa(binary);
|
|
726
|
+
}
|
|
727
|
+
function encryptText(text, random) {
|
|
728
|
+
const message = new TextEncoder().encode(text);
|
|
729
|
+
try {
|
|
730
|
+
return base64(rsaesPkcs1v15Encrypt(SHIJI_STS_KEY, message, random));
|
|
731
|
+
}
|
|
732
|
+
finally {
|
|
733
|
+
message.fill(0);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function encryptShijiStsCard(card, facilities = {}) {
|
|
737
|
+
const refusal = shijiStsKeepOnFileRefusal();
|
|
738
|
+
if (refusal)
|
|
739
|
+
throw new ShijiStsError(refusal);
|
|
740
|
+
const random = facilities.crypto ?? globalThis.crypto;
|
|
741
|
+
const texts = shijiStsPlaintexts(card);
|
|
742
|
+
return { pan: encryptText(texts.pan, random), expires: encryptText(texts.expires, random), cvv: encryptText(texts.cvv, random) };
|
|
743
|
+
}
|
|
744
|
+
function shijiStsKeyUsable(authorization) {
|
|
745
|
+
if (!record(authorization) || !record(authorization.cse))
|
|
746
|
+
return false;
|
|
747
|
+
const cse = authorization.cse;
|
|
748
|
+
if (cse.psp !== SHIJI_STS_PSP || cse.scheme !== "rsa-pkcs1v15")
|
|
749
|
+
return false;
|
|
750
|
+
if (cse.public_key != null || cse.client_key != null || Object.prototype.hasOwnProperty.call(cse, "merchant_hosted"))
|
|
751
|
+
return false;
|
|
752
|
+
if (shijiStsEndpoint(authorization.url) !== "token")
|
|
753
|
+
return false;
|
|
754
|
+
const environment = shijiStsEnvironment(authorization.url);
|
|
755
|
+
if (cse.environment !== environment || authorization.sandbox !== (environment === "sandbox"))
|
|
756
|
+
return false;
|
|
757
|
+
try {
|
|
758
|
+
return parseRsaPublicKey(SHIJI_STS_KEY).size === SHIJI_STS_CIPHERTEXT_BYTES;
|
|
759
|
+
}
|
|
760
|
+
catch {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
var SHIJI_CIPHERTEXT = /* @__PURE__ */ Object.freeze({ accepts: isShijiStsCiphertext, label: "a Shiji STS ciphertext" });
|
|
765
|
+
function exactFields(kind, fields) {
|
|
766
|
+
if (!record(fields))
|
|
767
|
+
return null;
|
|
768
|
+
const names = SHIJI_STS_FIELDS[kind];
|
|
769
|
+
const keys = Object.keys(fields);
|
|
770
|
+
if (keys.length !== names.length || !names.every((name) => keys.includes(name)))
|
|
771
|
+
return null;
|
|
772
|
+
return Object.fromEntries(names.map((name) => [name, fields[name]]));
|
|
773
|
+
}
|
|
774
|
+
function substituteShijiSts(url, body, fields) {
|
|
775
|
+
const policy = shijiStsKeepOnFileRefusal();
|
|
776
|
+
if (policy)
|
|
777
|
+
throw new SubstitutionError(shijiStsRefusalMessage(policy));
|
|
778
|
+
const kind = shijiStsEndpoint(url);
|
|
779
|
+
if (!kind)
|
|
780
|
+
throw new SubstitutionError("the paused request is not a Shiji STS card request");
|
|
781
|
+
const paused = readShijiStsBody(kind, body);
|
|
782
|
+
if (!paused.ok)
|
|
783
|
+
throw new SubstitutionError(shijiStsRefusalMessage(paused.reason));
|
|
784
|
+
const swap = exactFields(kind, fields);
|
|
785
|
+
if (!swap)
|
|
786
|
+
throw new SubstitutionError(`the substitution must carry exactly ${SHIJI_STS_FIELDS[kind].join(" and ")}`);
|
|
787
|
+
const taken = [paused.accessToken, paused.keySequence, ...Object.values(paused.fields)];
|
|
788
|
+
for (const [name, value] of Object.entries(swap)) {
|
|
789
|
+
if (!isShijiStsCiphertextUnderKey(value))
|
|
790
|
+
throw new SubstitutionError(`substitution for ${name} is not a ciphertext under the pinned Shiji key`);
|
|
791
|
+
if (taken.includes(value))
|
|
792
|
+
throw new SubstitutionError("a ciphertext in this substitution is already in the body");
|
|
793
|
+
}
|
|
794
|
+
const values = Object.values(swap);
|
|
795
|
+
if (new Set(values).size !== values.length)
|
|
796
|
+
throw new SubstitutionError("the substitution carries one ciphertext twice");
|
|
797
|
+
const out = swapMerchantHostedFields(body, { path: [], fields: swap }, SHIJI_CIPHERTEXT);
|
|
798
|
+
const written = readShijiStsBody(kind, out);
|
|
799
|
+
if (!written.ok || written.accessToken !== paused.accessToken || written.keySequence !== paused.keySequence || Object.entries(swap).some(([name, value]) => written.fields[name] !== value)) {
|
|
800
|
+
throw new SubstitutionError("the substitution did not land at the body root");
|
|
801
|
+
}
|
|
802
|
+
return out;
|
|
803
|
+
}
|
|
804
|
+
function shijiStsSubstitutionsRefusal(template, substitutions) {
|
|
805
|
+
const policy = shijiStsKeepOnFileRefusal();
|
|
806
|
+
if (policy)
|
|
807
|
+
return { code: policy, message: shijiStsRefusalMessage(policy) };
|
|
808
|
+
const refuse2 = (message) => ({ code: "invalid_substitutions", message });
|
|
809
|
+
if (!record(template) || shijiStsEndpoint(template.url) !== "token")
|
|
810
|
+
return refuse2("This authorization is not a Shiji STS card request.");
|
|
811
|
+
const paused = readShijiStsBody("token", template.body);
|
|
812
|
+
if (!paused.ok)
|
|
813
|
+
return refuse2("The paused Shiji STS request is not the shape the vault fills.");
|
|
814
|
+
const names = ["pan", "expires", "cvv"];
|
|
815
|
+
const keys = record(substitutions) ? Object.keys(substitutions) : [];
|
|
816
|
+
if (keys.length !== names.length || !names.every((name) => keys.includes(name))) {
|
|
817
|
+
return refuse2("substitutions must carry exactly these fields: pan, expires, cvv.");
|
|
818
|
+
}
|
|
819
|
+
for (const name of names) {
|
|
820
|
+
if (!isShijiStsCiphertextUnderKey(substitutions[name]))
|
|
821
|
+
return refuse2(`substitutions.${name} is not a ciphertext under the pinned Shiji key.`);
|
|
822
|
+
}
|
|
823
|
+
const values = names.map((name) => substitutions[name]);
|
|
824
|
+
const taken = [paused.accessToken, paused.keySequence, ...Object.values(paused.fields)];
|
|
825
|
+
if (new Set(values).size !== values.length || values.some((value) => taken.includes(value))) {
|
|
826
|
+
return refuse2("substitutions must be three new ciphertexts, none already in the paused request.");
|
|
827
|
+
}
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
var PAGE = /^[\x21-\x7e]{1,256}$/;
|
|
831
|
+
var instant = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
832
|
+
var REVIEWED_KEY_SEQUENCE = /^[\x21-\x7e]{1,256}$/;
|
|
833
|
+
var REVIEWED_KEY_SEQUENCES_LIMIT = 16;
|
|
834
|
+
function reviewedKeySequences(list) {
|
|
835
|
+
if (!Array.isArray(list) || list.length === 0 || list.length > REVIEWED_KEY_SEQUENCES_LIMIT)
|
|
836
|
+
return null;
|
|
837
|
+
if (!list.every((key) => typeof key === "string" && REVIEWED_KEY_SEQUENCE.test(key)))
|
|
838
|
+
return null;
|
|
839
|
+
return Object.freeze([...list]);
|
|
840
|
+
}
|
|
841
|
+
function createShijiStsCompanion(tokenRequest, { page, now, cvvKeySequences } = {}) {
|
|
842
|
+
const policy = shijiStsKeepOnFileRefusal();
|
|
843
|
+
if (policy)
|
|
844
|
+
throw new ShijiStsError(policy);
|
|
845
|
+
if (!record(tokenRequest) || shijiStsEndpoint(tokenRequest.url, tokenRequest.method) !== "token")
|
|
846
|
+
throw new ShijiStsError("unsupported");
|
|
847
|
+
const token = readShijiStsBody("token", tokenRequest.body);
|
|
848
|
+
if (!token.ok)
|
|
849
|
+
throw new ShijiStsError(token.reason);
|
|
850
|
+
if (typeof page !== "string" || !PAGE.test(page) || !instant(now))
|
|
851
|
+
throw new ShijiStsError("unsupported");
|
|
852
|
+
const reviewed = reviewedKeySequences(cvvKeySequences);
|
|
853
|
+
if (!reviewed)
|
|
854
|
+
throw new ShijiStsError("unsupported");
|
|
855
|
+
const host = new URL(tokenRequest.url).hostname;
|
|
856
|
+
const boundPage = page;
|
|
857
|
+
const boundAt = now;
|
|
858
|
+
let used = false;
|
|
859
|
+
const claim = (request, context = {}) => {
|
|
860
|
+
if (!record(request) || shijiStsEndpoint(request.url, request.method) !== "cvv")
|
|
861
|
+
return "unsupported";
|
|
862
|
+
const cvv = readShijiStsBody("cvv", request.body);
|
|
863
|
+
if (!cvv.ok)
|
|
864
|
+
return cvv.reason;
|
|
865
|
+
if (used)
|
|
866
|
+
return "companion_used";
|
|
867
|
+
if (new URL(request.url).hostname !== host || context.page !== boundPage || cvv.accessToken !== token.accessToken || cvv.keySequence === null || !reviewed.includes(cvv.keySequence))
|
|
868
|
+
return "companion_unbound";
|
|
869
|
+
if (!instant(context.now) || context.now < boundAt || context.now - boundAt > SHIJI_STS_COMPANION_WINDOW_MS)
|
|
870
|
+
return "companion_expired";
|
|
871
|
+
used = true;
|
|
872
|
+
return "swap";
|
|
873
|
+
};
|
|
874
|
+
return Object.freeze({ claim });
|
|
875
|
+
}
|
|
876
|
+
var RESPONSE_LIMIT = 16384;
|
|
877
|
+
var RESP_CODE = /^[A-Za-z0-9]{1,8}$/;
|
|
878
|
+
var SUCCESS = "00";
|
|
879
|
+
function readAnswer(text) {
|
|
880
|
+
if (typeof text !== "string" || text.length > RESPONSE_LIMIT)
|
|
881
|
+
return null;
|
|
882
|
+
try {
|
|
883
|
+
const value = JSON.parse(text);
|
|
884
|
+
return record(value) ? value : null;
|
|
885
|
+
}
|
|
886
|
+
catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function shijiStsResponseFacts(status, text) {
|
|
891
|
+
const unknown = { outcome: "unknown", code: null };
|
|
892
|
+
if (!Number.isInteger(status) || status < 100 || status >= 500)
|
|
893
|
+
return unknown;
|
|
894
|
+
const answer = readAnswer(text);
|
|
895
|
+
const code = answer && typeof answer.respCode === "string" && RESP_CODE.test(answer.respCode) ? answer.respCode : null;
|
|
896
|
+
if (code && code !== SUCCESS)
|
|
897
|
+
return { outcome: "refused", code };
|
|
898
|
+
if (!code && status >= 400)
|
|
899
|
+
return { outcome: "refused", code: `http_${status}` };
|
|
900
|
+
if (code === SUCCESS && status >= 200 && status < 300) {
|
|
901
|
+
const id = typeof answer.tokenId === "string" ? answer.tokenId : answer.cvvId;
|
|
902
|
+
if (typeof id === "string" && id.length > 0)
|
|
903
|
+
return { outcome: "token", code: SUCCESS };
|
|
904
|
+
}
|
|
905
|
+
return unknown;
|
|
906
|
+
}
|
|
907
|
+
function sanitizeShijiStsResponse(text, status = 200) {
|
|
908
|
+
const facts = shijiStsResponseFacts(status, text);
|
|
909
|
+
if (facts.outcome === "unknown")
|
|
910
|
+
throw new ShijiStsError("unsupported");
|
|
911
|
+
return JSON.stringify(RESP_CODE.test(facts.code) ? { respCode: facts.code } : {});
|
|
912
|
+
}
|
|
913
|
+
export { SHIJI_STS_CIPHERTEXT_BYTES, SHIJI_STS_COMPANION_WINDOW_MS, SHIJI_STS_CVV_PATH, SHIJI_STS_FIELDS, SHIJI_STS_HOSTS, SHIJI_STS_KEEP_ON_FILE, SHIJI_STS_KEY, SHIJI_STS_PSP, SHIJI_STS_REFUSAL_REASONS, SHIJI_STS_SANDBOX_HOST, SHIJI_STS_TOKEN_PATH, ShijiStsError, classifyShijiStsRequest, createShijiStsCompanion, encryptShijiStsCard, isShijiStsCardRequest, isShijiStsCiphertext, isShijiStsCiphertextUnderKey, isShijiStsHost, readShijiStsBody, sanitizeShijiStsResponse, shijiStsBodyRefusal, shijiStsEndpoint, shijiStsEnvironment, shijiStsKeepOnFileRefusal, shijiStsKeyUsable, shijiStsPlaintexts, shijiStsRefusalMessage, shijiStsResponseFacts, shijiStsSubstitutionsRefusal, substituteShijiSts };
|