@payloops/processor-core 0.0.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/.env.example +4 -0
- package/Dockerfile +41 -0
- package/README.md +248 -0
- package/dist/activities/index.d.ts +2 -0
- package/dist/activities/index.js +19 -0
- package/dist/chunk-AV4OEJXY.js +132 -0
- package/dist/chunk-CZTZBCNV.js +133 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-X2Y2ZUQA.js +297 -0
- package/dist/index-CXmmtGo_.d.ts +25 -0
- package/dist/index-CsnpS83V.d.ts +72 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +21 -0
- package/dist/workflows/index.d.ts +27 -0
- package/dist/workflows/index.js +13 -0
- package/package.json +54 -0
- package/src/activities/index.ts +224 -0
- package/src/index.ts +22 -0
- package/src/lib/crypto.ts +24 -0
- package/src/lib/db.ts +91 -0
- package/src/lib/observability/index.ts +2 -0
- package/src/lib/observability/logger.ts +44 -0
- package/src/lib/observability/otel.ts +53 -0
- package/src/lib/registry.ts +28 -0
- package/src/types/index.ts +95 -0
- package/src/worker.ts +105 -0
- package/src/workflows/index.ts +5 -0
- package/src/workflows/payment.ts +111 -0
- package/src/workflows/webhook.ts +64 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { proxyActivities, sleep, continueAsNew, defineSignal, setHandler } from '@temporalio/workflow';
|
|
2
|
+
import type * as activities from '../activities';
|
|
3
|
+
import type { PaymentInput, PaymentResult } from '../types';
|
|
4
|
+
|
|
5
|
+
const { processPayment, updateOrderStatus, capturePayment } = proxyActivities<typeof activities>({
|
|
6
|
+
startToCloseTimeout: '2 minutes',
|
|
7
|
+
retry: {
|
|
8
|
+
initialInterval: '1s',
|
|
9
|
+
maximumInterval: '1m',
|
|
10
|
+
backoffCoefficient: 2,
|
|
11
|
+
maximumAttempts: 5
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export interface PaymentWorkflowInput {
|
|
16
|
+
orderId: string;
|
|
17
|
+
merchantId: string;
|
|
18
|
+
amount: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
processor: string;
|
|
21
|
+
returnUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Signals for external events
|
|
25
|
+
export const completePaymentSignal = defineSignal<[{ success: boolean; processorTransactionId?: string }]>(
|
|
26
|
+
'completePayment'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export const cancelPaymentSignal = defineSignal('cancelPayment');
|
|
30
|
+
|
|
31
|
+
export async function PaymentWorkflow(input: PaymentWorkflowInput): Promise<PaymentResult> {
|
|
32
|
+
let paymentCompleted = false;
|
|
33
|
+
let paymentResult: PaymentResult | null = null;
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
|
|
36
|
+
// Handle external payment completion (3DS, redirect flows)
|
|
37
|
+
setHandler(completePaymentSignal, (result) => {
|
|
38
|
+
paymentCompleted = true;
|
|
39
|
+
paymentResult = {
|
|
40
|
+
success: result.success,
|
|
41
|
+
status: result.success ? 'captured' : 'failed',
|
|
42
|
+
processorTransactionId: result.processorTransactionId
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Handle cancellation
|
|
47
|
+
setHandler(cancelPaymentSignal, () => {
|
|
48
|
+
cancelled = true;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Step 1: Process payment
|
|
53
|
+
const result = await processPayment({
|
|
54
|
+
orderId: input.orderId,
|
|
55
|
+
merchantId: input.merchantId,
|
|
56
|
+
amount: input.amount,
|
|
57
|
+
currency: input.currency,
|
|
58
|
+
processor: input.processor,
|
|
59
|
+
returnUrl: input.returnUrl
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// If payment requires redirect (3DS, UPI, etc.)
|
|
63
|
+
if (result.status === 'requires_action') {
|
|
64
|
+
await updateOrderStatus(input.orderId, 'requires_action', result.processorOrderId);
|
|
65
|
+
|
|
66
|
+
// Wait for completion signal (with timeout)
|
|
67
|
+
const timeout = 15 * 60 * 1000; // 15 minutes
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
|
|
70
|
+
while (!paymentCompleted && !cancelled && Date.now() - startTime < timeout) {
|
|
71
|
+
await sleep('10 seconds');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (cancelled) {
|
|
75
|
+
await updateOrderStatus(input.orderId, 'cancelled');
|
|
76
|
+
return { success: false, status: 'failed', errorCode: 'cancelled', errorMessage: 'Payment cancelled' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!paymentCompleted) {
|
|
80
|
+
await updateOrderStatus(input.orderId, 'failed');
|
|
81
|
+
return { success: false, status: 'failed', errorCode: 'timeout', errorMessage: 'Payment timeout' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (paymentResult !== null) {
|
|
85
|
+
const finalResult = paymentResult as PaymentResult;
|
|
86
|
+
await updateOrderStatus(
|
|
87
|
+
input.orderId,
|
|
88
|
+
finalResult.status,
|
|
89
|
+
result.processorOrderId,
|
|
90
|
+
finalResult.processorTransactionId
|
|
91
|
+
);
|
|
92
|
+
return finalResult;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Update order with result
|
|
97
|
+
await updateOrderStatus(input.orderId, result.status, result.processorOrderId, result.processorTransactionId);
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
102
|
+
await updateOrderStatus(input.orderId, 'failed');
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
status: 'failed',
|
|
107
|
+
errorCode: 'workflow_error',
|
|
108
|
+
errorMessage
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { proxyActivities, sleep } from '@temporalio/workflow';
|
|
2
|
+
import type * as activities from '../activities';
|
|
3
|
+
import type { WebhookDeliveryResult } from '../types';
|
|
4
|
+
|
|
5
|
+
const { deliverWebhook, getMerchantWebhookUrl } = proxyActivities<typeof activities>({
|
|
6
|
+
startToCloseTimeout: '1 minute',
|
|
7
|
+
retry: {
|
|
8
|
+
initialInterval: '1s',
|
|
9
|
+
maximumInterval: '30s',
|
|
10
|
+
backoffCoefficient: 2,
|
|
11
|
+
maximumAttempts: 3
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export interface WebhookDeliveryWorkflowInput {
|
|
16
|
+
webhookEventId: string;
|
|
17
|
+
merchantId: string;
|
|
18
|
+
webhookUrl: string;
|
|
19
|
+
payload: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const MAX_ATTEMPTS = 5;
|
|
23
|
+
const RETRY_DELAYS = [
|
|
24
|
+
60 * 1000, // 1 minute
|
|
25
|
+
5 * 60 * 1000, // 5 minutes
|
|
26
|
+
30 * 60 * 1000, // 30 minutes
|
|
27
|
+
2 * 60 * 60 * 1000, // 2 hours
|
|
28
|
+
24 * 60 * 60 * 1000 // 24 hours
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export async function WebhookDeliveryWorkflow(input: WebhookDeliveryWorkflowInput): Promise<WebhookDeliveryResult> {
|
|
32
|
+
// Get merchant's webhook secret
|
|
33
|
+
const { secret } = await getMerchantWebhookUrl(input.merchantId);
|
|
34
|
+
|
|
35
|
+
let attempt = 0;
|
|
36
|
+
let lastResult: WebhookDeliveryResult | null = null;
|
|
37
|
+
|
|
38
|
+
while (attempt < MAX_ATTEMPTS) {
|
|
39
|
+
attempt++;
|
|
40
|
+
|
|
41
|
+
const result = await deliverWebhook(input.webhookEventId, input.webhookUrl, secret || undefined, input.payload);
|
|
42
|
+
|
|
43
|
+
lastResult = result;
|
|
44
|
+
|
|
45
|
+
if (result.success) {
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If we've exhausted all attempts, return failure
|
|
50
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
attempts: attempt,
|
|
54
|
+
errorMessage: result.errorMessage || 'Max attempts reached'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Wait before next retry
|
|
59
|
+
const delay = RETRY_DELAYS[attempt - 1] || RETRY_DELAYS[RETRY_DELAYS.length - 1];
|
|
60
|
+
await sleep(delay);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return lastResult || { success: false, attempts: attempt, errorMessage: 'Unknown error' };
|
|
64
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"baseUrl": ".",
|
|
14
|
+
"paths": {
|
|
15
|
+
"@/*": ["src/*"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|