paybridge 0.4.0 → 0.6.0
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/README.md +117 -1
- package/dist/crypto/index.d.ts +3 -1
- package/dist/crypto/index.js +23 -0
- package/dist/crypto/ramp.d.ts +36 -0
- package/dist/crypto/ramp.js +179 -0
- package/dist/crypto/transak.d.ts +32 -0
- package/dist/crypto/transak.js +212 -0
- package/dist/index.js +68 -0
- package/dist/providers/adyen.d.ts +46 -0
- package/dist/providers/adyen.js +289 -0
- package/dist/providers/mercadopago.d.ts +36 -0
- package/dist/providers/mercadopago.js +297 -0
- package/dist/providers/mollie.d.ts +39 -0
- package/dist/providers/mollie.js +178 -0
- package/dist/providers/pesapal.d.ts +47 -0
- package/dist/providers/pesapal.js +236 -0
- package/dist/providers/razorpay.d.ts +37 -0
- package/dist/providers/razorpay.js +328 -0
- package/dist/providers/square.d.ts +41 -0
- package/dist/providers/square.js +278 -0
- package/dist/types.d.ts +1 -1
- package/package.json +13 -2
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pesapal payment provider
|
|
4
|
+
* East Africa-focused payment gateway (Kenya, Uganda, Tanzania)
|
|
5
|
+
* @see https://developer.pesapal.com/
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.PesapalProvider = void 0;
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const base_1 = require("./base");
|
|
44
|
+
const fetch_1 = require("../utils/fetch");
|
|
45
|
+
let webhookSecretWarned = false;
|
|
46
|
+
class PesapalProvider extends base_1.PaymentProvider {
|
|
47
|
+
constructor(config) {
|
|
48
|
+
super();
|
|
49
|
+
this.name = 'pesapal';
|
|
50
|
+
this.supportedCurrencies = ['KES', 'UGX', 'TZS', 'USD'];
|
|
51
|
+
this.consumerKey = config.consumerKey;
|
|
52
|
+
this.consumerSecret = config.consumerSecret;
|
|
53
|
+
this.notificationId = config.notificationId;
|
|
54
|
+
this.username = config.username;
|
|
55
|
+
this.webhookSecret = config.webhookSecret;
|
|
56
|
+
this.sandbox = config.sandbox ?? false;
|
|
57
|
+
this.baseUrl = this.sandbox
|
|
58
|
+
? 'https://cybqa.pesapal.com/pesapalv3'
|
|
59
|
+
: 'https://pay.pesapal.com/v3';
|
|
60
|
+
if (this.webhookSecret && !webhookSecretWarned) {
|
|
61
|
+
console.warn('[PayBridge:Pesapal] Pesapal IPN has no webhook signature scheme. Webhook validation relies on getPayment() round-trip. Validate by source IP if possible.');
|
|
62
|
+
webhookSecretWarned = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async getToken() {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
if (this.tokenCache && this.tokenCache.expiresAt > now + 60000) {
|
|
68
|
+
return this.tokenCache.token;
|
|
69
|
+
}
|
|
70
|
+
const response = await (0, fetch_1.timedFetchOrThrow)(`${this.baseUrl}/api/Auth/RequestToken`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
consumer_key: this.consumerKey,
|
|
77
|
+
consumer_secret: this.consumerSecret,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
const data = (await response.json());
|
|
81
|
+
this.tokenCache = {
|
|
82
|
+
token: data.token,
|
|
83
|
+
expiresAt: now + 4 * 60 * 1000,
|
|
84
|
+
};
|
|
85
|
+
return this.tokenCache.token;
|
|
86
|
+
}
|
|
87
|
+
async apiRequest(method, path, data) {
|
|
88
|
+
const token = await this.getToken();
|
|
89
|
+
const url = `${this.baseUrl}${path}`;
|
|
90
|
+
const response = await (0, fetch_1.timedFetchOrThrow)(url, {
|
|
91
|
+
method,
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${token}`,
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
97
|
+
});
|
|
98
|
+
return (await response.json());
|
|
99
|
+
}
|
|
100
|
+
async createPayment(params) {
|
|
101
|
+
this.validateCurrency(params.currency);
|
|
102
|
+
const requestBody = {
|
|
103
|
+
id: params.reference,
|
|
104
|
+
currency: params.currency,
|
|
105
|
+
amount: params.amount,
|
|
106
|
+
description: params.description || params.reference,
|
|
107
|
+
callback_url: params.urls.success,
|
|
108
|
+
billing_address: {
|
|
109
|
+
email_address: params.customer.email,
|
|
110
|
+
phone_number: params.customer.phone || '',
|
|
111
|
+
first_name: params.customer.name.split(' ')[0] || params.customer.name,
|
|
112
|
+
last_name: params.customer.name.split(' ').slice(1).join(' ') || '',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
if (this.notificationId) {
|
|
116
|
+
requestBody.notification_id = this.notificationId;
|
|
117
|
+
}
|
|
118
|
+
const response = await this.apiRequest('POST', '/api/Transactions/SubmitOrderRequest', requestBody);
|
|
119
|
+
return {
|
|
120
|
+
id: response.order_tracking_id,
|
|
121
|
+
checkoutUrl: response.redirect_url,
|
|
122
|
+
status: 'pending',
|
|
123
|
+
amount: params.amount,
|
|
124
|
+
currency: params.currency.toUpperCase(),
|
|
125
|
+
reference: response.merchant_reference || params.reference,
|
|
126
|
+
provider: 'pesapal',
|
|
127
|
+
createdAt: new Date().toISOString(),
|
|
128
|
+
raw: response,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async createSubscription(_params) {
|
|
132
|
+
throw new Error('Pesapal subscriptions not yet supported by paybridge. Use the Pesapal Recurring Billing API directly.');
|
|
133
|
+
}
|
|
134
|
+
async getPayment(id) {
|
|
135
|
+
const response = await this.apiRequest('GET', `/api/Transactions/GetTransactionStatus?orderTrackingId=${id}`);
|
|
136
|
+
let status = 'pending';
|
|
137
|
+
if (response.payment_status_description === 'Completed') {
|
|
138
|
+
status = 'completed';
|
|
139
|
+
}
|
|
140
|
+
else if (response.payment_status_description === 'Failed' || response.payment_status_description === 'Invalid') {
|
|
141
|
+
status = 'failed';
|
|
142
|
+
}
|
|
143
|
+
else if (response.payment_status_description === 'Pending') {
|
|
144
|
+
status = 'pending';
|
|
145
|
+
}
|
|
146
|
+
const currency = (response.currency || 'KES').toUpperCase();
|
|
147
|
+
const amount = response.amount || 0;
|
|
148
|
+
return {
|
|
149
|
+
id,
|
|
150
|
+
checkoutUrl: '',
|
|
151
|
+
status,
|
|
152
|
+
amount,
|
|
153
|
+
currency,
|
|
154
|
+
reference: response.merchant_reference || id,
|
|
155
|
+
provider: 'pesapal',
|
|
156
|
+
createdAt: response.created_date || new Date().toISOString(),
|
|
157
|
+
raw: response,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async refund(params) {
|
|
161
|
+
if (!this.username) {
|
|
162
|
+
throw new Error('Pesapal refunds require username config (merchant username)');
|
|
163
|
+
}
|
|
164
|
+
const refundData = {
|
|
165
|
+
confirmation_code: params.paymentId,
|
|
166
|
+
username: this.username,
|
|
167
|
+
remarks: params.reason || 'Refund',
|
|
168
|
+
};
|
|
169
|
+
if (params.amount !== undefined) {
|
|
170
|
+
refundData.amount = params.amount;
|
|
171
|
+
}
|
|
172
|
+
const response = await this.apiRequest('POST', '/api/Transactions/RefundRequest', refundData);
|
|
173
|
+
const currency = (response.currency || 'KES').toUpperCase();
|
|
174
|
+
const amount = response.amount || 0;
|
|
175
|
+
return {
|
|
176
|
+
id: response.refund_id || response.id || crypto.randomUUID(),
|
|
177
|
+
status: response.status === 'Success' ? 'completed' : 'pending',
|
|
178
|
+
amount,
|
|
179
|
+
currency,
|
|
180
|
+
paymentId: params.paymentId,
|
|
181
|
+
createdAt: response.created_date || new Date().toISOString(),
|
|
182
|
+
raw: response,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
parseWebhook(body, _headers) {
|
|
186
|
+
let orderTrackingId;
|
|
187
|
+
let merchantReference;
|
|
188
|
+
if (typeof body === 'string') {
|
|
189
|
+
const parsed = new URLSearchParams(body);
|
|
190
|
+
orderTrackingId = parsed.get('OrderTrackingId') || '';
|
|
191
|
+
merchantReference = parsed.get('OrderMerchantReference') || orderTrackingId;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
orderTrackingId = body.OrderTrackingId || body.order_tracking_id || '';
|
|
195
|
+
merchantReference = body.OrderMerchantReference || body.merchant_reference || orderTrackingId;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
type: 'payment.pending',
|
|
199
|
+
payment: {
|
|
200
|
+
id: orderTrackingId,
|
|
201
|
+
checkoutUrl: '',
|
|
202
|
+
status: 'pending',
|
|
203
|
+
amount: 0,
|
|
204
|
+
currency: 'KES',
|
|
205
|
+
reference: merchantReference,
|
|
206
|
+
provider: 'pesapal',
|
|
207
|
+
createdAt: new Date().toISOString(),
|
|
208
|
+
},
|
|
209
|
+
raw: body,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Pesapal IPN has no signature verification scheme.
|
|
214
|
+
* Security comes from:
|
|
215
|
+
* 1. Validating source IP (caller's responsibility)
|
|
216
|
+
* 2. Calling getPayment(id) to verify actual status
|
|
217
|
+
*
|
|
218
|
+
* Always returns true to indicate no validation error (Pesapal design limitation).
|
|
219
|
+
*/
|
|
220
|
+
verifyWebhook(_body, _headers) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
getCapabilities() {
|
|
224
|
+
return {
|
|
225
|
+
fees: {
|
|
226
|
+
fixed: 0,
|
|
227
|
+
percent: 3.5,
|
|
228
|
+
currency: 'KES',
|
|
229
|
+
},
|
|
230
|
+
currencies: this.supportedCurrencies,
|
|
231
|
+
country: 'KE',
|
|
232
|
+
avgLatencyMs: 900,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
exports.PesapalProvider = PesapalProvider;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Razorpay payment provider
|
|
3
|
+
* Leading payment gateway for India
|
|
4
|
+
* @see https://razorpay.com/docs/api
|
|
5
|
+
*/
|
|
6
|
+
import { PaymentProvider } from './base';
|
|
7
|
+
import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
|
|
8
|
+
import { ProviderCapabilities } from '../routing-types';
|
|
9
|
+
interface RazorpayConfig {
|
|
10
|
+
keyId: string;
|
|
11
|
+
keySecret: string;
|
|
12
|
+
webhookSecret?: string;
|
|
13
|
+
sandbox?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class RazorpayProvider extends PaymentProvider {
|
|
16
|
+
readonly name = "razorpay";
|
|
17
|
+
readonly supportedCurrencies: string[];
|
|
18
|
+
private keyId;
|
|
19
|
+
private keySecret;
|
|
20
|
+
private webhookSecret?;
|
|
21
|
+
private sandbox;
|
|
22
|
+
private baseUrl;
|
|
23
|
+
constructor(config: RazorpayConfig);
|
|
24
|
+
private apiRequest;
|
|
25
|
+
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
26
|
+
createSubscription(params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
27
|
+
getPayment(id: string): Promise<PaymentResult>;
|
|
28
|
+
refund(params: RefundParams): Promise<RefundResult>;
|
|
29
|
+
parseWebhook(body: any, _headers?: any): WebhookEvent;
|
|
30
|
+
/**
|
|
31
|
+
* Verify Razorpay webhook signature using HMAC-SHA256.
|
|
32
|
+
* Note: Razorpay does not include timestamp in webhook signature (no replay protection).
|
|
33
|
+
*/
|
|
34
|
+
verifyWebhook(body: string | Buffer, headers?: any): boolean;
|
|
35
|
+
getCapabilities(): ProviderCapabilities;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Razorpay payment provider
|
|
4
|
+
* Leading payment gateway for India
|
|
5
|
+
* @see https://razorpay.com/docs/api
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.RazorpayProvider = void 0;
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const base_1 = require("./base");
|
|
44
|
+
const currency_1 = require("../utils/currency");
|
|
45
|
+
const fetch_1 = require("../utils/fetch");
|
|
46
|
+
class RazorpayProvider extends base_1.PaymentProvider {
|
|
47
|
+
constructor(config) {
|
|
48
|
+
super();
|
|
49
|
+
this.name = 'razorpay';
|
|
50
|
+
this.supportedCurrencies = ['INR', 'USD', 'EUR', 'GBP', 'SGD', 'AED', 'AUD'];
|
|
51
|
+
this.baseUrl = 'https://api.razorpay.com/v1';
|
|
52
|
+
this.keyId = config.keyId;
|
|
53
|
+
this.keySecret = config.keySecret;
|
|
54
|
+
this.webhookSecret = config.webhookSecret;
|
|
55
|
+
this.sandbox = config.sandbox ?? this.keyId.startsWith('rzp_test_');
|
|
56
|
+
}
|
|
57
|
+
async apiRequest(method, path, data) {
|
|
58
|
+
const url = `${this.baseUrl}${path}`;
|
|
59
|
+
const authString = Buffer.from(`${this.keyId}:${this.keySecret}`).toString('base64');
|
|
60
|
+
const response = await (0, fetch_1.timedFetch)(url, {
|
|
61
|
+
method,
|
|
62
|
+
headers: {
|
|
63
|
+
Authorization: `Basic ${authString}`,
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
67
|
+
});
|
|
68
|
+
const json = await response.json();
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const message = json.error?.description || `Razorpay API error (${method} ${path}): ${response.status}`;
|
|
71
|
+
throw new Error(message);
|
|
72
|
+
}
|
|
73
|
+
return json;
|
|
74
|
+
}
|
|
75
|
+
async createPayment(params) {
|
|
76
|
+
this.validateCurrency(params.currency);
|
|
77
|
+
const amountInMinorUnits = (0, currency_1.toMinorUnit)(params.amount, params.currency);
|
|
78
|
+
const orderData = {
|
|
79
|
+
amount: amountInMinorUnits,
|
|
80
|
+
currency: params.currency,
|
|
81
|
+
receipt: params.reference,
|
|
82
|
+
notes: {
|
|
83
|
+
reference: params.reference,
|
|
84
|
+
customerEmail: params.customer.email,
|
|
85
|
+
...(params.metadata || {}),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const order = await this.apiRequest('POST', '/orders', orderData);
|
|
89
|
+
const checkoutUrl = `https://api.razorpay.com/v1/checkout/embedded?key_id=${this.keyId}&order_id=${order.id}`;
|
|
90
|
+
return {
|
|
91
|
+
id: order.id,
|
|
92
|
+
checkoutUrl,
|
|
93
|
+
status: 'pending',
|
|
94
|
+
amount: (0, currency_1.toMajorUnit)(order.amount, params.currency),
|
|
95
|
+
currency: order.currency,
|
|
96
|
+
reference: params.reference,
|
|
97
|
+
provider: 'razorpay',
|
|
98
|
+
createdAt: new Date(order.created_at * 1000).toISOString(),
|
|
99
|
+
raw: order,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async createSubscription(params) {
|
|
103
|
+
this.validateCurrency(params.currency);
|
|
104
|
+
const amountInMinorUnits = (0, currency_1.toMinorUnit)(params.amount, params.currency);
|
|
105
|
+
const intervalMap = {
|
|
106
|
+
weekly: 'weekly',
|
|
107
|
+
monthly: 'monthly',
|
|
108
|
+
yearly: 'yearly',
|
|
109
|
+
};
|
|
110
|
+
const planData = {
|
|
111
|
+
period: intervalMap[params.interval],
|
|
112
|
+
interval: 1,
|
|
113
|
+
item: {
|
|
114
|
+
name: params.description || params.reference,
|
|
115
|
+
amount: amountInMinorUnits,
|
|
116
|
+
currency: params.currency,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
const plan = await this.apiRequest('POST', '/plans', planData);
|
|
120
|
+
const subscriptionData = {
|
|
121
|
+
plan_id: plan.id,
|
|
122
|
+
total_count: 12,
|
|
123
|
+
customer_notify: 1,
|
|
124
|
+
notes: {
|
|
125
|
+
reference: params.reference,
|
|
126
|
+
...(params.metadata || {}),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const subscription = await this.apiRequest('POST', '/subscriptions', subscriptionData);
|
|
130
|
+
const checkoutUrl = `https://api.razorpay.com/v1/checkout/embedded?key_id=${this.keyId}&subscription_id=${subscription.id}`;
|
|
131
|
+
return {
|
|
132
|
+
id: subscription.id,
|
|
133
|
+
checkoutUrl,
|
|
134
|
+
status: 'pending',
|
|
135
|
+
amount: (0, currency_1.toMajorUnit)(subscription.plan_id ? amountInMinorUnits : 0, params.currency),
|
|
136
|
+
currency: params.currency,
|
|
137
|
+
interval: params.interval,
|
|
138
|
+
reference: params.reference,
|
|
139
|
+
provider: 'razorpay',
|
|
140
|
+
startsAt: params.startDate,
|
|
141
|
+
createdAt: new Date(subscription.created_at * 1000).toISOString(),
|
|
142
|
+
raw: subscription,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async getPayment(id) {
|
|
146
|
+
const paymentsResponse = await this.apiRequest('GET', `/orders/${id}/payments`);
|
|
147
|
+
if (!paymentsResponse.items || paymentsResponse.items.length === 0) {
|
|
148
|
+
const order = await this.apiRequest('GET', `/orders/${id}`);
|
|
149
|
+
const currency = order.currency || 'INR';
|
|
150
|
+
return {
|
|
151
|
+
id: order.id,
|
|
152
|
+
checkoutUrl: '',
|
|
153
|
+
status: 'pending',
|
|
154
|
+
amount: (0, currency_1.toMajorUnit)(order.amount || 0, currency),
|
|
155
|
+
currency,
|
|
156
|
+
reference: order.receipt || id,
|
|
157
|
+
provider: 'razorpay',
|
|
158
|
+
createdAt: new Date(order.created_at * 1000).toISOString(),
|
|
159
|
+
raw: order,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const payment = paymentsResponse.items[0];
|
|
163
|
+
const currency = payment.currency || 'INR';
|
|
164
|
+
let status = 'pending';
|
|
165
|
+
if (payment.status === 'captured') {
|
|
166
|
+
status = 'completed';
|
|
167
|
+
}
|
|
168
|
+
else if (payment.status === 'authorized') {
|
|
169
|
+
status = 'pending';
|
|
170
|
+
}
|
|
171
|
+
else if (payment.status === 'failed') {
|
|
172
|
+
status = 'failed';
|
|
173
|
+
}
|
|
174
|
+
else if (payment.status === 'refunded') {
|
|
175
|
+
status = 'refunded';
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: payment.id,
|
|
179
|
+
checkoutUrl: '',
|
|
180
|
+
status,
|
|
181
|
+
amount: (0, currency_1.toMajorUnit)(payment.amount || 0, currency),
|
|
182
|
+
currency,
|
|
183
|
+
reference: payment.notes?.reference || payment.order_id || id,
|
|
184
|
+
provider: 'razorpay',
|
|
185
|
+
createdAt: new Date(payment.created_at * 1000).toISOString(),
|
|
186
|
+
raw: paymentsResponse,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async refund(params) {
|
|
190
|
+
let paymentId = params.paymentId;
|
|
191
|
+
if (paymentId.startsWith('order_')) {
|
|
192
|
+
const paymentsResponse = await this.apiRequest('GET', `/orders/${paymentId}/payments`);
|
|
193
|
+
if (paymentsResponse.items && paymentsResponse.items.length > 0) {
|
|
194
|
+
paymentId = paymentsResponse.items[0].id;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
throw new Error('Order has no payments to refund');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const refundData = {
|
|
201
|
+
speed: 'normal',
|
|
202
|
+
};
|
|
203
|
+
if (params.amount !== undefined) {
|
|
204
|
+
refundData.amount = (0, currency_1.toMinorUnit)(params.amount, 'INR');
|
|
205
|
+
}
|
|
206
|
+
if (params.reason) {
|
|
207
|
+
refundData.notes = { reason: params.reason };
|
|
208
|
+
}
|
|
209
|
+
const response = await this.apiRequest('POST', `/payments/${paymentId}/refund`, refundData);
|
|
210
|
+
const currency = response.currency || 'INR';
|
|
211
|
+
return {
|
|
212
|
+
id: response.id,
|
|
213
|
+
status: response.status === 'processed' ? 'completed' : 'pending',
|
|
214
|
+
amount: (0, currency_1.toMajorUnit)(response.amount || 0, currency),
|
|
215
|
+
currency,
|
|
216
|
+
paymentId: params.paymentId,
|
|
217
|
+
createdAt: new Date(response.created_at * 1000).toISOString(),
|
|
218
|
+
raw: response,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
parseWebhook(body, _headers) {
|
|
222
|
+
const event = typeof body === 'string' ? JSON.parse(body) : body;
|
|
223
|
+
const typeMap = {
|
|
224
|
+
'payment.captured': 'payment.completed',
|
|
225
|
+
'payment.failed': 'payment.failed',
|
|
226
|
+
'subscription.activated': 'subscription.created',
|
|
227
|
+
'subscription.cancelled': 'subscription.cancelled',
|
|
228
|
+
'refund.created': 'refund.completed',
|
|
229
|
+
'refund.processed': 'refund.completed',
|
|
230
|
+
};
|
|
231
|
+
const eventType = typeMap[event.event] || 'payment.pending';
|
|
232
|
+
const data = event.payload?.payment?.entity || event.payload?.subscription?.entity || event.payload?.refund?.entity || {};
|
|
233
|
+
let payment;
|
|
234
|
+
let subscription;
|
|
235
|
+
let refund;
|
|
236
|
+
if (event.event.startsWith('payment.')) {
|
|
237
|
+
const currency = (data.currency || 'INR').toUpperCase();
|
|
238
|
+
let status = 'pending';
|
|
239
|
+
if (event.event === 'payment.captured') {
|
|
240
|
+
status = 'completed';
|
|
241
|
+
}
|
|
242
|
+
else if (event.event === 'payment.failed') {
|
|
243
|
+
status = 'failed';
|
|
244
|
+
}
|
|
245
|
+
payment = {
|
|
246
|
+
id: data.id || '',
|
|
247
|
+
checkoutUrl: '',
|
|
248
|
+
status,
|
|
249
|
+
amount: (0, currency_1.toMajorUnit)(data.amount || 0, currency),
|
|
250
|
+
currency,
|
|
251
|
+
reference: data.order_id || data.id || '',
|
|
252
|
+
provider: 'razorpay',
|
|
253
|
+
createdAt: new Date((data.created_at || Date.now() / 1000) * 1000).toISOString(),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
else if (event.event.startsWith('subscription.')) {
|
|
257
|
+
const currency = 'INR';
|
|
258
|
+
subscription = {
|
|
259
|
+
id: data.id || '',
|
|
260
|
+
checkoutUrl: '',
|
|
261
|
+
status: event.event === 'subscription.cancelled' ? 'cancelled' : 'active',
|
|
262
|
+
amount: (0, currency_1.toMajorUnit)(data.plan_id?.amount || 0, currency),
|
|
263
|
+
currency,
|
|
264
|
+
interval: 'monthly',
|
|
265
|
+
reference: data.notes?.reference || data.id || '',
|
|
266
|
+
provider: 'razorpay',
|
|
267
|
+
createdAt: new Date((data.created_at || Date.now() / 1000) * 1000).toISOString(),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
else if (event.event.startsWith('refund.')) {
|
|
271
|
+
const currency = (data.currency || 'INR').toUpperCase();
|
|
272
|
+
refund = {
|
|
273
|
+
id: data.id || '',
|
|
274
|
+
status: event.event === 'refund.processed' ? 'completed' : 'pending',
|
|
275
|
+
amount: (0, currency_1.toMajorUnit)(data.amount || 0, currency),
|
|
276
|
+
currency,
|
|
277
|
+
paymentId: data.payment_id || '',
|
|
278
|
+
createdAt: new Date((data.created_at || Date.now() / 1000) * 1000).toISOString(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
type: eventType,
|
|
283
|
+
payment,
|
|
284
|
+
subscription,
|
|
285
|
+
refund,
|
|
286
|
+
raw: event,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Verify Razorpay webhook signature using HMAC-SHA256.
|
|
291
|
+
* Note: Razorpay does not include timestamp in webhook signature (no replay protection).
|
|
292
|
+
*/
|
|
293
|
+
verifyWebhook(body, headers) {
|
|
294
|
+
if (!this.webhookSecret) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
const signature = headers?.['x-razorpay-signature'] || headers?.['X-Razorpay-Signature'];
|
|
298
|
+
if (!signature) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
const rawBody = typeof body === 'string' ? body : body.toString('utf8');
|
|
302
|
+
const computedSig = crypto.createHmac('sha256', this.webhookSecret).update(rawBody, 'utf8').digest('hex');
|
|
303
|
+
try {
|
|
304
|
+
const computedBuffer = Buffer.from(computedSig, 'hex');
|
|
305
|
+
const expectedBuffer = Buffer.from(signature, 'hex');
|
|
306
|
+
if (computedBuffer.length !== expectedBuffer.length) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return crypto.timingSafeEqual(computedBuffer, expectedBuffer);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
getCapabilities() {
|
|
316
|
+
return {
|
|
317
|
+
fees: {
|
|
318
|
+
fixed: 0,
|
|
319
|
+
percent: 2.0,
|
|
320
|
+
currency: 'INR',
|
|
321
|
+
},
|
|
322
|
+
currencies: this.supportedCurrencies,
|
|
323
|
+
country: 'IN',
|
|
324
|
+
avgLatencyMs: 500,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.RazorpayProvider = RazorpayProvider;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Square payment provider
|
|
3
|
+
* Payment Links API supporting USD, CAD, GBP, AUD, EUR, JPY
|
|
4
|
+
* @see https://developer.squareup.com/reference/square
|
|
5
|
+
*/
|
|
6
|
+
import { PaymentProvider } from './base';
|
|
7
|
+
import { CreatePaymentParams, PaymentResult, CreateSubscriptionParams, SubscriptionResult, RefundParams, RefundResult, WebhookEvent } from '../types';
|
|
8
|
+
import { ProviderCapabilities } from '../routing-types';
|
|
9
|
+
interface SquareConfig {
|
|
10
|
+
accessToken: string;
|
|
11
|
+
locationId: string;
|
|
12
|
+
notificationUrl?: string;
|
|
13
|
+
webhookSecret?: string;
|
|
14
|
+
sandbox?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class SquareProvider extends PaymentProvider {
|
|
17
|
+
readonly name = "square";
|
|
18
|
+
readonly supportedCurrencies: string[];
|
|
19
|
+
private accessToken;
|
|
20
|
+
private locationId;
|
|
21
|
+
private notificationUrl?;
|
|
22
|
+
private webhookSecret?;
|
|
23
|
+
private sandbox;
|
|
24
|
+
private baseUrl;
|
|
25
|
+
constructor(config: SquareConfig);
|
|
26
|
+
private apiRequest;
|
|
27
|
+
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
28
|
+
createSubscription(_params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
29
|
+
getPayment(id: string): Promise<PaymentResult>;
|
|
30
|
+
refund(params: RefundParams): Promise<RefundResult>;
|
|
31
|
+
parseWebhook(body: any, _headers?: any): WebhookEvent;
|
|
32
|
+
/**
|
|
33
|
+
* Verify webhook signature using Square's HMAC-SHA256 scheme.
|
|
34
|
+
*
|
|
35
|
+
* Square signs the concatenation of: notificationUrl + rawBody
|
|
36
|
+
* TODO(verify): Confirm Square's current signing scheme matches this implementation.
|
|
37
|
+
*/
|
|
38
|
+
verifyWebhook(body: string | Buffer, headers?: any): boolean;
|
|
39
|
+
getCapabilities(): ProviderCapabilities;
|
|
40
|
+
}
|
|
41
|
+
export {};
|