@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 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;
@@ -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}({});
@@ -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
+ }