remita-inline 0.1.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/LICENSE +21 -0
- package/README.md +308 -0
- package/dist/index.cjs +371 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +102 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 remita-inline contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# remita-inline
|
|
2
|
+
|
|
3
|
+
A fully-typed TypeScript wrapper for the Remita Inline payment SDK with an RRR-only integration flow.
|
|
4
|
+
|
|
5
|
+
The library supports one payment path:
|
|
6
|
+
- generate or receive an RRR upstream
|
|
7
|
+
- pass that RRR into the SDK
|
|
8
|
+
- open the Remita payment widget
|
|
9
|
+
|
|
10
|
+
It works as a plain function, a React hook, or a React button component.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install remita-inline
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If you use the React hook or button component, install React separately:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react react-dom
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { startRemitaPayment } from "remita-inline";
|
|
28
|
+
|
|
29
|
+
await startRemitaPayment(
|
|
30
|
+
{
|
|
31
|
+
key: "your-public-key",
|
|
32
|
+
rrr: "321489526389",
|
|
33
|
+
transactionId: "unique-tx-id",
|
|
34
|
+
onSuccess(response) {
|
|
35
|
+
console.log("Payment successful", response.transactionId);
|
|
36
|
+
},
|
|
37
|
+
onError(error) {
|
|
38
|
+
console.error("Payment failed", error.message);
|
|
39
|
+
},
|
|
40
|
+
onClose() {
|
|
41
|
+
console.log("Payment modal closed");
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
environment: "demo",
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
By default the widget opens automatically after the SDK loads. Pass `autoOpen: false` if you want to control when the widget appears.
|
|
51
|
+
|
|
52
|
+
## React hook
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { useRemitaInline } from "remita-inline";
|
|
56
|
+
|
|
57
|
+
function RrrPaymentButton() {
|
|
58
|
+
const { pay, isLoading, error, reset } = useRemitaInline({ environment: "demo" });
|
|
59
|
+
|
|
60
|
+
const handlePay = async () => {
|
|
61
|
+
try {
|
|
62
|
+
await pay({
|
|
63
|
+
key: "your-public-key",
|
|
64
|
+
rrr: "321489526389",
|
|
65
|
+
transactionId: "unique-tx-id",
|
|
66
|
+
onSuccess(res) {
|
|
67
|
+
console.log("Paid", res.transactionId);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
// error is already available in hook state
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
{error && (
|
|
78
|
+
<p>
|
|
79
|
+
Payment failed: {error.message}{" "}
|
|
80
|
+
<button onClick={reset}>Dismiss</button>
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
<button onClick={handlePay} disabled={isLoading}>
|
|
84
|
+
{isLoading ? "Processing..." : "Pay with RRR"}
|
|
85
|
+
</button>
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Per-call options are merged over hook-level defaults.
|
|
92
|
+
|
|
93
|
+
## React button component
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { RemitaPayButton } from "remita-inline";
|
|
97
|
+
|
|
98
|
+
function Checkout() {
|
|
99
|
+
return (
|
|
100
|
+
<RemitaPayButton
|
|
101
|
+
paymentConfig={{
|
|
102
|
+
key: "your-public-key",
|
|
103
|
+
rrr: "321489526389",
|
|
104
|
+
transactionId: "unique-tx-id",
|
|
105
|
+
onSuccess(res) {
|
|
106
|
+
console.log("Paid", res.transactionId);
|
|
107
|
+
},
|
|
108
|
+
}}
|
|
109
|
+
paymentOptions={{ environment: "demo" }}
|
|
110
|
+
onPaymentStart={() => console.log("Payment started")}
|
|
111
|
+
onPaymentError={(err) => console.error(err.message)}
|
|
112
|
+
loadingText="Please wait..."
|
|
113
|
+
>
|
|
114
|
+
Pay with RRR
|
|
115
|
+
</RemitaPayButton>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
All standard button attributes are forwarded. If your custom `onClick` calls `event.preventDefault()`, the payment flow is cancelled.
|
|
121
|
+
|
|
122
|
+
## Low-level API
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { loadRemitaScript, initRemitaEngine } from "remita-inline";
|
|
126
|
+
|
|
127
|
+
await loadRemitaScript({ environment: "demo", timeoutMs: 10000 });
|
|
128
|
+
|
|
129
|
+
const session = initRemitaEngine({
|
|
130
|
+
key: "your-public-key",
|
|
131
|
+
rrr: "321489526389",
|
|
132
|
+
transactionId: "unique-tx-id",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
session.openIframe();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or defer opening with `startRemitaPayment`:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { startRemitaPayment } from "remita-inline";
|
|
142
|
+
|
|
143
|
+
const { open } = await startRemitaPayment(
|
|
144
|
+
{
|
|
145
|
+
key: "your-public-key",
|
|
146
|
+
rrr: "321489526389",
|
|
147
|
+
transactionId: "unique-tx-id",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
autoOpen: false,
|
|
151
|
+
environment: "demo",
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
open();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Configuration reference
|
|
159
|
+
|
|
160
|
+
### `RemitaPaymentConfig`
|
|
161
|
+
|
|
162
|
+
| Field | Type | Required | Description |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `key` | `string` | Yes | Your Remita public key |
|
|
165
|
+
| `rrr` | `string \| number` | Yes | The Remita Retrieval Reference to pay |
|
|
166
|
+
| `transactionId` | `string \| number` | No | Optional transaction reference from your application |
|
|
167
|
+
| `extendedData` | `RemitaExtendedData` | No | Extra data merged into the internal Remita payload |
|
|
168
|
+
| `currency` | `RemitaCurrencyCode` | No | Optional currency override |
|
|
169
|
+
| `metadata` | `Record<string, string \| number \| boolean \| null>` | No | Arbitrary metadata |
|
|
170
|
+
| `onSuccess` | `(res: RemitaSuccessfulPayment) => void` | No | Called on successful payment |
|
|
171
|
+
| `onError` | `(err: RemitaPaymentError) => void` | No | Called on payment failure |
|
|
172
|
+
| `onClose` | `() => void` | No | Called when the payment modal closes |
|
|
173
|
+
|
|
174
|
+
### `RemitaExtendedData`
|
|
175
|
+
|
|
176
|
+
| Field | Type | Description |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `customFields` | `RemitaExtendedDataCustomField[]` | Additional custom fields merged into the Remita payload |
|
|
179
|
+
|
|
180
|
+
The library always injects the `rrr` field into `extendedData.customFields` before calling the Remita engine. Any extra custom fields you provide are preserved.
|
|
181
|
+
|
|
182
|
+
### `RemitaStartPaymentOptions` / `RemitaLibraryOptions`
|
|
183
|
+
|
|
184
|
+
| Field | Type | Default | Description |
|
|
185
|
+
|---|---|---|---|
|
|
186
|
+
| `autoOpen` | `boolean` | `true` | Open the payment widget immediately |
|
|
187
|
+
| `environment` | `"demo" \| "production"` | `"production"` | Select the Remita SDK environment |
|
|
188
|
+
| `scriptUrl` | `string` | Environment-specific URL | Override the SDK script URL manually |
|
|
189
|
+
| `scriptId` | `string` | `"remita-inline-sdk"` | `id` attribute of the injected script tag |
|
|
190
|
+
| `timeoutMs` | `number` | `15000` | Script load timeout in milliseconds |
|
|
191
|
+
| `nonce` | `string` | None | CSP nonce for the injected script tag |
|
|
192
|
+
|
|
193
|
+
## Error handling
|
|
194
|
+
|
|
195
|
+
All library errors extend `RemitaError`.
|
|
196
|
+
|
|
197
|
+
| Class | Thrown when |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `ValidationError` | Required fields such as `key` or `rrr` are missing or invalid |
|
|
200
|
+
| `ScriptLoadError` | The Remita SDK script fails to load, times out, or runs outside a browser |
|
|
201
|
+
| `EngineError` | `window.RmPaymentEngine` is missing or returns an invalid session |
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import {
|
|
205
|
+
startRemitaPayment,
|
|
206
|
+
ValidationError,
|
|
207
|
+
ScriptLoadError,
|
|
208
|
+
} from "remita-inline";
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await startRemitaPayment({
|
|
212
|
+
key: "your-public-key",
|
|
213
|
+
rrr: "321489526389",
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error instanceof ValidationError) {
|
|
217
|
+
showFormError(error.message);
|
|
218
|
+
} else if (error instanceof ScriptLoadError) {
|
|
219
|
+
showNetworkError();
|
|
220
|
+
} else {
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Advanced options
|
|
227
|
+
|
|
228
|
+
### Demo vs production
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
await startRemitaPayment(config, { environment: "demo" });
|
|
232
|
+
await startRemitaPayment(config, { environment: "production" });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Pre-loading the script
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { loadRemitaScript } from "remita-inline";
|
|
239
|
+
|
|
240
|
+
await loadRemitaScript({ environment: "demo" });
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`loadRemitaScript` is idempotent: concurrent calls share one in-flight Promise, and if the engine is already on `window` it returns immediately.
|
|
244
|
+
|
|
245
|
+
### Content Security Policy (CSP)
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
await startRemitaPayment(config, { nonce: "abc123" });
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Adding extra custom fields
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
await startRemitaPayment({
|
|
255
|
+
key: "your-public-key",
|
|
256
|
+
rrr: "321489526389",
|
|
257
|
+
extendedData: {
|
|
258
|
+
customFields: [
|
|
259
|
+
{ name: "orderId", value: "ORD-123" },
|
|
260
|
+
{ name: "customerTier", value: "gold" },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## TypeScript
|
|
267
|
+
|
|
268
|
+
All public types are exported from the package root:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import type {
|
|
272
|
+
RemitaEngine,
|
|
273
|
+
RemitaEnginePaymentConfig,
|
|
274
|
+
RemitaEngineSession,
|
|
275
|
+
RemitaExtendedData,
|
|
276
|
+
RemitaExtendedDataCustomField,
|
|
277
|
+
RemitaPaymentConfig,
|
|
278
|
+
RemitaPaymentError,
|
|
279
|
+
RemitaPaymentSession,
|
|
280
|
+
RemitaStartPaymentOptions,
|
|
281
|
+
RemitaSuccessfulPayment,
|
|
282
|
+
UseRemitaInlineResult,
|
|
283
|
+
} from "remita-inline";
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The package ships with declaration files and source maps in `dist/`. Both ESM and CJS consumers are supported via the `exports` field in `package.json`.
|
|
287
|
+
|
|
288
|
+
## Browser support
|
|
289
|
+
|
|
290
|
+
Requires a browser with native `Promise`, `addEventListener`, and `document.createElement`. Equivalent to ES2020 environments. The library is browser-only because the Remita SDK depends on `window` and `document`.
|
|
291
|
+
|
|
292
|
+
For Next.js, render payment UI inside a `"use client"` component or guard execution with `typeof window !== "undefined"`.
|
|
293
|
+
|
|
294
|
+
## Contributing
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
git clone https://github.com/your-org/remita-inline
|
|
298
|
+
cd remita-inline
|
|
299
|
+
npm install
|
|
300
|
+
|
|
301
|
+
npm run typecheck
|
|
302
|
+
npm test
|
|
303
|
+
npm run build
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
[MIT](LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/core/constant.ts
|
|
7
|
+
var REMITA_SCRIPT_ID = "remita-inline-sdk";
|
|
8
|
+
var REMITA_PRODUCTION_SCRIPT_URL = "https://login.remita.net/payment/v1/remita-pay-inline.bundle.js";
|
|
9
|
+
var REMITA_DEMO_SCRIPT_URL = "https://demo.remita.net/payment/v1/remita-pay-inline.bundle.js";
|
|
10
|
+
var REMITA_SCRIPT_URL = REMITA_PRODUCTION_SCRIPT_URL;
|
|
11
|
+
var REMITA_ENGINE = "RmPaymentEngine";
|
|
12
|
+
|
|
13
|
+
// src/utils/once.ts
|
|
14
|
+
var promise = null;
|
|
15
|
+
function getPromise() {
|
|
16
|
+
return promise;
|
|
17
|
+
}
|
|
18
|
+
function setPromise(value) {
|
|
19
|
+
promise = value;
|
|
20
|
+
}
|
|
21
|
+
function clearPromise() {
|
|
22
|
+
promise = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/utils/errors.ts
|
|
26
|
+
var RemitaError = class extends Error {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "RemitaError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ValidationError = class extends RemitaError {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "ValidationError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var ScriptLoadError = class extends RemitaError {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "ScriptLoadError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var EngineError = class extends RemitaError {
|
|
45
|
+
constructor(message) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "EngineError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/core/loader.ts
|
|
52
|
+
function isBrowserEnvironment() {
|
|
53
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
54
|
+
}
|
|
55
|
+
function getEngine(win) {
|
|
56
|
+
return win[REMITA_ENGINE];
|
|
57
|
+
}
|
|
58
|
+
function resolveScriptUrl(options) {
|
|
59
|
+
if (options.scriptUrl) {
|
|
60
|
+
return options.scriptUrl;
|
|
61
|
+
}
|
|
62
|
+
if (options.environment === "demo") {
|
|
63
|
+
return REMITA_DEMO_SCRIPT_URL;
|
|
64
|
+
}
|
|
65
|
+
if (options.environment === "production") {
|
|
66
|
+
return REMITA_PRODUCTION_SCRIPT_URL;
|
|
67
|
+
}
|
|
68
|
+
return REMITA_SCRIPT_URL;
|
|
69
|
+
}
|
|
70
|
+
function waitForExistingScript(script, timeoutMs, win) {
|
|
71
|
+
if (script.dataset.loaded === "true" && getEngine(win)) {
|
|
72
|
+
return Promise.resolve();
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
let timeoutId;
|
|
76
|
+
const onLoad = () => {
|
|
77
|
+
cleanup();
|
|
78
|
+
script.dataset.loaded = "true";
|
|
79
|
+
if (!getEngine(win)) {
|
|
80
|
+
reject(new ScriptLoadError("Remita script loaded but payment engine is unavailable."));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolve();
|
|
84
|
+
};
|
|
85
|
+
const onError = () => {
|
|
86
|
+
cleanup();
|
|
87
|
+
reject(new ScriptLoadError("Failed to load Remita script."));
|
|
88
|
+
};
|
|
89
|
+
const cleanup = () => {
|
|
90
|
+
script.removeEventListener("load", onLoad);
|
|
91
|
+
script.removeEventListener("error", onError);
|
|
92
|
+
if (timeoutId) {
|
|
93
|
+
clearTimeout(timeoutId);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
script.addEventListener("load", onLoad, { once: true });
|
|
97
|
+
script.addEventListener("error", onError, { once: true });
|
|
98
|
+
timeoutId = setTimeout(() => {
|
|
99
|
+
cleanup();
|
|
100
|
+
reject(new ScriptLoadError(`Timed out waiting for Remita script after ${timeoutMs}ms.`));
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function createAndLoadScript(scriptId, scriptUrl, timeoutMs, nonce) {
|
|
105
|
+
const win = window;
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const script = document.createElement("script");
|
|
108
|
+
let timeoutId;
|
|
109
|
+
script.id = scriptId;
|
|
110
|
+
script.src = scriptUrl;
|
|
111
|
+
script.async = true;
|
|
112
|
+
if (nonce) {
|
|
113
|
+
script.nonce = nonce;
|
|
114
|
+
}
|
|
115
|
+
const onLoad = () => {
|
|
116
|
+
cleanup();
|
|
117
|
+
script.dataset.loaded = "true";
|
|
118
|
+
if (!getEngine(win)) {
|
|
119
|
+
reject(new ScriptLoadError("Remita script loaded but payment engine is unavailable."));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
const onError = () => {
|
|
125
|
+
cleanup();
|
|
126
|
+
reject(new ScriptLoadError("Failed to load Remita script."));
|
|
127
|
+
};
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
script.removeEventListener("load", onLoad);
|
|
130
|
+
script.removeEventListener("error", onError);
|
|
131
|
+
if (timeoutId) {
|
|
132
|
+
clearTimeout(timeoutId);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
script.addEventListener("load", onLoad, { once: true });
|
|
136
|
+
script.addEventListener("error", onError, { once: true });
|
|
137
|
+
timeoutId = setTimeout(() => {
|
|
138
|
+
cleanup();
|
|
139
|
+
script.remove();
|
|
140
|
+
reject(new ScriptLoadError(`Timed out loading Remita script after ${timeoutMs}ms.`));
|
|
141
|
+
}, timeoutMs);
|
|
142
|
+
document.head.appendChild(script);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async function loadRemitaScript(options = {}) {
|
|
146
|
+
if (!isBrowserEnvironment()) {
|
|
147
|
+
throw new ScriptLoadError("Remita script loader can only run in a browser environment.");
|
|
148
|
+
}
|
|
149
|
+
const scriptId = options.scriptId ?? REMITA_SCRIPT_ID;
|
|
150
|
+
const scriptUrl = resolveScriptUrl(options);
|
|
151
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
152
|
+
const win = window;
|
|
153
|
+
if (getEngine(win)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const inFlight = getPromise();
|
|
157
|
+
if (inFlight) {
|
|
158
|
+
return inFlight;
|
|
159
|
+
}
|
|
160
|
+
const task = (async () => {
|
|
161
|
+
const existingScript = document.getElementById(scriptId);
|
|
162
|
+
if (existingScript) {
|
|
163
|
+
if (existingScript.src && existingScript.src !== scriptUrl) {
|
|
164
|
+
throw new ScriptLoadError(
|
|
165
|
+
`Script with id "${scriptId}" already exists with a different src.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
await waitForExistingScript(existingScript, timeoutMs, win);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await createAndLoadScript(scriptId, scriptUrl, timeoutMs, options.nonce);
|
|
172
|
+
})();
|
|
173
|
+
setPromise(task);
|
|
174
|
+
try {
|
|
175
|
+
await task;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
clearPromise();
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/core/engine.ts
|
|
183
|
+
function isBrowserEnvironment2() {
|
|
184
|
+
return typeof window !== "undefined";
|
|
185
|
+
}
|
|
186
|
+
function getRemitaEngine(win) {
|
|
187
|
+
if (!isBrowserEnvironment2()) {
|
|
188
|
+
throw new EngineError("Remita engine is only available in a browser environment.");
|
|
189
|
+
}
|
|
190
|
+
const targetWindow = win ?? window;
|
|
191
|
+
const engine = targetWindow[REMITA_ENGINE];
|
|
192
|
+
if (!engine || typeof engine.init !== "function") {
|
|
193
|
+
throw new EngineError("Remita payment engine is unavailable on window.");
|
|
194
|
+
}
|
|
195
|
+
return engine;
|
|
196
|
+
}
|
|
197
|
+
function initRemitaEngine(config, win) {
|
|
198
|
+
const engine = getRemitaEngine(win);
|
|
199
|
+
const rawSession = engine.init(toEnginePaymentConfig(config));
|
|
200
|
+
const openIframe = typeof rawSession.openIframe === "function" ? rawSession.openIframe.bind(rawSession) : typeof rawSession.showPaymentWidget === "function" ? rawSession.showPaymentWidget.bind(rawSession) : void 0;
|
|
201
|
+
if (!openIframe) {
|
|
202
|
+
throw new EngineError("Remita engine returned an invalid payment session.");
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
openIframe,
|
|
206
|
+
...typeof rawSession.showPaymentWidget === "function" ? { showPaymentWidget: rawSession.showPaymentWidget.bind(rawSession) } : {}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function toEnginePaymentConfig(config) {
|
|
210
|
+
const { rrr, extendedData, ...rest } = config;
|
|
211
|
+
const engineExtendedData = {
|
|
212
|
+
...extendedData ?? {},
|
|
213
|
+
customFields: [
|
|
214
|
+
...(extendedData?.customFields ?? []).filter((field) => field?.name !== "rrr"),
|
|
215
|
+
{
|
|
216
|
+
name: "rrr",
|
|
217
|
+
value: rrr
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
...rest,
|
|
223
|
+
processRrr: true,
|
|
224
|
+
extendedData: engineExtendedData
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/core/validator.ts
|
|
229
|
+
function assertNonEmptyString(value, fieldName) {
|
|
230
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
231
|
+
throw new ValidationError(`${fieldName} is required and must be a non-empty string.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function assertValidTransactionId(value) {
|
|
235
|
+
if (value === void 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const isString = typeof value === "string" && value.trim().length > 0;
|
|
239
|
+
const isNumber = typeof value === "number" && Number.isFinite(value);
|
|
240
|
+
if (!isString && !isNumber) {
|
|
241
|
+
throw new ValidationError(
|
|
242
|
+
"transactionId must be a non-empty string or a finite number when provided."
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function assertValidRrr(value) {
|
|
247
|
+
const isString = typeof value === "string" && value.trim().length > 0;
|
|
248
|
+
const isNumber = typeof value === "number" && Number.isFinite(value);
|
|
249
|
+
if (!isString && !isNumber) {
|
|
250
|
+
throw new ValidationError("rrr is required and must be a non-empty string or a finite number.");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function validateRrrConfig(config) {
|
|
254
|
+
assertValidRrr(config.rrr);
|
|
255
|
+
}
|
|
256
|
+
function validatePaymentConfig(config) {
|
|
257
|
+
if (!config || typeof config !== "object") {
|
|
258
|
+
throw new ValidationError("Payment config is required.");
|
|
259
|
+
}
|
|
260
|
+
assertNonEmptyString(config.key, "key");
|
|
261
|
+
assertValidRrr(config.rrr);
|
|
262
|
+
assertValidTransactionId(config.transactionId);
|
|
263
|
+
validateRrrConfig(config);
|
|
264
|
+
return config;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/core/payment.ts
|
|
268
|
+
async function startRemitaPayment(config, options = {}) {
|
|
269
|
+
validatePaymentConfig(config);
|
|
270
|
+
await loadRemitaScript(options);
|
|
271
|
+
const session = initRemitaEngine(config);
|
|
272
|
+
const open = () => {
|
|
273
|
+
if (typeof session.showPaymentWidget === "function") {
|
|
274
|
+
session.showPaymentWidget();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
session.openIframe();
|
|
278
|
+
};
|
|
279
|
+
if (options.autoOpen !== false) {
|
|
280
|
+
open();
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
open,
|
|
284
|
+
session
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function useRemitaInline(defaultOptions = {}) {
|
|
288
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
289
|
+
const [error, setError] = react.useState(null);
|
|
290
|
+
const pay = react.useCallback(
|
|
291
|
+
async (config, options = {}) => {
|
|
292
|
+
setIsLoading(true);
|
|
293
|
+
setError(null);
|
|
294
|
+
try {
|
|
295
|
+
await startRemitaPayment(config, {
|
|
296
|
+
...defaultOptions,
|
|
297
|
+
...options
|
|
298
|
+
});
|
|
299
|
+
} catch (err) {
|
|
300
|
+
const nextError = err instanceof Error ? err : new Error("Payment initialization failed.");
|
|
301
|
+
setError(nextError);
|
|
302
|
+
throw nextError;
|
|
303
|
+
} finally {
|
|
304
|
+
setIsLoading(false);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[defaultOptions]
|
|
308
|
+
);
|
|
309
|
+
const reset = react.useCallback(() => {
|
|
310
|
+
setError(null);
|
|
311
|
+
}, []);
|
|
312
|
+
return {
|
|
313
|
+
pay,
|
|
314
|
+
isLoading,
|
|
315
|
+
error,
|
|
316
|
+
reset
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function RemitaPayButton({
|
|
320
|
+
paymentConfig,
|
|
321
|
+
paymentOptions,
|
|
322
|
+
onPaymentStart,
|
|
323
|
+
onPaymentError,
|
|
324
|
+
onClick,
|
|
325
|
+
loadingText = "Processing...",
|
|
326
|
+
disabled,
|
|
327
|
+
children,
|
|
328
|
+
...props
|
|
329
|
+
}) {
|
|
330
|
+
const { pay, isLoading } = useRemitaInline();
|
|
331
|
+
const handleClick = react.useCallback(
|
|
332
|
+
async (event) => {
|
|
333
|
+
onClick?.(event);
|
|
334
|
+
if (event.defaultPrevented || isLoading) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
onPaymentStart?.();
|
|
338
|
+
try {
|
|
339
|
+
await pay(paymentConfig, paymentOptions);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (onPaymentError) {
|
|
342
|
+
onPaymentError(error instanceof Error ? error : new Error("Payment failed."));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
[isLoading, onClick, onPaymentError, onPaymentStart, pay, paymentConfig, paymentOptions]
|
|
347
|
+
);
|
|
348
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
349
|
+
"button",
|
|
350
|
+
{
|
|
351
|
+
type: "button",
|
|
352
|
+
...props,
|
|
353
|
+
disabled: disabled || isLoading,
|
|
354
|
+
onClick: handleClick,
|
|
355
|
+
children: isLoading ? loadingText : children ?? "Pay with Remita"
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
exports.REMITA_DEMO_SCRIPT_URL = REMITA_DEMO_SCRIPT_URL;
|
|
361
|
+
exports.REMITA_PRODUCTION_SCRIPT_URL = REMITA_PRODUCTION_SCRIPT_URL;
|
|
362
|
+
exports.RemitaPayButton = RemitaPayButton;
|
|
363
|
+
exports.getRemitaEngine = getRemitaEngine;
|
|
364
|
+
exports.initRemitaEngine = initRemitaEngine;
|
|
365
|
+
exports.loadRemitaScript = loadRemitaScript;
|
|
366
|
+
exports.startRemitaPayment = startRemitaPayment;
|
|
367
|
+
exports.toEnginePaymentConfig = toEnginePaymentConfig;
|
|
368
|
+
exports.useRemitaInline = useRemitaInline;
|
|
369
|
+
exports.validatePaymentConfig = validatePaymentConfig;
|
|
370
|
+
//# sourceMappingURL=index.cjs.map
|
|
371
|
+
//# sourceMappingURL=index.cjs.map
|