@henrylabs-interview/payment-processor 0.1.14 → 0.2.1

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/dist/index.d.ts CHANGED
@@ -19,5 +19,5 @@ export declare class EmbeddedCheckout {
19
19
  * @param callbackFn - Callback function to handle the payment token
20
20
  * @returns - Whether the UI rendered successfully
21
21
  */
22
- render(containerElementId: string, callbackFn: (paymentToken: string) => void): boolean;
22
+ render(containerElementId: string, callbackFn: (paymentToken: string) => void): Promise<boolean>;
23
23
  }
@@ -14,7 +14,7 @@ export class Checkout {
14
14
  */
15
15
  async create(params) {
16
16
  await sleep(Math.random() * 100);
17
- const hashId = this.buildHistoryHash(params);
17
+ const hashId = await this.buildHistoryHash(params);
18
18
  const history = await readHistory();
19
19
  const sameRecords = history.filter((v) => v.id === hashId);
20
20
  // ---------------------------------------
@@ -24,7 +24,7 @@ export class Checkout {
24
24
  // ---------------------------------------
25
25
  // Phase 2: Business Logic
26
26
  // ---------------------------------------
27
- const response = validationFailure ?? this.processCreateDecision(params, hashId, sameRecords.length);
27
+ const response = validationFailure ?? (await this.processCreateDecision(params, hashId, sameRecords.length));
28
28
  // ---------------------------------------
29
29
  // Phase 3: Webhook Scheduling
30
30
  // ---------------------------------------
@@ -91,7 +91,7 @@ export class Checkout {
91
91
  }
92
92
  return null;
93
93
  }
94
- processCreateDecision(params, hashId, duplicateCount) {
94
+ async processCreateDecision(params, hashId, duplicateCount) {
95
95
  const resCase = this.determineResponseCase(params.amount, duplicateCount);
96
96
  if (resCase === 'failure-retry') {
97
97
  return {
@@ -109,7 +109,7 @@ export class Checkout {
109
109
  message: 'Potential fraud detected with this purchase',
110
110
  };
111
111
  }
112
- const checkoutId = this.createCheckoutRecord(hashId);
112
+ const checkoutId = await this.createCheckoutRecord(hashId);
113
113
  if (resCase === 'success-deferred') {
114
114
  return {
115
115
  status: 'success',
@@ -129,8 +129,8 @@ export class Checkout {
129
129
  },
130
130
  };
131
131
  }
132
- createCheckoutRecord(hashId) {
133
- const checkoutId = `cki_${generateTimeBasedID('checkout')}`;
132
+ async createCheckoutRecord(hashId) {
133
+ const checkoutId = `cki_${await generateTimeBasedID('checkout')}`;
134
134
  INTERNAL_CHECKOUTS[`${checkoutId}`] = {
135
135
  historyRecordId: hashId,
136
136
  };
@@ -138,12 +138,12 @@ export class Checkout {
138
138
  }
139
139
  scheduleCreateWebhook(hashId, response) {
140
140
  const webhookDelay = Math.random() * 3000;
141
- setTimeout(() => {
141
+ setTimeout(async () => {
142
142
  // Deferred flow resolves later
143
143
  if (response.status === 'success' && response.substatus === '202-deferred') {
144
144
  const isSuccess = Math.random() > 0.35;
145
145
  if (isSuccess) {
146
- const checkoutId = this.createCheckoutRecord(hashId);
146
+ const checkoutId = await this.createCheckoutRecord(hashId);
147
147
  this.sendWebhookResponse('checkout.create', {
148
148
  status: 'success',
149
149
  substatus: '201-immediate',
@@ -169,8 +169,8 @@ export class Checkout {
169
169
  this.sendWebhookResponse('checkout.create', response);
170
170
  }, webhookDelay);
171
171
  }
172
- buildHistoryHash(params) {
173
- return hashToString(JSON.stringify({
172
+ async buildHistoryHash(params) {
173
+ return await hashToString(JSON.stringify({
174
174
  type: 'HISTORY_RECORD',
175
175
  amount: params.amount,
176
176
  currency: params.currency,
@@ -206,7 +206,7 @@ export class Checkout {
206
206
  }
207
207
  }
208
208
  if (params.type === 'embedded') {
209
- const paymentToken = `pmt_${generateTimeBasedID('payment-token')}`;
209
+ const paymentToken = `pmt_${await generateTimeBasedID('payment-token')}`;
210
210
  if (params.data.paymentToken !== paymentToken) {
211
211
  return {
212
212
  status: 'failure',
@@ -294,12 +294,12 @@ export class Checkout {
294
294
  };
295
295
  }
296
296
  const confirmationId = 'cof_' +
297
- hashToString(JSON.stringify({
297
+ (await hashToString(JSON.stringify({
298
298
  type: 'CONFIRMATION_ID',
299
299
  amount: record.amount,
300
300
  currency: record.currency,
301
301
  customerId: record.customerId,
302
- }));
302
+ })));
303
303
  return {
304
304
  status: 'success',
305
305
  substatus: '201-immediate',
@@ -1 +1 @@
1
- export declare function renderEmbeddedCheckout(containerElementId: string, checkoutId: string, callbackFn: (paymentToken: string) => void): boolean;
1
+ export declare function renderEmbeddedCheckout(containerElementId: string, checkoutId: string, callbackFn: (paymentToken: string) => void): Promise<boolean>;
@@ -1,5 +1,5 @@
1
1
  import { generateTimeBasedID } from '../utils/crypto';
2
- export function renderEmbeddedCheckout(containerElementId, checkoutId, callbackFn) {
2
+ export async function renderEmbeddedCheckout(containerElementId, checkoutId, callbackFn) {
3
3
  // Ensure browser environment
4
4
  if (typeof window === 'undefined' || typeof document === 'undefined') {
5
5
  console.warn('EmbeddedCheckoutUI can only be used in a browser environment.');
@@ -9,7 +9,7 @@ export function renderEmbeddedCheckout(containerElementId, checkoutId, callbackF
9
9
  console.warn(`Invalid checkout ID: "${checkoutId}".`);
10
10
  return false;
11
11
  }
12
- if (`${checkoutId}` === `cki_${generateTimeBasedID('checkout')}`) {
12
+ if (`${checkoutId}` === `cki_${await generateTimeBasedID('checkout')}`) {
13
13
  console.warn(`Expired checkout ID: "${checkoutId}".`);
14
14
  return false;
15
15
  }
@@ -143,7 +143,7 @@ export function renderEmbeddedCheckout(containerElementId, checkoutId, callbackF
143
143
  </div>
144
144
  `;
145
145
  const button = container.querySelector('#confirm-btn');
146
- button?.addEventListener('click', () => {
146
+ button?.addEventListener('click', async () => {
147
147
  const number = (container.querySelector('#card-number')?.value ?? '').replace(/\s+/g, '');
148
148
  const expMonth = Number(container.querySelector('#exp-month')?.value);
149
149
  const expYear = Number(container.querySelector('#exp-year')?.value);
@@ -155,7 +155,7 @@ export function renderEmbeddedCheckout(containerElementId, checkoutId, callbackF
155
155
  // expYear: expYear > 2000 ? expYear : expYear + 2000,
156
156
  // cvc: cvc,
157
157
  // };
158
- const paymentToken = `pmt_${generateTimeBasedID('payment-token')}`;
158
+ const paymentToken = `pmt_${await generateTimeBasedID('payment-token')}`;
159
159
  callbackFn(paymentToken);
160
160
  });
161
161
  return true;
@@ -1,4 +1,4 @@
1
1
  export declare function generateID(length?: number): number;
2
- export declare function generateTimeBasedID(extra?: string): string;
3
- export declare function hashToString(input: string): string;
2
+ export declare function generateTimeBasedID(extra?: string): Promise<string>;
3
+ export declare function hashToString(input: string): Promise<string>;
4
4
  export declare function signPayload(payload: string, secret: string): string;
@@ -1,3 +1,5 @@
1
+ import { sha256 } from '@noble/hashes/sha2.js';
2
+ import { bytesToHex } from '@noble/hashes/utils.js';
1
3
  import crypto from 'crypto';
2
4
  export function generateID(length = 12) {
3
5
  const digits = [];
@@ -7,14 +9,14 @@ export function generateID(length = 12) {
7
9
  }
8
10
  return parseInt(digits.join(''));
9
11
  }
10
- export function generateTimeBasedID(extra = '') {
12
+ export async function generateTimeBasedID(extra = '') {
11
13
  const now = Date.now(); // current time in ms
12
14
  const oneMinute = 60 * 1000;
13
15
  const ms = Math.floor(now / oneMinute) * oneMinute;
14
- return hashToString(`${ms}---${extra}`);
16
+ return await hashToString(`${ms}---${extra}`);
15
17
  }
16
- export function hashToString(input) {
17
- const hash = crypto.createHash('sha256').update(input).digest('hex');
18
+ export async function hashToString(input) {
19
+ const hash = bytesToHex(sha256(new TextEncoder().encode(input)));
18
20
  // 16 hex characters = 64 bits
19
21
  return hash.slice(0, 16);
20
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henrylabs-interview/payment-processor",
3
- "version": "0.1.14",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,5 +21,8 @@
21
21
  "devDependencies": {
22
22
  "@types/bun": "latest",
23
23
  "typescript": "^5"
24
+ },
25
+ "dependencies": {
26
+ "@noble/hashes": "^2.0.1"
24
27
  }
25
28
  }