@henrylabs-interview/payment-processor 0.2.11 → 0.2.13

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.
@@ -1,5 +1,6 @@
1
1
  type CheckoutCreateResponse = CheckoutCreateSuccessDeferred | CheckoutCreateSuccessImmediate | CheckoutCreateFailure;
2
2
  interface CheckoutCreateGeneric {
3
+ _reqId: string;
3
4
  status: 'success' | 'failure';
4
5
  code: number;
5
6
  message: string;
@@ -42,6 +43,7 @@ interface CheckoutConfirmParamsRawCard extends CheckoutConfirmParamsGeneric {
42
43
  }
43
44
  type CheckoutConfirmResponse = CheckoutConfirmSuccessDeferred | CheckoutConfirmSuccessImmediate | CheckoutConfirmFailure;
44
45
  interface CheckoutConfirmGeneric {
46
+ _reqId: string;
45
47
  status: 'success' | 'failure';
46
48
  code: number;
47
49
  message: string;
@@ -1,5 +1,5 @@
1
1
  import { readHistory, writeHistory } from '../utils/store';
2
- import { generateTimeBasedID, hashToString, signPayload } from '../utils/crypto';
2
+ import { generateTimeBasedID, genReqId, hashToString, signPayload } from '../utils/crypto';
3
3
  import { INTERNAL_WEBHOOKS } from './webhooks';
4
4
  import { sleep } from '../utils/async';
5
5
  import { isValidCardNumber, isValidExpiry } from '../utils/card';
@@ -67,6 +67,7 @@ export class Checkout {
67
67
  validateCreate(params) {
68
68
  if (params.amount <= 0) {
69
69
  return {
70
+ _reqId: genReqId(),
70
71
  status: 'failure',
71
72
  substatus: '500-error',
72
73
  code: 500,
@@ -75,6 +76,7 @@ export class Checkout {
75
76
  }
76
77
  if (params.currency === 'JPY') {
77
78
  return {
79
+ _reqId: genReqId(),
78
80
  status: 'failure',
79
81
  substatus: '501-not-supported',
80
82
  code: 501,
@@ -84,6 +86,7 @@ export class Checkout {
84
86
  // Simulated random internal failure
85
87
  if (Math.random() > 0.85) {
86
88
  return {
89
+ _reqId: genReqId(),
87
90
  status: 'failure',
88
91
  substatus: '500-error',
89
92
  code: 500,
@@ -96,6 +99,7 @@ export class Checkout {
96
99
  const resCase = this.determineResponseCase(params.amount, duplicateCount);
97
100
  if (resCase === 'failure-retry') {
98
101
  return {
102
+ _reqId: genReqId(),
99
103
  status: 'failure',
100
104
  substatus: '503-retry',
101
105
  code: 503,
@@ -104,6 +108,7 @@ export class Checkout {
104
108
  }
105
109
  if (resCase === 'failure-fraud') {
106
110
  return {
111
+ _reqId: genReqId(),
107
112
  status: 'failure',
108
113
  substatus: '502-fraud',
109
114
  code: 502,
@@ -113,6 +118,7 @@ export class Checkout {
113
118
  const checkoutId = await this.createCheckoutRecord(hashId);
114
119
  if (resCase === 'success-deferred') {
115
120
  return {
121
+ _reqId: genReqId(),
116
122
  status: 'success',
117
123
  substatus: '202-deferred',
118
124
  code: 202,
@@ -120,6 +126,7 @@ export class Checkout {
120
126
  };
121
127
  }
122
128
  return {
129
+ _reqId: genReqId(),
123
130
  status: 'success',
124
131
  substatus: '201-immediate',
125
132
  code: 201,
@@ -145,6 +152,7 @@ export class Checkout {
145
152
  if (Math.random() > 0.2) {
146
153
  const checkoutId = await this.createCheckoutRecord(hashId);
147
154
  this.sendWebhookResponse('checkout.create', {
155
+ _reqId: response._reqId,
148
156
  status: 'success',
149
157
  substatus: '201-immediate',
150
158
  code: 201,
@@ -157,6 +165,7 @@ export class Checkout {
157
165
  }
158
166
  else if (Math.random() > 0.05) {
159
167
  this.sendWebhookResponse('checkout.create', {
168
+ _reqId: response._reqId,
160
169
  status: 'failure',
161
170
  substatus: '503-retry',
162
171
  code: 503,
@@ -165,6 +174,7 @@ export class Checkout {
165
174
  }
166
175
  else {
167
176
  this.sendWebhookResponse('checkout.create', {
177
+ _reqId: response._reqId,
168
178
  status: 'failure',
169
179
  substatus: '502-fraud',
170
180
  code: 502,
@@ -189,6 +199,7 @@ export class Checkout {
189
199
  async validateConfirm(params) {
190
200
  if (`${params.checkoutId}`.length !== 20 || !`${params.checkoutId}`.startsWith('cki_')) {
191
201
  return {
202
+ _reqId: genReqId(),
192
203
  status: 'failure',
193
204
  substatus: '500-error',
194
205
  code: 500,
@@ -197,6 +208,7 @@ export class Checkout {
197
208
  }
198
209
  if (!INTERNAL_CHECKOUTS[`${params.checkoutId}`]) {
199
210
  return {
211
+ _reqId: genReqId(),
200
212
  status: 'failure',
201
213
  substatus: '503-retry',
202
214
  code: 504,
@@ -206,6 +218,7 @@ export class Checkout {
206
218
  if (params.type === 'embedded') {
207
219
  if (params.data.paymentToken.length !== 20 || !params.data.paymentToken.startsWith('pmt_')) {
208
220
  return {
221
+ _reqId: genReqId(),
209
222
  status: 'failure',
210
223
  substatus: '500-error',
211
224
  code: 500,
@@ -217,6 +230,7 @@ export class Checkout {
217
230
  const paymentToken = `pmt_${await generateTimeBasedID('payment-token')}`;
218
231
  if (params.data.paymentToken !== paymentToken) {
219
232
  return {
233
+ _reqId: genReqId(),
220
234
  status: 'failure',
221
235
  substatus: '503-retry',
222
236
  code: 503,
@@ -228,6 +242,7 @@ export class Checkout {
228
242
  const { number, expMonth, expYear, cvc } = params.data;
229
243
  if (!isValidCardNumber(number)) {
230
244
  return {
245
+ _reqId: genReqId(),
231
246
  status: 'failure',
232
247
  substatus: '500-error',
233
248
  code: 500,
@@ -236,6 +251,7 @@ export class Checkout {
236
251
  }
237
252
  if (!isValidExpiry(expMonth, expYear)) {
238
253
  return {
254
+ _reqId: genReqId(),
239
255
  status: 'failure',
240
256
  substatus: '500-error',
241
257
  code: 500,
@@ -244,6 +260,7 @@ export class Checkout {
244
260
  }
245
261
  if (!/^\d{3,4}$/.test(cvc)) {
246
262
  return {
263
+ _reqId: genReqId(),
247
264
  status: 'failure',
248
265
  substatus: '500-error',
249
266
  code: 500,
@@ -257,6 +274,7 @@ export class Checkout {
257
274
  const decision = Math.random();
258
275
  if (decision > 0.95) {
259
276
  return {
277
+ _reqId: genReqId(),
260
278
  status: 'failure',
261
279
  substatus: '502-fraud',
262
280
  code: 502,
@@ -265,6 +283,7 @@ export class Checkout {
265
283
  }
266
284
  if (decision > 0.65) {
267
285
  return {
286
+ _reqId: genReqId(),
268
287
  status: 'failure',
269
288
  substatus: '503-retry',
270
289
  code: 503,
@@ -273,6 +292,7 @@ export class Checkout {
273
292
  }
274
293
  if (decision > 0.35) {
275
294
  return {
295
+ _reqId: genReqId(),
276
296
  status: 'success',
277
297
  substatus: '202-deferred',
278
298
  code: 202,
@@ -285,6 +305,7 @@ export class Checkout {
285
305
  const { historyRecordId } = INTERNAL_CHECKOUTS[`${checkoutId}`] ?? {};
286
306
  if (!historyRecordId) {
287
307
  return {
308
+ _reqId: genReqId(),
288
309
  status: 'failure',
289
310
  substatus: '500-error',
290
311
  code: 500,
@@ -295,6 +316,7 @@ export class Checkout {
295
316
  const record = history.find((v) => v.id === historyRecordId);
296
317
  if (!record) {
297
318
  return {
319
+ _reqId: genReqId(),
298
320
  status: 'failure',
299
321
  substatus: '500-error',
300
322
  code: 500,
@@ -309,6 +331,7 @@ export class Checkout {
309
331
  customerId: record.customerId,
310
332
  })));
311
333
  return {
334
+ _reqId: genReqId(),
312
335
  status: 'success',
313
336
  substatus: '201-immediate',
314
337
  code: 201,
@@ -332,6 +355,7 @@ export class Checkout {
332
355
  }
333
356
  else if (Math.random() > 0.05) {
334
357
  this.sendWebhookResponse('checkout.confirm', {
358
+ _reqId: response._reqId,
335
359
  status: 'failure',
336
360
  substatus: '503-retry',
337
361
  code: 503,
@@ -340,6 +364,7 @@ export class Checkout {
340
364
  }
341
365
  else {
342
366
  this.sendWebhookResponse('checkout.confirm', {
367
+ _reqId: response._reqId,
343
368
  status: 'failure',
344
369
  substatus: '502-fraud',
345
370
  code: 502,
@@ -402,7 +427,7 @@ export class Checkout {
402
427
  const matchingTypes = ['checkout', baseType, eventType];
403
428
  const hooks = INTERNAL_WEBHOOKS.filter((w) => matchingTypes.includes(w.event));
404
429
  const event = {
405
- id: crypto.randomUUID(),
430
+ uid: `_${genReqId()}_`,
406
431
  type: eventType,
407
432
  createdAt: Date.now(),
408
433
  data: response,
@@ -1,6 +1,6 @@
1
1
  export type EventType = 'checkout' | 'checkout.create' | 'checkout.create.success' | 'checkout.create.failure' | 'checkout.confirm' | 'checkout.confirm.success' | 'checkout.confirm.failure';
2
2
  export interface WebhookEvent {
3
- id: string;
3
+ uid: string;
4
4
  type: EventType;
5
5
  createdAt: number;
6
6
  data: Record<string, any>;
@@ -2,3 +2,4 @@ export declare function generateID(length?: number): number;
2
2
  export declare function generateTimeBasedID(extra?: string): Promise<string>;
3
3
  export declare function hashToString(input: string): Promise<string>;
4
4
  export declare function signPayload(payload: string, secret: string): string;
5
+ export declare function genReqId(): string;
@@ -23,3 +23,6 @@ export async function hashToString(input) {
23
23
  export function signPayload(payload, secret) {
24
24
  return crypto.createHmac('sha256', secret).update(payload).digest('hex');
25
25
  }
26
+ export function genReqId() {
27
+ return crypto.randomUUID().split('-')[0] ?? '';
28
+ }
@@ -1,12 +1,16 @@
1
1
  const path = new URL('../db-store/history.json', import.meta.url);
2
+ async function ensureFileExists() {
3
+ const file = Bun.file(path);
4
+ if (!(await file.exists())) {
5
+ await Bun.write(path, '[]');
6
+ }
7
+ }
2
8
  /**
3
9
  * Reads all history records from the JSON store.
4
10
  */
5
11
  export async function readHistory() {
12
+ await ensureFileExists();
6
13
  const file = Bun.file(path);
7
- if (!(await file.exists())) {
8
- return [];
9
- }
10
14
  const text = await file.text();
11
15
  if (!text)
12
16
  return [];
@@ -14,6 +18,8 @@ export async function readHistory() {
14
18
  return JSON.parse(text);
15
19
  }
16
20
  catch {
21
+ // If file somehow becomes corrupted, reset it
22
+ await Bun.write(path, '[]');
17
23
  return [];
18
24
  }
19
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henrylabs-interview/payment-processor",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",