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
package/README.md
CHANGED
|
@@ -194,6 +194,12 @@ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
|
194
194
|
| **Stripe** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
195
195
|
| **Peach Payments** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
196
196
|
| **Flutterwave** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
197
|
+
| **Adyen** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
198
|
+
| **Mercado Pago** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
199
|
+
| **Razorpay** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
200
|
+
| **Mollie** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
201
|
+
| **Square** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
202
|
+
| **Pesapal** | ✅ | ⛔ | ✅ | ✅ | **Production** |
|
|
197
203
|
|
|
198
204
|
### Crypto on/off-ramp providers
|
|
199
205
|
|
|
@@ -201,13 +207,15 @@ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
|
201
207
|
|----------|---------|----------|-------|----------|--------|
|
|
202
208
|
| **MoonPay** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
203
209
|
| **Yellow Card** | ⚠️ | ⚠️ | ⚠️ | ⚠️ | **Experimental** |
|
|
210
|
+
| **Transak** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
211
|
+
| **Ramp Network** | ✅ | ✅ | ✅ | ✅ | **Production** |
|
|
204
212
|
|
|
205
213
|
**Legend:** ✅ Supported | ⛔ Not supported by upstream API | ⚠️ Experimental (spec unverified)
|
|
206
214
|
|
|
207
215
|
**Notes:**
|
|
208
216
|
- `⛔` marks features the underlying provider's API doesn't support — those methods throw a clear error explaining the limitation. Use a different provider for that capability or use `PayBridgeRouter` to route accordingly.
|
|
209
217
|
- **Yellow Card** is gated behind `@experimental` until partner API documentation is verified — it logs a warning on instantiation. Do not use in production without partner-confirmed spec.
|
|
210
|
-
- **Sandbox testing.** PayFast / PayStack / Stripe / Peach / Flutterwave are wired and unit-tested, but have not yet been validated against live sandbox credentials. To validate against real sandboxes, set the relevant `*_API_KEY` env vars and run `npm run test:e2e:sandbox`.
|
|
218
|
+
- **Sandbox testing.** PayFast / PayStack / Stripe / Peach / Flutterwave / Adyen / Mercado Pago / Razorpay are wired and unit-tested, but have not yet been validated against live sandbox credentials. To validate against real sandboxes, set the relevant `*_API_KEY` env vars and run `npm run test:e2e:sandbox`.
|
|
211
219
|
|
|
212
220
|
## Provider Configuration
|
|
213
221
|
|
|
@@ -258,6 +266,114 @@ const pay = new PayBridge({
|
|
|
258
266
|
|
|
259
267
|
**Docs:** [Ozow Hub](https://hub.ozow.com)
|
|
260
268
|
|
|
269
|
+
### Adyen
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const pay = new PayBridge({
|
|
273
|
+
provider: 'adyen',
|
|
274
|
+
credentials: {
|
|
275
|
+
apiKey: 'your_api_key',
|
|
276
|
+
merchantAccount: 'YourMerchantAccount',
|
|
277
|
+
liveUrlPrefix: 'abc123' // Only for live mode
|
|
278
|
+
},
|
|
279
|
+
sandbox: true,
|
|
280
|
+
webhookSecret: 'your_hmac_key_hex'
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Docs:** [Adyen API Explorer](https://docs.adyen.com/api-explorer/)
|
|
285
|
+
|
|
286
|
+
**Note:** Adyen subscriptions require recurring tokenization flow (not yet supported). Use Stripe or PayFast for subscriptions.
|
|
287
|
+
|
|
288
|
+
### Mercado Pago
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const pay = new PayBridge({
|
|
292
|
+
provider: 'mercadopago',
|
|
293
|
+
credentials: {
|
|
294
|
+
apiKey: 'TEST-...' // Or APP_USR-... for live
|
|
295
|
+
},
|
|
296
|
+
sandbox: true,
|
|
297
|
+
webhookSecret: 'your_webhook_secret'
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Docs:** [Mercado Pago Developers](https://www.mercadopago.com/developers/en/reference)
|
|
302
|
+
|
|
303
|
+
### Razorpay
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
const pay = new PayBridge({
|
|
307
|
+
provider: 'razorpay',
|
|
308
|
+
credentials: {
|
|
309
|
+
apiKey: 'rzp_test_...', // key_id
|
|
310
|
+
secretKey: 'your_key_secret'
|
|
311
|
+
},
|
|
312
|
+
sandbox: true,
|
|
313
|
+
webhookSecret: 'your_webhook_secret'
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Docs:** [Razorpay API Reference](https://razorpay.com/docs/api)
|
|
318
|
+
|
|
319
|
+
**Note:** Razorpay webhooks do not include timestamp-based replay protection.
|
|
320
|
+
|
|
321
|
+
### Mollie
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const pay = new PayBridge({
|
|
325
|
+
provider: 'mollie',
|
|
326
|
+
credentials: {
|
|
327
|
+
apiKey: 'test_...' // Or live_... for production
|
|
328
|
+
},
|
|
329
|
+
sandbox: true,
|
|
330
|
+
webhookSecret: 'optional_webhook_secret'
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Docs:** [Mollie API Reference](https://docs.mollie.com/reference)
|
|
335
|
+
|
|
336
|
+
**Note:** Mollie subscriptions require Customer + Mandate setup (not yet supported by paybridge). Mollie webhooks have no signature scheme — security relies on getPayment() round-trip + source IP validation.
|
|
337
|
+
|
|
338
|
+
### Square
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const pay = new PayBridge({
|
|
342
|
+
provider: 'square',
|
|
343
|
+
credentials: {
|
|
344
|
+
apiKey: 'EAAAEOuL...', // Access token
|
|
345
|
+
locationId: 'LOCATION123',
|
|
346
|
+
notificationUrl: 'https://example.com/webhook' // Required for signature verification
|
|
347
|
+
},
|
|
348
|
+
sandbox: true,
|
|
349
|
+
webhookSecret: 'your_webhook_secret'
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Docs:** [Square API Reference](https://developer.squareup.com/reference/square)
|
|
354
|
+
|
|
355
|
+
**Note:** Square subscriptions require multi-step Catalog + Customer + Plan setup (not yet supported by paybridge). Webhook signature uses notificationUrl + raw body.
|
|
356
|
+
|
|
357
|
+
### Pesapal
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const pay = new PayBridge({
|
|
361
|
+
provider: 'pesapal',
|
|
362
|
+
credentials: {
|
|
363
|
+
apiKey: 'qkio1BGG...', // consumer_key
|
|
364
|
+
secretKey: 'osGQ364R...', // consumer_secret
|
|
365
|
+
notificationId: 'IPN123', // Register IPN URL with Pesapal first
|
|
366
|
+
username: 'merchant@example.com' // Required for refunds
|
|
367
|
+
},
|
|
368
|
+
sandbox: true,
|
|
369
|
+
webhookSecret: 'optional_webhook_secret'
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Docs:** [Pesapal API Reference](https://developer.pesapal.com/)
|
|
374
|
+
|
|
375
|
+
**Note:** Pesapal subscriptions not yet supported. Pesapal IPN has no signature scheme — security relies on getPayment() round-trip + source IP validation. OAuth-style token caching (5min expiry).
|
|
376
|
+
|
|
261
377
|
## Switch Providers in 1 Line
|
|
262
378
|
|
|
263
379
|
```typescript
|
package/dist/crypto/index.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export * from './base';
|
|
|
8
8
|
export * from './moonpay';
|
|
9
9
|
export * from './yellowcard';
|
|
10
10
|
export * from './mock';
|
|
11
|
+
export * from './transak';
|
|
12
|
+
export * from './ramp';
|
|
11
13
|
export * from './router';
|
|
12
|
-
export type CryptoProvider = 'moonpay' | 'yellowcard' | 'mock';
|
|
14
|
+
export type CryptoProvider = 'moonpay' | 'yellowcard' | 'mock' | 'transak' | 'ramp';
|
|
13
15
|
export interface CryptoRampConfig {
|
|
14
16
|
provider: CryptoProvider;
|
|
15
17
|
credentials: {
|
package/dist/crypto/index.js
CHANGED
|
@@ -21,11 +21,15 @@ exports.CryptoRamp = void 0;
|
|
|
21
21
|
const moonpay_1 = require("./moonpay");
|
|
22
22
|
const yellowcard_1 = require("./yellowcard");
|
|
23
23
|
const mock_1 = require("./mock");
|
|
24
|
+
const transak_1 = require("./transak");
|
|
25
|
+
const ramp_1 = require("./ramp");
|
|
24
26
|
__exportStar(require("./types"), exports);
|
|
25
27
|
__exportStar(require("./base"), exports);
|
|
26
28
|
__exportStar(require("./moonpay"), exports);
|
|
27
29
|
__exportStar(require("./yellowcard"), exports);
|
|
28
30
|
__exportStar(require("./mock"), exports);
|
|
31
|
+
__exportStar(require("./transak"), exports);
|
|
32
|
+
__exportStar(require("./ramp"), exports);
|
|
29
33
|
__exportStar(require("./router"), exports);
|
|
30
34
|
class CryptoRamp {
|
|
31
35
|
constructor(config) {
|
|
@@ -59,6 +63,25 @@ class CryptoRamp {
|
|
|
59
63
|
sandbox,
|
|
60
64
|
webhookSecret,
|
|
61
65
|
});
|
|
66
|
+
case 'transak':
|
|
67
|
+
if (!credentials.apiKey || !credentials.secretKey) {
|
|
68
|
+
throw new Error('Transak requires apiKey and secretKey');
|
|
69
|
+
}
|
|
70
|
+
return new transak_1.TransakProvider({
|
|
71
|
+
apiKey: credentials.apiKey,
|
|
72
|
+
apiSecret: credentials.secretKey,
|
|
73
|
+
sandbox,
|
|
74
|
+
webhookSecret,
|
|
75
|
+
});
|
|
76
|
+
case 'ramp':
|
|
77
|
+
if (!credentials.apiKey) {
|
|
78
|
+
throw new Error('Ramp Network requires apiKey (hostApiKey)');
|
|
79
|
+
}
|
|
80
|
+
return new ramp_1.RampProvider({
|
|
81
|
+
hostApiKey: credentials.apiKey,
|
|
82
|
+
webhookSecret,
|
|
83
|
+
sandbox,
|
|
84
|
+
});
|
|
62
85
|
case 'mock':
|
|
63
86
|
return new mock_1.MockCryptoRampProvider();
|
|
64
87
|
default:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ramp Network crypto on/off-ramp provider
|
|
3
|
+
* @see https://docs.ramp.network/
|
|
4
|
+
*/
|
|
5
|
+
import { CryptoRampProvider } from './base';
|
|
6
|
+
import { OnRampParams, OffRampParams, RampQuote, RampResult, CryptoRampCapabilities } from './types';
|
|
7
|
+
interface RampConfig {
|
|
8
|
+
hostApiKey: string;
|
|
9
|
+
webhookSecret?: string;
|
|
10
|
+
sandbox: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class RampProvider extends CryptoRampProvider {
|
|
13
|
+
readonly name = "ramp";
|
|
14
|
+
private hostApiKey;
|
|
15
|
+
private webhookSecret?;
|
|
16
|
+
private sandbox;
|
|
17
|
+
private widgetUrl;
|
|
18
|
+
private apiUrl;
|
|
19
|
+
constructor(config: RampConfig);
|
|
20
|
+
getQuote(_direction: 'on' | 'off', fiatAmount: number, _fiatCurrency: string, _cryptoAsset: string, _network: string): Promise<RampQuote>;
|
|
21
|
+
createOnRamp(params: OnRampParams): Promise<RampResult>;
|
|
22
|
+
createOffRamp(params: OffRampParams): Promise<RampResult>;
|
|
23
|
+
getRamp(id: string): Promise<RampResult>;
|
|
24
|
+
parseWebhook(body: any, _headers?: any): any;
|
|
25
|
+
/**
|
|
26
|
+
* Ramp Network webhook verification (HMAC placeholder).
|
|
27
|
+
*
|
|
28
|
+
* TODO(verify): Ramp Network uses ECDSA secp256k1 for webhook signatures.
|
|
29
|
+
* This implementation uses HMAC-SHA256 as a placeholder. Production deployments
|
|
30
|
+
* should implement ECDSA verification using Ramp's public key.
|
|
31
|
+
*/
|
|
32
|
+
verifyWebhook(body: string | Buffer, headers?: any): boolean;
|
|
33
|
+
getCapabilities(): CryptoRampCapabilities;
|
|
34
|
+
private mapRampStatus;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ramp Network crypto on/off-ramp provider
|
|
4
|
+
* @see https://docs.ramp.network/
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.RampProvider = void 0;
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const base_1 = require("./base");
|
|
13
|
+
const fetch_1 = require("../utils/fetch");
|
|
14
|
+
class RampProvider extends base_1.CryptoRampProvider {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super();
|
|
17
|
+
this.name = 'ramp';
|
|
18
|
+
this.hostApiKey = config.hostApiKey;
|
|
19
|
+
this.webhookSecret = config.webhookSecret;
|
|
20
|
+
this.sandbox = config.sandbox;
|
|
21
|
+
this.widgetUrl = this.sandbox
|
|
22
|
+
? 'https://ri-widget-staging.firebaseapp.com'
|
|
23
|
+
: 'https://buy.ramp.network';
|
|
24
|
+
this.apiUrl = 'https://api.ramp.network/api/host-api/v3';
|
|
25
|
+
}
|
|
26
|
+
async getQuote(_direction, fiatAmount, _fiatCurrency, _cryptoAsset, _network) {
|
|
27
|
+
return {
|
|
28
|
+
fiatAmount,
|
|
29
|
+
cryptoAmount: 0,
|
|
30
|
+
rate: 0,
|
|
31
|
+
feeFixed: 0,
|
|
32
|
+
feePercent: 2.9,
|
|
33
|
+
feeTotal: (fiatAmount * 2.9) / 100,
|
|
34
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async createOnRamp(params) {
|
|
38
|
+
(0, base_1.validateWalletAddress)(params.destinationWallet, params.network);
|
|
39
|
+
const quote = await this.getQuote('on', params.fiatAmount, params.fiatCurrency, params.asset, params.network);
|
|
40
|
+
const swapAsset = `${params.asset}_${params.network}`;
|
|
41
|
+
const queryParams = new URLSearchParams({
|
|
42
|
+
hostApiKey: this.hostApiKey,
|
|
43
|
+
swapAsset: swapAsset.toUpperCase(),
|
|
44
|
+
fiatCurrency: params.fiatCurrency.toUpperCase(),
|
|
45
|
+
fiatValue: params.fiatAmount.toString(),
|
|
46
|
+
userAddress: params.destinationWallet,
|
|
47
|
+
userEmailAddress: params.customer.email,
|
|
48
|
+
finalUrl: params.urls.success,
|
|
49
|
+
});
|
|
50
|
+
const checkoutUrl = `${this.widgetUrl}?${queryParams}`;
|
|
51
|
+
return {
|
|
52
|
+
id: `ramp_on_${params.reference}`,
|
|
53
|
+
direction: 'on',
|
|
54
|
+
status: 'pending',
|
|
55
|
+
quote,
|
|
56
|
+
checkoutUrl,
|
|
57
|
+
createdAt: new Date().toISOString(),
|
|
58
|
+
expiresAt: quote.expiresAt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async createOffRamp(params) {
|
|
62
|
+
if (params.sourceWallet) {
|
|
63
|
+
(0, base_1.validateWalletAddress)(params.sourceWallet, params.network);
|
|
64
|
+
}
|
|
65
|
+
const quote = await this.getQuote('off', params.cryptoAmount, params.fiatCurrency, params.asset, params.network);
|
|
66
|
+
const swapAsset = `${params.asset}_${params.network}`;
|
|
67
|
+
const queryParams = new URLSearchParams({
|
|
68
|
+
hostApiKey: this.hostApiKey,
|
|
69
|
+
swapAsset: swapAsset.toUpperCase(),
|
|
70
|
+
defaultFlow: 'OFFRAMP',
|
|
71
|
+
});
|
|
72
|
+
const checkoutUrl = `${this.widgetUrl}?${queryParams}`;
|
|
73
|
+
return {
|
|
74
|
+
id: `ramp_off_${params.reference}`,
|
|
75
|
+
direction: 'off',
|
|
76
|
+
status: 'pending',
|
|
77
|
+
quote,
|
|
78
|
+
checkoutUrl,
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
expiresAt: quote.expiresAt,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async getRamp(id) {
|
|
84
|
+
const response = await (0, fetch_1.timedFetch)(`${this.apiUrl}/${id}`);
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Ramp Network getRamp failed: ${response.status}`);
|
|
87
|
+
}
|
|
88
|
+
const data = (await response.json());
|
|
89
|
+
const direction = data.type === 'ONRAMP' ? 'on' : 'off';
|
|
90
|
+
const status = this.mapRampStatus(data.status);
|
|
91
|
+
const quote = {
|
|
92
|
+
fiatAmount: data.fiatValue || 0,
|
|
93
|
+
cryptoAmount: data.cryptoAmount || 0,
|
|
94
|
+
rate: data.assetExchangeRate || 0,
|
|
95
|
+
feeFixed: 0,
|
|
96
|
+
feePercent: 0,
|
|
97
|
+
feeTotal: data.appliedFee || 0,
|
|
98
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
id: data.id,
|
|
102
|
+
direction,
|
|
103
|
+
status,
|
|
104
|
+
quote,
|
|
105
|
+
txHash: data.cryptoTxHash,
|
|
106
|
+
createdAt: data.createdAt,
|
|
107
|
+
raw: data,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
parseWebhook(body, _headers) {
|
|
111
|
+
const event = typeof body === 'string' ? JSON.parse(body) : body;
|
|
112
|
+
return {
|
|
113
|
+
type: event.type,
|
|
114
|
+
data: event.purchase || event,
|
|
115
|
+
raw: event,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Ramp Network webhook verification (HMAC placeholder).
|
|
120
|
+
*
|
|
121
|
+
* TODO(verify): Ramp Network uses ECDSA secp256k1 for webhook signatures.
|
|
122
|
+
* This implementation uses HMAC-SHA256 as a placeholder. Production deployments
|
|
123
|
+
* should implement ECDSA verification using Ramp's public key.
|
|
124
|
+
*/
|
|
125
|
+
verifyWebhook(body, headers) {
|
|
126
|
+
if (!this.webhookSecret)
|
|
127
|
+
return false;
|
|
128
|
+
const signature = headers?.['x-body-signature'];
|
|
129
|
+
if (!signature)
|
|
130
|
+
return false;
|
|
131
|
+
const rawBody = typeof body === 'string' ? body : body.toString('utf8');
|
|
132
|
+
const computedSig = crypto_1.default
|
|
133
|
+
.createHmac('sha256', this.webhookSecret)
|
|
134
|
+
.update(rawBody)
|
|
135
|
+
.digest('hex');
|
|
136
|
+
try {
|
|
137
|
+
const computedBuffer = Buffer.from(computedSig, 'hex');
|
|
138
|
+
const expectedBuffer = Buffer.from(signature, 'hex');
|
|
139
|
+
if (computedBuffer.length !== expectedBuffer.length) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return crypto_1.default.timingSafeEqual(computedBuffer, expectedBuffer);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
getCapabilities() {
|
|
149
|
+
return {
|
|
150
|
+
supportedAssets: ['BTC', 'ETH', 'USDT', 'USDC', 'DAI', 'MATIC'],
|
|
151
|
+
supportedNetworks: ['BTC', 'ETH', 'POLYGON', 'BSC'],
|
|
152
|
+
supportedFiat: ['USD', 'EUR', 'GBP', 'ZAR', 'AUD', 'CAD'],
|
|
153
|
+
country: 'GLOBAL',
|
|
154
|
+
kycRequired: true,
|
|
155
|
+
onRampLimits: { min: 30, max: 30000 },
|
|
156
|
+
offRampLimits: { min: 50, max: 30000 },
|
|
157
|
+
fees: {
|
|
158
|
+
onRampPercent: 2.9,
|
|
159
|
+
offRampPercent: 1.9,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
mapRampStatus(rampStatus) {
|
|
164
|
+
const statusMap = {
|
|
165
|
+
INITIALIZED: 'pending',
|
|
166
|
+
PAYMENT_STARTED: 'pending',
|
|
167
|
+
PAYMENT_IN_PROGRESS: 'pending',
|
|
168
|
+
PAYMENT_EXECUTED: 'pending',
|
|
169
|
+
FIAT_SENT: 'pending',
|
|
170
|
+
FIAT_RECEIVED: 'pending',
|
|
171
|
+
RELEASING: 'pending',
|
|
172
|
+
RELEASED: 'completed',
|
|
173
|
+
EXPIRED: 'expired',
|
|
174
|
+
CANCELLED: 'expired',
|
|
175
|
+
};
|
|
176
|
+
return statusMap[rampStatus] || 'pending';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.RampProvider = RampProvider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transak crypto on/off-ramp provider
|
|
3
|
+
* @see https://docs.transak.com/
|
|
4
|
+
*/
|
|
5
|
+
import { CryptoRampProvider } from './base';
|
|
6
|
+
import { OnRampParams, OffRampParams, RampQuote, RampResult, CryptoRampCapabilities } from './types';
|
|
7
|
+
interface TransakConfig {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
apiSecret: string;
|
|
10
|
+
sandbox: boolean;
|
|
11
|
+
webhookSecret?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class TransakProvider extends CryptoRampProvider {
|
|
14
|
+
readonly name = "transak";
|
|
15
|
+
private apiKey;
|
|
16
|
+
private apiSecret;
|
|
17
|
+
private sandbox;
|
|
18
|
+
private widgetUrl;
|
|
19
|
+
private apiUrl;
|
|
20
|
+
private webhookSecret?;
|
|
21
|
+
constructor(config: TransakConfig);
|
|
22
|
+
getQuote(direction: 'on' | 'off', fiatAmount: number, fiatCurrency: string, cryptoAsset: string, _network: string): Promise<RampQuote>;
|
|
23
|
+
createOnRamp(params: OnRampParams): Promise<RampResult>;
|
|
24
|
+
createOffRamp(params: OffRampParams): Promise<RampResult>;
|
|
25
|
+
getRamp(id: string): Promise<RampResult>;
|
|
26
|
+
parseWebhook(body: any, _headers?: any): any;
|
|
27
|
+
verifyWebhook(body: string | Buffer, headers?: any): boolean;
|
|
28
|
+
getCapabilities(): CryptoRampCapabilities;
|
|
29
|
+
private signWidgetUrl;
|
|
30
|
+
private mapTransakStatus;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Transak crypto on/off-ramp provider
|
|
4
|
+
* @see https://docs.transak.com/
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.TransakProvider = void 0;
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const base_1 = require("./base");
|
|
13
|
+
const fetch_1 = require("../utils/fetch");
|
|
14
|
+
class TransakProvider extends base_1.CryptoRampProvider {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super();
|
|
17
|
+
this.name = 'transak';
|
|
18
|
+
this.apiKey = config.apiKey;
|
|
19
|
+
this.apiSecret = config.apiSecret;
|
|
20
|
+
this.sandbox = config.sandbox;
|
|
21
|
+
this.webhookSecret = config.webhookSecret;
|
|
22
|
+
this.widgetUrl = this.sandbox
|
|
23
|
+
? 'https://global-stg.transak.com'
|
|
24
|
+
: 'https://global.transak.com';
|
|
25
|
+
this.apiUrl = this.sandbox
|
|
26
|
+
? 'https://api-stg.transak.com'
|
|
27
|
+
: 'https://api.transak.com';
|
|
28
|
+
}
|
|
29
|
+
async getQuote(direction, fiatAmount, fiatCurrency, cryptoAsset, _network) {
|
|
30
|
+
const params = new URLSearchParams({
|
|
31
|
+
fiatCurrency: fiatCurrency.toUpperCase(),
|
|
32
|
+
cryptoCurrency: cryptoAsset.toUpperCase(),
|
|
33
|
+
fiatAmount: fiatAmount.toString(),
|
|
34
|
+
paymentMethod: 'credit_debit_card',
|
|
35
|
+
isBuyOrSell: direction === 'on' ? 'BUY' : 'SELL',
|
|
36
|
+
});
|
|
37
|
+
const url = `${this.apiUrl}/api/v2/currencies/price?${params}`;
|
|
38
|
+
const response = await (0, fetch_1.timedFetch)(url, {
|
|
39
|
+
headers: {
|
|
40
|
+
'api-secret': this.apiSecret,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(`Transak quote failed: ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
const data = (await response.json());
|
|
47
|
+
const cryptoAmount = data.response?.cryptoAmount || 0;
|
|
48
|
+
const totalFee = data.response?.totalFee || 0;
|
|
49
|
+
const rate = fiatAmount > 0 ? cryptoAmount / fiatAmount : 0;
|
|
50
|
+
return {
|
|
51
|
+
fiatAmount,
|
|
52
|
+
cryptoAmount,
|
|
53
|
+
rate,
|
|
54
|
+
feeFixed: 0,
|
|
55
|
+
feePercent: (totalFee / fiatAmount) * 100,
|
|
56
|
+
feeTotal: totalFee,
|
|
57
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async createOnRamp(params) {
|
|
61
|
+
(0, base_1.validateWalletAddress)(params.destinationWallet, params.network);
|
|
62
|
+
const quote = await this.getQuote('on', params.fiatAmount, params.fiatCurrency, params.asset, params.network);
|
|
63
|
+
const queryParams = new URLSearchParams({
|
|
64
|
+
apiKey: this.apiKey,
|
|
65
|
+
fiatAmount: params.fiatAmount.toString(),
|
|
66
|
+
fiatCurrency: params.fiatCurrency.toUpperCase(),
|
|
67
|
+
cryptoCurrencyCode: params.asset.toUpperCase(),
|
|
68
|
+
network: params.network.toUpperCase(),
|
|
69
|
+
walletAddress: params.destinationWallet,
|
|
70
|
+
email: params.customer.email,
|
|
71
|
+
partnerOrderId: params.reference,
|
|
72
|
+
redirectURL: params.urls.success,
|
|
73
|
+
});
|
|
74
|
+
const signature = this.signWidgetUrl(queryParams.toString());
|
|
75
|
+
queryParams.append('signature', signature);
|
|
76
|
+
const checkoutUrl = `${this.widgetUrl}?${queryParams}`;
|
|
77
|
+
return {
|
|
78
|
+
id: `transak_on_${params.reference}`,
|
|
79
|
+
direction: 'on',
|
|
80
|
+
status: 'pending',
|
|
81
|
+
quote,
|
|
82
|
+
checkoutUrl,
|
|
83
|
+
createdAt: new Date().toISOString(),
|
|
84
|
+
expiresAt: quote.expiresAt,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async createOffRamp(params) {
|
|
88
|
+
if (params.sourceWallet) {
|
|
89
|
+
(0, base_1.validateWalletAddress)(params.sourceWallet, params.network);
|
|
90
|
+
}
|
|
91
|
+
const quote = await this.getQuote('off', params.cryptoAmount, params.fiatCurrency, params.asset, params.network);
|
|
92
|
+
const queryParams = new URLSearchParams({
|
|
93
|
+
apiKey: this.apiKey,
|
|
94
|
+
productsAvailed: 'SELL',
|
|
95
|
+
cryptoCurrencyCode: params.asset.toUpperCase(),
|
|
96
|
+
network: params.network.toUpperCase(),
|
|
97
|
+
fiatCurrency: params.fiatCurrency.toUpperCase(),
|
|
98
|
+
partnerOrderId: params.reference,
|
|
99
|
+
});
|
|
100
|
+
if (params.urls?.success) {
|
|
101
|
+
queryParams.append('redirectURL', params.urls.success);
|
|
102
|
+
}
|
|
103
|
+
const signature = this.signWidgetUrl(queryParams.toString());
|
|
104
|
+
queryParams.append('signature', signature);
|
|
105
|
+
const checkoutUrl = `${this.widgetUrl}?${queryParams}`;
|
|
106
|
+
return {
|
|
107
|
+
id: `transak_off_${params.reference}`,
|
|
108
|
+
direction: 'off',
|
|
109
|
+
status: 'pending',
|
|
110
|
+
quote,
|
|
111
|
+
checkoutUrl,
|
|
112
|
+
createdAt: new Date().toISOString(),
|
|
113
|
+
expiresAt: quote.expiresAt,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async getRamp(id) {
|
|
117
|
+
const response = await (0, fetch_1.timedFetch)(`${this.apiUrl}/api/v2/orders/${id}`, {
|
|
118
|
+
headers: {
|
|
119
|
+
'api-secret': this.apiSecret,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
throw new Error(`Transak getRamp failed: ${response.status}`);
|
|
124
|
+
}
|
|
125
|
+
const data = (await response.json());
|
|
126
|
+
const order = data.response;
|
|
127
|
+
const direction = order.isBuyOrSell === 'BUY' ? 'on' : 'off';
|
|
128
|
+
const status = this.mapTransakStatus(order.status);
|
|
129
|
+
const quote = {
|
|
130
|
+
fiatAmount: order.fiatAmount || 0,
|
|
131
|
+
cryptoAmount: order.cryptoAmount || 0,
|
|
132
|
+
rate: order.conversionPrice || 0,
|
|
133
|
+
feeFixed: 0,
|
|
134
|
+
feePercent: 0,
|
|
135
|
+
feeTotal: order.totalFee || 0,
|
|
136
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
id: order.id,
|
|
140
|
+
direction,
|
|
141
|
+
status,
|
|
142
|
+
quote,
|
|
143
|
+
txHash: order.transactionHash,
|
|
144
|
+
createdAt: order.createdAt,
|
|
145
|
+
raw: data,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
parseWebhook(body, _headers) {
|
|
149
|
+
const event = typeof body === 'string' ? JSON.parse(body) : body;
|
|
150
|
+
return {
|
|
151
|
+
type: event.eventName,
|
|
152
|
+
data: event.data,
|
|
153
|
+
raw: event,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
verifyWebhook(body, headers) {
|
|
157
|
+
if (!this.webhookSecret)
|
|
158
|
+
return false;
|
|
159
|
+
const signature = headers?.['x-transak-signature'];
|
|
160
|
+
if (!signature)
|
|
161
|
+
return false;
|
|
162
|
+
const rawBody = typeof body === 'string' ? body : body.toString('utf8');
|
|
163
|
+
const computedSig = crypto_1.default
|
|
164
|
+
.createHmac('sha256', this.webhookSecret)
|
|
165
|
+
.update(rawBody)
|
|
166
|
+
.digest('hex');
|
|
167
|
+
try {
|
|
168
|
+
const computedBuffer = Buffer.from(computedSig, 'hex');
|
|
169
|
+
const expectedBuffer = Buffer.from(signature, 'hex');
|
|
170
|
+
if (computedBuffer.length !== expectedBuffer.length) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return crypto_1.default.timingSafeEqual(computedBuffer, expectedBuffer);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
getCapabilities() {
|
|
180
|
+
return {
|
|
181
|
+
supportedAssets: ['BTC', 'ETH', 'USDT', 'USDC', 'MATIC', 'BNB'],
|
|
182
|
+
supportedNetworks: ['BTC', 'ETH', 'POLYGON', 'BSC', 'TRON'],
|
|
183
|
+
supportedFiat: ['USD', 'EUR', 'GBP', 'ZAR', 'INR', 'AUD'],
|
|
184
|
+
country: 'GLOBAL',
|
|
185
|
+
kycRequired: true,
|
|
186
|
+
onRampLimits: { min: 30, max: 50000 },
|
|
187
|
+
offRampLimits: { min: 50, max: 50000 },
|
|
188
|
+
fees: {
|
|
189
|
+
onRampPercent: 2.0,
|
|
190
|
+
offRampPercent: 1.5,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
signWidgetUrl(queryString) {
|
|
195
|
+
return crypto_1.default
|
|
196
|
+
.createHmac('sha256', this.apiSecret)
|
|
197
|
+
.update(queryString)
|
|
198
|
+
.digest('base64');
|
|
199
|
+
}
|
|
200
|
+
mapTransakStatus(transakStatus) {
|
|
201
|
+
const statusMap = {
|
|
202
|
+
AWAITING_PAYMENT_FROM_USER: 'pending',
|
|
203
|
+
PROCESSING: 'pending',
|
|
204
|
+
COMPLETED: 'completed',
|
|
205
|
+
FAILED: 'failed',
|
|
206
|
+
EXPIRED: 'expired',
|
|
207
|
+
CANCELLED: 'expired',
|
|
208
|
+
};
|
|
209
|
+
return statusMap[transakStatus] || 'pending';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.TransakProvider = TransakProvider;
|