@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.
@@ -0,0 +1,297 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-MLKGABMK.js";
4
+
5
+ // src/activities/index.ts
6
+ var activities_exports = {};
7
+ __export(activities_exports, {
8
+ capturePayment: () => capturePayment,
9
+ deliverWebhook: () => deliverWebhook,
10
+ getMerchantWebhookUrl: () => getMerchantWebhookUrl,
11
+ getProcessorConfig: () => getProcessorConfig,
12
+ processPayment: () => processPayment,
13
+ refundPayment: () => refundPayment,
14
+ updateOrderStatus: () => updateOrderStatus
15
+ });
16
+ import { eq, and } from "drizzle-orm";
17
+
18
+ // src/lib/db.ts
19
+ import { drizzle } from "drizzle-orm/node-postgres";
20
+ import { Pool } from "pg";
21
+ import {
22
+ pgTable,
23
+ text,
24
+ timestamp,
25
+ integer,
26
+ boolean,
27
+ jsonb,
28
+ varchar,
29
+ uuid
30
+ } from "drizzle-orm/pg-core";
31
+ var orders = pgTable("orders", {
32
+ id: uuid("id").primaryKey().defaultRandom(),
33
+ merchantId: uuid("merchant_id").notNull(),
34
+ externalId: varchar("external_id", { length: 255 }).notNull(),
35
+ amount: integer("amount").notNull(),
36
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
37
+ status: varchar("status", { length: 50 }).notNull().default("pending"),
38
+ processor: varchar("processor", { length: 50 }),
39
+ processorOrderId: varchar("processor_order_id", { length: 255 }),
40
+ metadata: jsonb("metadata").default({}),
41
+ workflowId: varchar("workflow_id", { length: 255 }),
42
+ createdAt: timestamp("created_at").defaultNow().notNull(),
43
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
44
+ });
45
+ var transactions = pgTable("transactions", {
46
+ id: uuid("id").primaryKey().defaultRandom(),
47
+ orderId: uuid("order_id").notNull(),
48
+ type: varchar("type", { length: 50 }).notNull(),
49
+ amount: integer("amount").notNull(),
50
+ status: varchar("status", { length: 50 }).notNull().default("pending"),
51
+ processorTransactionId: varchar("processor_transaction_id", { length: 255 }),
52
+ processorResponse: jsonb("processor_response"),
53
+ errorCode: varchar("error_code", { length: 100 }),
54
+ errorMessage: text("error_message"),
55
+ createdAt: timestamp("created_at").defaultNow().notNull()
56
+ });
57
+ var processorConfigs = pgTable("processor_configs", {
58
+ id: uuid("id").primaryKey().defaultRandom(),
59
+ merchantId: uuid("merchant_id").notNull(),
60
+ processor: varchar("processor", { length: 50 }).notNull(),
61
+ credentialsEncrypted: text("credentials_encrypted").notNull(),
62
+ priority: integer("priority").notNull().default(1),
63
+ enabled: boolean("enabled").notNull().default(true),
64
+ testMode: boolean("test_mode").notNull().default(true),
65
+ createdAt: timestamp("created_at").defaultNow().notNull(),
66
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
67
+ });
68
+ var webhookEvents = pgTable("webhook_events", {
69
+ id: uuid("id").primaryKey().defaultRandom(),
70
+ merchantId: uuid("merchant_id").notNull(),
71
+ orderId: uuid("order_id"),
72
+ eventType: varchar("event_type", { length: 100 }).notNull(),
73
+ payload: jsonb("payload").notNull(),
74
+ status: varchar("status", { length: 50 }).notNull().default("pending"),
75
+ attempts: integer("attempts").notNull().default(0),
76
+ lastAttemptAt: timestamp("last_attempt_at"),
77
+ nextRetryAt: timestamp("next_retry_at"),
78
+ deliveredAt: timestamp("delivered_at"),
79
+ workflowId: varchar("workflow_id", { length: 255 }),
80
+ createdAt: timestamp("created_at").defaultNow().notNull()
81
+ });
82
+ var merchants = pgTable("merchants", {
83
+ id: uuid("id").primaryKey().defaultRandom(),
84
+ name: varchar("name", { length: 255 }).notNull(),
85
+ email: varchar("email", { length: 255 }).notNull().unique(),
86
+ webhookUrl: text("webhook_url"),
87
+ webhookSecret: text("webhook_secret")
88
+ });
89
+ var dbInstance = null;
90
+ function getDb() {
91
+ if (!dbInstance) {
92
+ const pool = new Pool({
93
+ connectionString: process.env.DATABASE_URL
94
+ });
95
+ dbInstance = drizzle(pool);
96
+ }
97
+ return dbInstance;
98
+ }
99
+
100
+ // src/lib/crypto.ts
101
+ import { createDecipheriv, createHash } from "crypto";
102
+ var ALGORITHM = "aes-256-gcm";
103
+ function getKey() {
104
+ const key = process.env.ENCRYPTION_KEY;
105
+ if (!key) throw new Error("ENCRYPTION_KEY not set");
106
+ return createHash("sha256").update(key).digest();
107
+ }
108
+ function decrypt(encryptedText) {
109
+ const [ivHex, authTagHex, encrypted] = encryptedText.split(":");
110
+ const iv = Buffer.from(ivHex, "hex");
111
+ const authTag = Buffer.from(authTagHex, "hex");
112
+ const decipher = createDecipheriv(ALGORITHM, getKey(), iv);
113
+ decipher.setAuthTag(authTag);
114
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
115
+ decrypted += decipher.final("utf8");
116
+ return decrypted;
117
+ }
118
+
119
+ // src/lib/registry.ts
120
+ var processors = /* @__PURE__ */ new Map();
121
+ function registerProcessor(processor) {
122
+ if (processors.has(processor.name)) {
123
+ throw new Error(`Processor ${processor.name} is already registered`);
124
+ }
125
+ processors.set(processor.name, processor);
126
+ console.log(`Registered processor: ${processor.name}`);
127
+ }
128
+ function getProcessor(name) {
129
+ const processor = processors.get(name);
130
+ if (!processor) {
131
+ throw new Error(`Processor ${name} not found. Available: ${Array.from(processors.keys()).join(", ")}`);
132
+ }
133
+ return processor;
134
+ }
135
+ function getRegisteredProcessors() {
136
+ return Array.from(processors.keys());
137
+ }
138
+ function hasProcessor(name) {
139
+ return processors.has(name);
140
+ }
141
+
142
+ // src/activities/index.ts
143
+ import crypto from "crypto";
144
+ async function getProcessorConfig(merchantId, processor) {
145
+ const db = getDb();
146
+ const config = await db.select().from(processorConfigs).where(and(eq(processorConfigs.merchantId, merchantId), eq(processorConfigs.processor, processor))).limit(1);
147
+ if (config.length === 0) return null;
148
+ const credentials = JSON.parse(decrypt(config[0].credentialsEncrypted));
149
+ return {
150
+ merchantId,
151
+ processor,
152
+ testMode: config[0].testMode,
153
+ credentials
154
+ };
155
+ }
156
+ async function updateOrderStatus(orderId, status, processorOrderId, processorTransactionId) {
157
+ const db = getDb();
158
+ await db.update(orders).set({
159
+ status,
160
+ processorOrderId: processorOrderId || void 0,
161
+ updatedAt: /* @__PURE__ */ new Date()
162
+ }).where(eq(orders.id, orderId));
163
+ if (processorTransactionId) {
164
+ const order = await db.select().from(orders).where(eq(orders.id, orderId)).limit(1);
165
+ if (order.length > 0) {
166
+ await db.insert(transactions).values({
167
+ orderId,
168
+ type: status === "captured" ? "capture" : status === "authorized" ? "authorization" : "authorization",
169
+ amount: order[0].amount,
170
+ status: status === "failed" ? "failed" : "success",
171
+ processorTransactionId
172
+ });
173
+ }
174
+ }
175
+ }
176
+ async function processPayment(input) {
177
+ const config = await getProcessorConfig(input.merchantId, input.processor);
178
+ if (!config) {
179
+ return {
180
+ success: false,
181
+ status: "failed",
182
+ errorCode: "no_processor_config",
183
+ errorMessage: `Processor ${input.processor} not configured for merchant`
184
+ };
185
+ }
186
+ const processor = getProcessor(input.processor);
187
+ return processor.createPayment(input, config);
188
+ }
189
+ async function capturePayment(processorOrderId, amount, merchantId, processor) {
190
+ const config = await getProcessorConfig(merchantId, processor);
191
+ if (!config) {
192
+ return {
193
+ success: false,
194
+ status: "failed",
195
+ errorCode: "no_processor_config",
196
+ errorMessage: `Processor ${processor} not configured`
197
+ };
198
+ }
199
+ const processorInstance = getProcessor(processor);
200
+ return processorInstance.capturePayment(processorOrderId, amount, config);
201
+ }
202
+ async function refundPayment(processorTransactionId, amount, merchantId, processor) {
203
+ const config = await getProcessorConfig(merchantId, processor);
204
+ if (!config) {
205
+ return {
206
+ success: false,
207
+ status: "failed",
208
+ errorCode: "no_processor_config",
209
+ errorMessage: `Processor ${processor} not configured`
210
+ };
211
+ }
212
+ const processorInstance = getProcessor(processor);
213
+ return processorInstance.refundPayment(processorTransactionId, amount, config);
214
+ }
215
+ async function deliverWebhook(webhookEventId, webhookUrl, webhookSecret, payload) {
216
+ const db = getDb();
217
+ const event = await db.select().from(webhookEvents).where(eq(webhookEvents.id, webhookEventId)).limit(1);
218
+ const attempts = (event[0]?.attempts || 0) + 1;
219
+ try {
220
+ const body = JSON.stringify(payload);
221
+ const headers = {
222
+ "Content-Type": "application/json",
223
+ "X-Loop-Event-Id": webhookEventId,
224
+ "X-Loop-Timestamp": String(Date.now())
225
+ };
226
+ if (webhookSecret) {
227
+ const timestamp2 = headers["X-Loop-Timestamp"];
228
+ const signaturePayload = `${timestamp2}.${body}`;
229
+ const signature = crypto.createHmac("sha256", webhookSecret).update(signaturePayload).digest("hex");
230
+ headers["X-Loop-Signature"] = `v1=${signature}`;
231
+ }
232
+ const response = await fetch(webhookUrl, {
233
+ method: "POST",
234
+ headers,
235
+ body,
236
+ signal: AbortSignal.timeout(3e4)
237
+ // 30 second timeout
238
+ });
239
+ const success = response.ok;
240
+ await db.update(webhookEvents).set({
241
+ status: success ? "delivered" : "pending",
242
+ attempts,
243
+ lastAttemptAt: /* @__PURE__ */ new Date(),
244
+ deliveredAt: success ? /* @__PURE__ */ new Date() : void 0,
245
+ nextRetryAt: success ? void 0 : new Date(Date.now() + getRetryDelay(attempts))
246
+ }).where(eq(webhookEvents.id, webhookEventId));
247
+ return {
248
+ success,
249
+ statusCode: response.status,
250
+ attempts,
251
+ deliveredAt: success ? /* @__PURE__ */ new Date() : void 0,
252
+ errorMessage: success ? void 0 : `HTTP ${response.status}`
253
+ };
254
+ } catch (error) {
255
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
256
+ await db.update(webhookEvents).set({
257
+ status: attempts >= 5 ? "failed" : "pending",
258
+ attempts,
259
+ lastAttemptAt: /* @__PURE__ */ new Date(),
260
+ nextRetryAt: attempts >= 5 ? void 0 : new Date(Date.now() + getRetryDelay(attempts))
261
+ }).where(eq(webhookEvents.id, webhookEventId));
262
+ return {
263
+ success: false,
264
+ attempts,
265
+ errorMessage
266
+ };
267
+ }
268
+ }
269
+ async function getMerchantWebhookUrl(merchantId) {
270
+ const db = getDb();
271
+ const merchant = await db.select().from(merchants).where(eq(merchants.id, merchantId)).limit(1);
272
+ return {
273
+ url: merchant[0]?.webhookUrl || null,
274
+ secret: merchant[0]?.webhookSecret || null
275
+ };
276
+ }
277
+ function getRetryDelay(attempt) {
278
+ const baseDelay = 60 * 1e3;
279
+ const maxDelay = 24 * 60 * 60 * 1e3;
280
+ const delay = baseDelay * Math.pow(2, attempt - 1);
281
+ return Math.min(delay, maxDelay);
282
+ }
283
+
284
+ export {
285
+ registerProcessor,
286
+ getProcessor,
287
+ getRegisteredProcessors,
288
+ hasProcessor,
289
+ getProcessorConfig,
290
+ updateOrderStatus,
291
+ processPayment,
292
+ capturePayment,
293
+ refundPayment,
294
+ deliverWebhook,
295
+ getMerchantWebhookUrl,
296
+ activities_exports
297
+ };
@@ -0,0 +1,25 @@
1
+ import { a as PaymentConfig, b as PaymentInput, c as PaymentResult, d as RefundResult, e as WebhookDeliveryResult } from './index-CsnpS83V.js';
2
+
3
+ declare function getProcessorConfig(merchantId: string, processor: string): Promise<PaymentConfig | null>;
4
+ declare function updateOrderStatus(orderId: string, status: string, processorOrderId?: string, processorTransactionId?: string): Promise<void>;
5
+ declare function processPayment(input: PaymentInput): Promise<PaymentResult>;
6
+ declare function capturePayment(processorOrderId: string, amount: number, merchantId: string, processor: string): Promise<PaymentResult>;
7
+ declare function refundPayment(processorTransactionId: string, amount: number, merchantId: string, processor: string): Promise<RefundResult>;
8
+ declare function deliverWebhook(webhookEventId: string, webhookUrl: string, webhookSecret: string | undefined, payload: Record<string, unknown>): Promise<WebhookDeliveryResult>;
9
+ declare function getMerchantWebhookUrl(merchantId: string): Promise<{
10
+ url: string | null;
11
+ secret: string | null;
12
+ }>;
13
+
14
+ declare const index_capturePayment: typeof capturePayment;
15
+ declare const index_deliverWebhook: typeof deliverWebhook;
16
+ declare const index_getMerchantWebhookUrl: typeof getMerchantWebhookUrl;
17
+ declare const index_getProcessorConfig: typeof getProcessorConfig;
18
+ declare const index_processPayment: typeof processPayment;
19
+ declare const index_refundPayment: typeof refundPayment;
20
+ declare const index_updateOrderStatus: typeof updateOrderStatus;
21
+ declare namespace index {
22
+ export { index_capturePayment as capturePayment, index_deliverWebhook as deliverWebhook, index_getMerchantWebhookUrl as getMerchantWebhookUrl, index_getProcessorConfig as getProcessorConfig, index_processPayment as processPayment, index_refundPayment as refundPayment, index_updateOrderStatus as updateOrderStatus };
23
+ }
24
+
25
+ export { getMerchantWebhookUrl as a, capturePayment as c, deliverWebhook as d, getProcessorConfig as g, index as i, processPayment as p, refundPayment as r, updateOrderStatus as u };
@@ -0,0 +1,72 @@
1
+ interface PaymentConfig {
2
+ merchantId: string;
3
+ processor: string;
4
+ testMode: boolean;
5
+ credentials: Record<string, string>;
6
+ }
7
+ interface PaymentInput {
8
+ orderId: string;
9
+ merchantId: string;
10
+ amount: number;
11
+ currency: string;
12
+ processor: string;
13
+ returnUrl?: string;
14
+ cancelUrl?: string;
15
+ metadata?: Record<string, unknown>;
16
+ customer?: {
17
+ id?: string;
18
+ email?: string;
19
+ name?: string;
20
+ };
21
+ paymentMethod?: {
22
+ type: 'card' | 'upi' | 'netbanking' | 'wallet';
23
+ token?: string;
24
+ };
25
+ }
26
+ interface PaymentResult {
27
+ success: boolean;
28
+ status: 'authorized' | 'captured' | 'failed' | 'pending' | 'requires_action';
29
+ processorOrderId?: string;
30
+ processorTransactionId?: string;
31
+ redirectUrl?: string;
32
+ errorCode?: string;
33
+ errorMessage?: string;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ interface RefundInput {
37
+ orderId: string;
38
+ transactionId: string;
39
+ amount: number;
40
+ reason?: string;
41
+ }
42
+ interface RefundResult {
43
+ success: boolean;
44
+ refundId?: string;
45
+ status: 'pending' | 'success' | 'failed';
46
+ errorCode?: string;
47
+ errorMessage?: string;
48
+ }
49
+ interface WebhookDeliveryInput {
50
+ webhookEventId: string;
51
+ merchantId: string;
52
+ webhookUrl: string;
53
+ webhookSecret?: string;
54
+ payload: Record<string, unknown>;
55
+ }
56
+ interface WebhookDeliveryResult {
57
+ success: boolean;
58
+ statusCode?: number;
59
+ attempts: number;
60
+ deliveredAt?: Date;
61
+ errorMessage?: string;
62
+ }
63
+ interface PaymentProcessor {
64
+ name: string;
65
+ createPayment(input: PaymentInput, config: PaymentConfig): Promise<PaymentResult>;
66
+ capturePayment(processorOrderId: string, amount: number, config: PaymentConfig): Promise<PaymentResult>;
67
+ refundPayment(processorTransactionId: string, amount: number, config: PaymentConfig): Promise<RefundResult>;
68
+ getPaymentStatus(processorOrderId: string, config: PaymentConfig): Promise<PaymentResult>;
69
+ }
70
+ type ProcessorRegistry = Map<string, PaymentProcessor>;
71
+
72
+ export type { PaymentProcessor as P, RefundInput as R, WebhookDeliveryInput as W, PaymentConfig as a, PaymentInput as b, PaymentResult as c, RefundResult as d, WebhookDeliveryResult as e, ProcessorRegistry as f };
@@ -0,0 +1,12 @@
1
+ import { P as PaymentProcessor } from './index-CsnpS83V.js';
2
+ export { a as PaymentConfig, b as PaymentInput, c as PaymentResult, f as ProcessorRegistry, R as RefundInput, d as RefundResult, W as WebhookDeliveryInput, e as WebhookDeliveryResult } from './index-CsnpS83V.js';
3
+ export { i as activities } from './index-CXmmtGo_.js';
4
+ export { PaymentWorkflow, PaymentWorkflowInput, WebhookDeliveryWorkflow, WebhookDeliveryWorkflowInput } from './workflows/index.js';
5
+ import '@temporalio/common';
6
+
7
+ declare function registerProcessor(processor: PaymentProcessor): void;
8
+ declare function getProcessor(name: string): PaymentProcessor;
9
+ declare function getRegisteredProcessors(): string[];
10
+ declare function hasProcessor(name: string): boolean;
11
+
12
+ export { PaymentProcessor, getProcessor, getRegisteredProcessors, hasProcessor, registerProcessor };
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import {
2
+ activities_exports,
3
+ getProcessor,
4
+ getRegisteredProcessors,
5
+ hasProcessor,
6
+ registerProcessor
7
+ } from "./chunk-X2Y2ZUQA.js";
8
+ import {
9
+ PaymentWorkflow,
10
+ WebhookDeliveryWorkflow
11
+ } from "./chunk-CZTZBCNV.js";
12
+ import "./chunk-MLKGABMK.js";
13
+ export {
14
+ PaymentWorkflow,
15
+ WebhookDeliveryWorkflow,
16
+ activities_exports as activities,
17
+ getProcessor,
18
+ getRegisteredProcessors,
19
+ hasProcessor,
20
+ registerProcessor
21
+ };
@@ -0,0 +1,27 @@
1
+ import * as _temporalio_common from '@temporalio/common';
2
+ import { c as PaymentResult, e as WebhookDeliveryResult } from '../index-CsnpS83V.js';
3
+
4
+ interface PaymentWorkflowInput {
5
+ orderId: string;
6
+ merchantId: string;
7
+ amount: number;
8
+ currency: string;
9
+ processor: string;
10
+ returnUrl?: string;
11
+ }
12
+ declare const completePaymentSignal: _temporalio_common.SignalDefinition<[{
13
+ success: boolean;
14
+ processorTransactionId?: string;
15
+ }], string>;
16
+ declare const cancelPaymentSignal: _temporalio_common.SignalDefinition<[], "cancelPayment">;
17
+ declare function PaymentWorkflow(input: PaymentWorkflowInput): Promise<PaymentResult>;
18
+
19
+ interface WebhookDeliveryWorkflowInput {
20
+ webhookEventId: string;
21
+ merchantId: string;
22
+ webhookUrl: string;
23
+ payload: Record<string, unknown>;
24
+ }
25
+ declare function WebhookDeliveryWorkflow(input: WebhookDeliveryWorkflowInput): Promise<WebhookDeliveryResult>;
26
+
27
+ export { PaymentWorkflow, type PaymentWorkflowInput, WebhookDeliveryWorkflow, type WebhookDeliveryWorkflowInput, cancelPaymentSignal, completePaymentSignal };
@@ -0,0 +1,13 @@
1
+ import {
2
+ PaymentWorkflow,
3
+ WebhookDeliveryWorkflow,
4
+ cancelPaymentSignal,
5
+ completePaymentSignal
6
+ } from "../chunk-CZTZBCNV.js";
7
+ import "../chunk-MLKGABMK.js";
8
+ export {
9
+ PaymentWorkflow,
10
+ WebhookDeliveryWorkflow,
11
+ cancelPaymentSignal,
12
+ completePaymentSignal
13
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@payloops/processor-core",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./workflows": {
13
+ "import": "./dist/workflows/index.js",
14
+ "types": "./dist/workflows/index.d.ts"
15
+ },
16
+ "./activities": {
17
+ "import": "./dist/activities/index.js",
18
+ "types": "./dist/activities/index.d.ts"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "dev": "tsx watch src/worker.ts",
23
+ "build": "tsup src/index.ts src/workflows/index.ts src/activities/index.ts --format esm --dts",
24
+ "start": "node dist/worker.js",
25
+ "lint": "eslint src/",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@astami/temporal-functions": "^0.1.0",
30
+ "@opentelemetry/api": "^1.9.0",
31
+ "@opentelemetry/auto-instrumentations-node": "^0.53.0",
32
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.57.0",
33
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
34
+ "@opentelemetry/sdk-node": "^0.57.0",
35
+ "@opentelemetry/semantic-conventions": "^1.28.0",
36
+ "@temporalio/activity": "^1.11.7",
37
+ "@temporalio/client": "^1.11.7",
38
+ "@temporalio/worker": "^1.11.7",
39
+ "@temporalio/workflow": "^1.11.7",
40
+ "drizzle-orm": "^0.39.1",
41
+ "pg": "^8.13.1",
42
+ "pino": "^9.6.0",
43
+ "zod": "^3.24.1"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "pino-pretty": "^13.0.0",
48
+ "@types/pg": "^8.11.10",
49
+ "eslint": "^9.0.0",
50
+ "tsup": "^8.3.5",
51
+ "tsx": "^4.19.2",
52
+ "typescript": "^5.9.3"
53
+ }
54
+ }