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,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mercado Pago payment provider
|
|
4
|
+
* Leading payment platform for Latin America
|
|
5
|
+
* @see https://www.mercadopago.com/developers/en/reference
|
|
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.MercadoPagoProvider = void 0;
|
|
42
|
+
const crypto = __importStar(require("crypto"));
|
|
43
|
+
const base_1 = require("./base");
|
|
44
|
+
const fetch_1 = require("../utils/fetch");
|
|
45
|
+
class MercadoPagoProvider extends base_1.PaymentProvider {
|
|
46
|
+
constructor(config) {
|
|
47
|
+
super();
|
|
48
|
+
this.name = 'mercadopago';
|
|
49
|
+
this.supportedCurrencies = ['BRL', 'ARS', 'USD', 'MXN', 'COP', 'CLP', 'ZAR'];
|
|
50
|
+
this.baseUrl = 'https://api.mercadopago.com';
|
|
51
|
+
this.accessToken = config.accessToken;
|
|
52
|
+
this.webhookSecret = config.webhookSecret;
|
|
53
|
+
this.sandbox = config.sandbox ?? this.accessToken.startsWith('TEST-');
|
|
54
|
+
}
|
|
55
|
+
async apiRequest(method, path, data) {
|
|
56
|
+
const url = `${this.baseUrl}${path}`;
|
|
57
|
+
const response = await (0, fetch_1.timedFetch)(url, {
|
|
58
|
+
method,
|
|
59
|
+
headers: {
|
|
60
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
},
|
|
63
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
64
|
+
});
|
|
65
|
+
const json = await response.json();
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const message = json.message || json.error || `Mercado Pago API error (${method} ${path}): ${response.status}`;
|
|
68
|
+
throw new Error(message);
|
|
69
|
+
}
|
|
70
|
+
return json;
|
|
71
|
+
}
|
|
72
|
+
async createPayment(params) {
|
|
73
|
+
this.validateCurrency(params.currency);
|
|
74
|
+
const [firstName, ...lastNameParts] = params.customer.name.split(' ');
|
|
75
|
+
const lastName = lastNameParts.join(' ') || firstName;
|
|
76
|
+
const metadata = {
|
|
77
|
+
reference: params.reference,
|
|
78
|
+
...(params.metadata || {}),
|
|
79
|
+
};
|
|
80
|
+
const requestBody = {
|
|
81
|
+
items: [
|
|
82
|
+
{
|
|
83
|
+
title: params.description || params.reference,
|
|
84
|
+
quantity: 1,
|
|
85
|
+
unit_price: params.amount,
|
|
86
|
+
currency_id: params.currency,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
payer: {
|
|
90
|
+
email: params.customer.email,
|
|
91
|
+
name: firstName,
|
|
92
|
+
surname: lastName,
|
|
93
|
+
},
|
|
94
|
+
external_reference: params.reference,
|
|
95
|
+
back_urls: {
|
|
96
|
+
success: params.urls.success,
|
|
97
|
+
failure: params.urls.cancel,
|
|
98
|
+
pending: params.urls.success,
|
|
99
|
+
},
|
|
100
|
+
notification_url: params.urls.webhook,
|
|
101
|
+
auto_return: 'approved',
|
|
102
|
+
metadata,
|
|
103
|
+
};
|
|
104
|
+
const response = await this.apiRequest('POST', '/checkout/preferences', requestBody);
|
|
105
|
+
const checkoutUrl = this.sandbox ? response.sandbox_init_point : response.init_point;
|
|
106
|
+
return {
|
|
107
|
+
id: response.id,
|
|
108
|
+
checkoutUrl,
|
|
109
|
+
status: 'pending',
|
|
110
|
+
amount: params.amount,
|
|
111
|
+
currency: params.currency.toUpperCase(),
|
|
112
|
+
reference: params.reference,
|
|
113
|
+
provider: 'mercadopago',
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
raw: response,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async createSubscription(params) {
|
|
119
|
+
this.validateCurrency(params.currency);
|
|
120
|
+
let frequency;
|
|
121
|
+
let frequencyType;
|
|
122
|
+
switch (params.interval) {
|
|
123
|
+
case 'weekly':
|
|
124
|
+
frequency = 7;
|
|
125
|
+
frequencyType = 'days';
|
|
126
|
+
break;
|
|
127
|
+
case 'monthly':
|
|
128
|
+
frequency = 1;
|
|
129
|
+
frequencyType = 'months';
|
|
130
|
+
break;
|
|
131
|
+
case 'yearly':
|
|
132
|
+
frequency = 12;
|
|
133
|
+
frequencyType = 'months';
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
const requestBody = {
|
|
137
|
+
reason: params.description || params.reference,
|
|
138
|
+
auto_recurring: {
|
|
139
|
+
frequency,
|
|
140
|
+
frequency_type: frequencyType,
|
|
141
|
+
transaction_amount: params.amount,
|
|
142
|
+
currency_id: params.currency,
|
|
143
|
+
},
|
|
144
|
+
payer_email: params.customer.email,
|
|
145
|
+
back_url: params.urls.success,
|
|
146
|
+
external_reference: params.reference,
|
|
147
|
+
};
|
|
148
|
+
const response = await this.apiRequest('POST', '/preapproval', requestBody);
|
|
149
|
+
return {
|
|
150
|
+
id: response.id,
|
|
151
|
+
checkoutUrl: response.init_point,
|
|
152
|
+
status: 'pending',
|
|
153
|
+
amount: params.amount,
|
|
154
|
+
currency: params.currency.toUpperCase(),
|
|
155
|
+
interval: params.interval,
|
|
156
|
+
reference: params.reference,
|
|
157
|
+
provider: 'mercadopago',
|
|
158
|
+
startsAt: params.startDate,
|
|
159
|
+
createdAt: new Date().toISOString(),
|
|
160
|
+
raw: response,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async getPayment(id) {
|
|
164
|
+
const response = await this.apiRequest('GET', `/v1/payments/search?external_reference=${encodeURIComponent(id)}`);
|
|
165
|
+
if (!response.results || response.results.length === 0) {
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
checkoutUrl: '',
|
|
169
|
+
status: 'pending',
|
|
170
|
+
amount: 0,
|
|
171
|
+
currency: 'BRL',
|
|
172
|
+
reference: id,
|
|
173
|
+
provider: 'mercadopago',
|
|
174
|
+
createdAt: new Date().toISOString(),
|
|
175
|
+
raw: response,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const payment = response.results[0];
|
|
179
|
+
const currency = (payment.currency_id || 'BRL').toUpperCase();
|
|
180
|
+
let status = 'pending';
|
|
181
|
+
if (payment.status === 'approved') {
|
|
182
|
+
status = 'completed';
|
|
183
|
+
}
|
|
184
|
+
else if (payment.status === 'rejected') {
|
|
185
|
+
status = 'failed';
|
|
186
|
+
}
|
|
187
|
+
else if (payment.status === 'cancelled') {
|
|
188
|
+
status = 'cancelled';
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
id: payment.id?.toString() || id,
|
|
192
|
+
checkoutUrl: '',
|
|
193
|
+
status,
|
|
194
|
+
amount: payment.transaction_amount || 0,
|
|
195
|
+
currency,
|
|
196
|
+
reference: payment.external_reference || id,
|
|
197
|
+
provider: 'mercadopago',
|
|
198
|
+
createdAt: new Date(payment.date_created || Date.now()).toISOString(),
|
|
199
|
+
raw: response,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async refund(params) {
|
|
203
|
+
const refundData = {};
|
|
204
|
+
if (params.amount !== undefined) {
|
|
205
|
+
refundData.amount = params.amount;
|
|
206
|
+
}
|
|
207
|
+
const response = await this.apiRequest('POST', `/v1/payments/${params.paymentId}/refunds`, refundData);
|
|
208
|
+
const currency = (response.payment?.currency_id || 'BRL').toUpperCase();
|
|
209
|
+
return {
|
|
210
|
+
id: response.id?.toString() || response.refund_id?.toString(),
|
|
211
|
+
status: response.status === 'approved' ? 'completed' : 'pending',
|
|
212
|
+
amount: response.amount || 0,
|
|
213
|
+
currency,
|
|
214
|
+
paymentId: params.paymentId,
|
|
215
|
+
createdAt: new Date(response.date_created || Date.now()).toISOString(),
|
|
216
|
+
raw: response,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parse Mercado Pago webhook notification.
|
|
221
|
+
* Note: MP webhooks are notification events that require a follow-up API call to get full payment details.
|
|
222
|
+
* This method returns a pending event; caller should use getPayment(data.id) to fetch real status.
|
|
223
|
+
*/
|
|
224
|
+
parseWebhook(body, _headers) {
|
|
225
|
+
const event = typeof body === 'string' ? JSON.parse(body) : body;
|
|
226
|
+
const eventType = 'payment.pending';
|
|
227
|
+
return {
|
|
228
|
+
type: eventType,
|
|
229
|
+
payment: {
|
|
230
|
+
id: event.data?.id?.toString() || '',
|
|
231
|
+
checkoutUrl: '',
|
|
232
|
+
status: 'pending',
|
|
233
|
+
amount: 0,
|
|
234
|
+
currency: 'BRL',
|
|
235
|
+
reference: '',
|
|
236
|
+
provider: 'mercadopago',
|
|
237
|
+
createdAt: new Date().toISOString(),
|
|
238
|
+
},
|
|
239
|
+
raw: event,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
verifyWebhook(body, headers) {
|
|
243
|
+
if (!this.webhookSecret) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const signature = headers?.['x-signature'] || headers?.['X-Signature'];
|
|
247
|
+
if (!signature) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
const parts = signature.split(',').reduce((acc, part) => {
|
|
251
|
+
const [key, value] = part.split('=');
|
|
252
|
+
if (key && value) {
|
|
253
|
+
acc[key.trim()] = value.trim();
|
|
254
|
+
}
|
|
255
|
+
return acc;
|
|
256
|
+
}, {});
|
|
257
|
+
const ts = parts.ts;
|
|
258
|
+
const v1 = parts.v1;
|
|
259
|
+
if (!ts || !v1) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const timestampNum = parseInt(ts, 10);
|
|
263
|
+
const now = Math.floor(Date.now() / 1000);
|
|
264
|
+
if (now - timestampNum > 300) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
const requestId = headers?.['x-request-id'] || headers?.['X-Request-Id'];
|
|
268
|
+
const eventData = typeof body === 'string' ? JSON.parse(body) : body;
|
|
269
|
+
const dataId = eventData.data?.id || '';
|
|
270
|
+
const template = `id:${dataId};request-id:${requestId};ts:${ts};`;
|
|
271
|
+
const computedSig = crypto.createHmac('sha256', this.webhookSecret).update(template, 'utf8').digest('hex');
|
|
272
|
+
try {
|
|
273
|
+
const computedBuffer = Buffer.from(computedSig, 'hex');
|
|
274
|
+
const expectedBuffer = Buffer.from(v1, 'hex');
|
|
275
|
+
if (computedBuffer.length !== expectedBuffer.length) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
return crypto.timingSafeEqual(computedBuffer, expectedBuffer);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
getCapabilities() {
|
|
285
|
+
return {
|
|
286
|
+
fees: {
|
|
287
|
+
fixed: 0,
|
|
288
|
+
percent: 4.99,
|
|
289
|
+
currency: 'BRL',
|
|
290
|
+
},
|
|
291
|
+
currencies: this.supportedCurrencies,
|
|
292
|
+
country: 'BR',
|
|
293
|
+
avgLatencyMs: 700,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
exports.MercadoPagoProvider = MercadoPagoProvider;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mollie payment provider
|
|
3
|
+
* EU-focused payment gateway supporting 9 currencies
|
|
4
|
+
* @see https://docs.mollie.com/reference
|
|
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 MollieConfig {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
webhookSecret?: string;
|
|
12
|
+
sandbox?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class MollieProvider extends PaymentProvider {
|
|
15
|
+
readonly name = "mollie";
|
|
16
|
+
readonly supportedCurrencies: string[];
|
|
17
|
+
private apiKey;
|
|
18
|
+
private webhookSecret?;
|
|
19
|
+
private sandbox;
|
|
20
|
+
private baseUrl;
|
|
21
|
+
constructor(config: MollieConfig);
|
|
22
|
+
private apiRequest;
|
|
23
|
+
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
24
|
+
createSubscription(_params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
25
|
+
getPayment(id: string): Promise<PaymentResult>;
|
|
26
|
+
refund(params: RefundParams): Promise<RefundResult>;
|
|
27
|
+
parseWebhook(body: any, _headers?: any): WebhookEvent;
|
|
28
|
+
/**
|
|
29
|
+
* Mollie webhooks have no signature verification scheme.
|
|
30
|
+
* Security comes from:
|
|
31
|
+
* 1. Validating source IP (caller's responsibility)
|
|
32
|
+
* 2. Calling getPayment(id) to verify actual status
|
|
33
|
+
*
|
|
34
|
+
* Always returns true to indicate no validation error (Mollie design limitation).
|
|
35
|
+
*/
|
|
36
|
+
verifyWebhook(_body: string | Buffer, _headers?: any): boolean;
|
|
37
|
+
getCapabilities(): ProviderCapabilities;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mollie payment provider
|
|
4
|
+
* EU-focused payment gateway supporting 9 currencies
|
|
5
|
+
* @see https://docs.mollie.com/reference
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.MollieProvider = void 0;
|
|
9
|
+
const base_1 = require("./base");
|
|
10
|
+
const fetch_1 = require("../utils/fetch");
|
|
11
|
+
let webhookSecretWarned = false;
|
|
12
|
+
class MollieProvider extends base_1.PaymentProvider {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
super();
|
|
15
|
+
this.name = 'mollie';
|
|
16
|
+
this.supportedCurrencies = ['EUR', 'USD', 'GBP', 'CHF', 'CAD', 'AUD', 'DKK', 'SEK', 'NOK'];
|
|
17
|
+
this.baseUrl = 'https://api.mollie.com/v2';
|
|
18
|
+
this.apiKey = config.apiKey;
|
|
19
|
+
this.webhookSecret = config.webhookSecret;
|
|
20
|
+
this.sandbox = config.sandbox ?? this.apiKey.startsWith('test_');
|
|
21
|
+
if (this.webhookSecret && !webhookSecretWarned) {
|
|
22
|
+
console.warn('[PayBridge:Mollie] Mollie has no webhook signature scheme. Webhook validation relies on getPayment() round-trip. Validate by source IP if possible.');
|
|
23
|
+
webhookSecretWarned = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async apiRequest(method, path, data) {
|
|
27
|
+
const url = `${this.baseUrl}${path}`;
|
|
28
|
+
const response = await (0, fetch_1.timedFetchOrThrow)(url, {
|
|
29
|
+
method,
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
},
|
|
34
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
35
|
+
});
|
|
36
|
+
return (await response.json());
|
|
37
|
+
}
|
|
38
|
+
async createPayment(params) {
|
|
39
|
+
this.validateCurrency(params.currency);
|
|
40
|
+
const requestBody = {
|
|
41
|
+
amount: {
|
|
42
|
+
value: params.amount.toFixed(2),
|
|
43
|
+
currency: params.currency,
|
|
44
|
+
},
|
|
45
|
+
description: params.description || params.reference,
|
|
46
|
+
redirectUrl: params.urls.success,
|
|
47
|
+
cancelUrl: params.urls.cancel,
|
|
48
|
+
webhookUrl: params.urls.webhook,
|
|
49
|
+
metadata: {
|
|
50
|
+
reference: params.reference,
|
|
51
|
+
...params.metadata,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const response = await this.apiRequest('POST', '/payments', requestBody);
|
|
55
|
+
return {
|
|
56
|
+
id: response.id,
|
|
57
|
+
checkoutUrl: response._links.checkout.href,
|
|
58
|
+
status: 'pending',
|
|
59
|
+
amount: params.amount,
|
|
60
|
+
currency: params.currency.toUpperCase(),
|
|
61
|
+
reference: params.reference,
|
|
62
|
+
provider: 'mollie',
|
|
63
|
+
createdAt: response.createdAt || new Date().toISOString(),
|
|
64
|
+
raw: response,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async createSubscription(_params) {
|
|
68
|
+
throw new Error('Mollie subscriptions require Customer + Mandate setup; not yet supported by paybridge. Use the Mollie Customers API directly or choose another provider.');
|
|
69
|
+
}
|
|
70
|
+
async getPayment(id) {
|
|
71
|
+
const response = await this.apiRequest('GET', `/payments/${id}`);
|
|
72
|
+
let status = 'pending';
|
|
73
|
+
if (response.status === 'paid') {
|
|
74
|
+
status = 'completed';
|
|
75
|
+
}
|
|
76
|
+
else if (response.status === 'failed' || response.status === 'expired') {
|
|
77
|
+
status = 'failed';
|
|
78
|
+
}
|
|
79
|
+
else if (response.status === 'canceled') {
|
|
80
|
+
status = 'cancelled';
|
|
81
|
+
}
|
|
82
|
+
else if (response.status === 'open' || response.status === 'pending') {
|
|
83
|
+
status = 'pending';
|
|
84
|
+
}
|
|
85
|
+
const currency = (response.amount?.currency || 'EUR').toUpperCase();
|
|
86
|
+
const amount = response.amount?.value ? parseFloat(response.amount.value) : 0;
|
|
87
|
+
return {
|
|
88
|
+
id: response.id,
|
|
89
|
+
checkoutUrl: response._links?.checkout?.href || '',
|
|
90
|
+
status,
|
|
91
|
+
amount,
|
|
92
|
+
currency,
|
|
93
|
+
reference: response.metadata?.reference || response.id,
|
|
94
|
+
provider: 'mollie',
|
|
95
|
+
createdAt: response.createdAt || new Date().toISOString(),
|
|
96
|
+
raw: response,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async refund(params) {
|
|
100
|
+
const refundData = {
|
|
101
|
+
description: params.reason || 'Refund',
|
|
102
|
+
};
|
|
103
|
+
if (params.amount !== undefined) {
|
|
104
|
+
const currency = 'EUR';
|
|
105
|
+
refundData.amount = {
|
|
106
|
+
value: params.amount.toFixed(2),
|
|
107
|
+
currency,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const response = await this.apiRequest('POST', `/payments/${params.paymentId}/refunds`, refundData);
|
|
111
|
+
const currency = (response.amount?.currency || 'EUR').toUpperCase();
|
|
112
|
+
const amount = response.amount?.value ? parseFloat(response.amount.value) : 0;
|
|
113
|
+
let refundStatus = 'pending';
|
|
114
|
+
if (response.status === 'refunded') {
|
|
115
|
+
refundStatus = 'completed';
|
|
116
|
+
}
|
|
117
|
+
else if (response.status === 'failed') {
|
|
118
|
+
refundStatus = 'failed';
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
id: response.id,
|
|
122
|
+
status: refundStatus,
|
|
123
|
+
amount,
|
|
124
|
+
currency,
|
|
125
|
+
paymentId: params.paymentId,
|
|
126
|
+
createdAt: response.createdAt || new Date().toISOString(),
|
|
127
|
+
raw: response,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
parseWebhook(body, _headers) {
|
|
131
|
+
let paymentId;
|
|
132
|
+
if (typeof body === 'string') {
|
|
133
|
+
const parsed = new URLSearchParams(body);
|
|
134
|
+
paymentId = parsed.get('id') || '';
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
paymentId = body.id || '';
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
type: 'payment.pending',
|
|
141
|
+
payment: {
|
|
142
|
+
id: paymentId,
|
|
143
|
+
checkoutUrl: '',
|
|
144
|
+
status: 'pending',
|
|
145
|
+
amount: 0,
|
|
146
|
+
currency: 'EUR',
|
|
147
|
+
reference: paymentId,
|
|
148
|
+
provider: 'mollie',
|
|
149
|
+
createdAt: new Date().toISOString(),
|
|
150
|
+
},
|
|
151
|
+
raw: body,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Mollie webhooks have no signature verification scheme.
|
|
156
|
+
* Security comes from:
|
|
157
|
+
* 1. Validating source IP (caller's responsibility)
|
|
158
|
+
* 2. Calling getPayment(id) to verify actual status
|
|
159
|
+
*
|
|
160
|
+
* Always returns true to indicate no validation error (Mollie design limitation).
|
|
161
|
+
*/
|
|
162
|
+
verifyWebhook(_body, _headers) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
getCapabilities() {
|
|
166
|
+
return {
|
|
167
|
+
fees: {
|
|
168
|
+
fixed: 0.25,
|
|
169
|
+
percent: 1.8,
|
|
170
|
+
currency: 'EUR',
|
|
171
|
+
},
|
|
172
|
+
currencies: this.supportedCurrencies,
|
|
173
|
+
country: 'EU',
|
|
174
|
+
avgLatencyMs: 350,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.MollieProvider = MollieProvider;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pesapal payment provider
|
|
3
|
+
* East Africa-focused payment gateway (Kenya, Uganda, Tanzania)
|
|
4
|
+
* @see https://developer.pesapal.com/
|
|
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 PesapalConfig {
|
|
10
|
+
consumerKey: string;
|
|
11
|
+
consumerSecret: string;
|
|
12
|
+
notificationId?: string;
|
|
13
|
+
username?: string;
|
|
14
|
+
webhookSecret?: string;
|
|
15
|
+
sandbox?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class PesapalProvider extends PaymentProvider {
|
|
18
|
+
readonly name = "pesapal";
|
|
19
|
+
readonly supportedCurrencies: string[];
|
|
20
|
+
private consumerKey;
|
|
21
|
+
private consumerSecret;
|
|
22
|
+
private notificationId?;
|
|
23
|
+
private username?;
|
|
24
|
+
private webhookSecret?;
|
|
25
|
+
private sandbox;
|
|
26
|
+
private baseUrl;
|
|
27
|
+
private tokenCache?;
|
|
28
|
+
constructor(config: PesapalConfig);
|
|
29
|
+
private getToken;
|
|
30
|
+
private apiRequest;
|
|
31
|
+
createPayment(params: CreatePaymentParams): Promise<PaymentResult>;
|
|
32
|
+
createSubscription(_params: CreateSubscriptionParams): Promise<SubscriptionResult>;
|
|
33
|
+
getPayment(id: string): Promise<PaymentResult>;
|
|
34
|
+
refund(params: RefundParams): Promise<RefundResult>;
|
|
35
|
+
parseWebhook(body: any, _headers?: any): WebhookEvent;
|
|
36
|
+
/**
|
|
37
|
+
* Pesapal IPN has no signature verification scheme.
|
|
38
|
+
* Security comes from:
|
|
39
|
+
* 1. Validating source IP (caller's responsibility)
|
|
40
|
+
* 2. Calling getPayment(id) to verify actual status
|
|
41
|
+
*
|
|
42
|
+
* Always returns true to indicate no validation error (Pesapal design limitation).
|
|
43
|
+
*/
|
|
44
|
+
verifyWebhook(_body: string | Buffer, _headers?: any): boolean;
|
|
45
|
+
getCapabilities(): ProviderCapabilities;
|
|
46
|
+
}
|
|
47
|
+
export {};
|