@raaamie/pay 1.0.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 +178 -0
- package/dist/core.d.ts +19 -0
- package/dist/index.d.ts +48 -0
- package/dist/raamiepay.cjs.js +244 -0
- package/dist/raamiepay.esm.js +237 -0
- package/dist/raamiepay.min.js +1 -0
- package/dist/types.d.ts +60 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# RaaamiePay JavaScript SDK
|
|
2
|
+
|
|
3
|
+
The official JavaScript SDK for integrating RaaamiePay payment gateway into any website or web application.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### NPM / Yarn
|
|
8
|
+
```bash
|
|
9
|
+
npm install @raaami/pay
|
|
10
|
+
# or
|
|
11
|
+
yarn add @raaami/pay
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### CDN (Script Tag)
|
|
15
|
+
```html
|
|
16
|
+
<script src="https://pay.raaami.com/sdk/v1/inline.js"></script>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Popup Mode (Recommended)
|
|
24
|
+
|
|
25
|
+
Payment happens in an overlay without leaving your site.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import RaaamiePay from '@raaami/pay';
|
|
29
|
+
|
|
30
|
+
document.getElementById('pay-btn').addEventListener('click', () => {
|
|
31
|
+
RaaamiePay.popup({
|
|
32
|
+
key: 'pk_live_xxxxx',
|
|
33
|
+
amount: 15000, // GHS 150.00 (in pesewas)
|
|
34
|
+
currency: 'GHS',
|
|
35
|
+
customer: {
|
|
36
|
+
email: 'buyer@example.com',
|
|
37
|
+
phone: '024XXXXXXX',
|
|
38
|
+
name: 'Kofi Mensah',
|
|
39
|
+
},
|
|
40
|
+
description: 'Order #1234',
|
|
41
|
+
channels: ['momo', 'card', 'bank'],
|
|
42
|
+
metadata: { order_id: 'ORD-1234' },
|
|
43
|
+
onSuccess(response) {
|
|
44
|
+
console.log('Payment successful!', response.reference);
|
|
45
|
+
// Verify on your server using the reference
|
|
46
|
+
},
|
|
47
|
+
onClose() {
|
|
48
|
+
console.log('Customer closed the checkout');
|
|
49
|
+
},
|
|
50
|
+
onError(error) {
|
|
51
|
+
console.error('Payment failed:', error.message);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Redirect Mode
|
|
58
|
+
|
|
59
|
+
Customer is sent to the hosted checkout page. They return to your `redirectUrl` after payment.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import RaaamiePay from '@raaami/pay';
|
|
63
|
+
|
|
64
|
+
RaaamiePay.redirect({
|
|
65
|
+
key: 'pk_live_xxxxx',
|
|
66
|
+
amount: 15000,
|
|
67
|
+
currency: 'GHS',
|
|
68
|
+
customer: { email: 'buyer@example.com' },
|
|
69
|
+
redirectUrl: 'https://yoursite.com/payment/verify',
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Deferred Open
|
|
74
|
+
|
|
75
|
+
Create the instance first, open later.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const payment = RaaamiePay.create({
|
|
79
|
+
key: 'pk_live_xxxxx',
|
|
80
|
+
amount: 15000,
|
|
81
|
+
customer: { email: 'buyer@example.com' },
|
|
82
|
+
onSuccess(res) { /* ... */ },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Open when user clicks
|
|
86
|
+
document.getElementById('pay-btn').onclick = () => payment.open();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CDN / Script Tag Usage
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<script src="https://pay.raaami.com/sdk/v1/inline.js"></script>
|
|
93
|
+
<script>
|
|
94
|
+
function payWithRaaami() {
|
|
95
|
+
RaaamiePay.popup({
|
|
96
|
+
key: 'pk_live_xxxxx',
|
|
97
|
+
amount: 15000,
|
|
98
|
+
currency: 'GHS',
|
|
99
|
+
customer: { email: 'buyer@example.com' },
|
|
100
|
+
onSuccess: function(response) {
|
|
101
|
+
alert('Payment complete! Ref: ' + response.reference);
|
|
102
|
+
},
|
|
103
|
+
onClose: function() {
|
|
104
|
+
alert('Checkout closed');
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<button onclick="payWithRaaami()">Pay GHS 150.00</button>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
| Parameter | Type | Required | Description |
|
|
118
|
+
|-----------|------|----------|-------------|
|
|
119
|
+
| `key` | `string` | ✅ | Your public key (`pk_live_xxx` or `pk_test_xxx`) |
|
|
120
|
+
| `amount` | `number` | ✅ | Amount in smallest unit (pesewas). 15000 = GHS 150.00 |
|
|
121
|
+
| `currency` | `string` | No | ISO currency code. Default: `'GHS'` |
|
|
122
|
+
| `reference` | `string` | No | Unique transaction ref. Auto-generated if omitted |
|
|
123
|
+
| `customer` | `object` | No | `{ email, phone, name }` — at least email or phone |
|
|
124
|
+
| `description` | `string` | No | Payment description shown on checkout |
|
|
125
|
+
| `channels` | `array` | No | Restrict payment methods: `['momo', 'card', 'bank', 'ussd', 'qr']` |
|
|
126
|
+
| `metadata` | `object` | No | Custom key-value data sent to your webhook |
|
|
127
|
+
| `redirectUrl` | `string` | No | Override redirect URL for this transaction |
|
|
128
|
+
| `onSuccess` | `function` | No | Called with response when payment succeeds |
|
|
129
|
+
| `onClose` | `function` | No | Called when customer closes the popup |
|
|
130
|
+
| `onError` | `function` | No | Called with error details on failure |
|
|
131
|
+
| `onLoad` | `function` | No | Called when checkout iframe is ready |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Response Object (onSuccess)
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
{
|
|
139
|
+
reference: string; // 'RPY-LX1K2M-A8B3C4D5'
|
|
140
|
+
transactionId: string; // 'TXN-xxxxxxxxxxxx'
|
|
141
|
+
status: 'success';
|
|
142
|
+
channel: 'momo' | 'card' | 'bank' | 'ussd' | 'qr';
|
|
143
|
+
amount: number; // 15000
|
|
144
|
+
currency: string; // 'GHS'
|
|
145
|
+
customer: { email, phone, name };
|
|
146
|
+
metadata?: object;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Verify Payment (Server-side)
|
|
153
|
+
|
|
154
|
+
Always verify on your backend. Never trust client-side callbacks alone.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
GET https://api.raaami.com/v1/transactions/:reference/verify
|
|
158
|
+
Authorization: Bearer sk_live_xxxxx
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Test Mode
|
|
164
|
+
|
|
165
|
+
Use `pk_test_xxxxx` to test without real charges.
|
|
166
|
+
|
|
167
|
+
Test card: `4084 0840 8408 4081` | Expiry: `12/30` | CVV: `408`
|
|
168
|
+
Test MoMo: Any phone number will simulate a successful prompt.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Browser Support
|
|
173
|
+
|
|
174
|
+
Chrome 60+, Firefox 55+, Safari 12+, Edge 79+, iOS Safari 12+, Android Chrome 60+
|
|
175
|
+
|
|
176
|
+
## Bundle Size
|
|
177
|
+
|
|
178
|
+
~3KB minified + gzipped. Zero dependencies.
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RaaaamiePayConfig } from './types';
|
|
2
|
+
export declare class RaaaamiePayPopup {
|
|
3
|
+
private config;
|
|
4
|
+
private reference;
|
|
5
|
+
private iframe;
|
|
6
|
+
private backdrop;
|
|
7
|
+
private messageHandler;
|
|
8
|
+
private isOpen;
|
|
9
|
+
constructor(config: RaaaamiePayConfig);
|
|
10
|
+
open(): void;
|
|
11
|
+
close(): void;
|
|
12
|
+
private handleMessage;
|
|
13
|
+
getReference(): string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Redirect mode — navigates the full browser to the hosted gateway page.
|
|
17
|
+
* Use when you don't want an iframe/popup.
|
|
18
|
+
*/
|
|
19
|
+
export declare function redirect(config: RaaaamiePayConfig): void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RaaaamiePayPopup, redirect } from './core';
|
|
2
|
+
import type { RaaaamiePayConfig, RaaaamiePayResponse, RaaaamiePayError, PaymentChannel, RaaaamiePayCustomer } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* RaaamiePay JavaScript SDK
|
|
5
|
+
*
|
|
6
|
+
* @example Popup mode
|
|
7
|
+
* ```js
|
|
8
|
+
* const payment = RaaamiePay.popup({
|
|
9
|
+
* key: 'pk_live_xxxxx',
|
|
10
|
+
* amount: 15000,
|
|
11
|
+
* currency: 'GHS',
|
|
12
|
+
* customer: { email: 'buyer@example.com' },
|
|
13
|
+
* onSuccess(response) { console.log('Paid!', response.reference); },
|
|
14
|
+
* onClose() { console.log('User closed checkout'); },
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example Redirect mode
|
|
19
|
+
* ```js
|
|
20
|
+
* RaaamiePay.redirect({
|
|
21
|
+
* key: 'pk_live_xxxxx',
|
|
22
|
+
* amount: 15000,
|
|
23
|
+
* currency: 'GHS',
|
|
24
|
+
* customer: { email: 'buyer@example.com' },
|
|
25
|
+
* redirectUrl: 'https://yoursite.com/payment/complete',
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare const RaaamiePay: {
|
|
30
|
+
/**
|
|
31
|
+
* Open payment checkout in a secure popup overlay.
|
|
32
|
+
* Returns the popup instance for manual control.
|
|
33
|
+
*/
|
|
34
|
+
popup(config: RaaaamiePayConfig): RaaaamiePayPopup;
|
|
35
|
+
/**
|
|
36
|
+
* Redirect the customer's browser to the hosted checkout page.
|
|
37
|
+
* Customer returns to your redirectUrl after payment.
|
|
38
|
+
*/
|
|
39
|
+
redirect(config: RaaaamiePayConfig): void;
|
|
40
|
+
/**
|
|
41
|
+
* Create a popup instance without immediately opening it.
|
|
42
|
+
* Call instance.open() when ready.
|
|
43
|
+
*/
|
|
44
|
+
create(config: RaaaamiePayConfig): RaaaamiePayPopup;
|
|
45
|
+
};
|
|
46
|
+
export default RaaamiePay;
|
|
47
|
+
export { RaaamiePay, RaaaamiePayPopup, redirect };
|
|
48
|
+
export type { RaaaamiePayConfig, RaaaamiePayResponse, RaaaamiePayError, PaymentChannel, RaaaamiePayCustomer };
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const GATEWAY_URL = 'https://pay.raaami.com';
|
|
6
|
+
const SDK_VERSION = '1.0.0';
|
|
7
|
+
function generateReference() {
|
|
8
|
+
const timestamp = Date.now().toString(36);
|
|
9
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
10
|
+
return `RPY-${timestamp}-${random}`.toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
function validateConfig(config) {
|
|
13
|
+
if (!config.key)
|
|
14
|
+
throw new Error('[RaaamiePay] "key" is required');
|
|
15
|
+
if (!config.key.startsWith('pk_'))
|
|
16
|
+
throw new Error('[RaaamiePay] Invalid public key. Must start with "pk_"');
|
|
17
|
+
if (!config.amount || config.amount <= 0)
|
|
18
|
+
throw new Error('[RaaamiePay] "amount" must be greater than 0');
|
|
19
|
+
if (config.customer && !config.customer.email && !config.customer.phone) {
|
|
20
|
+
throw new Error('[RaaamiePay] Customer must have at least "email" or "phone"');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function buildCheckoutUrl(config, reference) {
|
|
24
|
+
var _a, _b, _c, _d;
|
|
25
|
+
const params = new URLSearchParams({
|
|
26
|
+
key: config.key,
|
|
27
|
+
amount: String(config.amount),
|
|
28
|
+
currency: config.currency || 'GHS',
|
|
29
|
+
reference,
|
|
30
|
+
sdk_version: SDK_VERSION,
|
|
31
|
+
mode: 'popup',
|
|
32
|
+
});
|
|
33
|
+
if ((_a = config.customer) === null || _a === void 0 ? void 0 : _a.email)
|
|
34
|
+
params.set('email', config.customer.email);
|
|
35
|
+
if ((_b = config.customer) === null || _b === void 0 ? void 0 : _b.phone)
|
|
36
|
+
params.set('phone', config.customer.phone);
|
|
37
|
+
if ((_c = config.customer) === null || _c === void 0 ? void 0 : _c.name)
|
|
38
|
+
params.set('name', config.customer.name);
|
|
39
|
+
if (config.description)
|
|
40
|
+
params.set('description', config.description);
|
|
41
|
+
if ((_d = config.channels) === null || _d === void 0 ? void 0 : _d.length)
|
|
42
|
+
params.set('channels', config.channels.join(','));
|
|
43
|
+
if (config.metadata)
|
|
44
|
+
params.set('metadata', btoa(JSON.stringify(config.metadata)));
|
|
45
|
+
if (config.redirectUrl)
|
|
46
|
+
params.set('redirect_url', config.redirectUrl);
|
|
47
|
+
return `${GATEWAY_URL}/pay/${reference}?${params.toString()}`;
|
|
48
|
+
}
|
|
49
|
+
function createIframeStyles() {
|
|
50
|
+
return [
|
|
51
|
+
'position:fixed',
|
|
52
|
+
'top:0',
|
|
53
|
+
'left:0',
|
|
54
|
+
'width:100%',
|
|
55
|
+
'height:100%',
|
|
56
|
+
'z-index:2147483647',
|
|
57
|
+
'border:none',
|
|
58
|
+
'opacity:0',
|
|
59
|
+
'transition:opacity 0.3s ease',
|
|
60
|
+
].join(';');
|
|
61
|
+
}
|
|
62
|
+
function createBackdropStyles() {
|
|
63
|
+
return [
|
|
64
|
+
'position:fixed',
|
|
65
|
+
'top:0',
|
|
66
|
+
'left:0',
|
|
67
|
+
'width:100%',
|
|
68
|
+
'height:100%',
|
|
69
|
+
'z-index:2147483646',
|
|
70
|
+
'background:rgba(0,0,0,0.6)',
|
|
71
|
+
'backdrop-filter:blur(4px)',
|
|
72
|
+
'-webkit-backdrop-filter:blur(4px)',
|
|
73
|
+
'opacity:0',
|
|
74
|
+
'transition:opacity 0.3s ease',
|
|
75
|
+
].join(';');
|
|
76
|
+
}
|
|
77
|
+
class RaaaamiePayPopup {
|
|
78
|
+
constructor(config) {
|
|
79
|
+
this.iframe = null;
|
|
80
|
+
this.backdrop = null;
|
|
81
|
+
this.messageHandler = null;
|
|
82
|
+
this.isOpen = false;
|
|
83
|
+
validateConfig(config);
|
|
84
|
+
this.config = config;
|
|
85
|
+
this.reference = config.reference || generateReference();
|
|
86
|
+
}
|
|
87
|
+
open() {
|
|
88
|
+
if (this.isOpen)
|
|
89
|
+
return;
|
|
90
|
+
this.isOpen = true;
|
|
91
|
+
// Prevent body scroll
|
|
92
|
+
document.body.style.overflow = 'hidden';
|
|
93
|
+
// Create backdrop
|
|
94
|
+
this.backdrop = document.createElement('div');
|
|
95
|
+
this.backdrop.setAttribute('id', 'raamiepay-backdrop');
|
|
96
|
+
this.backdrop.setAttribute('style', createBackdropStyles());
|
|
97
|
+
this.backdrop.addEventListener('click', () => this.close());
|
|
98
|
+
document.body.appendChild(this.backdrop);
|
|
99
|
+
// Create iframe
|
|
100
|
+
const url = buildCheckoutUrl(this.config, this.reference);
|
|
101
|
+
this.iframe = document.createElement('iframe');
|
|
102
|
+
this.iframe.setAttribute('id', 'raamiepay-checkout');
|
|
103
|
+
this.iframe.setAttribute('src', url);
|
|
104
|
+
this.iframe.setAttribute('style', createIframeStyles());
|
|
105
|
+
this.iframe.setAttribute('allow', 'payment');
|
|
106
|
+
this.iframe.setAttribute('sandbox', 'allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation');
|
|
107
|
+
this.iframe.setAttribute('title', 'RaaamiePay Secure Checkout');
|
|
108
|
+
this.iframe.setAttribute('aria-label', 'Payment checkout');
|
|
109
|
+
document.body.appendChild(this.iframe);
|
|
110
|
+
// Fade in
|
|
111
|
+
requestAnimationFrame(() => {
|
|
112
|
+
if (this.backdrop)
|
|
113
|
+
this.backdrop.style.opacity = '1';
|
|
114
|
+
if (this.iframe)
|
|
115
|
+
this.iframe.style.opacity = '1';
|
|
116
|
+
});
|
|
117
|
+
// Listen for messages from gateway
|
|
118
|
+
this.messageHandler = (event) => this.handleMessage(event);
|
|
119
|
+
window.addEventListener('message', this.messageHandler);
|
|
120
|
+
}
|
|
121
|
+
close() {
|
|
122
|
+
var _a, _b;
|
|
123
|
+
if (!this.isOpen)
|
|
124
|
+
return;
|
|
125
|
+
this.isOpen = false;
|
|
126
|
+
// Fade out then remove
|
|
127
|
+
if (this.backdrop)
|
|
128
|
+
this.backdrop.style.opacity = '0';
|
|
129
|
+
if (this.iframe)
|
|
130
|
+
this.iframe.style.opacity = '0';
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
if (this.iframe) {
|
|
133
|
+
this.iframe.remove();
|
|
134
|
+
this.iframe = null;
|
|
135
|
+
}
|
|
136
|
+
if (this.backdrop) {
|
|
137
|
+
this.backdrop.remove();
|
|
138
|
+
this.backdrop = null;
|
|
139
|
+
}
|
|
140
|
+
document.body.style.overflow = '';
|
|
141
|
+
}, 300);
|
|
142
|
+
// Clean up listener
|
|
143
|
+
if (this.messageHandler) {
|
|
144
|
+
window.removeEventListener('message', this.messageHandler);
|
|
145
|
+
this.messageHandler = null;
|
|
146
|
+
}
|
|
147
|
+
(_b = (_a = this.config).onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
148
|
+
}
|
|
149
|
+
handleMessage(event) {
|
|
150
|
+
var _a, _b, _c, _d, _e, _f;
|
|
151
|
+
// Only accept messages from our gateway
|
|
152
|
+
if (!event.origin.includes('raaami.com') && !event.origin.includes('localhost'))
|
|
153
|
+
return;
|
|
154
|
+
const payload = event.data;
|
|
155
|
+
if (!(payload === null || payload === void 0 ? void 0 : payload.type))
|
|
156
|
+
return;
|
|
157
|
+
switch (payload.type) {
|
|
158
|
+
case 'payment.success':
|
|
159
|
+
(_b = (_a = this.config).onSuccess) === null || _b === void 0 ? void 0 : _b.call(_a, payload.data);
|
|
160
|
+
this.close();
|
|
161
|
+
break;
|
|
162
|
+
case 'payment.failed':
|
|
163
|
+
(_d = (_c = this.config).onError) === null || _d === void 0 ? void 0 : _d.call(_c, payload.data);
|
|
164
|
+
this.close();
|
|
165
|
+
break;
|
|
166
|
+
case 'payment.close':
|
|
167
|
+
this.close();
|
|
168
|
+
break;
|
|
169
|
+
case 'payment.loaded':
|
|
170
|
+
(_f = (_e = this.config).onLoad) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
getReference() {
|
|
175
|
+
return this.reference;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Redirect mode — navigates the full browser to the hosted gateway page.
|
|
180
|
+
* Use when you don't want an iframe/popup.
|
|
181
|
+
*/
|
|
182
|
+
function redirect(config) {
|
|
183
|
+
validateConfig(config);
|
|
184
|
+
const reference = config.reference || generateReference();
|
|
185
|
+
const url = buildCheckoutUrl(config, reference);
|
|
186
|
+
window.location.href = url.replace('mode=popup', 'mode=redirect');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* RaaamiePay JavaScript SDK
|
|
191
|
+
*
|
|
192
|
+
* @example Popup mode
|
|
193
|
+
* ```js
|
|
194
|
+
* const payment = RaaamiePay.popup({
|
|
195
|
+
* key: 'pk_live_xxxxx',
|
|
196
|
+
* amount: 15000,
|
|
197
|
+
* currency: 'GHS',
|
|
198
|
+
* customer: { email: 'buyer@example.com' },
|
|
199
|
+
* onSuccess(response) { console.log('Paid!', response.reference); },
|
|
200
|
+
* onClose() { console.log('User closed checkout'); },
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @example Redirect mode
|
|
205
|
+
* ```js
|
|
206
|
+
* RaaamiePay.redirect({
|
|
207
|
+
* key: 'pk_live_xxxxx',
|
|
208
|
+
* amount: 15000,
|
|
209
|
+
* currency: 'GHS',
|
|
210
|
+
* customer: { email: 'buyer@example.com' },
|
|
211
|
+
* redirectUrl: 'https://yoursite.com/payment/complete',
|
|
212
|
+
* });
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
const RaaamiePay = {
|
|
216
|
+
/**
|
|
217
|
+
* Open payment checkout in a secure popup overlay.
|
|
218
|
+
* Returns the popup instance for manual control.
|
|
219
|
+
*/
|
|
220
|
+
popup(config) {
|
|
221
|
+
const instance = new RaaaamiePayPopup(config);
|
|
222
|
+
instance.open();
|
|
223
|
+
return instance;
|
|
224
|
+
},
|
|
225
|
+
/**
|
|
226
|
+
* Redirect the customer's browser to the hosted checkout page.
|
|
227
|
+
* Customer returns to your redirectUrl after payment.
|
|
228
|
+
*/
|
|
229
|
+
redirect(config) {
|
|
230
|
+
redirect(config);
|
|
231
|
+
},
|
|
232
|
+
/**
|
|
233
|
+
* Create a popup instance without immediately opening it.
|
|
234
|
+
* Call instance.open() when ready.
|
|
235
|
+
*/
|
|
236
|
+
create(config) {
|
|
237
|
+
return new RaaaamiePayPopup(config);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
exports.RaaaamiePayPopup = RaaaamiePayPopup;
|
|
242
|
+
exports.RaaamiePay = RaaamiePay;
|
|
243
|
+
exports.default = RaaamiePay;
|
|
244
|
+
exports.redirect = redirect;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
const GATEWAY_URL = 'https://pay.raaami.com';
|
|
2
|
+
const SDK_VERSION = '1.0.0';
|
|
3
|
+
function generateReference() {
|
|
4
|
+
const timestamp = Date.now().toString(36);
|
|
5
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
6
|
+
return `RPY-${timestamp}-${random}`.toUpperCase();
|
|
7
|
+
}
|
|
8
|
+
function validateConfig(config) {
|
|
9
|
+
if (!config.key)
|
|
10
|
+
throw new Error('[RaaamiePay] "key" is required');
|
|
11
|
+
if (!config.key.startsWith('pk_'))
|
|
12
|
+
throw new Error('[RaaamiePay] Invalid public key. Must start with "pk_"');
|
|
13
|
+
if (!config.amount || config.amount <= 0)
|
|
14
|
+
throw new Error('[RaaamiePay] "amount" must be greater than 0');
|
|
15
|
+
if (config.customer && !config.customer.email && !config.customer.phone) {
|
|
16
|
+
throw new Error('[RaaamiePay] Customer must have at least "email" or "phone"');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function buildCheckoutUrl(config, reference) {
|
|
20
|
+
var _a, _b, _c, _d;
|
|
21
|
+
const params = new URLSearchParams({
|
|
22
|
+
key: config.key,
|
|
23
|
+
amount: String(config.amount),
|
|
24
|
+
currency: config.currency || 'GHS',
|
|
25
|
+
reference,
|
|
26
|
+
sdk_version: SDK_VERSION,
|
|
27
|
+
mode: 'popup',
|
|
28
|
+
});
|
|
29
|
+
if ((_a = config.customer) === null || _a === void 0 ? void 0 : _a.email)
|
|
30
|
+
params.set('email', config.customer.email);
|
|
31
|
+
if ((_b = config.customer) === null || _b === void 0 ? void 0 : _b.phone)
|
|
32
|
+
params.set('phone', config.customer.phone);
|
|
33
|
+
if ((_c = config.customer) === null || _c === void 0 ? void 0 : _c.name)
|
|
34
|
+
params.set('name', config.customer.name);
|
|
35
|
+
if (config.description)
|
|
36
|
+
params.set('description', config.description);
|
|
37
|
+
if ((_d = config.channels) === null || _d === void 0 ? void 0 : _d.length)
|
|
38
|
+
params.set('channels', config.channels.join(','));
|
|
39
|
+
if (config.metadata)
|
|
40
|
+
params.set('metadata', btoa(JSON.stringify(config.metadata)));
|
|
41
|
+
if (config.redirectUrl)
|
|
42
|
+
params.set('redirect_url', config.redirectUrl);
|
|
43
|
+
return `${GATEWAY_URL}/pay/${reference}?${params.toString()}`;
|
|
44
|
+
}
|
|
45
|
+
function createIframeStyles() {
|
|
46
|
+
return [
|
|
47
|
+
'position:fixed',
|
|
48
|
+
'top:0',
|
|
49
|
+
'left:0',
|
|
50
|
+
'width:100%',
|
|
51
|
+
'height:100%',
|
|
52
|
+
'z-index:2147483647',
|
|
53
|
+
'border:none',
|
|
54
|
+
'opacity:0',
|
|
55
|
+
'transition:opacity 0.3s ease',
|
|
56
|
+
].join(';');
|
|
57
|
+
}
|
|
58
|
+
function createBackdropStyles() {
|
|
59
|
+
return [
|
|
60
|
+
'position:fixed',
|
|
61
|
+
'top:0',
|
|
62
|
+
'left:0',
|
|
63
|
+
'width:100%',
|
|
64
|
+
'height:100%',
|
|
65
|
+
'z-index:2147483646',
|
|
66
|
+
'background:rgba(0,0,0,0.6)',
|
|
67
|
+
'backdrop-filter:blur(4px)',
|
|
68
|
+
'-webkit-backdrop-filter:blur(4px)',
|
|
69
|
+
'opacity:0',
|
|
70
|
+
'transition:opacity 0.3s ease',
|
|
71
|
+
].join(';');
|
|
72
|
+
}
|
|
73
|
+
class RaaaamiePayPopup {
|
|
74
|
+
constructor(config) {
|
|
75
|
+
this.iframe = null;
|
|
76
|
+
this.backdrop = null;
|
|
77
|
+
this.messageHandler = null;
|
|
78
|
+
this.isOpen = false;
|
|
79
|
+
validateConfig(config);
|
|
80
|
+
this.config = config;
|
|
81
|
+
this.reference = config.reference || generateReference();
|
|
82
|
+
}
|
|
83
|
+
open() {
|
|
84
|
+
if (this.isOpen)
|
|
85
|
+
return;
|
|
86
|
+
this.isOpen = true;
|
|
87
|
+
// Prevent body scroll
|
|
88
|
+
document.body.style.overflow = 'hidden';
|
|
89
|
+
// Create backdrop
|
|
90
|
+
this.backdrop = document.createElement('div');
|
|
91
|
+
this.backdrop.setAttribute('id', 'raamiepay-backdrop');
|
|
92
|
+
this.backdrop.setAttribute('style', createBackdropStyles());
|
|
93
|
+
this.backdrop.addEventListener('click', () => this.close());
|
|
94
|
+
document.body.appendChild(this.backdrop);
|
|
95
|
+
// Create iframe
|
|
96
|
+
const url = buildCheckoutUrl(this.config, this.reference);
|
|
97
|
+
this.iframe = document.createElement('iframe');
|
|
98
|
+
this.iframe.setAttribute('id', 'raamiepay-checkout');
|
|
99
|
+
this.iframe.setAttribute('src', url);
|
|
100
|
+
this.iframe.setAttribute('style', createIframeStyles());
|
|
101
|
+
this.iframe.setAttribute('allow', 'payment');
|
|
102
|
+
this.iframe.setAttribute('sandbox', 'allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation');
|
|
103
|
+
this.iframe.setAttribute('title', 'RaaamiePay Secure Checkout');
|
|
104
|
+
this.iframe.setAttribute('aria-label', 'Payment checkout');
|
|
105
|
+
document.body.appendChild(this.iframe);
|
|
106
|
+
// Fade in
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
if (this.backdrop)
|
|
109
|
+
this.backdrop.style.opacity = '1';
|
|
110
|
+
if (this.iframe)
|
|
111
|
+
this.iframe.style.opacity = '1';
|
|
112
|
+
});
|
|
113
|
+
// Listen for messages from gateway
|
|
114
|
+
this.messageHandler = (event) => this.handleMessage(event);
|
|
115
|
+
window.addEventListener('message', this.messageHandler);
|
|
116
|
+
}
|
|
117
|
+
close() {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
if (!this.isOpen)
|
|
120
|
+
return;
|
|
121
|
+
this.isOpen = false;
|
|
122
|
+
// Fade out then remove
|
|
123
|
+
if (this.backdrop)
|
|
124
|
+
this.backdrop.style.opacity = '0';
|
|
125
|
+
if (this.iframe)
|
|
126
|
+
this.iframe.style.opacity = '0';
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
if (this.iframe) {
|
|
129
|
+
this.iframe.remove();
|
|
130
|
+
this.iframe = null;
|
|
131
|
+
}
|
|
132
|
+
if (this.backdrop) {
|
|
133
|
+
this.backdrop.remove();
|
|
134
|
+
this.backdrop = null;
|
|
135
|
+
}
|
|
136
|
+
document.body.style.overflow = '';
|
|
137
|
+
}, 300);
|
|
138
|
+
// Clean up listener
|
|
139
|
+
if (this.messageHandler) {
|
|
140
|
+
window.removeEventListener('message', this.messageHandler);
|
|
141
|
+
this.messageHandler = null;
|
|
142
|
+
}
|
|
143
|
+
(_b = (_a = this.config).onClose) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
144
|
+
}
|
|
145
|
+
handleMessage(event) {
|
|
146
|
+
var _a, _b, _c, _d, _e, _f;
|
|
147
|
+
// Only accept messages from our gateway
|
|
148
|
+
if (!event.origin.includes('raaami.com') && !event.origin.includes('localhost'))
|
|
149
|
+
return;
|
|
150
|
+
const payload = event.data;
|
|
151
|
+
if (!(payload === null || payload === void 0 ? void 0 : payload.type))
|
|
152
|
+
return;
|
|
153
|
+
switch (payload.type) {
|
|
154
|
+
case 'payment.success':
|
|
155
|
+
(_b = (_a = this.config).onSuccess) === null || _b === void 0 ? void 0 : _b.call(_a, payload.data);
|
|
156
|
+
this.close();
|
|
157
|
+
break;
|
|
158
|
+
case 'payment.failed':
|
|
159
|
+
(_d = (_c = this.config).onError) === null || _d === void 0 ? void 0 : _d.call(_c, payload.data);
|
|
160
|
+
this.close();
|
|
161
|
+
break;
|
|
162
|
+
case 'payment.close':
|
|
163
|
+
this.close();
|
|
164
|
+
break;
|
|
165
|
+
case 'payment.loaded':
|
|
166
|
+
(_f = (_e = this.config).onLoad) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
getReference() {
|
|
171
|
+
return this.reference;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Redirect mode — navigates the full browser to the hosted gateway page.
|
|
176
|
+
* Use when you don't want an iframe/popup.
|
|
177
|
+
*/
|
|
178
|
+
function redirect(config) {
|
|
179
|
+
validateConfig(config);
|
|
180
|
+
const reference = config.reference || generateReference();
|
|
181
|
+
const url = buildCheckoutUrl(config, reference);
|
|
182
|
+
window.location.href = url.replace('mode=popup', 'mode=redirect');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* RaaamiePay JavaScript SDK
|
|
187
|
+
*
|
|
188
|
+
* @example Popup mode
|
|
189
|
+
* ```js
|
|
190
|
+
* const payment = RaaamiePay.popup({
|
|
191
|
+
* key: 'pk_live_xxxxx',
|
|
192
|
+
* amount: 15000,
|
|
193
|
+
* currency: 'GHS',
|
|
194
|
+
* customer: { email: 'buyer@example.com' },
|
|
195
|
+
* onSuccess(response) { console.log('Paid!', response.reference); },
|
|
196
|
+
* onClose() { console.log('User closed checkout'); },
|
|
197
|
+
* });
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example Redirect mode
|
|
201
|
+
* ```js
|
|
202
|
+
* RaaamiePay.redirect({
|
|
203
|
+
* key: 'pk_live_xxxxx',
|
|
204
|
+
* amount: 15000,
|
|
205
|
+
* currency: 'GHS',
|
|
206
|
+
* customer: { email: 'buyer@example.com' },
|
|
207
|
+
* redirectUrl: 'https://yoursite.com/payment/complete',
|
|
208
|
+
* });
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
const RaaamiePay = {
|
|
212
|
+
/**
|
|
213
|
+
* Open payment checkout in a secure popup overlay.
|
|
214
|
+
* Returns the popup instance for manual control.
|
|
215
|
+
*/
|
|
216
|
+
popup(config) {
|
|
217
|
+
const instance = new RaaaamiePayPopup(config);
|
|
218
|
+
instance.open();
|
|
219
|
+
return instance;
|
|
220
|
+
},
|
|
221
|
+
/**
|
|
222
|
+
* Redirect the customer's browser to the hosted checkout page.
|
|
223
|
+
* Customer returns to your redirectUrl after payment.
|
|
224
|
+
*/
|
|
225
|
+
redirect(config) {
|
|
226
|
+
redirect(config);
|
|
227
|
+
},
|
|
228
|
+
/**
|
|
229
|
+
* Create a popup instance without immediately opening it.
|
|
230
|
+
* Call instance.open() when ready.
|
|
231
|
+
*/
|
|
232
|
+
create(config) {
|
|
233
|
+
return new RaaaamiePayPopup(config);
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export { RaaaamiePayPopup, RaaamiePay, RaaamiePay as default, redirect };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var RaaamiePay=function(e){"use strict";function t(){return`RPY-${Date.now().toString(36)}-${Math.random().toString(36).substring(2,10)}`.toUpperCase()}function i(e){if(!e.key)throw new Error('[RaaamiePay] "key" is required');if(!e.key.startsWith("pk_"))throw new Error('[RaaamiePay] Invalid public key. Must start with "pk_"');if(!e.amount||e.amount<=0)throw new Error('[RaaamiePay] "amount" must be greater than 0');if(e.customer&&!e.customer.email&&!e.customer.phone)throw new Error('[RaaamiePay] Customer must have at least "email" or "phone"')}function a(e,t){var i,a,r,s;const o=new URLSearchParams({key:e.key,amount:String(e.amount),currency:e.currency||"GHS",reference:t,sdk_version:"1.0.0",mode:"popup"});return(null===(i=e.customer)||void 0===i?void 0:i.email)&&o.set("email",e.customer.email),(null===(a=e.customer)||void 0===a?void 0:a.phone)&&o.set("phone",e.customer.phone),(null===(r=e.customer)||void 0===r?void 0:r.name)&&o.set("name",e.customer.name),e.description&&o.set("description",e.description),(null===(s=e.channels)||void 0===s?void 0:s.length)&&o.set("channels",e.channels.join(",")),e.metadata&&o.set("metadata",btoa(JSON.stringify(e.metadata))),e.redirectUrl&&o.set("redirect_url",e.redirectUrl),`https://pay.raaami.com/pay/${t}?${o.toString()}`}class r{constructor(e){this.iframe=null,this.backdrop=null,this.messageHandler=null,this.isOpen=!1,i(e),this.config=e,this.reference=e.reference||t()}open(){if(this.isOpen)return;this.isOpen=!0,document.body.style.overflow="hidden",this.backdrop=document.createElement("div"),this.backdrop.setAttribute("id","raamiepay-backdrop"),this.backdrop.setAttribute("style",["position:fixed","top:0","left:0","width:100%","height:100%","z-index:2147483646","background:rgba(0,0,0,0.6)","backdrop-filter:blur(4px)","-webkit-backdrop-filter:blur(4px)","opacity:0","transition:opacity 0.3s ease"].join(";")),this.backdrop.addEventListener("click",()=>this.close()),document.body.appendChild(this.backdrop);const e=a(this.config,this.reference);this.iframe=document.createElement("iframe"),this.iframe.setAttribute("id","raamiepay-checkout"),this.iframe.setAttribute("src",e),this.iframe.setAttribute("style",["position:fixed","top:0","left:0","width:100%","height:100%","z-index:2147483647","border:none","opacity:0","transition:opacity 0.3s ease"].join(";")),this.iframe.setAttribute("allow","payment"),this.iframe.setAttribute("sandbox","allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation"),this.iframe.setAttribute("title","RaaamiePay Secure Checkout"),this.iframe.setAttribute("aria-label","Payment checkout"),document.body.appendChild(this.iframe),requestAnimationFrame(()=>{this.backdrop&&(this.backdrop.style.opacity="1"),this.iframe&&(this.iframe.style.opacity="1")}),this.messageHandler=e=>this.handleMessage(e),window.addEventListener("message",this.messageHandler)}close(){var e,t;this.isOpen&&(this.isOpen=!1,this.backdrop&&(this.backdrop.style.opacity="0"),this.iframe&&(this.iframe.style.opacity="0"),setTimeout(()=>{this.iframe&&(this.iframe.remove(),this.iframe=null),this.backdrop&&(this.backdrop.remove(),this.backdrop=null),document.body.style.overflow=""},300),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),null===(t=(e=this.config).onClose)||void 0===t||t.call(e))}handleMessage(e){var t,i,a,r,s,o;if(!e.origin.includes("raaami.com")&&!e.origin.includes("localhost"))return;const n=e.data;if(null==n?void 0:n.type)switch(n.type){case"payment.success":null===(i=(t=this.config).onSuccess)||void 0===i||i.call(t,n.data),this.close();break;case"payment.failed":null===(r=(a=this.config).onError)||void 0===r||r.call(a,n.data),this.close();break;case"payment.close":this.close();break;case"payment.loaded":null===(o=(s=this.config).onLoad)||void 0===o||o.call(s)}}getReference(){return this.reference}}function s(e){i(e);const r=a(e,e.reference||t());window.location.href=r.replace("mode=popup","mode=redirect")}const o={popup(e){const t=new r(e);return t.open(),t},redirect(e){s(e)},create:e=>new r(e)};return e.RaaaamiePayPopup=r,e.RaaamiePay=o,e.default=o,e.redirect=s,Object.defineProperty(e,"__esModule",{value:!0}),e}({});
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type PaymentChannel = 'momo' | 'card' | 'bank' | 'ussd' | 'qr';
|
|
2
|
+
export interface RaaaamiePayCustomer {
|
|
3
|
+
email?: string;
|
|
4
|
+
phone?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RaaaamiePayConfig {
|
|
8
|
+
/** Your public key (pk_live_xxx or pk_test_xxx) */
|
|
9
|
+
key: string;
|
|
10
|
+
/** Amount in the smallest currency unit (pesewas for GHS). E.g. 15000 = GHS 150.00 */
|
|
11
|
+
amount: number;
|
|
12
|
+
/** ISO currency code. Default: 'GHS' */
|
|
13
|
+
currency?: string;
|
|
14
|
+
/** Unique transaction reference. Auto-generated if not provided */
|
|
15
|
+
reference?: string;
|
|
16
|
+
/** Customer information */
|
|
17
|
+
customer?: RaaaamiePayCustomer;
|
|
18
|
+
/** Description shown on checkout */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Restrict to specific payment channels */
|
|
21
|
+
channels?: PaymentChannel[];
|
|
22
|
+
/** Custom metadata passed to webhooks */
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
/** Override the default success redirect URL */
|
|
25
|
+
redirectUrl?: string;
|
|
26
|
+
/** Callback when payment is successful */
|
|
27
|
+
onSuccess?: (response: RaaaamiePayResponse) => void;
|
|
28
|
+
/** Callback when customer closes the popup */
|
|
29
|
+
onClose?: () => void;
|
|
30
|
+
/** Callback when payment fails */
|
|
31
|
+
onError?: (error: RaaaamiePayError) => void;
|
|
32
|
+
/** Callback fired when popup is loaded and ready */
|
|
33
|
+
onLoad?: () => void;
|
|
34
|
+
}
|
|
35
|
+
export interface RaaaamiePayResponse {
|
|
36
|
+
reference: string;
|
|
37
|
+
transactionId: string;
|
|
38
|
+
status: 'success';
|
|
39
|
+
channel: PaymentChannel;
|
|
40
|
+
amount: number;
|
|
41
|
+
currency: string;
|
|
42
|
+
customer: RaaaamiePayCustomer;
|
|
43
|
+
metadata?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
export interface RaaaamiePayError {
|
|
46
|
+
reference: string;
|
|
47
|
+
status: 'failed' | 'cancelled';
|
|
48
|
+
message: string;
|
|
49
|
+
}
|
|
50
|
+
export type RaaaamiePayEvent = {
|
|
51
|
+
type: 'payment.success';
|
|
52
|
+
data: RaaaamiePayResponse;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'payment.failed';
|
|
55
|
+
data: RaaaamiePayError;
|
|
56
|
+
} | {
|
|
57
|
+
type: 'payment.close';
|
|
58
|
+
} | {
|
|
59
|
+
type: 'payment.loaded';
|
|
60
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@raaamie/pay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "RaaamiePay JavaScript SDK — Accept payments via popup or redirect",
|
|
5
|
+
"author": "RaaamiePay",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/raamiepay.cjs.js",
|
|
8
|
+
"module": "dist/raamiepay.esm.js",
|
|
9
|
+
"browser": "dist/raamiepay.min.js",
|
|
10
|
+
"unpkg": "dist/raamiepay.min.js",
|
|
11
|
+
"jsdelivr": "dist/raamiepay.min.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"files": ["dist"],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/raamiepay.esm.js",
|
|
17
|
+
"require": "./dist/raamiepay.cjs.js",
|
|
18
|
+
"browser": "./dist/raamiepay.min.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rollup -c",
|
|
24
|
+
"dev": "rollup -c -w",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
29
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
30
|
+
"rollup": "^4.18.0",
|
|
31
|
+
"tslib": "^2.6.3",
|
|
32
|
+
"typescript": "^5.6.3"
|
|
33
|
+
},
|
|
34
|
+
"keywords": ["payment", "fintech", "checkout", "mobile-money", "ghana", "africa", "sdk"]
|
|
35
|
+
}
|