@bayarkit/testing 0.0.0-stage → 0.1.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/README.md +27 -2
- package/dist/cli/2c2p.d.ts +4 -0
- package/dist/cli/2c2p.d.ts.map +1 -0
- package/dist/cli/2c2p.js +7 -0
- package/dist/cli/2c2p.js.map +1 -0
- package/dist/cli/bayarcash.d.ts +4 -0
- package/dist/cli/bayarcash.d.ts.map +1 -0
- package/dist/cli/bayarcash.js +11 -0
- package/dist/cli/bayarcash.js.map +1 -0
- package/dist/cli/billplz.d.ts +4 -0
- package/dist/cli/billplz.d.ts.map +1 -0
- package/dist/cli/billplz.js +11 -0
- package/dist/cli/billplz.js.map +1 -0
- package/dist/cli/chip.d.ts +4 -0
- package/dist/cli/chip.d.ts.map +1 -0
- package/dist/cli/chip.js +7 -0
- package/dist/cli/chip.js.map +1 -0
- package/dist/cli/curlec.d.ts +4 -0
- package/dist/cli/curlec.d.ts.map +1 -0
- package/dist/cli/curlec.js +11 -0
- package/dist/cli/curlec.js.map +1 -0
- package/dist/cli/eghl.d.ts +4 -0
- package/dist/cli/eghl.d.ts.map +1 -0
- package/dist/cli/eghl.js +7 -0
- package/dist/cli/eghl.js.map +1 -0
- package/dist/cli/fiuu.d.ts +4 -0
- package/dist/cli/fiuu.d.ts.map +1 -0
- package/dist/cli/fiuu.js +17 -0
- package/dist/cli/fiuu.js.map +1 -0
- package/dist/cli/gkash.d.ts +4 -0
- package/dist/cli/gkash.d.ts.map +1 -0
- package/dist/cli/gkash.js +7 -0
- package/dist/cli/gkash.js.map +1 -0
- package/dist/cli/hitpay.d.ts +4 -0
- package/dist/cli/hitpay.d.ts.map +1 -0
- package/dist/cli/hitpay.js +7 -0
- package/dist/cli/hitpay.js.map +1 -0
- package/dist/cli/ipay88.d.ts +4 -0
- package/dist/cli/ipay88.d.ts.map +1 -0
- package/dist/cli/ipay88.js +7 -0
- package/dist/cli/ipay88.js.map +1 -0
- package/dist/cli/senangpay.d.ts +4 -0
- package/dist/cli/senangpay.d.ts.map +1 -0
- package/dist/cli/senangpay.js +17 -0
- package/dist/cli/senangpay.js.map +1 -0
- package/dist/cli/stripe.d.ts +4 -0
- package/dist/cli/stripe.d.ts.map +1 -0
- package/dist/cli/stripe.js +7 -0
- package/dist/cli/stripe.js.map +1 -0
- package/dist/cli/toyyibpay.d.ts +4 -0
- package/dist/cli/toyyibpay.d.ts.map +1 -0
- package/dist/cli/toyyibpay.js +7 -0
- package/dist/cli/toyyibpay.js.map +1 -0
- package/dist/cli/types.d.ts +7 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/xendit.d.ts +4 -0
- package/dist/cli/xendit.d.ts.map +1 -0
- package/dist/cli/xendit.js +7 -0
- package/dist/cli/xendit.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +46 -0
- package/dist/cli.js.map +1 -0
- package/dist/conformance.d.ts +13 -0
- package/dist/conformance.d.ts.map +1 -0
- package/dist/conformance.js +166 -0
- package/dist/conformance.js.map +1 -0
- package/dist/control.d.ts +6 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +46 -0
- package/dist/control.js.map +1 -0
- package/dist/fakes/2c2p.d.ts +21 -0
- package/dist/fakes/2c2p.d.ts.map +1 -0
- package/dist/fakes/2c2p.js +287 -0
- package/dist/fakes/2c2p.js.map +1 -0
- package/dist/fakes/bayarcash.d.ts +15 -0
- package/dist/fakes/bayarcash.d.ts.map +1 -0
- package/dist/fakes/bayarcash.js +133 -0
- package/dist/fakes/bayarcash.js.map +1 -0
- package/dist/fakes/billplz.d.ts +20 -0
- package/dist/fakes/billplz.d.ts.map +1 -0
- package/dist/fakes/billplz.js +233 -0
- package/dist/fakes/billplz.js.map +1 -0
- package/dist/fakes/chip.d.ts +18 -0
- package/dist/fakes/chip.d.ts.map +1 -0
- package/dist/fakes/chip.js +227 -0
- package/dist/fakes/chip.js.map +1 -0
- package/dist/fakes/curlec.d.ts +30 -0
- package/dist/fakes/curlec.d.ts.map +1 -0
- package/dist/fakes/curlec.js +396 -0
- package/dist/fakes/curlec.js.map +1 -0
- package/dist/fakes/eghl.d.ts +36 -0
- package/dist/fakes/eghl.d.ts.map +1 -0
- package/dist/fakes/eghl.js +267 -0
- package/dist/fakes/eghl.js.map +1 -0
- package/dist/fakes/fiuu.d.ts +27 -0
- package/dist/fakes/fiuu.d.ts.map +1 -0
- package/dist/fakes/fiuu.js +230 -0
- package/dist/fakes/fiuu.js.map +1 -0
- package/dist/fakes/gkash.d.ts +18 -0
- package/dist/fakes/gkash.d.ts.map +1 -0
- package/dist/fakes/gkash.js +193 -0
- package/dist/fakes/gkash.js.map +1 -0
- package/dist/fakes/hitpay.d.ts +36 -0
- package/dist/fakes/hitpay.d.ts.map +1 -0
- package/dist/fakes/hitpay.js +233 -0
- package/dist/fakes/hitpay.js.map +1 -0
- package/dist/fakes/ipay88.d.ts +16 -0
- package/dist/fakes/ipay88.d.ts.map +1 -0
- package/dist/fakes/ipay88.js +195 -0
- package/dist/fakes/ipay88.js.map +1 -0
- package/dist/fakes/senangpay.d.ts +51 -0
- package/dist/fakes/senangpay.d.ts.map +1 -0
- package/dist/fakes/senangpay.js +255 -0
- package/dist/fakes/senangpay.js.map +1 -0
- package/dist/fakes/stripe.d.ts +26 -0
- package/dist/fakes/stripe.d.ts.map +1 -0
- package/dist/fakes/stripe.js +243 -0
- package/dist/fakes/stripe.js.map +1 -0
- package/dist/fakes/toyyibpay.d.ts +14 -0
- package/dist/fakes/toyyibpay.d.ts.map +1 -0
- package/dist/fakes/toyyibpay.js +138 -0
- package/dist/fakes/toyyibpay.js.map +1 -0
- package/dist/fakes/types.d.ts +19 -0
- package/dist/fakes/types.d.ts.map +1 -0
- package/dist/fakes/types.js +3 -0
- package/dist/fakes/types.js.map +1 -0
- package/dist/fakes/xendit.d.ts +36 -0
- package/dist/fakes/xendit.d.ts.map +1 -0
- package/dist/fakes/xendit.js +300 -0
- package/dist/fakes/xendit.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -4
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake iPay88 Malaysia gateway (OPSG, now NTT DATA Payment Services), built from the official public artefacts on
|
|
3
|
+
* the gateway host (signature test tools, the gateway's own `hmac-sha512.js`, a live probe of `enquiry.asp`) and the
|
|
4
|
+
* iPay88-authored but third-party-hosted Technical Specification 1.6.4.4 — see gateways/ipay88/spec.md. NOT measured
|
|
5
|
+
* against a sandbox (none is documented): when someone does, change this file first, then the adapter.
|
|
6
|
+
*
|
|
7
|
+
* - `POST <url>/entry.asp`: the browser form. Checks the parameters, MerchantCode and the HMAC-SHA512 request
|
|
8
|
+
* Signature, then records the payment as pending and answers an HTML page. Errors are the SPEC's error strings.
|
|
9
|
+
* - `GET|POST <url>/enquiry.asp` (`MerchantCode`, `RefNo`, `Amount`): a bare `text/html` body with HTTP 200 for every
|
|
10
|
+
* outcome, like the live endpoint: `00` paid, `Invalid parameters`, `Record not found`, `Incorrect amount`,
|
|
11
|
+
* `Payment fail`.
|
|
12
|
+
* - callbacks are HTML form POSTs (url-encoded) carrying the response fields, with Amount in the `1,278.99` format and
|
|
13
|
+
* Signature = HMAC-SHA512(MerchantKey, MerchantKey+MerchantCode+PaymentId+RefNo+Amount(no "." or ",")+Currency+Status).
|
|
14
|
+
*
|
|
15
|
+
* Payment ids: iPay88 has no payment id of its own before the customer pays, and its re-query needs RefNo AND Amount,
|
|
16
|
+
* so the adapter's id is `<RefNo>:<amount in sen>`. This fake accepts that id; `callbackFor` / `markPaid` record the
|
|
17
|
+
* payment when the browser form was never posted (the customer reached the payment page in a real flow).
|
|
18
|
+
*
|
|
19
|
+
* ASSUMED where nothing public says (the adapter must not depend on these beyond "not paid"):
|
|
20
|
+
* - an existing unpaid record re-queries as `Payment fail` (the SPEC lists no "pending" answer);
|
|
21
|
+
* - a wrong MerchantCode on enquiry.asp answers `Invalid parameters`;
|
|
22
|
+
* - the request signature includes Xfield1 as the SPEC says (identical to the official tool while Xfield1 is empty).
|
|
23
|
+
*/
|
|
24
|
+
import { createHmac } from 'node:crypto';
|
|
25
|
+
import { createServer } from 'node:http';
|
|
26
|
+
import { flip } from './types.js';
|
|
27
|
+
/** Written independently of the adapter: every "." and "," removed (SPEC wording), not just the first of each. */
|
|
28
|
+
const digits = (amount) => amount.replaceAll('.', '').replaceAll(',', '');
|
|
29
|
+
/** HMAC-SHA512 keyed with the MerchantKey over a message that ALSO starts with the MerchantKey; lowercase hex. */
|
|
30
|
+
const hmac = (key, message) => createHmac('sha512', key).update(message, 'utf8').digest('hex');
|
|
31
|
+
/** 1278.99 RM in sen → "1,278.99" (the SPEC's response format). */
|
|
32
|
+
const display = (sen) => {
|
|
33
|
+
const whole = Math.floor(sen / 100)
|
|
34
|
+
.toString()
|
|
35
|
+
.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
36
|
+
return `${whole}.${String(sen % 100).padStart(2, '0')}`;
|
|
37
|
+
};
|
|
38
|
+
/** "1,278.99" or "1278.99" → sen; NaN otherwise. */
|
|
39
|
+
const senOf = (amount) => /^(\d{1,3}(,\d{3})+|\d+)\.\d{2}$/.test(amount) ? Number(digits(amount)) : NaN;
|
|
40
|
+
const REQUIRED = [
|
|
41
|
+
'MerchantCode',
|
|
42
|
+
'PaymentId',
|
|
43
|
+
'RefNo',
|
|
44
|
+
'Amount',
|
|
45
|
+
'Currency',
|
|
46
|
+
'ProdDesc',
|
|
47
|
+
'UserName',
|
|
48
|
+
'UserEmail',
|
|
49
|
+
'UserContact',
|
|
50
|
+
'Remark',
|
|
51
|
+
'Lang',
|
|
52
|
+
'SignatureType',
|
|
53
|
+
'Signature',
|
|
54
|
+
'ResponseURL',
|
|
55
|
+
'BackendURL',
|
|
56
|
+
];
|
|
57
|
+
export async function startFakeIPay88(opts) {
|
|
58
|
+
const payments = new Map();
|
|
59
|
+
const requests = [];
|
|
60
|
+
let n = 0;
|
|
61
|
+
const parseId = (id) => {
|
|
62
|
+
const at = id.lastIndexOf(':');
|
|
63
|
+
const refNo = id.slice(0, at);
|
|
64
|
+
const amount = Number(id.slice(at + 1));
|
|
65
|
+
if (at < 1 || !/^\d+$/.test(id.slice(at + 1)) || !Number.isSafeInteger(amount)) {
|
|
66
|
+
throw new Error(`fake iPay88: not a payment id (<RefNo>:<sen>): ${id}`);
|
|
67
|
+
}
|
|
68
|
+
return { refNo, amount };
|
|
69
|
+
};
|
|
70
|
+
const myNow = () => {
|
|
71
|
+
const d = new Date(Date.now() + 8 * 3600_000).toISOString();
|
|
72
|
+
return `${d.slice(0, 10)} ${d.slice(11, 19)}`;
|
|
73
|
+
};
|
|
74
|
+
const record = (refNo, amount, currency = 'MYR') => {
|
|
75
|
+
let p = payments.get(refNo);
|
|
76
|
+
if (!p) {
|
|
77
|
+
n += 1;
|
|
78
|
+
p = {
|
|
79
|
+
refNo,
|
|
80
|
+
amount,
|
|
81
|
+
currency,
|
|
82
|
+
status: 'pending',
|
|
83
|
+
paymentId: '2',
|
|
84
|
+
transId: `T${String(n).padStart(9, '0')}`,
|
|
85
|
+
tranDate: myNow(),
|
|
86
|
+
};
|
|
87
|
+
payments.set(refNo, p);
|
|
88
|
+
}
|
|
89
|
+
return p;
|
|
90
|
+
};
|
|
91
|
+
const byId = (id) => {
|
|
92
|
+
const { refNo, amount } = parseId(id);
|
|
93
|
+
return record(refNo, amount);
|
|
94
|
+
};
|
|
95
|
+
const server = createServer((req, res) => {
|
|
96
|
+
let raw = '';
|
|
97
|
+
req.on('data', (d) => {
|
|
98
|
+
raw += d;
|
|
99
|
+
});
|
|
100
|
+
req.on('end', () => {
|
|
101
|
+
const [path = '', query = ''] = (req.url ?? '').split('?');
|
|
102
|
+
const params = Object.fromEntries(new URLSearchParams(req.method === 'POST' ? raw : query));
|
|
103
|
+
requests.push({ method: req.method ?? '', path, params });
|
|
104
|
+
// Live probe: bare text, Content-Type text/html, HTTP 200 for every outcome.
|
|
105
|
+
const text = (body) => {
|
|
106
|
+
res.writeHead(200, { 'content-type': 'text/html' });
|
|
107
|
+
res.end(body);
|
|
108
|
+
};
|
|
109
|
+
if (path === '/epayment/enquiry.asp' && (req.method === 'GET' || req.method === 'POST')) {
|
|
110
|
+
const { MerchantCode, RefNo, Amount } = params;
|
|
111
|
+
if (!MerchantCode || !RefNo || !Amount || MerchantCode !== opts.merchantCode)
|
|
112
|
+
return text('Invalid parameters');
|
|
113
|
+
const p = payments.get(RefNo);
|
|
114
|
+
if (!p)
|
|
115
|
+
return text('Record not found');
|
|
116
|
+
if (senOf(Amount) !== p.amount)
|
|
117
|
+
return text('Incorrect amount');
|
|
118
|
+
return text(p.status === 'paid' ? '00' : 'Payment fail');
|
|
119
|
+
}
|
|
120
|
+
if (path === '/epayment/entry.asp' && req.method === 'POST') {
|
|
121
|
+
// SPEC: "O" fields may be empty but the parameter must exist.
|
|
122
|
+
if (REQUIRED.some((k) => params[k] === undefined))
|
|
123
|
+
return text('Invalid parameters');
|
|
124
|
+
const p = params;
|
|
125
|
+
if (p.MerchantCode !== opts.merchantCode)
|
|
126
|
+
return text('Invalid merchant code');
|
|
127
|
+
const expected = hmac(opts.merchantKey, `${opts.merchantKey}${p.MerchantCode}${p.RefNo}${digits(p.Amount ?? '')}${p.Currency}${p.Xfield1 ?? ''}`);
|
|
128
|
+
if (p.SignatureType !== 'HMACSHA512' || p.Signature !== expected)
|
|
129
|
+
return text('Signature not match');
|
|
130
|
+
const amount = senOf(p.Amount ?? '');
|
|
131
|
+
if (!Number.isFinite(amount) || !p.RefNo || !p.ResponseURL || !p.BackendURL)
|
|
132
|
+
return text('Invalid parameters');
|
|
133
|
+
const existing = payments.get(p.RefNo);
|
|
134
|
+
if (existing?.status === 'paid')
|
|
135
|
+
return text('Duplicate reference number');
|
|
136
|
+
if (existing)
|
|
137
|
+
payments.delete(p.RefNo); // a failed RefNo may be retried
|
|
138
|
+
record(p.RefNo, amount, p.Currency);
|
|
139
|
+
return text(`<!doctype html><title>Fake iPay88</title><p>Fake iPay88 payment page for ${p.RefNo}</p>`);
|
|
140
|
+
}
|
|
141
|
+
res.writeHead(404, { 'content-type': 'text/html' });
|
|
142
|
+
res.end('Not found');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
146
|
+
const origin = `http://127.0.0.1:${server.address().port}`;
|
|
147
|
+
return {
|
|
148
|
+
url: `${origin}/epayment`,
|
|
149
|
+
requests,
|
|
150
|
+
markPaid(id) {
|
|
151
|
+
byId(id).status = 'paid';
|
|
152
|
+
},
|
|
153
|
+
markFailed(id) {
|
|
154
|
+
byId(id).status = 'failed';
|
|
155
|
+
},
|
|
156
|
+
callbackFor(id, override = {}) {
|
|
157
|
+
const p = byId(id);
|
|
158
|
+
// The response fields (SPEC p.11), as the gateway posts them to ResponseURL and BackendURL. It claims success
|
|
159
|
+
// even before markPaid (a replayed or forged-with-a-leaked-key callback).
|
|
160
|
+
const fields = {
|
|
161
|
+
MerchantCode: opts.merchantCode,
|
|
162
|
+
PaymentId: p.paymentId,
|
|
163
|
+
RefNo: p.refNo,
|
|
164
|
+
Amount: display(p.amount),
|
|
165
|
+
Currency: p.currency,
|
|
166
|
+
Remark: '',
|
|
167
|
+
TransId: p.transId,
|
|
168
|
+
AuthCode: '',
|
|
169
|
+
Status: '1',
|
|
170
|
+
ErrDesc: '',
|
|
171
|
+
CCName: '',
|
|
172
|
+
CCNo: '',
|
|
173
|
+
S_bankname: '',
|
|
174
|
+
S_country: '',
|
|
175
|
+
TranDate: p.tranDate,
|
|
176
|
+
Xfield1: '',
|
|
177
|
+
...override,
|
|
178
|
+
};
|
|
179
|
+
if (override.Signature === undefined) {
|
|
180
|
+
fields.Signature = hmac(opts.merchantKey, `${opts.merchantKey}${fields.MerchantCode}${fields.PaymentId}${fields.RefNo}${digits(fields.Amount ?? '')}${fields.Currency}${fields.Status}`);
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
fields,
|
|
184
|
+
rawBody: new URLSearchParams(fields).toString(),
|
|
185
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
tamper: (cb) => {
|
|
189
|
+
const fields = { ...cb.fields, Signature: flip(cb.fields.Signature ?? '') };
|
|
190
|
+
return { ...cb, fields, rawBody: new URLSearchParams(fields).toString() };
|
|
191
|
+
},
|
|
192
|
+
close: () => new Promise((r) => server.close(() => r())),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=ipay88.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ipay88.js","sourceRoot":"","sources":["../../src/fakes/ipay88.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAA;AAGnF,OAAO,EAAoB,IAAI,EAAE,MAAM,YAAY,CAAA;AAEnD,kHAAkH;AAClH,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AACjF,kHAAkH;AAClH,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,OAAe,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAE9G,mEAAmE;AACnE,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;SAChC,QAAQ,EAAE;SACV,OAAO,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAA;IACxC,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;AACzD,CAAC,CAAA;AACD,oDAAoD;AACpD,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE,CAC/B,iCAAiC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;AAmB/E,MAAM,QAAQ,GAAG;IACf,cAAc;IACd,WAAW;IACX,OAAO;IACP,QAAQ;IACR,UAAU;IACV,UAAU;IACV,UAAU;IACV,WAAW;IACX,aAAa;IACb,QAAQ;IACR,MAAM;IACN,eAAe;IACf,WAAW;IACX,aAAa;IACb,YAAY;CACb,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAGrC;IACC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAA;IAC3C,MAAM,QAAQ,GAA2B,EAAE,CAAA;IAC3C,IAAI,CAAC,GAAG,CAAC,CAAA;IAET,MAAM,OAAO,GAAG,CAAC,EAAU,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QACvC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/E,MAAM,IAAI,KAAK,CAAC,kDAAkD,EAAE,EAAE,CAAC,CAAA;QACzE,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;IAC1B,CAAC,CAAA;IACD,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;QAC3D,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;IAC/C,CAAC,CAAA;IACD,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,MAAc,EAAE,QAAQ,GAAG,KAAK,EAAE,EAAE;QACjE,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,IAAI,CAAC,CAAA;YACN,CAAC,GAAG;gBACF,KAAK;gBACL,MAAM;gBACN,QAAQ;gBACR,MAAM,EAAE,SAAS;gBACjB,SAAS,EAAE,GAAG;gBACd,OAAO,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;gBACzC,QAAQ,EAAE,KAAK,EAAE;aAClB,CAAA;YACD,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QACxB,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,EAAE;QAC1B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,CAAA;QACrC,OAAO,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC9B,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAoB,EAAE,GAAmB,EAAE,EAAE;QACxE,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;YACnB,GAAG,IAAI,CAAC,CAAA;QACV,CAAC,CAAC,CAAA;QACF,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACjB,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;YAC3F,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;YACzD,6EAA6E;YAC7E,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE;gBAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAA;gBACnD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACf,CAAC,CAAA;YAED,IAAI,IAAI,KAAK,uBAAuB,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;gBACxF,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAA;gBAC9C,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,YAAY,KAAK,IAAI,CAAC,YAAY;oBAC1E,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAA;gBACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gBAC7B,IAAI,CAAC,CAAC;oBAAE,OAAO,IAAI,CAAC,kBAAkB,CAAC,CAAA;gBACvC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM;oBAAE,OAAO,IAAI,CAAC,kBAAkB,CAAC,CAAA;gBAC/D,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;YAC1D,CAAC;YAED,IAAI,IAAI,KAAK,qBAAqB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC5D,8DAA8D;gBAC9D,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;oBAAE,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAA;gBACpF,MAAM,CAAC,GAAG,MAAgC,CAAA;gBAC1C,IAAI,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,YAAY;oBAAE,OAAO,IAAI,CAAC,uBAAuB,CAAC,CAAA;gBAC9E,MAAM,QAAQ,GAAG,IAAI,CACnB,IAAI,CAAC,WAAW,EAChB,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CACzG,CAAA;gBACD,IAAI,CAAC,CAAC,aAAa,KAAK,YAAY,IAAI,CAAC,CAAC,SAAS,KAAK,QAAQ;oBAAE,OAAO,IAAI,CAAC,qBAAqB,CAAC,CAAA;gBACpG,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;gBACpC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,UAAU;oBACzE,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAA;gBACnC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;gBACtC,IAAI,QAAQ,EAAE,MAAM,KAAK,MAAM;oBAAE,OAAO,IAAI,CAAC,4BAA4B,CAAC,CAAA;gBAC1E,IAAI,QAAQ;oBAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA,CAAC,gCAAgC;gBACvE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;gBACnC,OAAO,IAAI,CAAC,4EAA4E,CAAC,CAAC,KAAK,MAAM,CAAC,CAAA;YACxG,CAAC;YACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAA;YACnD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACtB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;IAChE,MAAM,MAAM,GAAG,oBAAqB,MAAM,CAAC,OAAO,EAAkB,CAAC,IAAI,EAAE,CAAA;IAE3E,OAAO;QACL,GAAG,EAAE,GAAG,MAAM,WAAW;QACzB,QAAQ;QACR,QAAQ,CAAC,EAAE;YACT,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,MAAM,CAAA;QAC1B,CAAC;QACD,UAAU,CAAC,EAAE;YACX,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAA;QAC5B,CAAC;QACD,WAAW,CAAC,EAAE,EAAE,QAAQ,GAAG,EAAE;YAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;YAClB,8GAA8G;YAC9G,0EAA0E;YAC1E,MAAM,MAAM,GAA2B;gBACrC,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;gBACzB,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,MAAM,EAAE,EAAE;gBACV,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,QAAQ,EAAE,EAAE;gBACZ,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,EAAE;gBACV,IAAI,EAAE,EAAE;gBACR,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,EAAE;gBACb,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,OAAO,EAAE,EAAE;gBACX,GAAG,QAAQ;aACZ,CAAA;YACD,IAAI,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACrC,MAAM,CAAC,SAAS,GAAG,IAAI,CACrB,IAAI,CAAC,WAAW,EAChB,GAAG,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAC9I,CAAA;YACH,CAAC;YACD,OAAO;gBACL,MAAM;gBACN,OAAO,EAAE,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE;gBAC/C,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;aACjE,CAAA;QACH,CAAC;QACD,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;YACb,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,CAAA;YAC3E,OAAO,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAA;QAC3E,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;KAC/D,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type FakeGateway } from './types.js';
|
|
2
|
+
type Json = Record<string, unknown>;
|
|
3
|
+
/** A checkout as the fake keeps it (what `GET /v3/checkouts/{id}` answers). */
|
|
4
|
+
export interface SenangPayCheckout {
|
|
5
|
+
id: string;
|
|
6
|
+
order: {
|
|
7
|
+
amount: number;
|
|
8
|
+
invoice_number: string;
|
|
9
|
+
currency: string;
|
|
10
|
+
status: string;
|
|
11
|
+
expired_at: string;
|
|
12
|
+
};
|
|
13
|
+
payment: {
|
|
14
|
+
checkout_url: string;
|
|
15
|
+
type: string;
|
|
16
|
+
status: string;
|
|
17
|
+
state: string;
|
|
18
|
+
channel?: string;
|
|
19
|
+
};
|
|
20
|
+
customer: Json;
|
|
21
|
+
metadata: Json;
|
|
22
|
+
}
|
|
23
|
+
export interface FakeSenangPay extends FakeGateway {
|
|
24
|
+
requests: {
|
|
25
|
+
method: string;
|
|
26
|
+
path: string;
|
|
27
|
+
rawBody: string;
|
|
28
|
+
body: unknown;
|
|
29
|
+
headers: Record<string, string | string[] | undefined>;
|
|
30
|
+
}[];
|
|
31
|
+
/** The fake's own record of a checkout; mutate it to set statuses a test needs. */
|
|
32
|
+
checkout(id: string): SenangPayCheckout | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** base64(SHA-256(raw body bytes)) [A4.1]. */
|
|
35
|
+
export declare const dokuDigest: (rawBody: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The DOKU Global signature exactly as the written rules (D-comp, D-gen) and the Java/Ruby samples build it: values
|
|
38
|
+
* only, one per line, no trailing newline, digest line only when there is a body. Not the Python/Node `Digest:` form.
|
|
39
|
+
*/
|
|
40
|
+
export declare function dokuSignature(secretKey: string, clientId: string, timestamp: string, path: string, rawBody?: string): string;
|
|
41
|
+
export declare function startFakeSenangPay(opts: {
|
|
42
|
+
clientId: string;
|
|
43
|
+
/** Signs requests and notifications. */
|
|
44
|
+
secretKey: string;
|
|
45
|
+
/** The API key used as the Basic-auth username. */
|
|
46
|
+
apiKey: string;
|
|
47
|
+
/** The path of the merchant's notification URL (Back Office → Settings → Webhook); notifications are signed over it. */
|
|
48
|
+
notificationPath: string;
|
|
49
|
+
}): Promise<FakeSenangPay>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=senangpay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"senangpay.d.ts","sourceRoot":"","sources":["../../src/fakes/senangpay.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAE,KAAK,WAAW,EAAQ,MAAM,YAAY,CAAA;AAEnD,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEnC,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IACvG,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChG,QAAQ,EAAE,IAAI,CAAA;IACd,QAAQ,EAAE,IAAI,CAAA;CACf;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,QAAQ,EAAE;QACR,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,OAAO,CAAA;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;KACvD,EAAE,CAAA;IACH,mFAAmF;IACnF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAAA;CACpD;AAED,8CAA8C;AAC9C,eAAO,MAAM,UAAU,YAAa,MAAM,KAAG,MACkB,CAAA;AAE/D;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAIR;AAOD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,wHAAwH;IACxH,gBAAgB,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,aAAa,CAAC,CAkOzB"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fake senangPay-on-DOKU API (DOKU Malaysia "Global" API, Checkout only), built from the official DOKU Malaysia API
|
|
3
|
+
* Reference (doku-developers.apidog.io, read 2026-10-23) and `gateways/senangpay/spec.md`. NOT yet measured against
|
|
4
|
+
* the DOKU sandbox (api-sandbox.doku.com): when someone does, change this file first so it answers like the real
|
|
5
|
+
* thing, then the adapter. The dead legacy API (app.senangpay.my) is not faked.
|
|
6
|
+
*
|
|
7
|
+
* What it checks, and where each rule comes from (spec sections in brackets):
|
|
8
|
+
* - `Authorization: Basic base64("<API key>:")` (with the colon), else 401 `unauthorized` [A1, D-auth];
|
|
9
|
+
* - `Client-Id` must be this merchant's, else 404 `invalid_client_id`; `Request-Timestamp` must be ISO 8601 UTC, else
|
|
10
|
+
* 400 `request_timestamp_invalid` (the allowed skew is UNCONFIRMED, so only the format is checked) [A1, D-err];
|
|
11
|
+
* - `API-Version` is required on every request (D-cco / D-rco `parameters`), else 400 `missing_parameter`;
|
|
12
|
+
* - `Signature: HMACSHA256=<base64>` over `Client-Id\nRequest-Timestamp\nPath\nDigest` (GET: no digest line), with
|
|
13
|
+
* Digest = base64(SHA-256(raw body)) [A4.1, D-comp, D-gen]. Create Checkout marks the header "Conditional"; this fake
|
|
14
|
+
* REQUIRES it on POST (stricter on purpose, so a test proves the adapter signs) and checks it on GET when sent.
|
|
15
|
+
* A wrong signature → 401 `invalid_signature`;
|
|
16
|
+
* - Create Checkout `POST /v3/checkouts`: `id` (Identity pattern), `order.amount` ringgit number ≥ 2.00 (RM2.00
|
|
17
|
+
* minimum, S-faq), `order.invoice_number`, `order.currency` = MYR, `order.expired_at` required [A2]; the same `id`
|
|
18
|
+
* twice → 400 `duplicate_transaction`; `Idempotency-Id` = same result for the same body, 409
|
|
19
|
+
* `idempotency_id_conflict` for another body [A5, D-idem];
|
|
20
|
+
* - Retrieve `GET /v3/checkouts/{id}` with the merchant's own `id`; order status PENDING/PAID/EXPIRED, payment status
|
|
21
|
+
* PENDING/SUCCESS/FAILED/EXPIRED [A3, D-rco, D-stat]; unknown id → 404 `transaction_not_found`;
|
|
22
|
+
* - notifications: the Global body (D-nglob example), POSTed as indented JSON and signed like a request, with the path
|
|
23
|
+
* of the MERCHANT's notification URL [A4, A4.1].
|
|
24
|
+
*/
|
|
25
|
+
import { createHash, createHmac, randomBytes } from 'node:crypto';
|
|
26
|
+
import { createServer } from 'node:http';
|
|
27
|
+
import { flip } from './types.js';
|
|
28
|
+
/** base64(SHA-256(raw body bytes)) [A4.1]. */
|
|
29
|
+
export const dokuDigest = (rawBody) => createHash('sha256').update(rawBody, 'utf8').digest('base64');
|
|
30
|
+
/**
|
|
31
|
+
* The DOKU Global signature exactly as the written rules (D-comp, D-gen) and the Java/Ruby samples build it: values
|
|
32
|
+
* only, one per line, no trailing newline, digest line only when there is a body. Not the Python/Node `Digest:` form.
|
|
33
|
+
*/
|
|
34
|
+
export function dokuSignature(secretKey, clientId, timestamp, path, rawBody) {
|
|
35
|
+
const lines = [clientId, timestamp, path];
|
|
36
|
+
if (rawBody !== undefined)
|
|
37
|
+
lines.push(dokuDigest(rawBody));
|
|
38
|
+
return `HMACSHA256=${createHmac('sha256', secretKey).update(lines.join('\n'), 'utf8').digest('base64')}`;
|
|
39
|
+
}
|
|
40
|
+
const IDENTITY = /^[A-Za-z0-9_-]{1,128}$/;
|
|
41
|
+
const ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
42
|
+
const obj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
43
|
+
const isoNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
44
|
+
export async function startFakeSenangPay(opts) {
|
|
45
|
+
const checkouts = new Map();
|
|
46
|
+
const idempotent = new Map();
|
|
47
|
+
const requests = [];
|
|
48
|
+
const basic = `Basic ${Buffer.from(`${opts.apiKey}:`).toString('base64')}`;
|
|
49
|
+
const server = createServer((req, res) => {
|
|
50
|
+
let raw = '';
|
|
51
|
+
req.on('data', (d) => {
|
|
52
|
+
raw += d;
|
|
53
|
+
});
|
|
54
|
+
req.on('end', () => {
|
|
55
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
56
|
+
let body = null;
|
|
57
|
+
try {
|
|
58
|
+
body = raw ? JSON.parse(raw) : null;
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
requests.push({ method: req.method ?? '', path, rawBody: raw, body, headers: req.headers });
|
|
62
|
+
const send = (status, o) => {
|
|
63
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
64
|
+
res.end(JSON.stringify(o));
|
|
65
|
+
};
|
|
66
|
+
const fail = (status, code, message) => send(status, { error: { code, message } });
|
|
67
|
+
const header = (name) => {
|
|
68
|
+
const v = req.headers[name];
|
|
69
|
+
return typeof v === 'string' ? v : '';
|
|
70
|
+
};
|
|
71
|
+
if (header('authorization') !== basic)
|
|
72
|
+
return fail(401, 'unauthorized', 'Authentication failed. Please provice a valid API key or token');
|
|
73
|
+
if (header('client-id') !== opts.clientId)
|
|
74
|
+
return fail(404, 'invalid_client_id', 'Client Id is not registered.');
|
|
75
|
+
const timestamp = header('request-timestamp');
|
|
76
|
+
if (!ISO_UTC.test(timestamp) || Number.isNaN(Date.parse(timestamp)))
|
|
77
|
+
return fail(400, 'request_timestamp_invalid', 'Request-Timestamp is missing or outside the allowed range.');
|
|
78
|
+
if (!header('api-version'))
|
|
79
|
+
return fail(400, 'missing_parameter', 'Required paramater API-Version is missing.');
|
|
80
|
+
const signature = header('signature');
|
|
81
|
+
if (req.method === 'POST' && !signature)
|
|
82
|
+
return fail(400, 'signature_missing', 'Signature is required but was not provided');
|
|
83
|
+
if (signature) {
|
|
84
|
+
const expected = dokuSignature(opts.secretKey, opts.clientId, timestamp, path, req.method === 'POST' ? raw : undefined);
|
|
85
|
+
if (signature !== expected)
|
|
86
|
+
return fail(401, 'invalid_signature', 'Invalid Signature, please check the signature components');
|
|
87
|
+
}
|
|
88
|
+
if (req.method === 'POST' && path === '/v3/checkouts') {
|
|
89
|
+
if (header('content-type').split(';')[0]?.trim() !== 'application/json')
|
|
90
|
+
return fail(400, 'unsupported_media_type', 'Content-Type must be application/json.');
|
|
91
|
+
const key = header('idempotency-id');
|
|
92
|
+
const digest = dokuDigest(raw);
|
|
93
|
+
const saved = key ? idempotent.get(key) : undefined;
|
|
94
|
+
if (saved) {
|
|
95
|
+
if (saved.digest !== digest)
|
|
96
|
+
return fail(409, 'idempotency_id_conflict', 'Duplicate request with the same idempotency id');
|
|
97
|
+
return send(saved.status, saved.body);
|
|
98
|
+
}
|
|
99
|
+
const result = create(body);
|
|
100
|
+
if (key)
|
|
101
|
+
idempotent.set(key, { digest, ...result });
|
|
102
|
+
return send(result.status, result.body);
|
|
103
|
+
}
|
|
104
|
+
const get = /^\/v3\/checkouts\/([^/]+)$/.exec(path);
|
|
105
|
+
if (req.method === 'GET' && get) {
|
|
106
|
+
const c = checkouts.get(decodeURIComponent(get[1] ?? ''));
|
|
107
|
+
if (!c)
|
|
108
|
+
return fail(404, 'transaction_not_found', 'Transaction with the given ID does not exist');
|
|
109
|
+
return send(200, c);
|
|
110
|
+
}
|
|
111
|
+
return fail(404, 'transaction_not_found', 'Transaction with the given ID does not exist');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
function create(body) {
|
|
115
|
+
const bad = (code, message) => ({ status: 400, body: { error: { code, message } } });
|
|
116
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
117
|
+
return bad('invalid_json', 'Request body must be valid JSON.');
|
|
118
|
+
const b = body;
|
|
119
|
+
const order = b.order;
|
|
120
|
+
if (typeof b.id !== 'string' || !IDENTITY.test(b.id))
|
|
121
|
+
return bad('invalid_parameter', 'Parameter id has invalid value or format.');
|
|
122
|
+
if (!order || typeof order !== 'object')
|
|
123
|
+
return bad('missing_parameter', 'Required paramater order is missing.');
|
|
124
|
+
const amount = order.amount;
|
|
125
|
+
if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 2)
|
|
126
|
+
return bad('invalid_parameter', 'Parameter order.amount has invalid value or format.');
|
|
127
|
+
if (typeof order.invoice_number !== 'string' || !order.invoice_number)
|
|
128
|
+
return bad('missing_parameter', 'Required paramater order.invoice_number is missing.');
|
|
129
|
+
if (order.currency !== 'MYR')
|
|
130
|
+
return bad('currency_not_support', 'The selected currency is not supported');
|
|
131
|
+
if (typeof order.expired_at !== 'string' || Number.isNaN(Date.parse(order.expired_at)))
|
|
132
|
+
return bad('missing_parameter', 'Required paramater order.expired_at is missing.');
|
|
133
|
+
if (checkouts.has(b.id))
|
|
134
|
+
return bad('duplicate_transaction', 'A transaction with the same reference already exists.');
|
|
135
|
+
const checkout = {
|
|
136
|
+
id: b.id,
|
|
137
|
+
order: {
|
|
138
|
+
amount,
|
|
139
|
+
invoice_number: order.invoice_number,
|
|
140
|
+
currency: 'MYR',
|
|
141
|
+
status: 'PENDING',
|
|
142
|
+
expired_at: order.expired_at,
|
|
143
|
+
},
|
|
144
|
+
payment: {
|
|
145
|
+
checkout_url: `https://sandbox.doku.com/checkout-link-v2/${randomBytes(16).toString('hex')}`,
|
|
146
|
+
type: 'SALE',
|
|
147
|
+
status: 'PENDING',
|
|
148
|
+
state: 'INITIATE',
|
|
149
|
+
},
|
|
150
|
+
customer: b.customer ?? {},
|
|
151
|
+
metadata: b.metadata ?? {},
|
|
152
|
+
};
|
|
153
|
+
checkouts.set(b.id, checkout);
|
|
154
|
+
const { order: o, payment, customer } = checkout;
|
|
155
|
+
return {
|
|
156
|
+
status: 200,
|
|
157
|
+
body: {
|
|
158
|
+
id: checkout.id,
|
|
159
|
+
order: {
|
|
160
|
+
amount: o.amount,
|
|
161
|
+
invoice_number: o.invoice_number,
|
|
162
|
+
currency: o.currency,
|
|
163
|
+
expired_at: o.expired_at,
|
|
164
|
+
},
|
|
165
|
+
checkout_experience: b.checkout_experience ?? {},
|
|
166
|
+
payment: {
|
|
167
|
+
checkout_url: payment.checkout_url,
|
|
168
|
+
type: payment.type,
|
|
169
|
+
status: payment.status,
|
|
170
|
+
state: payment.state,
|
|
171
|
+
},
|
|
172
|
+
customer,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
await new Promise((r) => server.listen(0, '127.0.0.1', r));
|
|
177
|
+
const url = `http://127.0.0.1:${server.address().port}`;
|
|
178
|
+
const find = (id) => {
|
|
179
|
+
const c = checkouts.get(id);
|
|
180
|
+
if (!c)
|
|
181
|
+
throw new Error(`fake senangPay: no checkout ${id}`);
|
|
182
|
+
return c;
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
url,
|
|
186
|
+
requests,
|
|
187
|
+
checkout: (id) => checkouts.get(id),
|
|
188
|
+
markPaid(id) {
|
|
189
|
+
const c = find(id);
|
|
190
|
+
c.order.status = 'PAID';
|
|
191
|
+
c.payment.status = 'SUCCESS';
|
|
192
|
+
c.payment.state = 'COMPLETED';
|
|
193
|
+
c.payment.channel = 'INTERNET_BANKING_FPX';
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* A Global payment notification claiming success (even before `markPaid`; the fake's record stays unpaid).
|
|
197
|
+
* `override` keys with a dot set nested fields (`order.invoice_number`); plain keys set `payment.<key>`. A numeric
|
|
198
|
+
* `amount` value is sent as a JSON number.
|
|
199
|
+
*/
|
|
200
|
+
callbackFor(id, override = {}) {
|
|
201
|
+
const c = find(id);
|
|
202
|
+
const notification = {
|
|
203
|
+
id: c.id,
|
|
204
|
+
order: {
|
|
205
|
+
amount: c.order.amount,
|
|
206
|
+
invoice_number: c.order.invoice_number,
|
|
207
|
+
currency: c.order.currency,
|
|
208
|
+
expired_at: c.order.expired_at,
|
|
209
|
+
},
|
|
210
|
+
payment: {
|
|
211
|
+
channel: c.payment.channel ?? 'INTERNET_BANKING_FPX',
|
|
212
|
+
type: 'SALE',
|
|
213
|
+
initiation_type: 'customer_initiate',
|
|
214
|
+
amount: c.order.amount,
|
|
215
|
+
currency: c.order.currency,
|
|
216
|
+
status: 'SUCCESS',
|
|
217
|
+
state: 'COMPLETED',
|
|
218
|
+
},
|
|
219
|
+
customer: c.customer,
|
|
220
|
+
metadata: c.metadata,
|
|
221
|
+
};
|
|
222
|
+
for (const [key, value] of Object.entries(override)) {
|
|
223
|
+
const [outer, inner] = key.includes('.') ? key.split('.', 2) : ['payment', key];
|
|
224
|
+
const slot = outer ?? '';
|
|
225
|
+
if (!obj(notification[slot]))
|
|
226
|
+
notification[slot] = {};
|
|
227
|
+
const target = notification[slot];
|
|
228
|
+
target[inner ?? ''] = /amount$/.test(key) && /^\d+(\.\d+)?$/.test(value) ? Number(value) : value;
|
|
229
|
+
}
|
|
230
|
+
// Indented, so a handler that re-serialises the parsed JSON gets different bytes and the signature fails.
|
|
231
|
+
const rawBody = JSON.stringify(notification, null, 2);
|
|
232
|
+
const timestamp = isoNow();
|
|
233
|
+
return {
|
|
234
|
+
fields: { id: c.id },
|
|
235
|
+
rawBody,
|
|
236
|
+
headers: {
|
|
237
|
+
'content-type': 'application/json',
|
|
238
|
+
'client-id': opts.clientId,
|
|
239
|
+
'request-timestamp': timestamp,
|
|
240
|
+
'api-version': 'doku-global.2025-12-01',
|
|
241
|
+
signature: dokuSignature(opts.secretKey, opts.clientId, timestamp, opts.notificationPath, rawBody),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
tamper: (cb) => ({
|
|
246
|
+
...cb,
|
|
247
|
+
headers: {
|
|
248
|
+
...cb.headers,
|
|
249
|
+
signature: (cb.headers.signature ?? '').replace(/^HMACSHA256=(.+)$/, (_m, b64) => `HMACSHA256=${flip(b64)}`),
|
|
250
|
+
},
|
|
251
|
+
}),
|
|
252
|
+
close: () => new Promise((r) => server.close(() => r())),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=senangpay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"senangpay.js","sourceRoot":"","sources":["../../src/fakes/senangpay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type FakeGateway } from './types.js';
|
|
2
|
+
export declare const ENDIVE = "2026-09-30.endive";
|
|
3
|
+
type Session = Record<string, unknown> & {
|
|
4
|
+
id: string;
|
|
5
|
+
};
|
|
6
|
+
export interface FakeStripe extends FakeGateway {
|
|
7
|
+
requests: {
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
/** The form body as received: bracket keys left as they are, repeated keys (`a[]`) joined with `,`. */
|
|
11
|
+
body: Record<string, string>;
|
|
12
|
+
/** Every key in body order, repeated keys included. */
|
|
13
|
+
keys: string[];
|
|
14
|
+
headers: Record<string, string | string[] | undefined>;
|
|
15
|
+
}[];
|
|
16
|
+
/** The fake's own record of a session (what `GET /v1/checkout/sessions/{id}` answers). */
|
|
17
|
+
session(id: string): Session | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** `t=<t>,v1=<hex>` exactly as Stripe builds it: HMAC-SHA256 over `${t}.${rawBody}`, key = the full `whsec_…` string. */
|
|
20
|
+
export declare function stripeSignature(webhookSecret: string, rawBody: string, t: number): string;
|
|
21
|
+
export declare function startFakeStripe(opts: {
|
|
22
|
+
secretKey: string;
|
|
23
|
+
webhookSecret: string;
|
|
24
|
+
}): Promise<FakeStripe>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=stripe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../../src/fakes/stripe.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,WAAW,EAAQ,MAAM,YAAY,CAAA;AAEnD,eAAO,MAAM,MAAM,sBAAsB,CAAA;AAEzC,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,EAAE;QACR,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,EAAE,MAAM,CAAA;QACZ,uGAAuG;QACvG,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC5B,uDAAuD;QACvD,IAAI,EAAE,MAAM,EAAE,CAAA;QACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;KACvD,EAAE,CAAA;IACH,0FAA0F;IAC1F,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CACzC;AAED,yHAAyH;AACzH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzF;AAID,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC,UAAU,CAAC,CAgPtB"}
|