@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;
|
package/dist/utils/crypto.d.ts
CHANGED
|
@@ -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;
|
package/dist/utils/crypto.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
}
|