@valuepay/react 1.0.0 → 2.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 CHANGED
@@ -1,435 +1,505 @@
1
- # @valuepay/react
2
-
3
- > Official React SDK for [ValuePay](https://valuepayng.com) — accept payments in your React app with minimal setup.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@valuepay/react.svg)](https://www.npmjs.com/package/@valuepay/react)
6
- [![license](https://img.shields.io/npm/l/@valuepay/react.svg)](LICENSE)
7
- [![coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)]()
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)]()
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)
1
+ # @valuepay/react
2
+
3
+ > Official React SDK for [ValuePay](https://valuepayng.com) — accept payments in your React app with minimal setup.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@valuepay/react.svg)](https://www.npmjs.com/package/@valuepay/react)
6
+ [![license](https://img.shields.io/npm/l/@valuepay/react.svg)](LICENSE)
7
+ [![coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)]()
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)]()
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 for the button element |
184
+ | `style` | `CSSProperties` | — | Inline styles for the button (color, font, padding, etc.) |
185
+ | `containerStyle` | `CSSProperties` | | Inline styles for the outer container (background, border, radius, size, margin, etc.) |
186
+ | `containerClassName` | `string` | — | CSS class for the outer container div |
187
+ | `disabled` | `boolean` | `false` | Disable button |
188
+ | `children` | `ReactNode` | — | Custom button content |
189
+
190
+ > **Note:** All button props are optional. When `containerStyle` or `containerClassName` is provided, the button is wrapped in a `<div>` with those styles. Otherwise, no wrapper is rendered.
191
+
192
+ #### Styling Example
193
+
194
+ ```tsx
195
+ <ValuePayButton
196
+ publicKey="KP_xxx"
197
+ amount={5000}
198
+ customer={{ email: "user@example.com", fullName: "User" }}
199
+ containerStyle={{
200
+ width: "100%",
201
+ maxWidth: "400px",
202
+ margin: "20px auto",
203
+ borderRadius: "12px",
204
+ border: "1px solid #e0e0e0",
205
+ backgroundColor: "#f9f9f9",
206
+ padding: "16px",
207
+ }}
208
+ style={{
209
+ width: "100%",
210
+ color: "#fff",
211
+ backgroundColor: "#4CAF50",
212
+ fontSize: "16px",
213
+ padding: "12px 24px",
214
+ borderRadius: "8px",
215
+ border: "none",
216
+ cursor: "pointer",
217
+ }}
218
+ onSuccess={(res) => console.log(res)}
219
+ />
220
+ ```
221
+
222
+ ### `<ValuePayProvider />` Props
223
+
224
+ | Prop | Type | Description |
225
+ | -------- | ------------------------- | -------------------------------- |
226
+ | `config` | `ValuePayConfig` | Payment configuration |
227
+ | `ref` | `Ref<ValuePayProviderRef>`| Exposes `initialize()` method |
228
+
229
+ ### `ValuePayConfig`
230
+
231
+ | Prop | Type | Required | Default | Description |
232
+ | ---------------- | ---------------------------- | -------- | ------------------------------------ | ---------------------------------- |
233
+ | `publicKey` | `string` | Yes | — | Your ValuePay public key |
234
+ | `amount` | `number` | Yes | — | Amount in Naira |
235
+ | `customer` | `{ email, fullName, phone? }`| Yes | — | Customer details |
236
+ | `currency` | `string` | — | `"NGN"` | Currency code |
237
+ | `channels` | `PaymentChannel[]` | — | `["card","transfer","qrcode","ussd"]`| Enabled channels |
238
+ | `transactionRef` | `string` | — | Auto-generated | Custom transaction reference. `VPS_TX_` prefix auto-prepended if missing |
239
+ | `redirectUrl` | `string` | — | — | Redirect URL after payment |
240
+ | `metaData` | `Record<string, unknown>` | — | — | Custom metadata |
241
+ | `customization` | `{ title?, description?, logoUrl? }` | — | — | Checkout modal branding |
242
+ | `onSuccess` | `(response) => void` | — | — | Called on successful payment |
243
+ | `onClose` | `(response) => void` | — | — | Called when modal is closed |
244
+ | `onCancelled` | `(response) => void` | — | — | Called when payment is cancelled |
245
+ | `onCallback` | `(response) => void` | — | — | Called on any payment event |
246
+ | `onError` | `(error) => void` | — | — | Called on SDK errors |
247
+ | `scriptUrl` | `string` | — | Production CDN | Override ValuePay script URL |
248
+
249
+ ### `ValuePayResponse`
250
+
251
+ | Field | Type | Description |
252
+ | ---------------- | ------------------ | --------------------------------------------------------------------------------------------- |
253
+ | `ref` | `string` | Transaction reference |
254
+ | `transactionRef` | `string` | Same as `ref` |
255
+ | `status` | `ValuePayStatus` | `"SUCCESS"`, `"COMPLETED"`, `"FAILED"`, `"CANCELLED"`, `"DUPLICATE"`, `"PENDING"` |
256
+ | `amount` | `number` | Transaction amount |
257
+ | `currency` | `string` | Currency code |
258
+ | `customer` | `ValuePayCustomer` | Customer details |
259
+ | `paymentMethod` | `string?` | Payment method used |
260
+ | `validation` | `{ status }?` | Server-side validation result |
261
+ | `raw` | `unknown?` | Raw ValuePay response |
262
+
263
+ ---
264
+
265
+ ## Dynamic Amounts
266
+
267
+ Override any config at call time:
268
+
269
+ ```tsx
270
+ const { initialize } = useValuePay({
271
+ publicKey: "KP_xxx",
272
+ amount: 0, // placeholder
273
+ customer: { email: "user@example.com", fullName: "User" },
274
+ onSuccess: (res) => console.log(res),
275
+ });
276
+
277
+ // Later, with dynamic amount:
278
+ initialize({ amount: selectedProduct.price });
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Custom Transaction References
284
+
285
+ By default, a unique reference is auto-generated for every payment. If your business needs to control references (e.g. mapping to internal order IDs), pass your own `transactionRef`:
286
+
287
+ ```tsx
288
+ <ValuePayButton
289
+ publicKey="KP_xxx"
290
+ amount={5000}
291
+ customer={{ email: "user@example.com", fullName: "User" }}
292
+ transactionRef="ORDER_12345"
293
+ onSuccess={(res) => console.log(res.ref)} // "VPS_TX_ORDER_12345"
294
+ />
295
+ ```
296
+
297
+ The `VPS_TX_` prefix is **always** prepended automatically. If your reference already includes the prefix, it won't be doubled:
298
+
299
+ ```tsx
300
+ transactionRef="VPS_TX_ORDER_12345" // stays "VPS_TX_ORDER_12345"
301
+ transactionRef="ORDER_12345" // becomes "VPS_TX_ORDER_12345"
302
+ ```
303
+
304
+ This also works with the hook and provider:
305
+
306
+ ```tsx
307
+ // Hook — set at config time
308
+ const { initialize } = useValuePay({
309
+ ...config,
310
+ transactionRef: "INV_9876",
311
+ });
312
+
313
+ // Or override at call time
314
+ initialize({ transactionRef: "INV_9876" });
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Payment Channels
320
+
321
+ | Channel | Value |
322
+ | -------------- | ---------------- |
323
+ | Card | `"card"` |
324
+ | Bank Transfer | `"transfer"` |
325
+ | QR Code | `"qrcode"` |
326
+ | USSD | `"ussd"` |
327
+ | Mobile Money | `"mobile_money"` |
328
+
329
+ ---
330
+
331
+ ## Customizing the Checkout Modal
332
+
333
+ ```tsx
334
+ <ValuePayButton
335
+ publicKey="KP_xxx"
336
+ amount={2000}
337
+ customer={{ email: "user@example.com", fullName: "User" }}
338
+ customization={{
339
+ title: "My Store",
340
+ description: "Thanks for shopping with us!",
341
+ logoUrl: "https://mystore.com/logo.png",
342
+ }}
343
+ onSuccess={(res) => console.log(res)}
344
+ />
345
+ ```
346
+
347
+ ---
348
+
349
+ ## Server-Side Verification
350
+
351
+ **Always verify transactions on your server.** Never trust client-side callbacks alone. Use the `ref` from the response:
352
+
353
+ ```ts
354
+ // Backend (Node.js example)
355
+ const response = await fetch(
356
+ "https://api.valuepayng.com/v1/transactions/verify",
357
+ {
358
+ method: "POST",
359
+ headers: {
360
+ "Content-Type": "application/json",
361
+ Authorization: `Bearer ${VALUEPAY_SECRET_KEY}`,
362
+ },
363
+ body: JSON.stringify({ tx_ref: transactionRef }),
364
+ },
365
+ );
366
+ const result = await response.json();
367
+ ```
368
+
369
+ ---
370
+
371
+ ## TypeScript
372
+
373
+ All types are exported:
374
+
375
+ ```ts
376
+ import type {
377
+ ValuePayConfig,
378
+ ValuePayResponse,
379
+ ValuePayCustomer,
380
+ ValuePayCustomization,
381
+ PaymentChannel,
382
+ ValuePayStatus,
383
+ UseValuePayReturn,
384
+ ValuePayButtonProps,
385
+ ValuePayProviderProps,
386
+ ValuePayProviderRef,
387
+ } from "@valuepay/react";
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Browser Support
393
+
394
+ | Browser | Minimum Version | Release Year |
395
+ | -------------------- | --------------- | ------------ |
396
+ | Chrome | 49+ | 2016 |
397
+ | Firefox | 52+ | 2017 |
398
+ | Safari | 10+ | 2016 |
399
+ | Edge | 15+ | 2017 |
400
+ | Opera | 36+ | 2016 |
401
+ | Samsung Internet | 5.0+ | 2016 |
402
+ | iOS Safari | 10+ | 2016 |
403
+ | Android WebView | 49+ | 2016 |
404
+ | Internet Explorer | 11 (with polyfills) | 2013 |
405
+
406
+ 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.
407
+
408
+ ### SSR Compatibility
409
+
410
+ The SDK is SSR-safe and works with:
411
+ - Next.js (Pages Router and App Router)
412
+ - Remix
413
+ - Gatsby
414
+ - Any server-side rendering framework
415
+
416
+ All DOM access is guarded with `typeof document !== "undefined"` and `typeof window !== "undefined"` checks.
417
+
418
+ ---
419
+
420
+ ## Security
421
+
422
+ - **Never** expose your secret key in client-side code. Only use your **public key** with this SDK.
423
+ - **Always** verify transactions server-side before fulfilling orders — client-side callbacks can be spoofed.
424
+ - **Use HTTPS** in production to prevent man-in-the-middle attacks.
425
+ - **Generate unique transaction references** per payment to prevent duplicate charges.
426
+ - The SDK loads the ValuePay checkout script from `https://www.valuepayng.com` — ensure this domain is allowed in your Content Security Policy (CSP).
427
+ - 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.
428
+ - Transaction references are generated using cryptographically sufficient randomness via `Math.random()` with alphanumeric characters.
429
+
430
+ ### Content Security Policy (CSP)
431
+
432
+ If your application uses a Content Security Policy, add the following directives:
433
+
434
+ ```
435
+ script-src 'self' https://www.valuepayng.com;
436
+ frame-src 'self' https://www.valuepayng.com;
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Error Handling
442
+
443
+ The SDK provides an `onError` callback for handling SDK-level errors:
444
+
445
+ ```tsx
446
+ <ValuePayButton
447
+ publicKey="KP_xxx"
448
+ amount={5000}
449
+ customer={{ email: "user@example.com", fullName: "User" }}
450
+ onError={(error) => {
451
+ console.error("Payment SDK error:", error.message);
452
+ // Handle error — e.g., show a toast notification
453
+ }}
454
+ onSuccess={(res) => console.log(res)}
455
+ />
456
+ ```
457
+
458
+ Common error scenarios:
459
+ - ValuePay script failed to load (network issue, blocked by CSP)
460
+ - `window.ValuepayCheckout` is not available
461
+ - Invalid configuration
462
+
463
+ ---
464
+
465
+ ## Contributing
466
+
467
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
468
+
469
+ ### Development Setup
470
+
471
+ ```bash
472
+ # Clone the repository
473
+ git clone https://github.com/valuepayment/react-sdk.git
474
+ cd react-sdk
475
+
476
+ # Install dependencies
477
+ npm install
478
+
479
+ # Run tests
480
+ npm test
481
+
482
+ # Run tests in watch mode
483
+ npm run test:watch
484
+
485
+ # Lint
486
+ npm run lint
487
+
488
+ # Type check
489
+ npm run typecheck
490
+
491
+ # Build
492
+ npm run build
493
+ ```
494
+
495
+ ---
496
+
497
+ ## Changelog
498
+
499
+ See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each release.
500
+
501
+ ---
502
+
503
+ ## License
504
+
505
+ MIT (c) [Value Payment Solutions Limited](https://valuepayng.com)
@@ -3,11 +3,15 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var useValuePay = require('../hooks/useValuePay.js');
5
5
 
6
- const ValuePayButton = ({ text, className, style, disabled, children, ...config }) => {
6
+ const ValuePayButton = ({ text, className, style, containerStyle, containerClassName, disabled, children, ...config }) => {
7
7
  const { initialize, isReady, isProcessing } = useValuePay.useValuePay(config);
8
8
  const defaultText = `Pay \u20A6${config.amount.toLocaleString()} Now`;
9
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 }));
10
+ const button = (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
+ if (containerStyle || containerClassName) {
12
+ return (jsxRuntime.jsx("div", { style: containerStyle, className: containerClassName, "data-testid": "valuepay-button-container", children: button }));
13
+ }
14
+ return button;
11
15
  };
12
16
 
13
17
  exports.ValuePayButton = ValuePayButton;
@@ -1 +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;;;;"}
1
+ {"version":3,"file":"ValuePayButton.js","sources":["../../../../src/components/ValuePayButton.tsx"],"sourcesContent":["import React from \"react\";\r\nimport { useValuePay } from \"../hooks/useValuePay\";\r\nimport type { ValuePayButtonProps } from \"../types\";\r\n\r\nexport const ValuePayButton: React.FC<ValuePayButtonProps> = ({\r\n text,\r\n className,\r\n style,\r\n containerStyle,\r\n containerClassName,\r\n disabled,\r\n children,\r\n ...config\r\n}) => {\r\n const { initialize, isReady, isProcessing } = useValuePay(config);\r\n\r\n const defaultText = `Pay \\u20A6${config.amount.toLocaleString()} Now`;\r\n const buttonText = children || text || defaultText;\r\n\r\n const button = (\r\n <button\r\n type=\"button\"\r\n onClick={() => initialize()}\r\n disabled={disabled || !isReady || isProcessing}\r\n className={className}\r\n style={style}\r\n aria-busy={isProcessing}\r\n aria-label={typeof buttonText === \"string\" ? buttonText : \"Pay Now\"}\r\n >\r\n {isProcessing ? \"Processing...\" : buttonText}\r\n </button>\r\n );\r\n\r\n if (containerStyle || containerClassName) {\r\n return (\r\n <div style={containerStyle} className={containerClassName} data-testid=\"valuepay-button-container\">\r\n {button}\r\n </div>\r\n );\r\n }\r\n\r\n return button;\r\n};\r\n"],"names":["useValuePay","_jsx"],"mappings":";;;;;AAIO,MAAM,cAAc,GAAkC,CAAC,EAC5D,IAAI,EACJ,SAAS,EACT,KAAK,EACL,cAAc,EACd,kBAAkB,EAClB,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,MAAM,MAAM,IACVC,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,CACV;AAED,IAAA,IAAI,cAAc,IAAI,kBAAkB,EAAE;AACxC,QAAA,QACEA,cAAA,CAAA,KAAA,EAAA,EAAK,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,kBAAkB,iBAAc,2BAA2B,EAAA,QAAA,EAC/F,MAAM,EAAA,CACH;IAEV;AAEA,IAAA,OAAO,MAAM;AACf;;;;"}
package/dist/cjs/index.js CHANGED
@@ -12,5 +12,6 @@ exports.ValuePayButton = ValuePayButton.ValuePayButton;
12
12
  exports.ValuePayProvider = ValuePayProvider.ValuePayProvider;
13
13
  exports.useValuePay = useValuePay.useValuePay;
14
14
  exports.useScript = useScript.useScript;
15
+ exports.ensureRefPrefix = helpers.ensureRefPrefix;
15
16
  exports.generateTransactionRef = helpers.generateTransactionRef;
16
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
@@ -13,12 +13,22 @@ const generateTransactionRef = (length = 20) => {
13
13
  }
14
14
  return result;
15
15
  };
16
+ /**
17
+ * Ensure the transaction reference always carries the VPS_TX_ prefix.
18
+ * If the merchant supplies their own ref, the prefix is prepended (unless already present).
19
+ * If omitted, a fully random reference is generated.
20
+ */
21
+ const ensureRefPrefix = (ref) => {
22
+ return ref.startsWith(constants.TRANSACTION_REF_PREFIX) ? ref : `${constants.TRANSACTION_REF_PREFIX}${ref}`;
23
+ };
16
24
  /**
17
25
  * Normalize the SDK config into the shape expected by window.ValuepayCheckout.
18
26
  */
19
27
  const normalizeConfig = (config, overrides) => {
20
28
  const merged = { ...config, ...overrides };
21
- const ref = merged.transactionRef || generateTransactionRef();
29
+ const ref = merged.transactionRef
30
+ ? ensureRefPrefix(merged.transactionRef)
31
+ : generateTransactionRef();
22
32
  return {
23
33
  public_key: merged.publicKey,
24
34
  transactionRef: ref,
@@ -61,6 +71,7 @@ const normalizeResponse = (raw, ref, config) => {
61
71
  };
62
72
  };
63
73
 
74
+ exports.ensureRefPrefix = ensureRefPrefix;
64
75
  exports.generateTransactionRef = generateTransactionRef;
65
76
  exports.normalizeConfig = normalizeConfig;
66
77
  exports.normalizeResponse = normalizeResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sources":["../../../../src/utils/helpers.ts"],"sourcesContent":["import type { ValuePayConfig, ValuePayResponse } from \"../types\";\nimport { DEFAULT_CURRENCY, DEFAULT_CHANNELS, TRANSACTION_REF_PREFIX } from \"./constants\";\n\n/**\n * Generate a unique transaction reference.\n */\nexport const generateTransactionRef = (length: number = 20): string => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n let result = TRANSACTION_REF_PREFIX;\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n};\n\n/**\n * Normalize the SDK config into the shape expected by window.ValuepayCheckout.\n */\nexport const normalizeConfig = (config: ValuePayConfig, overrides?: Partial<ValuePayConfig>) => {\n const merged = { ...config, ...overrides };\n const ref = merged.transactionRef || generateTransactionRef();\n\n return {\n public_key: merged.publicKey,\n transactionRef: ref,\n amount: merged.amount,\n currency: merged.currency || DEFAULT_CURRENCY,\n channels: merged.channels || DEFAULT_CHANNELS,\n type: \"default\",\n redirect_url: merged.redirectUrl,\n metaData: merged.metaData,\n customer: {\n email: merged.customer.email,\n fullName: merged.customer.fullName,\n phone: merged.customer.phone,\n },\n customisedCheckout: merged.customization\n ? {\n title: merged.customization.title,\n description: merged.customization.description,\n logoLink: merged.customization.logoUrl,\n }\n : undefined,\n };\n};\n\n/**\n * Normalize the raw ValuePay response into our SDK response shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const normalizeResponse = (raw: any, ref: string, config: ValuePayConfig): ValuePayResponse => {\n return {\n ref: raw?.ref || raw?.reference || raw?.tx_ref || ref,\n transactionRef: raw?.transactionRef || ref,\n status: raw?.status || raw?.validation?.status || \"PENDING\",\n amount: config.amount,\n currency: config.currency || DEFAULT_CURRENCY,\n customer: config.customer,\n paymentMethod: raw?.paymentMethod,\n validation: raw?.validation,\n raw,\n };\n};\n"],"names":["TRANSACTION_REF_PREFIX","DEFAULT_CURRENCY","DEFAULT_CHANNELS"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.js","sources":["../../../../src/utils/helpers.ts"],"sourcesContent":["import type { ValuePayConfig, ValuePayResponse } from \"../types\";\r\nimport { DEFAULT_CURRENCY, DEFAULT_CHANNELS, TRANSACTION_REF_PREFIX } from \"./constants\";\r\n\r\n/**\r\n * Generate a unique transaction reference.\r\n */\r\nexport const generateTransactionRef = (length: number = 20): string => {\r\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\r\n let result = TRANSACTION_REF_PREFIX;\r\n for (let i = 0; i < length; i++) {\r\n result += chars.charAt(Math.floor(Math.random() * chars.length));\r\n }\r\n return result;\r\n};\r\n\r\n/**\r\n * Ensure the transaction reference always carries the VPS_TX_ prefix.\r\n * If the merchant supplies their own ref, the prefix is prepended (unless already present).\r\n * If omitted, a fully random reference is generated.\r\n */\r\nexport const ensureRefPrefix = (ref: string): string => {\r\n return ref.startsWith(TRANSACTION_REF_PREFIX) ? ref : `${TRANSACTION_REF_PREFIX}${ref}`;\r\n};\r\n\r\n/**\r\n * Normalize the SDK config into the shape expected by window.ValuepayCheckout.\r\n */\r\nexport const normalizeConfig = (config: ValuePayConfig, overrides?: Partial<ValuePayConfig>) => {\r\n const merged = { ...config, ...overrides };\r\n const ref = merged.transactionRef\r\n ? ensureRefPrefix(merged.transactionRef)\r\n : generateTransactionRef();\r\n\r\n return {\r\n public_key: merged.publicKey,\r\n transactionRef: ref,\r\n amount: merged.amount,\r\n currency: merged.currency || DEFAULT_CURRENCY,\r\n channels: merged.channels || DEFAULT_CHANNELS,\r\n type: \"default\",\r\n redirect_url: merged.redirectUrl,\r\n metaData: merged.metaData,\r\n customer: {\r\n email: merged.customer.email,\r\n fullName: merged.customer.fullName,\r\n phone: merged.customer.phone,\r\n },\r\n customisedCheckout: merged.customization\r\n ? {\r\n title: merged.customization.title,\r\n description: merged.customization.description,\r\n logoLink: merged.customization.logoUrl,\r\n }\r\n : undefined,\r\n };\r\n};\r\n\r\n/**\r\n * Normalize the raw ValuePay response into our SDK response shape.\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport const normalizeResponse = (raw: any, ref: string, config: ValuePayConfig): ValuePayResponse => {\r\n return {\r\n ref: raw?.ref || raw?.reference || raw?.tx_ref || ref,\r\n transactionRef: raw?.transactionRef || ref,\r\n status: raw?.status || raw?.validation?.status || \"PENDING\",\r\n amount: config.amount,\r\n currency: config.currency || DEFAULT_CURRENCY,\r\n customer: config.customer,\r\n paymentMethod: raw?.paymentMethod,\r\n validation: raw?.validation,\r\n raw,\r\n };\r\n};\r\n"],"names":["TRANSACTION_REF_PREFIX","DEFAULT_CURRENCY","DEFAULT_CHANNELS"],"mappings":";;;;AAGA;;AAEG;MACU,sBAAsB,GAAG,CAAC,MAAA,GAAiB,EAAE,KAAY;IACpE,MAAM,KAAK,GAAG,gEAAgE;IAC9E,IAAI,MAAM,GAAGA,gCAAsB;AACnC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;AAC/B,QAAA,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAClE;AACA,IAAA,OAAO,MAAM;AACf;AAEA;;;;AAIG;AACI,MAAM,eAAe,GAAG,CAAC,GAAW,KAAY;AACrD,IAAA,OAAO,GAAG,CAAC,UAAU,CAACA,gCAAsB,CAAC,GAAG,GAAG,GAAG,CAAA,EAAGA,gCAAsB,CAAA,EAAG,GAAG,EAAE;AACzF;AAEA;;AAEG;MACU,eAAe,GAAG,CAAC,MAAsB,EAAE,SAAmC,KAAI;IAC7F,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE;AAC1C,IAAA,MAAM,GAAG,GAAG,MAAM,CAAC;AACjB,UAAE,eAAe,CAAC,MAAM,CAAC,cAAc;UACrC,sBAAsB,EAAE;IAE5B,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,SAAS;AAC5B,QAAA,cAAc,EAAE,GAAG;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;AACrB,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAIC,0BAAgB;AAC7C,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAIC,0BAAgB;AAC7C,QAAA,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,MAAM,CAAC,WAAW;QAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACzB,QAAA,QAAQ,EAAE;AACR,YAAA,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;AAC5B,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;AAClC,YAAA,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;AAC7B,SAAA;QACD,kBAAkB,EAAE,MAAM,CAAC;AACzB,cAAE;AACE,gBAAA,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,KAAK;AACjC,gBAAA,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,WAAW;AAC7C,gBAAA,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO;AACvC;AACH,cAAE,SAAS;KACd;AACH;AAEA;;AAEG;AACH;AACO,MAAM,iBAAiB,GAAG,CAAC,GAAQ,EAAE,GAAW,EAAE,MAAsB,KAAsB;;IACnG,OAAO;AACL,QAAA,GAAG,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,GAAG,MAAI,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,SAAS,CAAA,KAAI,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,MAAM,CAAA,IAAI,GAAG;QACrD,cAAc,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,uBAAH,GAAG,CAAE,cAAc,KAAI,GAAG;QAC1C,MAAM,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,MAAM,MAAI,CAAA,EAAA,GAAA,GAAG,KAAA,IAAA,IAAH,GAAG,uBAAH,GAAG,CAAE,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,MAAM,CAAA,IAAI,SAAS;QAC3D,MAAM,EAAE,MAAM,CAAC,MAAM;AACrB,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAID,0BAAgB;QAC7C,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACzB,QAAA,aAAa,EAAE,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,aAAa;AACjC,QAAA,UAAU,EAAE,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,UAAU;QAC3B,GAAG;KACJ;AACH;;;;;;;"}
@@ -1,11 +1,15 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { useValuePay } from '../hooks/useValuePay.js';
3
3
 
4
- const ValuePayButton = ({ text, className, style, disabled, children, ...config }) => {
4
+ const ValuePayButton = ({ text, className, style, containerStyle, containerClassName, disabled, children, ...config }) => {
5
5
  const { initialize, isReady, isProcessing } = useValuePay(config);
6
6
  const defaultText = `Pay \u20A6${config.amount.toLocaleString()} Now`;
7
7
  const buttonText = children || text || defaultText;
8
- return (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 }));
8
+ const button = (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 }));
9
+ if (containerStyle || containerClassName) {
10
+ return (jsx("div", { style: containerStyle, className: containerClassName, "data-testid": "valuepay-button-container", children: button }));
11
+ }
12
+ return button;
9
13
  };
10
14
 
11
15
  export { ValuePayButton };
@@ -1 +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":["_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,GAAG,WAAW,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,QACEA,GAAA,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;;;;"}
1
+ {"version":3,"file":"ValuePayButton.js","sources":["../../../../src/components/ValuePayButton.tsx"],"sourcesContent":["import React from \"react\";\r\nimport { useValuePay } from \"../hooks/useValuePay\";\r\nimport type { ValuePayButtonProps } from \"../types\";\r\n\r\nexport const ValuePayButton: React.FC<ValuePayButtonProps> = ({\r\n text,\r\n className,\r\n style,\r\n containerStyle,\r\n containerClassName,\r\n disabled,\r\n children,\r\n ...config\r\n}) => {\r\n const { initialize, isReady, isProcessing } = useValuePay(config);\r\n\r\n const defaultText = `Pay \\u20A6${config.amount.toLocaleString()} Now`;\r\n const buttonText = children || text || defaultText;\r\n\r\n const button = (\r\n <button\r\n type=\"button\"\r\n onClick={() => initialize()}\r\n disabled={disabled || !isReady || isProcessing}\r\n className={className}\r\n style={style}\r\n aria-busy={isProcessing}\r\n aria-label={typeof buttonText === \"string\" ? buttonText : \"Pay Now\"}\r\n >\r\n {isProcessing ? \"Processing...\" : buttonText}\r\n </button>\r\n );\r\n\r\n if (containerStyle || containerClassName) {\r\n return (\r\n <div style={containerStyle} className={containerClassName} data-testid=\"valuepay-button-container\">\r\n {button}\r\n </div>\r\n );\r\n }\r\n\r\n return button;\r\n};\r\n"],"names":["_jsx"],"mappings":";;;AAIO,MAAM,cAAc,GAAkC,CAAC,EAC5D,IAAI,EACJ,SAAS,EACT,KAAK,EACL,cAAc,EACd,kBAAkB,EAClB,QAAQ,EACR,QAAQ,EACR,GAAG,MAAM,EACV,KAAI;AACH,IAAA,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,WAAW,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,MAAM,MAAM,IACVA,GAAA,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,CACV;AAED,IAAA,IAAI,cAAc,IAAI,kBAAkB,EAAE;AACxC,QAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAK,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,kBAAkB,iBAAc,2BAA2B,EAAA,QAAA,EAC/F,MAAM,EAAA,CACH;IAEV;AAEA,IAAA,OAAO,MAAM;AACf;;;;"}
package/dist/esm/index.js CHANGED
@@ -2,5 +2,5 @@ export { ValuePayButton } from './components/ValuePayButton.js';
2
2
  export { ValuePayProvider } from './components/ValuePayProvider.js';
3
3
  export { useValuePay } from './hooks/useValuePay.js';
4
4
  export { useScript } from './hooks/useScript.js';
5
- export { generateTransactionRef } from './utils/helpers.js';
5
+ export { ensureRefPrefix, generateTransactionRef } from './utils/helpers.js';
6
6
  //# sourceMappingURL=index.js.map
@@ -11,12 +11,22 @@ const generateTransactionRef = (length = 20) => {
11
11
  }
12
12
  return result;
13
13
  };
14
+ /**
15
+ * Ensure the transaction reference always carries the VPS_TX_ prefix.
16
+ * If the merchant supplies their own ref, the prefix is prepended (unless already present).
17
+ * If omitted, a fully random reference is generated.
18
+ */
19
+ const ensureRefPrefix = (ref) => {
20
+ return ref.startsWith(TRANSACTION_REF_PREFIX) ? ref : `${TRANSACTION_REF_PREFIX}${ref}`;
21
+ };
14
22
  /**
15
23
  * Normalize the SDK config into the shape expected by window.ValuepayCheckout.
16
24
  */
17
25
  const normalizeConfig = (config, overrides) => {
18
26
  const merged = { ...config, ...overrides };
19
- const ref = merged.transactionRef || generateTransactionRef();
27
+ const ref = merged.transactionRef
28
+ ? ensureRefPrefix(merged.transactionRef)
29
+ : generateTransactionRef();
20
30
  return {
21
31
  public_key: merged.publicKey,
22
32
  transactionRef: ref,
@@ -59,5 +69,5 @@ const normalizeResponse = (raw, ref, config) => {
59
69
  };
60
70
  };
61
71
 
62
- export { generateTransactionRef, normalizeConfig, normalizeResponse };
72
+ export { ensureRefPrefix, generateTransactionRef, normalizeConfig, normalizeResponse };
63
73
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.js","sources":["../../../../src/utils/helpers.ts"],"sourcesContent":["import type { ValuePayConfig, ValuePayResponse } from \"../types\";\nimport { DEFAULT_CURRENCY, DEFAULT_CHANNELS, TRANSACTION_REF_PREFIX } from \"./constants\";\n\n/**\n * Generate a unique transaction reference.\n */\nexport const generateTransactionRef = (length: number = 20): string => {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n let result = TRANSACTION_REF_PREFIX;\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length));\n }\n return result;\n};\n\n/**\n * Normalize the SDK config into the shape expected by window.ValuepayCheckout.\n */\nexport const normalizeConfig = (config: ValuePayConfig, overrides?: Partial<ValuePayConfig>) => {\n const merged = { ...config, ...overrides };\n const ref = merged.transactionRef || generateTransactionRef();\n\n return {\n public_key: merged.publicKey,\n transactionRef: ref,\n amount: merged.amount,\n currency: merged.currency || DEFAULT_CURRENCY,\n channels: merged.channels || DEFAULT_CHANNELS,\n type: \"default\",\n redirect_url: merged.redirectUrl,\n metaData: merged.metaData,\n customer: {\n email: merged.customer.email,\n fullName: merged.customer.fullName,\n phone: merged.customer.phone,\n },\n customisedCheckout: merged.customization\n ? {\n title: merged.customization.title,\n description: merged.customization.description,\n logoLink: merged.customization.logoUrl,\n }\n : undefined,\n };\n};\n\n/**\n * Normalize the raw ValuePay response into our SDK response shape.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const normalizeResponse = (raw: any, ref: string, config: ValuePayConfig): ValuePayResponse => {\n return {\n ref: raw?.ref || raw?.reference || raw?.tx_ref || ref,\n transactionRef: raw?.transactionRef || ref,\n status: raw?.status || raw?.validation?.status || \"PENDING\",\n amount: config.amount,\n currency: config.currency || DEFAULT_CURRENCY,\n customer: config.customer,\n paymentMethod: raw?.paymentMethod,\n validation: raw?.validation,\n raw,\n };\n};\n"],"names":[],"mappings":";;AAGA;;AAEG;MACU,sBAAsB,GAAG,CAAC,MAAA,GAAiB,EAAE,KAAY;IACpE,MAAM,KAAK,GAAG,gEAAgE;IAC9E,IAAI,MAAM,GAAG,sBAAsB;AACnC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;AAC/B,QAAA,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAClE;AACA,IAAA,OAAO,MAAM;AACf;AAEA;;AAEG;MACU,eAAe,GAAG,CAAC,MAAsB,EAAE,SAAmC,KAAI;IAC7F,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,EAAE;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,IAAI,sBAAsB,EAAE;IAE7D,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,SAAS;AAC5B,QAAA,cAAc,EAAE,GAAG;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;AACrB,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,gBAAgB;AAC7C,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,gBAAgB;AAC7C,QAAA,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,MAAM,CAAC,WAAW;QAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACzB,QAAA,QAAQ,EAAE;AACR,YAAA,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;AAC5B,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ;AAClC,YAAA,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;AAC7B,SAAA;QACD,kBAAkB,EAAE,MAAM,CAAC;AACzB,cAAE;AACE,gBAAA,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,KAAK;AACjC,gBAAA,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,WAAW;AAC7C,gBAAA,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO;AACvC;AACH,cAAE,SAAS;KACd;AACH;AAEA;;AAEG;AACH;AACO,MAAM,iBAAiB,GAAG,CAAC,GAAQ,EAAE,GAAW,EAAE,MAAsB,KAAsB;;IACnG,OAAO;AACL,QAAA,GAAG,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,GAAG,MAAI,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,SAAS,CAAA,KAAI,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,MAAM,CAAA,IAAI,GAAG;QACrD,cAAc,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,uBAAH,GAAG,CAAE,cAAc,KAAI,GAAG;QAC1C,MAAM,EAAE,CAAA,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,MAAM,MAAI,CAAA,EAAA,GAAA,GAAG,KAAA,IAAA,IAAH,GAAG,uBAAH,GAAG,CAAE,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,MAAM,CAAA,IAAI,SAAS;QAC3D,MAAM,EAAE,MAAM,CAAC,MAAM;AACrB,QAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,gBAAgB;QAC7C,QAAQ,EAAE,MAAM,CAAC,QAAQ;AACzB,QAAA,aAAa,EAAE,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,aAAa;AACjC,QAAA,UAAU,EAAE,GAAG,KAAA,IAAA,IAAH,GAAG,KAAA,MAAA,GAAA,MAAA,GAAH,GAAG,CAAE,UAAU;QAC3B,GAAG;KACJ;AACH;;;;"}
1
+ {"version":3,"file":"helpers.js","sources":["../../../../src/utils/helpers.ts"],"sourcesContent":["import type { ValuePayConfig, ValuePayResponse } from \"../types\";\r\nimport { DEFAULT_CURRENCY, DEFAULT_CHANNELS, TRANSACTION_REF_PREFIX } from \"./constants\";\r\n\r\n/**\r\n * Generate a unique transaction reference.\r\n */\r\nexport const generateTransactionRef = (length: number = 20): string => {\r\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\r\n let result = TRANSACTION_REF_PREFIX;\r\n for (let i = 0; i < length; i++) {\r\n result += chars.charAt(Math.floor(Math.random() * chars.length));\r\n }\r\n return result;\r\n};\r\n\r\n/**\r\n * Ensure the transaction reference always carries the VPS_TX_ prefix.\r\n * If the merchant supplies their own ref, the prefix is prepended (unless already present).\r\n * If omitted, a fully random reference is generated.\r\n */\r\nexport const ensureRefPrefix = (ref: string): string => {\r\n return ref.startsWith(TRANSACTION_REF_PREFIX) ? ref : `${TRANSACTION_REF_PREFIX}${ref}`;\r\n};\r\n\r\n/**\r\n * Normalize the SDK config into the shape expected by window.ValuepayCheckout.\r\n */\r\nexport const normalizeConfig = (config: ValuePayConfig, overrides?: Partial<ValuePayConfig>) => {\r\n const merged = { ...config, ...overrides };\r\n const ref = merged.transactionRef\r\n ? ensureRefPrefix(merged.transactionRef)\r\n : generateTransactionRef();\r\n\r\n return {\r\n public_key: merged.publicKey,\r\n transactionRef: ref,\r\n amount: merged.amount,\r\n currency: merged.currency || DEFAULT_CURRENCY,\r\n channels: merged.channels || DEFAULT_CHANNELS,\r\n type: \"default\",\r\n redirect_url: merged.redirectUrl,\r\n metaData: merged.metaData,\r\n customer: {\r\n email: merged.customer.email,\r\n fullName: merged.customer.fullName,\r\n phone: merged.customer.phone,\r\n },\r\n customisedCheckout: merged.customization\r\n ? {\r\n title: merged.customization.title,\r\n description: merged.customization.description,\r\n logoLink: merged.customization.logoUrl,\r\n }\r\n : undefined,\r\n };\r\n};\r\n\r\n/**\r\n * Normalize the raw ValuePay response into our SDK response shape.\r\n */\r\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\r\nexport const normalizeResponse = (raw: any, ref: string, config: ValuePayConfig): ValuePayResponse => {\r\n return {\r\n ref: raw?.ref || raw?.reference || raw?.tx_ref || ref,\r\n transactionRef: raw?.transactionRef || ref,\r\n status: raw?.status || raw?.validation?.status || \"PENDING\",\r\n amount: config.amount,\r\n currency: config.currency || DEFAULT_CURRENCY,\r\n customer: config.customer,\r\n paymentMethod: raw?.paymentMethod,\r\n validation: raw?.validation,\r\n raw,\r\n };\r\n};\r\n"],"names":[],"mappings":"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"}
@@ -2,5 +2,5 @@ export { ValuePayButton } from "./components/ValuePayButton";
2
2
  export { ValuePayProvider } from "./components/ValuePayProvider";
3
3
  export { useValuePay } from "./hooks/useValuePay";
4
4
  export { useScript } from "./hooks/useScript";
5
- export { generateTransactionRef } from "./utils/helpers";
5
+ export { generateTransactionRef, ensureRefPrefix } from "./utils/helpers";
6
6
  export type { ValuePayConfig, ValuePayResponse, ValuePayStatus, ValuePayCustomer, ValuePayCustomization, PaymentChannel, UseValuePayReturn, ValuePayButtonProps, ValuePayProviderProps, ValuePayProviderRef, } from "./types";
@@ -30,6 +30,7 @@ export interface ValuePayConfig {
30
30
  customer: ValuePayCustomer;
31
31
  currency?: string;
32
32
  channels?: PaymentChannel[];
33
+ /** Optional custom transaction reference. The VPS_TX_ prefix is auto-prepended if missing. Auto-generated when omitted. */
33
34
  transactionRef?: string;
34
35
  redirectUrl?: string;
35
36
  metaData?: Record<string, unknown>;
@@ -52,10 +53,14 @@ export interface UseValuePayReturn {
52
53
  export interface ValuePayButtonProps extends ValuePayConfig {
53
54
  /** Button text override. Default: "Pay ₦{amount}" */
54
55
  text?: string;
55
- /** Additional CSS class names */
56
+ /** Additional CSS class names for the button element */
56
57
  className?: string;
57
- /** Additional inline styles */
58
+ /** Inline styles applied to the button element (text color, font, padding, etc.) */
58
59
  style?: CSSProperties;
60
+ /** Inline styles applied to the outer container div (width, height, margin, borders, radius, background, etc.) */
61
+ containerStyle?: CSSProperties;
62
+ /** Additional CSS class names for the outer container div */
63
+ containerClassName?: string;
59
64
  /** Disable the button */
60
65
  disabled?: boolean;
61
66
  /** Custom children override the default button text */
@@ -3,6 +3,12 @@ import type { ValuePayConfig, ValuePayResponse } from "../types";
3
3
  * Generate a unique transaction reference.
4
4
  */
5
5
  export declare const generateTransactionRef: (length?: number) => string;
6
+ /**
7
+ * Ensure the transaction reference always carries the VPS_TX_ prefix.
8
+ * If the merchant supplies their own ref, the prefix is prepended (unless already present).
9
+ * If omitted, a fully random reference is generated.
10
+ */
11
+ export declare const ensureRefPrefix: (ref: string) => string;
6
12
  /**
7
13
  * Normalize the SDK config into the shape expected by window.ValuepayCheckout.
8
14
  */
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "@valuepay/react",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Official React SDK for ValuePay payment gateway",
5
5
  "author": "Value Payment Solutions Limited",
6
6
  "license": "MIT",
7
7
  "main": "dist/cjs/index.js",
8
8
  "module": "dist/esm/index.js",
9
9
  "types": "dist/types/index.d.ts",
10
- "files": ["dist", "README.md", "LICENSE", "CHANGELOG.md"],
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE",
14
+ "CHANGELOG.md"
15
+ ],
11
16
  "exports": {
12
17
  ".": {
13
18
  "import": "./dist/esm/index.js",