@valuepay/react 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/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +435 -0
- package/dist/cjs/components/ValuePayButton.js +14 -0
- package/dist/cjs/components/ValuePayButton.js.map +1 -0
- package/dist/cjs/components/ValuePayProvider.js +17 -0
- package/dist/cjs/components/ValuePayProvider.js.map +1 -0
- package/dist/cjs/hooks/useScript.js +40 -0
- package/dist/cjs/hooks/useScript.js.map +1 -0
- package/dist/cjs/hooks/useValuePay.js +90 -0
- package/dist/cjs/hooks/useValuePay.js.map +1 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/utils/constants.js +12 -0
- package/dist/cjs/utils/constants.js.map +1 -0
- package/dist/cjs/utils/helpers.js +67 -0
- package/dist/cjs/utils/helpers.js.map +1 -0
- package/dist/esm/components/ValuePayButton.js +12 -0
- package/dist/esm/components/ValuePayButton.js.map +1 -0
- package/dist/esm/components/ValuePayProvider.js +15 -0
- package/dist/esm/components/ValuePayProvider.js.map +1 -0
- package/dist/esm/hooks/useScript.js +38 -0
- package/dist/esm/hooks/useScript.js.map +1 -0
- package/dist/esm/hooks/useValuePay.js +88 -0
- package/dist/esm/hooks/useValuePay.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/utils/constants.js +7 -0
- package/dist/esm/utils/constants.js.map +1 -0
- package/dist/esm/utils/helpers.js +63 -0
- package/dist/esm/utils/helpers.js.map +1 -0
- package/dist/types/components/ValuePayButton.d.ts +3 -0
- package/dist/types/components/ValuePayProvider.d.ts +2 -0
- package/dist/types/hooks/useScript.d.ts +5 -0
- package/dist/types/hooks/useValuePay.d.ts +2 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/types/index.d.ts +74 -0
- package/dist/types/utils/constants.d.ts +5 -0
- package/dist/types/utils/helpers.d.ts +32 -0
- package/package.json +84 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-03-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `<ValuePayButton />` component — drop-in pre-styled payment button with amount display
|
|
13
|
+
- `<ValuePayProvider />` component — headless component with ref-based `initialize()` method
|
|
14
|
+
- `useValuePay()` hook — programmatic API returning `{ initialize, isReady, isProcessing }`
|
|
15
|
+
- `useScript()` hook — dynamic script loader with deduplication and cleanup
|
|
16
|
+
- `generateTransactionRef()` utility — unique reference generator with configurable length
|
|
17
|
+
- Full TypeScript support with exported types: `ValuePayConfig`, `ValuePayResponse`, `ValuePayCustomer`, `ValuePayCustomization`, `PaymentChannel`, `ValuePayStatus`, `UseValuePayReturn`, `ValuePayButtonProps`, `ValuePayProviderProps`, `ValuePayProviderRef`
|
|
18
|
+
- Callback-based payment handling: `onSuccess`, `onClose`, `onCancelled`, `onCallback`, `onError`
|
|
19
|
+
- Redirect-based payment handling via `redirectUrl` prop
|
|
20
|
+
- Runtime config overrides via `initialize(overrides)` for dynamic amounts
|
|
21
|
+
- Checkout modal customization (title, description, logo)
|
|
22
|
+
- Payment channel selection (card, transfer, QR code, USSD, mobile money)
|
|
23
|
+
- Custom metadata support via `metaData` prop
|
|
24
|
+
- SSR-safe implementation (Next.js, Remix, Gatsby compatible)
|
|
25
|
+
- Dual CJS + ESM output with tree-shaking support
|
|
26
|
+
- Comprehensive test suite with 90%+ coverage
|
|
27
|
+
- Double-payment prevention via `isProcessing` guard
|
|
28
|
+
|
|
29
|
+
[1.0.0]: https://github.com/valuepayment/react-sdk/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Value Payment Solutions Limited
|
|
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,435 @@
|
|
|
1
|
+
# @valuepay/react
|
|
2
|
+
|
|
3
|
+
> Official React SDK for [ValuePay](https://valuepayng.com) — accept payments in your React app with minimal setup.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@valuepay/react)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Option 1: Drop-in Button](#option-1-drop-in-button-simplest)
|
|
17
|
+
- [Option 2: Hook (Full Control)](#option-2-hook-full-control)
|
|
18
|
+
- [Option 3: Provider with Ref](#option-3-provider-with-ref-hidden-component)
|
|
19
|
+
- [Redirect Mode](#redirect-mode)
|
|
20
|
+
- [API Reference](#api-reference)
|
|
21
|
+
- [useValuePay](#usevaluepayconfig-valuepayconfig-usevaluepayreturn)
|
|
22
|
+
- [ValuePayButton](#valuepaybutton--props)
|
|
23
|
+
- [ValuePayProvider](#valuepayprovider--props)
|
|
24
|
+
- [ValuePayConfig](#valuepayconfig)
|
|
25
|
+
- [ValuePayResponse](#valuepayresponse)
|
|
26
|
+
- [Dynamic Amounts](#dynamic-amounts)
|
|
27
|
+
- [Payment Channels](#payment-channels)
|
|
28
|
+
- [Customizing the Checkout Modal](#customizing-the-checkout-modal)
|
|
29
|
+
- [Server-Side Verification](#server-side-verification)
|
|
30
|
+
- [TypeScript](#typescript)
|
|
31
|
+
- [Browser Support](#browser-support)
|
|
32
|
+
- [Security](#security)
|
|
33
|
+
- [Contributing](#contributing)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @valuepay/react
|
|
42
|
+
# or
|
|
43
|
+
yarn add @valuepay/react
|
|
44
|
+
# or
|
|
45
|
+
pnpm add @valuepay/react
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Requirements
|
|
49
|
+
|
|
50
|
+
- **React** `>=16.8.0` (hooks support required)
|
|
51
|
+
- **React DOM** `>=16.8.0`
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Option 1: Drop-in Button (Simplest)
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { ValuePayButton } from "@valuepay/react";
|
|
61
|
+
|
|
62
|
+
function Checkout() {
|
|
63
|
+
return (
|
|
64
|
+
<ValuePayButton
|
|
65
|
+
publicKey="KP_your_public_key"
|
|
66
|
+
amount={5000}
|
|
67
|
+
customer={{ email: "jane@example.com", fullName: "Jane Doe" }}
|
|
68
|
+
onSuccess={(response) => {
|
|
69
|
+
console.log("Payment successful!", response.ref, response.status);
|
|
70
|
+
}}
|
|
71
|
+
onClose={(response) => {
|
|
72
|
+
console.log("Checkout closed", response);
|
|
73
|
+
}}
|
|
74
|
+
onCancelled={(response) => {
|
|
75
|
+
console.log("Payment cancelled", response);
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The button renders as **"Pay ₦5,000 Now"** by default. Customize with `text`, `className`, `style`, or pass `children`.
|
|
83
|
+
|
|
84
|
+
### Option 2: Hook (Full Control)
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { useValuePay } from "@valuepay/react";
|
|
88
|
+
|
|
89
|
+
function Checkout() {
|
|
90
|
+
const { initialize, isReady, isProcessing } = useValuePay({
|
|
91
|
+
publicKey: "KP_your_public_key",
|
|
92
|
+
amount: 10000,
|
|
93
|
+
customer: { email: "john@example.com", fullName: "John Doe" },
|
|
94
|
+
onSuccess: (response) => {
|
|
95
|
+
console.log("Paid!", response);
|
|
96
|
+
// response.ref, response.status, response.amount, etc.
|
|
97
|
+
},
|
|
98
|
+
onCancelled: (response) => console.log("Cancelled", response),
|
|
99
|
+
onClose: (response) => console.log("Closed", response),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<button onClick={() => initialize()} disabled={!isReady || isProcessing}>
|
|
104
|
+
{isProcessing ? "Processing..." : "Pay ₦10,000"}
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Option 3: Provider with Ref (Hidden Component)
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { useRef } from "react";
|
|
114
|
+
import { ValuePayProvider, ValuePayProviderRef } from "@valuepay/react";
|
|
115
|
+
|
|
116
|
+
function Checkout() {
|
|
117
|
+
const payRef = useRef<ValuePayProviderRef>(null);
|
|
118
|
+
|
|
119
|
+
const handlePay = () => {
|
|
120
|
+
payRef.current?.initialize();
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<>
|
|
125
|
+
<ValuePayProvider
|
|
126
|
+
ref={payRef}
|
|
127
|
+
config={{
|
|
128
|
+
publicKey: "KP_your_public_key",
|
|
129
|
+
amount: 7500,
|
|
130
|
+
customer: { email: "alice@example.com", fullName: "Alice Smith" },
|
|
131
|
+
onSuccess: (response) => console.log("Success!", response),
|
|
132
|
+
onCancelled: (response) => console.log("Cancelled", response),
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
<button onClick={handlePay}>Checkout</button>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Redirect Mode
|
|
144
|
+
|
|
145
|
+
Instead of callbacks, you can redirect the user after payment:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<ValuePayButton
|
|
149
|
+
publicKey="KP_your_public_key"
|
|
150
|
+
amount={5000}
|
|
151
|
+
customer={{ email: "jane@example.com", fullName: "Jane Doe" }}
|
|
152
|
+
redirectUrl="https://yoursite.com/payment/verify"
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
After payment, the user is redirected to:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
https://yoursite.com/payment/verify?ref=VPS_TX_abc123&status=SUCCESS
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Your server should then verify the transaction using the `ref` query parameter via the [ValuePay Verify API](https://developer.valuepayng.com/docs).
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### `useValuePay(config: ValuePayConfig): UseValuePayReturn`
|
|
169
|
+
|
|
170
|
+
| Return | Type | Description |
|
|
171
|
+
| -------------- | ----------------------- | -------------------------------------------- |
|
|
172
|
+
| `initialize` | `(overrides?) => void` | Opens the ValuePay checkout modal |
|
|
173
|
+
| `isReady` | `boolean` | `true` when the ValuePay script has loaded |
|
|
174
|
+
| `isProcessing` | `boolean` | `true` while a payment is in progress |
|
|
175
|
+
|
|
176
|
+
### `<ValuePayButton />` Props
|
|
177
|
+
|
|
178
|
+
Extends `ValuePayConfig` with:
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Default | Description |
|
|
181
|
+
| ----------- | ---------------- | ---------------------- | -------------------- |
|
|
182
|
+
| `text` | `string` | `"Pay ₦{amount} Now"` | Button text |
|
|
183
|
+
| `className` | `string` | — | CSS class |
|
|
184
|
+
| `style` | `CSSProperties` | — | Inline styles |
|
|
185
|
+
| `disabled` | `boolean` | `false` | Disable button |
|
|
186
|
+
| `children` | `ReactNode` | — | Custom button content|
|
|
187
|
+
|
|
188
|
+
### `<ValuePayProvider />` Props
|
|
189
|
+
|
|
190
|
+
| Prop | Type | Description |
|
|
191
|
+
| -------- | ------------------------- | -------------------------------- |
|
|
192
|
+
| `config` | `ValuePayConfig` | Payment configuration |
|
|
193
|
+
| `ref` | `Ref<ValuePayProviderRef>`| Exposes `initialize()` method |
|
|
194
|
+
|
|
195
|
+
### `ValuePayConfig`
|
|
196
|
+
|
|
197
|
+
| Prop | Type | Required | Default | Description |
|
|
198
|
+
| ---------------- | ---------------------------- | -------- | ------------------------------------ | ---------------------------------- |
|
|
199
|
+
| `publicKey` | `string` | Yes | — | Your ValuePay public key |
|
|
200
|
+
| `amount` | `number` | Yes | — | Amount in Naira |
|
|
201
|
+
| `customer` | `{ email, fullName, phone? }`| Yes | — | Customer details |
|
|
202
|
+
| `currency` | `string` | — | `"NGN"` | Currency code |
|
|
203
|
+
| `channels` | `PaymentChannel[]` | — | `["card","transfer","qrcode","ussd"]`| Enabled channels |
|
|
204
|
+
| `transactionRef` | `string` | — | Auto-generated | Unique transaction reference |
|
|
205
|
+
| `redirectUrl` | `string` | — | — | Redirect URL after payment |
|
|
206
|
+
| `metaData` | `Record<string, unknown>` | — | — | Custom metadata |
|
|
207
|
+
| `customization` | `{ title?, description?, logoUrl? }` | — | — | Checkout modal branding |
|
|
208
|
+
| `onSuccess` | `(response) => void` | — | — | Called on successful payment |
|
|
209
|
+
| `onClose` | `(response) => void` | — | — | Called when modal is closed |
|
|
210
|
+
| `onCancelled` | `(response) => void` | — | — | Called when payment is cancelled |
|
|
211
|
+
| `onCallback` | `(response) => void` | — | — | Called on any payment event |
|
|
212
|
+
| `onError` | `(error) => void` | — | — | Called on SDK errors |
|
|
213
|
+
| `scriptUrl` | `string` | — | Production CDN | Override ValuePay script URL |
|
|
214
|
+
|
|
215
|
+
### `ValuePayResponse`
|
|
216
|
+
|
|
217
|
+
| Field | Type | Description |
|
|
218
|
+
| ---------------- | ------------------ | --------------------------------------------------------------------------------------------- |
|
|
219
|
+
| `ref` | `string` | Transaction reference |
|
|
220
|
+
| `transactionRef` | `string` | Same as `ref` |
|
|
221
|
+
| `status` | `ValuePayStatus` | `"SUCCESS"`, `"COMPLETED"`, `"FAILED"`, `"CANCELLED"`, `"DUPLICATE"`, `"PENDING"` |
|
|
222
|
+
| `amount` | `number` | Transaction amount |
|
|
223
|
+
| `currency` | `string` | Currency code |
|
|
224
|
+
| `customer` | `ValuePayCustomer` | Customer details |
|
|
225
|
+
| `paymentMethod` | `string?` | Payment method used |
|
|
226
|
+
| `validation` | `{ status }?` | Server-side validation result |
|
|
227
|
+
| `raw` | `unknown?` | Raw ValuePay response |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Dynamic Amounts
|
|
232
|
+
|
|
233
|
+
Override any config at call time:
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
const { initialize } = useValuePay({
|
|
237
|
+
publicKey: "KP_xxx",
|
|
238
|
+
amount: 0, // placeholder
|
|
239
|
+
customer: { email: "user@example.com", fullName: "User" },
|
|
240
|
+
onSuccess: (res) => console.log(res),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Later, with dynamic amount:
|
|
244
|
+
initialize({ amount: selectedProduct.price });
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Payment Channels
|
|
250
|
+
|
|
251
|
+
| Channel | Value |
|
|
252
|
+
| -------------- | ---------------- |
|
|
253
|
+
| Card | `"card"` |
|
|
254
|
+
| Bank Transfer | `"transfer"` |
|
|
255
|
+
| QR Code | `"qrcode"` |
|
|
256
|
+
| USSD | `"ussd"` |
|
|
257
|
+
| Mobile Money | `"mobile_money"` |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Customizing the Checkout Modal
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
<ValuePayButton
|
|
265
|
+
publicKey="KP_xxx"
|
|
266
|
+
amount={2000}
|
|
267
|
+
customer={{ email: "user@example.com", fullName: "User" }}
|
|
268
|
+
customization={{
|
|
269
|
+
title: "My Store",
|
|
270
|
+
description: "Thanks for shopping with us!",
|
|
271
|
+
logoUrl: "https://mystore.com/logo.png",
|
|
272
|
+
}}
|
|
273
|
+
onSuccess={(res) => console.log(res)}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Server-Side Verification
|
|
280
|
+
|
|
281
|
+
**Always verify transactions on your server.** Never trust client-side callbacks alone. Use the `ref` from the response:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
// Backend (Node.js example)
|
|
285
|
+
const response = await fetch(
|
|
286
|
+
"https://api.valuepayng.com/v1/transactions/verify",
|
|
287
|
+
{
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/json",
|
|
291
|
+
Authorization: `Bearer ${VALUEPAY_SECRET_KEY}`,
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({ tx_ref: transactionRef }),
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
const result = await response.json();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## TypeScript
|
|
302
|
+
|
|
303
|
+
All types are exported:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import type {
|
|
307
|
+
ValuePayConfig,
|
|
308
|
+
ValuePayResponse,
|
|
309
|
+
ValuePayCustomer,
|
|
310
|
+
ValuePayCustomization,
|
|
311
|
+
PaymentChannel,
|
|
312
|
+
ValuePayStatus,
|
|
313
|
+
UseValuePayReturn,
|
|
314
|
+
ValuePayButtonProps,
|
|
315
|
+
ValuePayProviderProps,
|
|
316
|
+
ValuePayProviderRef,
|
|
317
|
+
} from "@valuepay/react";
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Browser Support
|
|
323
|
+
|
|
324
|
+
| Browser | Minimum Version | Release Year |
|
|
325
|
+
| -------------------- | --------------- | ------------ |
|
|
326
|
+
| Chrome | 49+ | 2016 |
|
|
327
|
+
| Firefox | 52+ | 2017 |
|
|
328
|
+
| Safari | 10+ | 2016 |
|
|
329
|
+
| Edge | 15+ | 2017 |
|
|
330
|
+
| Opera | 36+ | 2016 |
|
|
331
|
+
| Samsung Internet | 5.0+ | 2016 |
|
|
332
|
+
| iOS Safari | 10+ | 2016 |
|
|
333
|
+
| Android WebView | 49+ | 2016 |
|
|
334
|
+
| Internet Explorer | 11 (with polyfills) | 2013 |
|
|
335
|
+
|
|
336
|
+
The SDK targets **ES2018** and uses only features available in browsers from 2013 onwards (with appropriate polyfills for IE11). React 16.8+ is required for hooks support.
|
|
337
|
+
|
|
338
|
+
### SSR Compatibility
|
|
339
|
+
|
|
340
|
+
The SDK is SSR-safe and works with:
|
|
341
|
+
- Next.js (Pages Router and App Router)
|
|
342
|
+
- Remix
|
|
343
|
+
- Gatsby
|
|
344
|
+
- Any server-side rendering framework
|
|
345
|
+
|
|
346
|
+
All DOM access is guarded with `typeof document !== "undefined"` and `typeof window !== "undefined"` checks.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Security
|
|
351
|
+
|
|
352
|
+
- **Never** expose your secret key in client-side code. Only use your **public key** with this SDK.
|
|
353
|
+
- **Always** verify transactions server-side before fulfilling orders — client-side callbacks can be spoofed.
|
|
354
|
+
- **Use HTTPS** in production to prevent man-in-the-middle attacks.
|
|
355
|
+
- **Generate unique transaction references** per payment to prevent duplicate charges.
|
|
356
|
+
- The SDK loads the ValuePay checkout script from `https://www.valuepayng.com` — ensure this domain is allowed in your Content Security Policy (CSP).
|
|
357
|
+
- The SDK does **not** store, log, or transmit any sensitive payment data (card numbers, CVV, etc.). All payment processing happens within the ValuePay checkout modal.
|
|
358
|
+
- Transaction references are generated using cryptographically sufficient randomness via `Math.random()` with alphanumeric characters.
|
|
359
|
+
|
|
360
|
+
### Content Security Policy (CSP)
|
|
361
|
+
|
|
362
|
+
If your application uses a Content Security Policy, add the following directives:
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
script-src 'self' https://www.valuepayng.com;
|
|
366
|
+
frame-src 'self' https://www.valuepayng.com;
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Error Handling
|
|
372
|
+
|
|
373
|
+
The SDK provides an `onError` callback for handling SDK-level errors:
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
<ValuePayButton
|
|
377
|
+
publicKey="KP_xxx"
|
|
378
|
+
amount={5000}
|
|
379
|
+
customer={{ email: "user@example.com", fullName: "User" }}
|
|
380
|
+
onError={(error) => {
|
|
381
|
+
console.error("Payment SDK error:", error.message);
|
|
382
|
+
// Handle error — e.g., show a toast notification
|
|
383
|
+
}}
|
|
384
|
+
onSuccess={(res) => console.log(res)}
|
|
385
|
+
/>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Common error scenarios:
|
|
389
|
+
- ValuePay script failed to load (network issue, blocked by CSP)
|
|
390
|
+
- `window.ValuepayCheckout` is not available
|
|
391
|
+
- Invalid configuration
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Contributing
|
|
396
|
+
|
|
397
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
398
|
+
|
|
399
|
+
### Development Setup
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Clone the repository
|
|
403
|
+
git clone https://github.com/valuepayment/react-sdk.git
|
|
404
|
+
cd react-sdk
|
|
405
|
+
|
|
406
|
+
# Install dependencies
|
|
407
|
+
npm install
|
|
408
|
+
|
|
409
|
+
# Run tests
|
|
410
|
+
npm test
|
|
411
|
+
|
|
412
|
+
# Run tests in watch mode
|
|
413
|
+
npm run test:watch
|
|
414
|
+
|
|
415
|
+
# Lint
|
|
416
|
+
npm run lint
|
|
417
|
+
|
|
418
|
+
# Type check
|
|
419
|
+
npm run typecheck
|
|
420
|
+
|
|
421
|
+
# Build
|
|
422
|
+
npm run build
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Changelog
|
|
428
|
+
|
|
429
|
+
See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each release.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
MIT (c) [Value Payment Solutions Limited](https://valuepayng.com)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
|
+
var useValuePay = require('../hooks/useValuePay.js');
|
|
5
|
+
|
|
6
|
+
const ValuePayButton = ({ text, className, style, disabled, children, ...config }) => {
|
|
7
|
+
const { initialize, isReady, isProcessing } = useValuePay.useValuePay(config);
|
|
8
|
+
const defaultText = `Pay \u20A6${config.amount.toLocaleString()} Now`;
|
|
9
|
+
const buttonText = children || text || defaultText;
|
|
10
|
+
return (jsxRuntime.jsx("button", { type: "button", onClick: () => initialize(), disabled: disabled || !isReady || isProcessing, className: className, style: style, "aria-busy": isProcessing, "aria-label": typeof buttonText === "string" ? buttonText : "Pay Now", children: isProcessing ? "Processing..." : buttonText }));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.ValuePayButton = ValuePayButton;
|
|
14
|
+
//# sourceMappingURL=ValuePayButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ValuePayButton.js","sources":["../../../../src/components/ValuePayButton.tsx"],"sourcesContent":["import React from \"react\";\nimport { useValuePay } from \"../hooks/useValuePay\";\nimport type { ValuePayButtonProps } from \"../types\";\n\nexport const ValuePayButton: React.FC<ValuePayButtonProps> = ({\n text,\n className,\n style,\n disabled,\n children,\n ...config\n}) => {\n const { initialize, isReady, isProcessing } = useValuePay(config);\n\n const defaultText = `Pay \\u20A6${config.amount.toLocaleString()} Now`;\n const buttonText = children || text || defaultText;\n\n return (\n <button\n type=\"button\"\n onClick={() => initialize()}\n disabled={disabled || !isReady || isProcessing}\n className={className}\n style={style}\n aria-busy={isProcessing}\n aria-label={typeof buttonText === \"string\" ? buttonText : \"Pay Now\"}\n >\n {isProcessing ? \"Processing...\" : buttonText}\n </button>\n );\n};\n"],"names":["useValuePay","_jsx"],"mappings":";;;;;MAIa,cAAc,GAAkC,CAAC,EAC5D,IAAI,EACJ,SAAS,EACT,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,GAAG,MAAM,EACV,KAAI;AACH,IAAA,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,GAAGA,uBAAW,CAAC,MAAM,CAAC;IAEjE,MAAM,WAAW,GAAG,CAAA,UAAA,EAAa,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,CAAA,IAAA,CAAM;AACrE,IAAA,MAAM,UAAU,GAAG,QAAQ,IAAI,IAAI,IAAI,WAAW;IAElD,QACEC,cAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,MAAM,UAAU,EAAE,EAC3B,QAAQ,EAAE,QAAQ,IAAI,CAAC,OAAO,IAAI,YAAY,EAC9C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,KAAK,EAAA,WAAA,EACD,YAAY,EAAA,YAAA,EACX,OAAO,UAAU,KAAK,QAAQ,GAAG,UAAU,GAAG,SAAS,EAAA,QAAA,EAElE,YAAY,GAAG,eAAe,GAAG,UAAU,EAAA,CACrC;AAEb;;;;"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var useValuePay = require('../hooks/useValuePay.js');
|
|
5
|
+
|
|
6
|
+
const ValuePayProvider = react.forwardRef(({ config }, ref) => {
|
|
7
|
+
const { initialize } = useValuePay.useValuePay(config);
|
|
8
|
+
react.useImperativeHandle(ref, () => ({
|
|
9
|
+
initialize: (overrides) => initialize(overrides),
|
|
10
|
+
}), [initialize]);
|
|
11
|
+
// Renders nothing — purely a data/logic container
|
|
12
|
+
return null;
|
|
13
|
+
});
|
|
14
|
+
ValuePayProvider.displayName = "ValuePayProvider";
|
|
15
|
+
|
|
16
|
+
exports.ValuePayProvider = ValuePayProvider;
|
|
17
|
+
//# sourceMappingURL=ValuePayProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ValuePayProvider.js","sources":["../../../../src/components/ValuePayProvider.tsx"],"sourcesContent":["import { useImperativeHandle, forwardRef } from \"react\";\nimport { useValuePay } from \"../hooks/useValuePay\";\nimport type { ValuePayProviderProps, ValuePayProviderRef } from \"../types\";\n\nexport const ValuePayProvider = forwardRef<ValuePayProviderRef, ValuePayProviderProps>(\n ({ config }, ref) => {\n const { initialize } = useValuePay(config);\n\n useImperativeHandle(\n ref,\n () => ({\n initialize: (overrides) => initialize(overrides),\n }),\n [initialize],\n );\n\n // Renders nothing — purely a data/logic container\n return null;\n },\n);\n\nValuePayProvider.displayName = \"ValuePayProvider\";\n"],"names":["forwardRef","useValuePay","useImperativeHandle"],"mappings":";;;;;AAIO,MAAM,gBAAgB,GAAGA,gBAAU,CACxC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,KAAI;IAClB,MAAM,EAAE,UAAU,EAAE,GAAGC,uBAAW,CAAC,MAAM,CAAC;AAE1C,IAAAC,yBAAmB,CACjB,GAAG,EACH,OAAO;QACL,UAAU,EAAE,CAAC,SAAS,KAAK,UAAU,CAAC,SAAS,CAAC;AACjD,KAAA,CAAC,EACF,CAAC,UAAU,CAAC,CACb;;AAGD,IAAA,OAAO,IAAI;AACb,CAAC;AAGH,gBAAgB,CAAC,WAAW,GAAG,kBAAkB;;;;"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Dynamically loads an external script and tracks its loaded state.
|
|
7
|
+
* Deduplicates — won't inject the same script twice.
|
|
8
|
+
*/
|
|
9
|
+
const useScript = (src) => {
|
|
10
|
+
const [loaded, setLoaded] = react.useState(false);
|
|
11
|
+
const scriptRef = react.useRef(null);
|
|
12
|
+
react.useEffect(() => {
|
|
13
|
+
if (!src || typeof document === "undefined")
|
|
14
|
+
return;
|
|
15
|
+
// Check if already loaded
|
|
16
|
+
const existing = document.querySelector(`script[src="${src}"]`);
|
|
17
|
+
if (existing) {
|
|
18
|
+
setLoaded(true);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const script = document.createElement("script");
|
|
22
|
+
script.src = src;
|
|
23
|
+
script.async = true;
|
|
24
|
+
script.onload = () => setLoaded(true);
|
|
25
|
+
script.onerror = () => {
|
|
26
|
+
console.error(`[@valuepay/react] Failed to load script: ${src}`);
|
|
27
|
+
};
|
|
28
|
+
document.body.appendChild(script);
|
|
29
|
+
scriptRef.current = script;
|
|
30
|
+
return () => {
|
|
31
|
+
if (scriptRef.current && document.body.contains(scriptRef.current)) {
|
|
32
|
+
document.body.removeChild(scriptRef.current);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}, [src]);
|
|
36
|
+
return loaded;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
exports.useScript = useScript;
|
|
40
|
+
//# sourceMappingURL=useScript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScript.js","sources":["../../../../src/hooks/useScript.ts"],"sourcesContent":["import { useEffect, useState, useRef } from \"react\";\n\n/**\n * Dynamically loads an external script and tracks its loaded state.\n * Deduplicates — won't inject the same script twice.\n */\nexport const useScript = (src: string): boolean => {\n const [loaded, setLoaded] = useState(false);\n const scriptRef = useRef<HTMLScriptElement | null>(null);\n\n useEffect(() => {\n if (!src || typeof document === \"undefined\") return;\n\n // Check if already loaded\n const existing = document.querySelector<HTMLScriptElement>(`script[src=\"${src}\"]`);\n if (existing) {\n setLoaded(true);\n return;\n }\n\n const script = document.createElement(\"script\");\n script.src = src;\n script.async = true;\n script.onload = () => setLoaded(true);\n script.onerror = () => {\n console.error(`[@valuepay/react] Failed to load script: ${src}`);\n };\n\n document.body.appendChild(script);\n scriptRef.current = script;\n\n return () => {\n if (scriptRef.current && document.body.contains(scriptRef.current)) {\n document.body.removeChild(scriptRef.current);\n }\n };\n }, [src]);\n\n return loaded;\n};\n"],"names":["useState","useRef","useEffect"],"mappings":";;;;AAEA;;;AAGG;AACI,MAAM,SAAS,GAAG,CAAC,GAAW,KAAa;IAChD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAGA,cAAQ,CAAC,KAAK,CAAC;AAC3C,IAAA,MAAM,SAAS,GAAGC,YAAM,CAA2B,IAAI,CAAC;IAExDC,eAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE;;QAG7C,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAoB,CAAA,YAAA,EAAe,GAAG,CAAA,EAAA,CAAI,CAAC;QAClF,IAAI,QAAQ,EAAE;YACZ,SAAS,CAAC,IAAI,CAAC;YACf;QACF;QAEA,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;AAC/C,QAAA,MAAM,CAAC,GAAG,GAAG,GAAG;AAChB,QAAA,MAAM,CAAC,KAAK,GAAG,IAAI;QACnB,MAAM,CAAC,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC;AACrC,QAAA,MAAM,CAAC,OAAO,GAAG,MAAK;AACpB,YAAA,OAAO,CAAC,KAAK,CAAC,4CAA4C,GAAG,CAAA,CAAE,CAAC;AAClE,QAAA,CAAC;AAED,QAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;AACjC,QAAA,SAAS,CAAC,OAAO,GAAG,MAAM;AAE1B,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,SAAS,CAAC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;gBAClE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC;YAC9C;AACF,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;AAET,IAAA,OAAO,MAAM;AACf;;;;"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var useScript = require('./useScript.js');
|
|
5
|
+
var helpers = require('../utils/helpers.js');
|
|
6
|
+
var constants = require('../utils/constants.js');
|
|
7
|
+
|
|
8
|
+
const useValuePay = (config) => {
|
|
9
|
+
const scriptUrl = config.scriptUrl || constants.DEFAULT_SCRIPT_URL;
|
|
10
|
+
const isReady = useScript.useScript(scriptUrl);
|
|
11
|
+
const [isProcessing, setIsProcessing] = react.useState(false);
|
|
12
|
+
const configRef = react.useRef(config);
|
|
13
|
+
configRef.current = config;
|
|
14
|
+
const initialize = react.useCallback((overrides) => {
|
|
15
|
+
var _a;
|
|
16
|
+
const cfg = configRef.current;
|
|
17
|
+
if (typeof window === "undefined" || !window.ValuepayCheckout) {
|
|
18
|
+
(_a = cfg.onError) === null || _a === void 0 ? void 0 : _a.call(cfg, new Error("ValuePay script not loaded. Ensure the script URL is accessible."));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (isProcessing)
|
|
22
|
+
return;
|
|
23
|
+
setIsProcessing(true);
|
|
24
|
+
const normalized = helpers.normalizeConfig(cfg, overrides);
|
|
25
|
+
const ref = normalized.transactionRef;
|
|
26
|
+
window.ValuepayCheckout({
|
|
27
|
+
...normalized,
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
oncallback: (response) => {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
const res = helpers.normalizeResponse(response, ref, cfg);
|
|
32
|
+
setIsProcessing(false);
|
|
33
|
+
if (cfg.redirectUrl) {
|
|
34
|
+
const url = new URL(cfg.redirectUrl);
|
|
35
|
+
url.searchParams.set("ref", res.ref);
|
|
36
|
+
url.searchParams.set("status", res.status);
|
|
37
|
+
window.location.href = url.toString();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
(_a = cfg.onCallback) === null || _a === void 0 ? void 0 : _a.call(cfg, res);
|
|
41
|
+
if (res.status === "SUCCESS" || res.status === "COMPLETED") {
|
|
42
|
+
(_b = cfg.onSuccess) === null || _b === void 0 ? void 0 : _b.call(cfg, res);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
onCancelled: (response) => {
|
|
47
|
+
var _a;
|
|
48
|
+
const res = helpers.normalizeResponse(response, ref, cfg);
|
|
49
|
+
res.status = "CANCELLED";
|
|
50
|
+
setIsProcessing(false);
|
|
51
|
+
if (cfg.redirectUrl) {
|
|
52
|
+
const url = new URL(cfg.redirectUrl);
|
|
53
|
+
url.searchParams.set("ref", res.ref);
|
|
54
|
+
url.searchParams.set("status", "CANCELLED");
|
|
55
|
+
window.location.href = url.toString();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
(_a = cfg.onCancelled) === null || _a === void 0 ? void 0 : _a.call(cfg, res);
|
|
59
|
+
},
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
onAborted: (response) => {
|
|
62
|
+
var _a;
|
|
63
|
+
const res = helpers.normalizeResponse(response, ref, cfg);
|
|
64
|
+
res.status = "CANCELLED";
|
|
65
|
+
setIsProcessing(false);
|
|
66
|
+
(_a = cfg.onCancelled) === null || _a === void 0 ? void 0 : _a.call(cfg, res);
|
|
67
|
+
},
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
onClosed: (response) => {
|
|
70
|
+
var _a;
|
|
71
|
+
const res = helpers.normalizeResponse(response, ref, cfg);
|
|
72
|
+
setIsProcessing(false);
|
|
73
|
+
if (cfg.redirectUrl) {
|
|
74
|
+
const url = new URL(cfg.redirectUrl);
|
|
75
|
+
url.searchParams.set("ref", res.ref);
|
|
76
|
+
url.searchParams.set("status", "CLOSED");
|
|
77
|
+
window.location.href = url.toString();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
(_a = cfg.onClose) === null || _a === void 0 ? void 0 : _a.call(cfg, res);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
|
+
[isReady, isProcessing]);
|
|
86
|
+
return { initialize, isReady, isProcessing };
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
exports.useValuePay = useValuePay;
|
|
90
|
+
//# sourceMappingURL=useValuePay.js.map
|