@reevit/node 0.3.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 ADDED
@@ -0,0 +1,1385 @@
1
+ # Reevit TypeScript SDK
2
+
3
+ The official Node.js/TypeScript SDK for [Reevit](https://reevit.io) — a unified payment orchestration platform for Africa.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@reevit/node.svg)](https://www.npmjs.com/package/@reevit/node)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Configuration](#configuration)
14
+ - [Payments](#payments)
15
+ - [Create Payment Intent](#create-payment-intent)
16
+ - [Get Payment](#get-payment)
17
+ - [List Payments](#list-payments)
18
+ - [Refund Payment](#refund-payment)
19
+ - [Connections](#connections)
20
+ - [Create Connection](#create-connection)
21
+ - [List Connections](#list-connections)
22
+ - [Test Connection](#test-connection)
23
+ - [Subscriptions](#subscriptions)
24
+ - [Create Subscription](#create-subscription)
25
+ - [List Subscriptions](#list-subscriptions)
26
+ - [Fraud Protection](#fraud-protection)
27
+ - [Get Fraud Policy](#get-fraud-policy)
28
+ - [Update Fraud Policy](#update-fraud-policy)
29
+ - [Error Handling](#error-handling)
30
+ - [TypeScript Types](#typescript-types)
31
+ - [Supported Providers](#supported-providers)
32
+ - [Examples](#examples)
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install @reevit/node
40
+ ```
41
+
42
+ Or using yarn:
43
+
44
+ ```bash
45
+ yarn add @reevit/node
46
+ ```
47
+
48
+ Or using pnpm:
49
+
50
+ ```bash
51
+ pnpm add @reevit/node
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Quick Start
57
+
58
+ ```typescript
59
+ import { Reevit } from '@reevit/node';
60
+
61
+ // Initialize the client
62
+ const reevit = new Reevit(
63
+ 'pfk_live_your_api_key', // Your API key
64
+ 'org_your_org_id' // Your organization ID
65
+ );
66
+
67
+ // Create a payment
68
+ const payment = await reevit.payments.createIntent({
69
+ amount: 5000, // 50.00 GHS (amount in smallest currency unit)
70
+ currency: 'GHS',
71
+ method: 'momo',
72
+ country: 'GH',
73
+ customer_id: 'cust_123'
74
+ });
75
+
76
+ console.log('Payment ID:', payment.id);
77
+ console.log('Status:', payment.status);
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Configuration
83
+
84
+ ### Basic Configuration
85
+
86
+ ```typescript
87
+ import { Reevit } from '@reevit/node';
88
+
89
+ const reevit = new Reevit(
90
+ 'pfk_live_your_api_key', // API Key (required)
91
+ 'org_your_org_id', // Organization ID (required)
92
+ 'https://api.reevit.io' // Base URL (optional, defaults to localhost:8080)
93
+ );
94
+ ```
95
+
96
+ ### Environment Variables (Recommended)
97
+
98
+ ```typescript
99
+ import { Reevit } from '@reevit/node';
100
+
101
+ const reevit = new Reevit(
102
+ process.env.REEVIT_API_KEY!,
103
+ process.env.REEVIT_ORG_ID!,
104
+ process.env.REEVIT_BASE_URL || 'https://api.reevit.io'
105
+ );
106
+ ```
107
+
108
+ ### API Key Types
109
+
110
+ | Key Prefix | Environment | Description |
111
+ |------------|-------------|-------------|
112
+ | `pfk_live_` | Production | Live transactions, real money |
113
+ | `pfk_test_` | Sandbox | Test transactions, no real charges |
114
+
115
+ ---
116
+
117
+ ## Payments
118
+
119
+ ### Create Payment Intent
120
+
121
+ Create a new payment intent to initiate a transaction.
122
+
123
+ ```typescript
124
+ const payment = await reevit.payments.createIntent({
125
+ amount: 10000, // 100.00 in smallest currency unit
126
+ currency: 'GHS', // Currency code (GHS, NGN, KES, USD)
127
+ method: 'momo', // Payment method
128
+ country: 'GH', // ISO country code
129
+ customer_id: 'cust_123', // Optional: Your customer reference
130
+ metadata: { // Optional: Custom metadata
131
+ order_id: 'order_456',
132
+ product: 'Premium Plan'
133
+ }
134
+ });
135
+ ```
136
+
137
+ #### Payment Methods by Country
138
+
139
+ | Country | Code | Supported Methods |
140
+ |---------|------|-------------------|
141
+ | Ghana | `GH` | `momo`, `card`, `bank_transfer` |
142
+ | Nigeria | `NG` | `card`, `bank_transfer`, `ussd` |
143
+ | Kenya | `KE` | `mpesa`, `card` |
144
+
145
+ #### Full Example with All Options
146
+
147
+ ```typescript
148
+ const payment = await reevit.payments.createIntent({
149
+ amount: 25000,
150
+ currency: 'NGN',
151
+ method: 'card',
152
+ country: 'NG',
153
+ customer_id: 'cust_789',
154
+ metadata: {
155
+ order_id: 'ORD-2024-001',
156
+ customer_email: 'customer@example.com',
157
+ description: 'Monthly subscription'
158
+ },
159
+ policy: {
160
+ prefer: ['paystack', 'flutterwave'], // Preferred providers
161
+ max_amount: 100000, // Max transaction amount
162
+ velocity_max_per_minute: 5 // Rate limiting
163
+ }
164
+ });
165
+
166
+ console.log('Payment created:', {
167
+ id: payment.id,
168
+ status: payment.status,
169
+ provider: payment.provider,
170
+ providerRef: payment.provider_ref_id
171
+ });
172
+ ```
173
+
174
+ ### Get Payment
175
+
176
+ Retrieve details of a specific payment.
177
+
178
+ ```typescript
179
+ const payment = await reevit.payments.get('pay_abc123');
180
+
181
+ console.log('Payment Details:', {
182
+ id: payment.id,
183
+ status: payment.status,
184
+ amount: payment.amount,
185
+ currency: payment.currency,
186
+ fee: payment.fee_amount,
187
+ net: payment.net_amount,
188
+ provider: payment.provider,
189
+ method: payment.method,
190
+ createdAt: payment.created_at
191
+ });
192
+
193
+ // Check routing attempts (useful for debugging)
194
+ if (payment.route && payment.route.length > 0) {
195
+ console.log('Routing attempts:');
196
+ payment.route.forEach((attempt, index) => {
197
+ console.log(` ${index + 1}. ${attempt.provider}: ${attempt.status}`);
198
+ if (attempt.error) {
199
+ console.log(` Error: ${attempt.error}`);
200
+ }
201
+ });
202
+ }
203
+ ```
204
+
205
+ ### List Payments
206
+
207
+ Retrieve a paginated list of payments.
208
+
209
+ ```typescript
210
+ // Basic listing (default: 50 payments)
211
+ const payments = await reevit.payments.list();
212
+
213
+ // With pagination
214
+ const page1 = await reevit.payments.list(10, 0); // First 10
215
+ const page2 = await reevit.payments.list(10, 10); // Next 10
216
+
217
+ // Process payments
218
+ payments.forEach(payment => {
219
+ console.log(`${payment.id}: ${payment.status} - ${payment.currency} ${payment.amount / 100}`);
220
+ });
221
+ ```
222
+
223
+ #### Pagination Example
224
+
225
+ ```typescript
226
+ import { PaymentSummary } from '@reevit/node';
227
+
228
+ async function getAllPayments(): Promise<PaymentSummary[]> {
229
+ const allPayments: PaymentSummary[] = [];
230
+ const pageSize = 50;
231
+ let offset = 0;
232
+ let hasMore = true;
233
+
234
+ while (hasMore) {
235
+ const batch = await reevit.payments.list(pageSize, offset);
236
+ allPayments.push(...batch);
237
+
238
+ if (batch.length < pageSize) {
239
+ hasMore = false;
240
+ } else {
241
+ offset += pageSize;
242
+ }
243
+ }
244
+
245
+ return allPayments;
246
+ }
247
+ ```
248
+
249
+ ### Refund Payment
250
+
251
+ Issue a full or partial refund for a payment.
252
+
253
+ ```typescript
254
+ // Full refund
255
+ const fullRefund = await reevit.payments.refund('pay_abc123');
256
+
257
+ // Partial refund
258
+ const partialRefund = await reevit.payments.refund(
259
+ 'pay_abc123',
260
+ 2500, // Refund 25.00
261
+ 'Customer requested' // Reason (optional)
262
+ );
263
+
264
+ console.log('Refund:', {
265
+ id: partialRefund.id,
266
+ paymentId: partialRefund.payment_id,
267
+ amount: partialRefund.amount,
268
+ status: partialRefund.status,
269
+ reason: partialRefund.reason
270
+ });
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Connections
276
+
277
+ Connections represent your integrations with payment service providers (PSPs).
278
+
279
+ ### Create Connection
280
+
281
+ ```typescript
282
+ // Paystack Connection (Nigeria)
283
+ const paystackConnection = await reevit.connections.create({
284
+ provider: 'paystack',
285
+ mode: 'live', // 'live' or 'test'
286
+ credentials: {
287
+ secret_key: 'sk_live_xxxxx'
288
+ },
289
+ labels: ['nigeria', 'primary'],
290
+ routing_hints: {
291
+ country_preference: ['NG'],
292
+ method_bias: { card: 'high', bank_transfer: 'medium' },
293
+ fallback_only: false
294
+ }
295
+ });
296
+
297
+ // Flutterwave Connection (Multi-country)
298
+ const flutterwaveConnection = await reevit.connections.create({
299
+ provider: 'flutterwave',
300
+ mode: 'live',
301
+ credentials: {
302
+ secret_key: 'FLWSECK-xxxxx',
303
+ encryption_key: 'xxxxx'
304
+ },
305
+ labels: ['multi-country', 'backup'],
306
+ routing_hints: {
307
+ country_preference: ['GH', 'NG', 'KE'],
308
+ fallback_only: true
309
+ }
310
+ });
311
+
312
+ // Hubtel Connection (Ghana)
313
+ const hubtelConnection = await reevit.connections.create({
314
+ provider: 'hubtel',
315
+ mode: 'live',
316
+ credentials: {
317
+ client_id: 'xxxxx',
318
+ client_secret: 'xxxxx'
319
+ },
320
+ labels: ['ghana', 'momo'],
321
+ routing_hints: {
322
+ country_preference: ['GH'],
323
+ method_bias: { momo: 'high' }
324
+ }
325
+ });
326
+
327
+ // M-Pesa Connection (Kenya)
328
+ const mpesaConnection = await reevit.connections.create({
329
+ provider: 'mpesa',
330
+ mode: 'live',
331
+ credentials: {
332
+ consumer_key: 'xxxxx',
333
+ consumer_secret: 'xxxxx',
334
+ passkey: 'xxxxx',
335
+ shortcode: '174379',
336
+ initiator_name: 'testapi',
337
+ security_credential: 'xxxxx' // Pre-encrypted
338
+ },
339
+ labels: ['kenya', 'mpesa'],
340
+ routing_hints: {
341
+ country_preference: ['KE'],
342
+ method_bias: { mpesa: 'high' }
343
+ }
344
+ });
345
+
346
+ // Monnify Connection (Nigeria)
347
+ const monnifyConnection = await reevit.connections.create({
348
+ provider: 'monnify',
349
+ mode: 'live',
350
+ credentials: {
351
+ api_key: 'xxxxx',
352
+ secret_key: 'xxxxx',
353
+ contract_code: 'xxxxx'
354
+ },
355
+ labels: ['nigeria', 'bank_transfer'],
356
+ routing_hints: {
357
+ country_preference: ['NG'],
358
+ method_bias: { bank_transfer: 'high' }
359
+ }
360
+ });
361
+ ```
362
+
363
+ ### List Connections
364
+
365
+ ```typescript
366
+ const connections = await reevit.connections.list();
367
+
368
+ connections.forEach(conn => {
369
+ console.log(`${conn.provider} (${conn.mode}): ${conn.status}`);
370
+ console.log(` Labels: ${conn.labels.join(', ')}`);
371
+ console.log(` Countries: ${conn.routing_hints.country_preference.join(', ')}`);
372
+ });
373
+ ```
374
+
375
+ ### Test Connection
376
+
377
+ Verify credentials before creating a connection.
378
+
379
+ ```typescript
380
+ const isValid = await reevit.connections.test({
381
+ provider: 'paystack',
382
+ mode: 'live',
383
+ credentials: {
384
+ secret_key: 'sk_live_xxxxx'
385
+ }
386
+ });
387
+
388
+ if (isValid) {
389
+ console.log('Connection credentials are valid');
390
+ } else {
391
+ console.log('Invalid credentials');
392
+ }
393
+ ```
394
+
395
+ ---
396
+
397
+ ## Subscriptions
398
+
399
+ Manage recurring billing and subscriptions.
400
+
401
+ ### Create Subscription
402
+
403
+ ```typescript
404
+ // Monthly subscription
405
+ const monthlySubscription = await reevit.subscriptions.create({
406
+ customer_id: 'cust_123',
407
+ plan_id: 'plan_premium',
408
+ amount: 9900, // 99.00 per month
409
+ currency: 'GHS',
410
+ method: 'momo',
411
+ interval: 'monthly',
412
+ metadata: {
413
+ plan_name: 'Premium',
414
+ features: ['unlimited_access', 'priority_support']
415
+ }
416
+ });
417
+
418
+ // Yearly subscription
419
+ const yearlySubscription = await reevit.subscriptions.create({
420
+ customer_id: 'cust_456',
421
+ plan_id: 'plan_enterprise',
422
+ amount: 99900, // 999.00 per year
423
+ currency: 'NGN',
424
+ method: 'card',
425
+ interval: 'yearly',
426
+ metadata: {
427
+ plan_name: 'Enterprise',
428
+ discount_applied: '2_months_free'
429
+ }
430
+ });
431
+
432
+ console.log('Subscription created:', {
433
+ id: monthlySubscription.id,
434
+ status: monthlySubscription.status,
435
+ nextRenewal: monthlySubscription.next_renewal_at
436
+ });
437
+ ```
438
+
439
+ ### List Subscriptions
440
+
441
+ ```typescript
442
+ const subscriptions = await reevit.subscriptions.list();
443
+
444
+ subscriptions.forEach(sub => {
445
+ console.log(`${sub.id}: ${sub.status}`);
446
+ console.log(` Customer: ${sub.customer_id}`);
447
+ console.log(` Amount: ${sub.currency} ${sub.amount / 100}/${sub.interval}`);
448
+ console.log(` Next renewal: ${sub.next_renewal_at}`);
449
+ });
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Fraud Protection
455
+
456
+ Configure fraud rules to protect your transactions.
457
+
458
+ ### Get Fraud Policy
459
+
460
+ ```typescript
461
+ const policy = await reevit.fraud.get();
462
+
463
+ console.log('Current Fraud Policy:', {
464
+ preferredProviders: policy.prefer,
465
+ maxAmount: policy.max_amount,
466
+ blockedBins: policy.blocked_bins,
467
+ allowedBins: policy.allowed_bins,
468
+ velocityLimit: policy.velocity_max_per_minute
469
+ });
470
+ ```
471
+
472
+ ### Update Fraud Policy
473
+
474
+ ```typescript
475
+ const updatedPolicy = await reevit.fraud.update({
476
+ prefer: ['paystack', 'flutterwave'],
477
+ max_amount: 500000, // Max 5,000.00
478
+ blocked_bins: ['123456', '654321'], // Block specific card BINs
479
+ allowed_bins: [], // Empty = allow all (except blocked)
480
+ velocity_max_per_minute: 10 // Max 10 transactions per minute
481
+ });
482
+
483
+ console.log('Policy updated successfully');
484
+ ```
485
+
486
+ #### Fraud Policy Options
487
+
488
+ | Option | Type | Description |
489
+ |--------|------|-------------|
490
+ | `prefer` | `string[]` | Preferred provider order for routing |
491
+ | `max_amount` | `number` | Maximum transaction amount (in smallest unit) |
492
+ | `blocked_bins` | `string[]` | Card BIN prefixes to block |
493
+ | `allowed_bins` | `string[]` | Only allow these BINs (empty = allow all) |
494
+ | `velocity_max_per_minute` | `number` | Rate limit per customer |
495
+
496
+ ---
497
+
498
+ ## Error Handling
499
+
500
+ The SDK throws errors for failed API calls. Always wrap calls in try-catch blocks.
501
+
502
+ ```typescript
503
+ import { AxiosError } from 'axios';
504
+
505
+ async function createPaymentSafely() {
506
+ try {
507
+ const payment = await reevit.payments.createIntent({
508
+ amount: 5000,
509
+ currency: 'GHS',
510
+ method: 'momo',
511
+ country: 'GH'
512
+ });
513
+ return payment;
514
+ } catch (error) {
515
+ if (error instanceof AxiosError) {
516
+ // API error
517
+ console.error('API Error:', {
518
+ status: error.response?.status,
519
+ message: error.response?.data?.message || error.message,
520
+ code: error.response?.data?.code
521
+ });
522
+
523
+ // Handle specific error codes
524
+ switch (error.response?.status) {
525
+ case 400:
526
+ console.error('Bad request - check your parameters');
527
+ break;
528
+ case 401:
529
+ console.error('Unauthorized - check your API key');
530
+ break;
531
+ case 403:
532
+ console.error('Forbidden - check your permissions');
533
+ break;
534
+ case 404:
535
+ console.error('Resource not found');
536
+ break;
537
+ case 429:
538
+ console.error('Rate limited - slow down requests');
539
+ break;
540
+ case 500:
541
+ console.error('Server error - try again later');
542
+ break;
543
+ }
544
+ } else {
545
+ // Network or other error
546
+ console.error('Unexpected error:', error);
547
+ }
548
+ throw error;
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### Retry Logic Example
554
+
555
+ ```typescript
556
+ import { Payment, PaymentIntentRequest } from '@reevit/node';
557
+ import { AxiosError } from 'axios';
558
+
559
+ async function createPaymentWithRetry(
560
+ data: PaymentIntentRequest,
561
+ maxRetries = 3,
562
+ delayMs = 1000
563
+ ): Promise<Payment> {
564
+ let lastError: Error | undefined;
565
+
566
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
567
+ try {
568
+ return await reevit.payments.createIntent(data);
569
+ } catch (error) {
570
+ lastError = error as Error;
571
+
572
+ if (error instanceof AxiosError) {
573
+ // Don't retry client errors (4xx)
574
+ if (error.response?.status && error.response.status < 500) {
575
+ throw error;
576
+ }
577
+ }
578
+
579
+ if (attempt < maxRetries) {
580
+ console.log(`Attempt ${attempt} failed, retrying in ${delayMs}ms...`);
581
+ await new Promise(resolve => setTimeout(resolve, delayMs));
582
+ delayMs *= 2; // Exponential backoff
583
+ }
584
+ }
585
+ }
586
+
587
+ throw lastError;
588
+ }
589
+ ```
590
+
591
+ ---
592
+
593
+ ## TypeScript Types
594
+
595
+ The SDK exports all types for use in your application.
596
+
597
+ ```typescript
598
+ import {
599
+ // Payment types
600
+ Payment,
601
+ PaymentSummary,
602
+ PaymentIntentRequest,
603
+ PaymentRouteAttempt,
604
+ Refund,
605
+
606
+ // Connection types
607
+ Connection,
608
+ ConnectionRequest,
609
+ RoutingHints,
610
+
611
+ // Subscription types
612
+ Subscription,
613
+ SubscriptionRequest,
614
+
615
+ // Fraud types
616
+ FraudPolicy,
617
+ FraudPolicyInput
618
+ } from '@reevit/node';
619
+
620
+ // Use types in your code
621
+ function processPayment(payment: Payment): void {
622
+ console.log(`Processing ${payment.id}`);
623
+ }
624
+
625
+ function buildPaymentRequest(): PaymentIntentRequest {
626
+ return {
627
+ amount: 5000,
628
+ currency: 'GHS',
629
+ method: 'momo',
630
+ country: 'GH'
631
+ };
632
+ }
633
+ ```
634
+ ## Supported Providers
635
+
636
+ Reevit currently supports the following payment service providers:
637
+
638
+ | Provider | Countries | Methods | Features |
639
+ |----------|-----------|---------|----------|
640
+ | **Paystack** | Nigeria, Ghana | Card, Bank Transfer, USSD | Refunds, Webhooks |
641
+ | **Flutterwave** | Nigeria, Ghana, Kenya, +30 | Card, Mobile Money, Bank | Refunds, Webhooks |
642
+ | **Hubtel** | Ghana | Mobile Money | Webhooks |
643
+ | **M-Pesa** | Kenya | M-Pesa (STK Push) | Reversals, Webhooks |
644
+ | **Monnify** | Nigeria | Bank Transfer, Card | Refunds, Webhooks |
645
+ | **Stripe** | Global | Card (incl. Apple Pay/Google Pay via card rails) | Refunds, Webhooks |
646
+
647
+ > Stripe webhooks: configure the Reevit webhook URL in Stripe; store the signing secret in the connection credentials (`stripe_webhook_secret`, `webhook_secret`, or `signing_secret`). Ensure PaymentIntent metadata includes `org_id`, `connection_id`, and `payment_id` so events map correctly.
648
+
649
+ ---
650
+
651
+ ## Examples
652
+
653
+ ### E-commerce Checkout
654
+
655
+ ```typescript
656
+ import { Reevit, Payment, PaymentIntentRequest } from '@reevit/node';
657
+
658
+ const reevit = new Reevit(
659
+ process.env.REEVIT_API_KEY!,
660
+ process.env.REEVIT_ORG_ID!,
661
+ process.env.REEVIT_BASE_URL
662
+ );
663
+
664
+ interface CheckoutData {
665
+ orderId: string;
666
+ customerId: string;
667
+ amount: number;
668
+ currency: string;
669
+ country: string;
670
+ paymentMethod: string;
671
+ customerEmail: string;
672
+ }
673
+
674
+ async function processCheckout(checkout: CheckoutData): Promise<Payment> {
675
+ const paymentRequest: PaymentIntentRequest = {
676
+ amount: checkout.amount,
677
+ currency: checkout.currency,
678
+ method: checkout.paymentMethod,
679
+ country: checkout.country,
680
+ customer_id: checkout.customerId,
681
+ metadata: {
682
+ order_id: checkout.orderId,
683
+ customer_email: checkout.customerEmail,
684
+ source: 'web_checkout'
685
+ }
686
+ };
687
+
688
+ const payment = await reevit.payments.createIntent(paymentRequest);
689
+
690
+ console.log(`Payment ${payment.id} created for order ${checkout.orderId}`);
691
+ console.log(`Status: ${payment.status}`);
692
+ console.log(`Provider: ${payment.provider}`);
693
+
694
+ return payment;
695
+ }
696
+
697
+ // Usage
698
+ processCheckout({
699
+ orderId: 'ORD-2024-001',
700
+ customerId: 'cust_abc123',
701
+ amount: 15000, // 150.00
702
+ currency: 'GHS',
703
+ country: 'GH',
704
+ paymentMethod: 'momo',
705
+ customerEmail: 'customer@example.com'
706
+ });
707
+ ```
708
+
709
+ ### Subscription Service
710
+
711
+ ```typescript
712
+ import { Reevit, Subscription } from '@reevit/node';
713
+
714
+ const reevit = new Reevit(
715
+ process.env.REEVIT_API_KEY!,
716
+ process.env.REEVIT_ORG_ID!
717
+ );
718
+
719
+ interface Plan {
720
+ id: string;
721
+ name: string;
722
+ monthlyPrice: number;
723
+ yearlyPrice: number;
724
+ currency: string;
725
+ }
726
+
727
+ const plans: Plan[] = [
728
+ { id: 'basic', name: 'Basic', monthlyPrice: 1999, yearlyPrice: 19990, currency: 'GHS' },
729
+ { id: 'pro', name: 'Pro', monthlyPrice: 4999, yearlyPrice: 49990, currency: 'GHS' },
730
+ { id: 'enterprise', name: 'Enterprise', monthlyPrice: 9999, yearlyPrice: 99990, currency: 'GHS' }
731
+ ];
732
+
733
+ async function subscribeToPlan(
734
+ customerId: string,
735
+ planId: string,
736
+ interval: 'monthly' | 'yearly',
737
+ paymentMethod: string
738
+ ): Promise<Subscription> {
739
+ const plan = plans.find(p => p.id === planId);
740
+ if (!plan) {
741
+ throw new Error(`Plan ${planId} not found`);
742
+ }
743
+
744
+ const amount = interval === 'monthly' ? plan.monthlyPrice : plan.yearlyPrice;
745
+
746
+ const subscription = await reevit.subscriptions.create({
747
+ customer_id: customerId,
748
+ plan_id: planId,
749
+ amount,
750
+ currency: plan.currency,
751
+ method: paymentMethod,
752
+ interval,
753
+ metadata: {
754
+ plan_name: plan.name,
755
+ billing_interval: interval
756
+ }
757
+ });
758
+
759
+ console.log(`Subscription created: ${subscription.id}`);
760
+ console.log(`Next renewal: ${subscription.next_renewal_at}`);
761
+
762
+ return subscription;
763
+ }
764
+
765
+ // Usage
766
+ subscribeToPlan('cust_123', 'pro', 'monthly', 'momo');
767
+ ```
768
+
769
+ ### Multi-Provider Setup
770
+
771
+ ```typescript
772
+ import { Reevit } from '@reevit/node';
773
+
774
+ const reevit = new Reevit(
775
+ process.env.REEVIT_API_KEY!,
776
+ process.env.REEVIT_ORG_ID!
777
+ );
778
+
779
+ async function setupProviders() {
780
+ // Primary provider for Nigeria
781
+ await reevit.connections.create({
782
+ provider: 'paystack',
783
+ mode: 'live',
784
+ credentials: {
785
+ secret_key: process.env.PAYSTACK_SECRET_KEY!
786
+ },
787
+ labels: ['nigeria', 'primary'],
788
+ routing_hints: {
789
+ country_preference: ['NG'],
790
+ method_bias: { card: 'high' },
791
+ fallback_only: false
792
+ }
793
+ });
794
+
795
+ // Backup provider for Nigeria
796
+ await reevit.connections.create({
797
+ provider: 'flutterwave',
798
+ mode: 'live',
799
+ credentials: {
800
+ secret_key: process.env.FLUTTERWAVE_SECRET_KEY!,
801
+ encryption_key: process.env.FLUTTERWAVE_ENCRYPTION_KEY!
802
+ },
803
+ labels: ['nigeria', 'backup'],
804
+ routing_hints: {
805
+ country_preference: ['NG'],
806
+ fallback_only: true
807
+ }
808
+ });
809
+
810
+ // Primary provider for Ghana
811
+ await reevit.connections.create({
812
+ provider: 'hubtel',
813
+ mode: 'live',
814
+ credentials: {
815
+ client_id: process.env.HUBTEL_CLIENT_ID!,
816
+ client_secret: process.env.HUBTEL_CLIENT_SECRET!
817
+ },
818
+ labels: ['ghana', 'momo'],
819
+ routing_hints: {
820
+ country_preference: ['GH'],
821
+ method_bias: { momo: 'high' },
822
+ fallback_only: false
823
+ }
824
+ });
825
+
826
+ // Primary provider for Kenya
827
+ await reevit.connections.create({
828
+ provider: 'mpesa',
829
+ mode: 'live',
830
+ credentials: {
831
+ consumer_key: process.env.MPESA_CONSUMER_KEY!,
832
+ consumer_secret: process.env.MPESA_CONSUMER_SECRET!,
833
+ passkey: process.env.MPESA_PASSKEY!,
834
+ shortcode: process.env.MPESA_SHORTCODE!,
835
+ initiator_name: process.env.MPESA_INITIATOR_NAME!,
836
+ security_credential: process.env.MPESA_SECURITY_CREDENTIAL!
837
+ },
838
+ labels: ['kenya', 'mpesa'],
839
+ routing_hints: {
840
+ country_preference: ['KE'],
841
+ method_bias: { mpesa: 'high' },
842
+ fallback_only: false
843
+ }
844
+ });
845
+
846
+ console.log('All providers configured successfully');
847
+
848
+ // List all connections
849
+ const connections = await reevit.connections.list();
850
+ console.log(`Total connections: ${connections.length}`);
851
+ connections.forEach(c => {
852
+ console.log(` - ${c.provider} (${c.mode}): ${c.status}`);
853
+ });
854
+ }
855
+
856
+ setupProviders();
857
+ ```
858
+
859
+ ### Payment Status Polling
860
+
861
+ ```typescript
862
+ import { Reevit, Payment } from '@reevit/node';
863
+
864
+ const reevit = new Reevit(
865
+ process.env.REEVIT_API_KEY!,
866
+ process.env.REEVIT_ORG_ID!
867
+ );
868
+
869
+ async function waitForPaymentCompletion(
870
+ paymentId: string,
871
+ timeoutMs = 300000, // 5 minutes
872
+ pollIntervalMs = 5000
873
+ ): Promise<Payment> {
874
+ const startTime = Date.now();
875
+ const terminalStatuses = ['succeeded', 'failed', 'canceled'];
876
+
877
+ while (Date.now() - startTime < timeoutMs) {
878
+ const payment = await reevit.payments.get(paymentId);
879
+
880
+ console.log(`Payment ${paymentId}: ${payment.status}`);
881
+
882
+ if (terminalStatuses.includes(payment.status)) {
883
+ return payment;
884
+ }
885
+
886
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
887
+ }
888
+
889
+ throw new Error(`Payment ${paymentId} did not complete within ${timeoutMs}ms`);
890
+ }
891
+
892
+ // Usage
893
+ async function processAndWait() {
894
+ const payment = await reevit.payments.createIntent({
895
+ amount: 5000,
896
+ currency: 'GHS',
897
+ method: 'momo',
898
+ country: 'GH'
899
+ });
900
+
901
+ console.log(`Created payment: ${payment.id}`);
902
+
903
+ const completedPayment = await waitForPaymentCompletion(payment.id);
904
+
905
+ if (completedPayment.status === 'succeeded') {
906
+ console.log('Payment successful!');
907
+ console.log(`Net amount: ${completedPayment.net_amount}`);
908
+ } else {
909
+ console.log(`Payment ${completedPayment.status}`);
910
+ }
911
+ }
912
+ ```
913
+
914
+ ### Refund Management
915
+
916
+ ```typescript
917
+ import { Reevit, Payment, Refund } from '@reevit/node';
918
+
919
+ const reevit = new Reevit(
920
+ process.env.REEVIT_API_KEY!,
921
+ process.env.REEVIT_ORG_ID!
922
+ );
923
+
924
+ interface RefundRequest {
925
+ paymentId: string;
926
+ amount?: number; // Optional for partial refund
927
+ reason: string;
928
+ }
929
+
930
+ async function processRefund(request: RefundRequest): Promise<Refund> {
931
+ // First, verify the payment exists and is refundable
932
+ const payment = await reevit.payments.get(request.paymentId);
933
+
934
+ if (payment.status !== 'succeeded') {
935
+ throw new Error(`Cannot refund payment with status: ${payment.status}`);
936
+ }
937
+
938
+ // Validate refund amount
939
+ const refundAmount = request.amount || payment.amount;
940
+ if (refundAmount > payment.amount) {
941
+ throw new Error(`Refund amount (${refundAmount}) exceeds payment amount (${payment.amount})`);
942
+ }
943
+
944
+ // Process the refund
945
+ const refund = await reevit.payments.refund(
946
+ request.paymentId,
947
+ request.amount,
948
+ request.reason
949
+ );
950
+
951
+ console.log(`Refund processed: ${refund.id}`);
952
+ console.log(` Amount: ${refund.amount}`);
953
+ console.log(` Status: ${refund.status}`);
954
+ console.log(` Reason: ${refund.reason}`);
955
+
956
+ return refund;
957
+ }
958
+
959
+ // Full refund
960
+ processRefund({
961
+ paymentId: 'pay_abc123',
962
+ reason: 'Customer requested cancellation'
963
+ });
964
+
965
+ // Partial refund
966
+ processRefund({
967
+ paymentId: 'pay_abc123',
968
+ amount: 2500, // Refund 25.00
969
+ reason: 'Partial order cancellation'
970
+ });
971
+ ```
972
+
973
+ ### Express.js Integration
974
+
975
+ ```typescript
976
+ import express from 'express';
977
+ import { Reevit, PaymentIntentRequest } from '@reevit/node';
978
+
979
+ const app = express();
980
+ app.use(express.json());
981
+
982
+ const reevit = new Reevit(
983
+ process.env.REEVIT_API_KEY!,
984
+ process.env.REEVIT_ORG_ID!,
985
+ process.env.REEVIT_BASE_URL
986
+ );
987
+
988
+ // Create payment endpoint
989
+ app.post('/api/payments', async (req, res) => {
990
+ try {
991
+ const { amount, currency, method, country, customerId, orderId } = req.body;
992
+
993
+ const payment = await reevit.payments.createIntent({
994
+ amount,
995
+ currency,
996
+ method,
997
+ country,
998
+ customer_id: customerId,
999
+ metadata: { order_id: orderId }
1000
+ });
1001
+
1002
+ res.json({
1003
+ success: true,
1004
+ payment: {
1005
+ id: payment.id,
1006
+ status: payment.status,
1007
+ provider: payment.provider
1008
+ }
1009
+ });
1010
+ } catch (error: any) {
1011
+ res.status(error.response?.status || 500).json({
1012
+ success: false,
1013
+ error: error.response?.data?.message || error.message
1014
+ });
1015
+ }
1016
+ });
1017
+
1018
+ // Get payment status endpoint
1019
+ app.get('/api/payments/:id', async (req, res) => {
1020
+ try {
1021
+ const payment = await reevit.payments.get(req.params.id);
1022
+ res.json({ success: true, payment });
1023
+ } catch (error: any) {
1024
+ res.status(error.response?.status || 500).json({
1025
+ success: false,
1026
+ error: error.response?.data?.message || error.message
1027
+ });
1028
+ }
1029
+ });
1030
+
1031
+ // Refund endpoint
1032
+ app.post('/api/payments/:id/refund', async (req, res) => {
1033
+ try {
1034
+ const { amount, reason } = req.body;
1035
+ const refund = await reevit.payments.refund(req.params.id, amount, reason);
1036
+ res.json({ success: true, refund });
1037
+ } catch (error: any) {
1038
+ res.status(error.response?.status || 500).json({
1039
+ success: false,
1040
+ error: error.response?.data?.message || error.message
1041
+ });
1042
+ }
1043
+ });
1044
+
1045
+ app.listen(3000, () => {
1046
+ console.log('Server running on port 3000');
1047
+ });
1048
+ ```
1049
+
1050
+ ### Next.js API Routes
1051
+
1052
+ ```typescript
1053
+ // pages/api/payments/create.ts
1054
+ import type { NextApiRequest, NextApiResponse } from 'next';
1055
+ import { Reevit } from '@reevit/node';
1056
+
1057
+ const reevit = new Reevit(
1058
+ process.env.REEVIT_API_KEY!,
1059
+ process.env.REEVIT_ORG_ID!,
1060
+ process.env.REEVIT_BASE_URL
1061
+ );
1062
+
1063
+ export default async function handler(
1064
+ req: NextApiRequest,
1065
+ res: NextApiResponse
1066
+ ) {
1067
+ if (req.method !== 'POST') {
1068
+ return res.status(405).json({ error: 'Method not allowed' });
1069
+ }
1070
+
1071
+ try {
1072
+ const { amount, currency, method, country, customerId } = req.body;
1073
+
1074
+ const payment = await reevit.payments.createIntent({
1075
+ amount,
1076
+ currency,
1077
+ method,
1078
+ country,
1079
+ customer_id: customerId
1080
+ });
1081
+
1082
+ res.status(200).json({ payment });
1083
+ } catch (error: any) {
1084
+ res.status(500).json({
1085
+ error: error.response?.data?.message || error.message
1086
+ });
1087
+ }
1088
+ }
1089
+ ```
1090
+
1091
+ ---
1092
+
1093
+ ## Webhook Verification
1094
+
1095
+ Reevit sends webhooks to notify your application of payment events. Always verify webhook signatures to ensure authenticity.
1096
+
1097
+ ### Understanding Webhooks
1098
+
1099
+ There are **two types of webhooks** in Reevit:
1100
+
1101
+ 1. **Inbound Webhooks (PSP → Reevit)**: Webhooks from payment providers to Reevit. You configure these in the PSP dashboard (e.g., Paystack). Reevit handles these automatically.
1102
+
1103
+ 2. **Outbound Webhooks (Reevit → Your App)**: Webhooks from Reevit to your application. You configure these in the Reevit Dashboard and create a handler in your app.
1104
+
1105
+ ### Signature Format
1106
+
1107
+ Reevit signs webhooks with HMAC-SHA256:
1108
+ - **Header**: `X-Reevit-Signature: sha256=<hex-signature>`
1109
+ - **Signature**: `HMAC-SHA256(request_body, signing_secret)`
1110
+
1111
+ ### Getting Your Signing Secret
1112
+
1113
+ 1. Go to **Reevit Dashboard > Developers > Webhooks**
1114
+ 2. Configure your webhook endpoint URL
1115
+ 3. Copy the signing secret (starts with `whsec_`)
1116
+ 4. Add to your environment: `REEVIT_WEBHOOK_SECRET=whsec_xxx...`
1117
+
1118
+ ### Next.js App Router Webhook Handler
1119
+
1120
+ ```typescript
1121
+ // app/api/webhooks/reevit/route.ts
1122
+ import { NextRequest, NextResponse } from "next/server";
1123
+ import crypto from "crypto";
1124
+
1125
+ // Webhook payload types
1126
+ interface PaymentData {
1127
+ id: string;
1128
+ status: string;
1129
+ amount: number;
1130
+ currency: string;
1131
+ provider: string;
1132
+ customer_id?: string;
1133
+ metadata?: Record<string, string>;
1134
+ }
1135
+
1136
+ interface SubscriptionData {
1137
+ id: string;
1138
+ customer_id: string;
1139
+ plan_id: string;
1140
+ status: string;
1141
+ amount: number;
1142
+ currency: string;
1143
+ interval: string;
1144
+ next_renewal_at?: string;
1145
+ }
1146
+
1147
+ interface WebhookPayload {
1148
+ id: string;
1149
+ type: string;
1150
+ org_id: string;
1151
+ created_at: string;
1152
+ data?: PaymentData | SubscriptionData;
1153
+ message?: string;
1154
+ }
1155
+
1156
+ /**
1157
+ * Verify webhook signature using HMAC-SHA256
1158
+ * Always verify signatures in production to prevent spoofed webhooks
1159
+ */
1160
+ function verifySignature(payload: string, signature: string, secret: string): boolean {
1161
+ if (!signature.startsWith("sha256=")) return false;
1162
+
1163
+ const expected = crypto
1164
+ .createHmac("sha256", secret)
1165
+ .update(payload)
1166
+ .digest("hex");
1167
+
1168
+ const received = signature.slice(7);
1169
+ if (received.length !== expected.length) return false;
1170
+
1171
+ return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
1172
+ }
1173
+
1174
+ export async function POST(request: NextRequest) {
1175
+ try {
1176
+ const rawBody = await request.text();
1177
+ const signature = request.headers.get("x-reevit-signature") || "";
1178
+ const secret = process.env.REEVIT_WEBHOOK_SECRET;
1179
+
1180
+ // Verify signature (required in production)
1181
+ if (secret && !verifySignature(rawBody, signature, secret)) {
1182
+ console.error("[Webhook] Invalid signature");
1183
+ return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
1184
+ }
1185
+
1186
+ const event: WebhookPayload = JSON.parse(rawBody);
1187
+ console.log(`[Webhook] Received: ${event.type} (${event.id})`);
1188
+
1189
+ // Handle different event types
1190
+ switch (event.type) {
1191
+ // Test event
1192
+ case "reevit.webhook.test":
1193
+ console.log("[Webhook] Test received:", event.message);
1194
+ break;
1195
+
1196
+ // Payment events
1197
+ case "payment.succeeded":
1198
+ await handlePaymentSucceeded(event.data as PaymentData);
1199
+ break;
1200
+
1201
+ case "payment.failed":
1202
+ await handlePaymentFailed(event.data as PaymentData);
1203
+ break;
1204
+
1205
+ case "payment.refunded":
1206
+ await handlePaymentRefunded(event.data as PaymentData);
1207
+ break;
1208
+
1209
+ case "payment.pending":
1210
+ console.log(`[Webhook] Payment pending: ${(event.data as PaymentData)?.id}`);
1211
+ break;
1212
+
1213
+ // Subscription events
1214
+ case "subscription.created":
1215
+ await handleSubscriptionCreated(event.data as SubscriptionData);
1216
+ break;
1217
+
1218
+ case "subscription.renewed":
1219
+ await handleSubscriptionRenewed(event.data as SubscriptionData);
1220
+ break;
1221
+
1222
+ case "subscription.canceled":
1223
+ await handleSubscriptionCanceled(event.data as SubscriptionData);
1224
+ break;
1225
+
1226
+ case "subscription.paused":
1227
+ console.log(`[Webhook] Subscription paused: ${(event.data as SubscriptionData)?.id}`);
1228
+ break;
1229
+
1230
+ default:
1231
+ console.log(`[Webhook] Unhandled event: ${event.type}`);
1232
+ }
1233
+
1234
+ return NextResponse.json({ received: true });
1235
+ } catch (error) {
1236
+ console.error("[Webhook] Processing error:", error);
1237
+ return NextResponse.json({ error: "Webhook processing failed" }, { status: 500 });
1238
+ }
1239
+ }
1240
+
1241
+ // Payment handlers
1242
+ async function handlePaymentSucceeded(data: PaymentData) {
1243
+ const orderId = data.metadata?.order_id;
1244
+ console.log(`[Webhook] Payment succeeded: ${data.id} for order ${orderId}`);
1245
+
1246
+ // TODO: Implement your business logic
1247
+ // - Update order status to "paid"
1248
+ // - Send confirmation email to customer
1249
+ // - Trigger fulfillment process
1250
+ // - Update inventory
1251
+ }
1252
+
1253
+ async function handlePaymentFailed(data: PaymentData) {
1254
+ console.log(`[Webhook] Payment failed: ${data.id}`);
1255
+
1256
+ // TODO: Implement your business logic
1257
+ // - Update order status to "payment_failed"
1258
+ // - Send notification to customer
1259
+ // - Allow retry
1260
+ }
1261
+
1262
+ async function handlePaymentRefunded(data: PaymentData) {
1263
+ const orderId = data.metadata?.order_id;
1264
+ console.log(`[Webhook] Payment refunded: ${data.id} for order ${orderId}`);
1265
+
1266
+ // TODO: Implement your business logic
1267
+ // - Update order status to "refunded"
1268
+ // - Restore inventory if applicable
1269
+ // - Send refund confirmation email
1270
+ }
1271
+
1272
+ // Subscription handlers
1273
+ async function handleSubscriptionCreated(data: SubscriptionData) {
1274
+ console.log(`[Webhook] Subscription created: ${data.id} for customer ${data.customer_id}`);
1275
+
1276
+ // TODO: Implement your business logic
1277
+ // - Grant access to subscription features
1278
+ // - Send welcome email
1279
+ }
1280
+
1281
+ async function handleSubscriptionRenewed(data: SubscriptionData) {
1282
+ console.log(`[Webhook] Subscription renewed: ${data.id}`);
1283
+
1284
+ // TODO: Implement your business logic
1285
+ // - Extend access period
1286
+ // - Send renewal confirmation
1287
+ }
1288
+
1289
+ async function handleSubscriptionCanceled(data: SubscriptionData) {
1290
+ console.log(`[Webhook] Subscription canceled: ${data.id}`);
1291
+
1292
+ // TODO: Implement your business logic
1293
+ // - Revoke access at end of billing period
1294
+ // - Send cancellation confirmation
1295
+ }
1296
+ ```
1297
+
1298
+ ### Express.js Webhook Handler
1299
+
1300
+ ```typescript
1301
+ import express from 'express';
1302
+ import crypto from 'crypto';
1303
+
1304
+ const app = express();
1305
+
1306
+ function verifySignature(payload: string, signature: string, secret: string): boolean {
1307
+ if (!signature.startsWith('sha256=')) return false;
1308
+
1309
+ const expected = crypto
1310
+ .createHmac('sha256', secret)
1311
+ .update(payload)
1312
+ .digest('hex');
1313
+
1314
+ const received = signature.slice(7);
1315
+ if (received.length !== expected.length) return false;
1316
+
1317
+ return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
1318
+ }
1319
+
1320
+ // IMPORTANT: Use raw body for signature verification
1321
+ app.post('/webhooks/reevit', express.raw({ type: 'application/json' }), async (req, res) => {
1322
+ try {
1323
+ const signature = req.headers['x-reevit-signature'] as string;
1324
+ const payload = req.body.toString();
1325
+ const secret = process.env.REEVIT_WEBHOOK_SECRET!;
1326
+
1327
+ if (secret && !verifySignature(payload, signature, secret)) {
1328
+ return res.status(401).json({ error: 'Invalid signature' });
1329
+ }
1330
+
1331
+ const event = JSON.parse(payload);
1332
+ console.log(`[Webhook] Received: ${event.type}`);
1333
+
1334
+ switch (event.type) {
1335
+ case 'reevit.webhook.test':
1336
+ console.log('[Webhook] Test received:', event.message);
1337
+ break;
1338
+ case 'payment.succeeded':
1339
+ // Fulfill order, send confirmation email
1340
+ break;
1341
+ case 'payment.failed':
1342
+ // Notify customer, allow retry
1343
+ break;
1344
+ case 'payment.refunded':
1345
+ // Update order status
1346
+ break;
1347
+ case 'subscription.renewed':
1348
+ // Extend access
1349
+ break;
1350
+ case 'subscription.canceled':
1351
+ // Revoke access
1352
+ break;
1353
+ }
1354
+
1355
+ res.status(200).json({ received: true });
1356
+ } catch (error) {
1357
+ console.error('[Webhook] Error:', error);
1358
+ res.status(500).json({ error: 'Webhook processing failed' });
1359
+ }
1360
+ });
1361
+
1362
+ app.listen(3000, () => console.log('Server running on port 3000'));
1363
+ ```
1364
+
1365
+ ### Environment Variables
1366
+
1367
+ ```bash
1368
+ # .env.local
1369
+ REEVIT_API_KEY=pfk_live_xxx
1370
+ REEVIT_ORG_ID=org_xxx
1371
+ REEVIT_WEBHOOK_SECRET=whsec_xxx # Get from Dashboard > Developers > Webhooks
1372
+ ```
1373
+
1374
+ ---
1375
+
1376
+ ## Support
1377
+
1378
+ - **Documentation**: [https://docs.reevit.io](https://docs.reevit.io)
1379
+ - **API Reference**: [https://api.reevit.io/docs](https://api.reevit.io/docs)
1380
+ - **GitHub Issues**: [https://github.com/reevit/reevit-node/issues](https://github.com/reevit/reevit-node/issues)
1381
+ - **Email**: support@reevit.io
1382
+
1383
+ ## License
1384
+
1385
+ MIT License - see [LICENSE](LICENSE) for details.