@inflow_pay/sdk 1.0.0 → 1.2.0-beta.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 CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2026-02-23
11
+
12
+ ### Added
13
+ - **Default success UI customization** – Style the built-in success screen via `style.successUI` (e.g. `backgroundColor`, `primaryTextColor`, `secondaryTextColor`). Use `dark.successUI` for dark mode overrides.
14
+
15
+ ### Fixed
16
+ - **Dynamic height for success UI** – Iframe now reports content height when the success screen is shown, so the SDK container resizes correctly and no longer leaves extra empty space or overflow.
17
+
18
+ ### Deprecated
19
+ - **General success styling** – `style.generalSuccess` and `style.dark.generalSuccess` are deprecated and have no effect (general success UI was removed from the iframe). They will be removed in the next major version. Use the default success UI and `style.successUI` instead.
20
+
10
21
  ## [0.8.0] - 2026-01-30
11
22
 
12
23
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # InflowPay SDK v2
2
2
 
3
- Accept card payments with a secure, iframe-based payment form. This version delivers **enhanced security**, a **built-in success UI**, and **automatic 3D Secure handling** - all with just a few lines of code.
3
+ Accept card payments with a secure, iframe-based payment form. This version delivers **enhanced security** and **automatic 3D Secure handling** - all with just a few lines of code.
4
4
 
5
5
  ## What's Best in This Version
6
6
 
@@ -9,6 +9,7 @@ Accept card payments with a secure, iframe-based payment form. This version deli
9
9
  - 🎯 **Auto 3D Secure**: SDK handles authentication modals automatically
10
10
  - 🚀 **Zero Configuration**: Works out of the box with sensible defaults
11
11
  - 🎨 **Fully Customizable**: Match your brand with comprehensive styling options
12
+ - 💳 **Save card (setup)**: Collect and tokenize a card without charging, using a `CustomerPaymentMethodRequest` ID (`setupId`)
12
13
 
13
14
  ## Installation
14
15
 
@@ -16,10 +17,16 @@ Accept card payments with a secure, iframe-based payment form. This version deli
16
17
  npm install @inflow_pay/sdk
17
18
  ```
18
19
 
20
+ **React apps** — import from `@inflow_pay/sdk/react` and ensure peer dependencies are installed:
21
+
22
+ ```bash
23
+ npm install @inflow_pay/sdk react react-dom
24
+ ```
25
+
19
26
  **CDN Option** (vanilla HTML pages only):
20
27
 
21
28
  ```html
22
- <script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk@1.0.0/dist/sdk.umd.js"></script>
29
+ <script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk@1.1.0/dist/sdk.umd.js"></script>
23
30
  ```
24
31
 
25
32
  ## Complete Example
@@ -28,22 +35,22 @@ npm install @inflow_pay/sdk
28
35
 
29
36
  ```javascript
30
37
  // backend/api/create-payment.js
31
- app.post('/api/create-payment', async (req, res) => {
32
- const response = await fetch('https://api.inflowpay.xyz/api/server/payment', {
33
- method: 'POST',
38
+ app.post("/api/create-payment", async (req, res) => {
39
+ const response = await fetch("https://api.inflowpay.xyz/api/server/payment", {
40
+ method: "POST",
34
41
  headers: {
35
- 'Content-Type': 'application/json',
36
- 'X-Inflow-Api-Key': process.env.INFLOW_PRIVATE_KEY
42
+ "Content-Type": "application/json",
43
+ "X-Inflow-Api-Key": process.env.INFLOW_PRIVATE_KEY,
37
44
  },
38
45
  body: JSON.stringify({
39
- products: [{ name: 'Product', price: 4999, quantity: 1 }],
40
- currency: 'EUR',
41
- customerEmail: 'customer@example.com',
42
- firstName: 'John',
43
- lastName: 'Doe',
44
- billingCountry: 'FR',
45
- postalCode: '75001'
46
- })
46
+ products: [{ name: "Product", price: 4999, quantity: 1 }],
47
+ currency: "EUR",
48
+ customerEmail: "customer@example.com",
49
+ firstName: "John",
50
+ lastName: "Doe",
51
+ billingCountry: "FR",
52
+ postalCode: "75001",
53
+ }),
47
54
  });
48
55
 
49
56
  const data = await response.json();
@@ -54,32 +61,64 @@ app.post('/api/create-payment', async (req, res) => {
54
61
  ### Frontend: React
55
62
 
56
63
  ```tsx
57
- import { InflowPayProvider, CardElement, PaymentResultStatus } from '@inflow_pay/sdk/react';
64
+ import {
65
+ InflowPayProvider,
66
+ CardElement,
67
+ PaymentResultStatus,
68
+ } from "@inflow_pay/sdk/react";
58
69
 
59
70
  export default function CheckoutPage() {
60
71
  const [paymentId, setPaymentId] = useState<string | null>(null);
61
72
 
62
73
  useEffect(() => {
63
- fetch('/api/create-payment', { method: 'POST' })
64
- .then(res => res.json())
65
- .then(data => setPaymentId(data.paymentId));
74
+ fetch("/api/create-payment", { method: "POST" })
75
+ .then((res) => res.json())
76
+ .then((data) => setPaymentId(data.paymentId));
66
77
  }, []);
67
78
 
68
79
  if (!paymentId) return <div>Loading...</div>;
69
80
 
70
81
  return (
71
- <InflowPayProvider config={{ publicKey: 'inflow_pub_xxx' }}>
82
+ <InflowPayProvider config={{ publicKey: "inflow_pub_xxx" }}>
72
83
  <CardElement
73
84
  paymentId={paymentId}
74
- showDefaultSuccessUI={true}
75
85
  onComplete={(result) => {
76
86
  if (result.status === PaymentResultStatus.SUCCESS) {
77
- setTimeout(() => window.location.href = '/confirmation', 2000);
87
+ setTimeout(() => (window.location.href = "/confirmation"), 2000);
78
88
  }
79
89
  }}
80
90
  style={{
81
- fontFamily: 'Inter',
82
- button: { backgroundColor: '#0070F3', textColor: '#FFFFFF' }
91
+ fontFamily: "Inter",
92
+ button: { backgroundColor: "#0070F3", textColor: "#FFFFFF" },
93
+ }}
94
+ />
95
+ </InflowPayProvider>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### Save card (setup) — React
101
+
102
+ Use **`setupId`** (ID returned when you create a **CustomerPaymentMethodRequest** on your server) instead of **`paymentId`**. The two are mutually exclusive. On success, `onComplete` receives a **`SaveCardResult`** with `status` and `setupId`.
103
+
104
+ ```tsx
105
+ import {
106
+ InflowPayProvider,
107
+ CardElement,
108
+ PaymentResultStatus,
109
+ type SaveCardResult,
110
+ } from "@inflow_pay/sdk/react";
111
+
112
+ export default function AddCardPage({ setupId }: { setupId: string }) {
113
+ return (
114
+ <InflowPayProvider config={{ publicKey: "inflow_pub_xxx" }}>
115
+ <CardElement
116
+ setupId={setupId}
117
+ onComplete={(result) => {
118
+ if (result.status === PaymentResultStatus.SUCCESS) {
119
+ const save = result as SaveCardResult;
120
+ console.log("Card saved for request:", save.setupId);
121
+ }
83
122
  }}
84
123
  />
85
124
  </InflowPayProvider>
@@ -87,94 +126,139 @@ export default function CheckoutPage() {
87
126
  }
88
127
  ```
89
128
 
129
+ Your backend should create the customer payment method request with your **private** API key and expose only the returned request ID to the browser as `setupId`.
130
+
90
131
  ### Frontend: Vanilla JavaScript
91
132
 
92
133
  ```html
93
134
  <div id="card-container"></div>
94
135
 
95
- <script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk@0.8.0/dist/sdk.umd.js"></script>
136
+ <script src="https://cdn.jsdelivr.net/npm/@inflow_pay/sdk@1.1.0/dist/sdk.umd.js"></script>
96
137
  <script>
97
138
  const provider = new InflowPaySDK.InflowPayProvider({
98
- config: { publicKey: 'inflow_pub_xxx' }
139
+ config: { publicKey: "inflow_pub_xxx" },
99
140
  });
100
141
 
101
- fetch('/api/create-payment', { method: 'POST' })
102
- .then(res => res.json())
103
- .then(data => {
142
+ fetch("/api/create-payment", { method: "POST" })
143
+ .then((res) => res.json())
144
+ .then((data) => {
104
145
  const cardElement = provider.createCardElement({
105
146
  paymentId: data.paymentId,
106
- container: '#card-container',
107
- showDefaultSuccessUI: true,
147
+ container: "#card-container",
108
148
  onComplete: (result) => {
109
149
  if (result.status === InflowPaySDK.PaymentResultStatus.SUCCESS) {
110
- setTimeout(() => window.location.href = '/confirmation', 2000);
150
+ setTimeout(() => (window.location.href = "/confirmation"), 2000);
111
151
  }
112
- }
152
+ },
113
153
  });
114
154
  cardElement.mount();
115
155
  });
116
156
  </script>
117
157
  ```
118
158
 
159
+ ### Save card (setup) — Vanilla JavaScript
160
+
161
+ ```javascript
162
+ const provider = new InflowPaySDK.InflowPayProvider({
163
+ config: { publicKey: "inflow_pub_xxx" },
164
+ });
165
+
166
+ fetch("/api/create-card-setup", { method: "POST" })
167
+ .then((res) => res.json())
168
+ .then((data) => {
169
+ const cardElement = provider.createCardElement({
170
+ setupId: data.setupId,
171
+ container: "#card-container",
172
+ onComplete: (result) => {
173
+ if (result.status === InflowPaySDK.PaymentResultStatus.SUCCESS) {
174
+ console.log("setupId:", result.setupId);
175
+ }
176
+ },
177
+ });
178
+ cardElement.mount();
179
+ });
180
+ ```
181
+
119
182
  ## Built-in Success UI Explained
120
183
 
121
184
  The SDK includes a polished success screen that appears automatically after successful payment:
122
185
 
123
186
  **What it shows:**
187
+
124
188
  - ✅ Success icon and "Payment successful!" message
125
189
  - 💳 Payment amount (when available from your payment data)
126
- - 🎨 Automatically matches your brand colors and theme
127
- - 🌙 Dark mode support
128
190
 
129
- **How to use it:**
191
+ **Default vs custom:**
130
192
 
131
- ```javascript
132
- // Option 1: Use default success UI (recommended)
133
- const cardElement = provider.createCardElement({
134
- paymentId: 'pay_xxx',
135
- container: '#card-container',
136
- showDefaultSuccessUI: true, // Shows built-in success screen
137
- onComplete: (result) => {
138
- if (result.status === PaymentResultStatus.SUCCESS) {
139
- // Success UI is already showing in the iframe
140
- // Optional: redirect after 2 seconds
141
- setTimeout(() => window.location.href = '/confirmation', 2000);
142
- }
143
- }
144
- });
193
+ - **Default UI** (default): Built-in success screen shows automatically. You don't need to do anything — quick integration, looks great out of the box.
194
+ - **Custom UI:** Set `showDefaultSuccessUI: false` and add your own success UI. Manage the logic in `onComplete`. Full control for custom branding or additional information. Examples below:
145
195
 
146
- // Option 2: Use your own custom success UI
196
+ _Vanilla JS:_
197
+
198
+ ```javascript
147
199
  const cardElement = provider.createCardElement({
148
- paymentId: 'pay_xxx',
149
- container: '#card-container',
150
- showDefaultSuccessUI: false, // Iframe unmounts on success
200
+ paymentId: "pay_xxx",
201
+ container: "#card-container",
202
+ showDefaultSuccessUI: false,
151
203
  onComplete: (result) => {
152
204
  if (result.status === PaymentResultStatus.SUCCESS) {
153
- // Show your custom success UI
154
- document.getElementById('card-container').innerHTML = `
205
+ document.getElementById("card-container").innerHTML = `
155
206
  <div class="custom-success">
156
207
  <h2>Thank you for your purchase!</h2>
157
208
  <p>Order ID: ${result.paymentId}</p>
158
209
  </div>
159
210
  `;
160
211
  }
161
- }
212
+ },
162
213
  });
163
214
  ```
164
215
 
165
- **When to use each option:**
166
- - **Default UI** (`showDefaultSuccessUI: true`): Quick integration, consistent experience, looks great out of the box
167
- - **Custom UI** (`showDefaultSuccessUI: false`): Full control when you need custom branding or additional information
216
+ _React:_
217
+
218
+ ```tsx
219
+ const [result, setResult] = useState<PaymentResult | null>(null);
220
+
221
+ return (
222
+ <>
223
+ {result ? (
224
+ <div className="custom-success">
225
+ <h2>Thank you for your purchase!</h2>
226
+ <p>Your payment was successful.</p>
227
+ </div>
228
+ ) : (
229
+ <InflowPayProvider config={{ publicKey: "inflow_pub_xxx" }}>
230
+ <CardElement
231
+ paymentId="pay_xxx"
232
+ showDefaultSuccessUI={false}
233
+ onComplete={(res) => {
234
+ if (res.status === PaymentResultStatus.SUCCESS) {
235
+ setResult(res);
236
+ }
237
+ }}
238
+ />
239
+ </InflowPayProvider>
240
+ )}
241
+ </>
242
+ );
243
+ ```
244
+
245
+ ## Payment flow
246
+
247
+ **Charge (payment)**
248
+
249
+ 1. Backend creates a payment → returns `paymentId`
250
+ 2. Frontend mounts `CardElement` with `paymentId` and your public key
251
+ 3. User enters card details in the secure iframe
252
+ 4. SDK tokenizes the card and processes the payment
253
+ 5. 3D Secure (if required) — handled automatically
254
+ 6. `onComplete` fires with a **`PaymentResult`** → redirect or update UI
168
255
 
169
- ## Payment Flow
256
+ **Save card (setup)**
170
257
 
171
- 1. Backend creates payment → returns `paymentId`
172
- 2. Frontend initializes SDK with `paymentId` and public key
173
- 3. User enters card details in secure iframe
174
- 4. SDK tokenizes card and processes payment
175
- 5. 3D Secure authentication (if required) - automatic modal
176
- 6. Success UI displays (built-in or custom)
177
- 7. `onComplete` callback fires → redirect or update UI
258
+ 1. Backend creates a **CustomerPaymentMethodRequest** → returns its ID as `setupId`
259
+ 2. Frontend mounts `CardElement` with **`setupId`** (not `paymentId`)
260
+ 3. User completes the card form; 3DS may still run when the issuer requires it
261
+ 4. `onComplete` fires with a **`SaveCardResult`** (`setupId` + `status`, and `error` on failure)
178
262
 
179
263
  ## API Reference
180
264
 
@@ -183,9 +267,9 @@ const cardElement = provider.createCardElement({
183
267
  ```javascript
184
268
  const provider = new InflowPayProvider({
185
269
  config: {
186
- publicKey: 'inflow_pub_xxx', // Required
187
- locale: 'en' // Optional - defaults to browser language
188
- }
270
+ publicKey: "inflow_pub_xxx", // Required
271
+ locale: "en", // Optional - defaults to browser language
272
+ },
189
273
  });
190
274
  ```
191
275
 
@@ -193,12 +277,14 @@ const provider = new InflowPayProvider({
193
277
 
194
278
  ### CardElement
195
279
 
280
+ Pass exactly one of **`paymentId`** (checkout) or **`setupId`** (save card). Passing both or neither throws.
281
+
196
282
  ```javascript
197
283
  const cardElement = provider.createCardElement({
198
- paymentId: 'pay_xxx', // Required - from your backend
199
- container: '#card-container', // Required - CSS selector or HTMLElement
200
- showDefaultSuccessUI: true, // Optional - default true
201
-
284
+ paymentId: "pay_xxx", // XOR setupId from your backend
285
+ // setupId: "cus_pm_req_xxx", // save-card flow (CustomerPaymentMethodRequest ID)
286
+ container: "#card-container", // Required - CSS selector or HTMLElement
287
+
202
288
  // Callbacks
203
289
  onComplete: (result) => {
204
290
  if (result.status === PaymentResultStatus.SUCCESS) {
@@ -207,59 +293,71 @@ const cardElement = provider.createCardElement({
207
293
  // Payment failed - result.error contains details
208
294
  }
209
295
  },
210
-
296
+
211
297
  onReady: () => {
212
298
  // Card element mounted and ready
213
299
  },
214
-
300
+
215
301
  onChange: (state) => {
216
302
  // state.complete - whether form is valid
217
303
  },
218
-
304
+
219
305
  onError: (error) => {
220
306
  // SDK-level errors
221
307
  },
222
-
308
+
223
309
  // Styling (optional)
224
310
  style: {
225
- fontFamily: 'Inter',
311
+ fontFamily: "Inter",
226
312
  button: {
227
- backgroundColor: '#0070F3',
228
- textColor: '#FFFFFF'
229
- }
313
+ backgroundColor: "#0070F3",
314
+ textColor: "#FFFFFF",
315
+ },
230
316
  },
231
-
317
+
232
318
  // Custom text (optional)
233
- buttonText: 'Pay Now',
319
+ buttonText: "Pay Now",
234
320
  placeholders: {
235
- cardNumber: '1234 5678 9012 3456',
236
- expiry: 'MM/YY',
237
- cvc: 'CVC'
238
- }
321
+ cardNumber: "1234 5678 9012 3456",
322
+ expiry: "MM/YY",
323
+ cvc: "CVC",
324
+ },
239
325
  });
240
326
 
241
327
  cardElement.mount();
242
328
  ```
243
329
 
244
- ### Payment Result
330
+ ### Payment and save-card results
245
331
 
246
332
  ```typescript
247
333
  interface PaymentResult {
248
- status: 'SUCCESS' | 'FAILED';
334
+ status: "SUCCESS" | "FAILED";
249
335
  paymentId: string;
250
336
  error?: {
251
- code: string; // Error code (e.g., 'THREE_DS_FAILED')
252
- message: string; // User-friendly message
253
- retryable: boolean; // Can retry?
254
- }
337
+ code: string; // Error code (e.g., 'THREE_DS_FAILED')
338
+ message: string; // User-friendly message
339
+ retryable: boolean; // Can retry?
340
+ };
341
+ }
342
+
343
+ interface SaveCardResult {
344
+ status: "SUCCESS" | "FAILED";
345
+ setupId: string;
346
+ error?: {
347
+ code: string;
348
+ message: string;
349
+ retryable: boolean;
350
+ };
255
351
  }
256
352
  ```
257
353
 
354
+ In **React**, `onComplete` is typed as `(result: PaymentResult | SaveCardResult) => void`. Use `result.status` and narrow with `'paymentId' in result` vs `'setupId' in result`, or cast when you know which mode you mounted.
355
+
258
356
  ### CardElement Methods
259
357
 
260
358
  ```javascript
261
- cardElement.mount(); // Mount to DOM
262
- cardElement.destroy(); // Cleanup and unmount
359
+ cardElement.mount(); // Mount to DOM
360
+ cardElement.destroy(); // Cleanup and unmount
263
361
  ```
264
362
 
265
363
  ## Styling
@@ -270,7 +368,7 @@ Customize the payment form to match your brand:
270
368
  style: {
271
369
  fontFamily: 'Inter', // 'DM Sans' | 'Inter' | 'Poppins' | 'Nunito' | etc.
272
370
  fillParent: true,
273
-
371
+
274
372
  // Input container
275
373
  inputContainer: {
276
374
  backgroundColor: '#F5F5F5',
@@ -278,14 +376,14 @@ style: {
278
376
  borderEnabled: true,
279
377
  borderColor: '#E0E0E0'
280
378
  },
281
-
379
+
282
380
  // Input fields
283
381
  input: {
284
382
  textColor: '#1A1A1A',
285
383
  placeholderColor: '#999999',
286
384
  backgroundColor: 'transparent'
287
385
  },
288
-
386
+
289
387
  // Button
290
388
  button: {
291
389
  backgroundColor: '#0070F3',
@@ -300,7 +398,14 @@ style: {
300
398
  opacity: 0.5
301
399
  }
302
400
  },
303
-
401
+
402
+ // Success screen (shown by default; customize with successUI)
403
+ successUI: {
404
+ backgroundColor: '#F5F5F5',
405
+ primaryTextColor: '#1A1A1A', // title and amount
406
+ secondaryTextColor: '#999999' // subtitle
407
+ },
408
+
304
409
  // Dark mode
305
410
  dark: {
306
411
  inputContainer: {
@@ -313,27 +418,32 @@ style: {
313
418
  },
314
419
  button: {
315
420
  backgroundColor: '#0066CC'
421
+ },
422
+ successUI: {
423
+ backgroundColor: '#1A1A1A',
424
+ primaryTextColor: '#FFFFFF',
425
+ secondaryTextColor: '#959499'
316
426
  }
317
427
  }
318
428
  }
319
429
  ```
320
430
 
321
- **Available style properties:** `inputContainer`, `input`, `button`, `generalError`, `generalSuccess`, `disclaimerColor`, `fieldErrorColor`, `dark` (for dark mode overrides)
431
+ **Available style properties:** `inputContainer`, `input`, `button`, `successUI`, `generalError`, `disclaimerColor`, `fieldErrorColor`, `dark` (for dark mode overrides)
322
432
 
323
433
  See full TypeScript types in the package for all styling options.
324
434
 
325
435
  ## Error Handling
326
436
 
327
437
  ```javascript
328
- import { PaymentResultStatus, PaymentResultErrorCode } from '@inflow_pay/sdk';
438
+ import { PaymentResultStatus, PaymentResultErrorCode } from "@inflow_pay/sdk";
329
439
 
330
440
  onComplete: (result) => {
331
441
  if (result.status === PaymentResultStatus.FAILED && result.error) {
332
- console.log(result.error.code); // e.g., 'THREE_DS_FAILED'
333
- console.log(result.error.message); // User-friendly message
442
+ console.log(result.error.code); // e.g., 'THREE_DS_FAILED'
443
+ console.log(result.error.message); // User-friendly message
334
444
  console.log(result.error.retryable); // Can user retry?
335
445
  }
336
- }
446
+ };
337
447
  ```
338
448
 
339
449
  **Common error codes:** `THREE_DS_FAILED`, `PAYMENT_PROCESSING_ERROR`
@@ -350,17 +460,28 @@ onComplete: (result) => {
350
460
 
351
461
  ## TypeScript
352
462
 
353
- Full type definitions included:
463
+ Full type definitions are included for both entry points:
354
464
 
355
465
  ```typescript
356
- import { InflowPayProvider, PaymentResultStatus } from '@inflow_pay/sdk';
357
- import type { PaymentResult, PaymentError } from '@inflow_pay/sdk';
358
-
359
- const provider = new InflowPayProvider({
360
- config: { publicKey: 'inflow_pub_xxx' }
361
- });
466
+ // Vanilla (InflowPayProvider, PaymentSDK, CardElement)
467
+ import {
468
+ InflowPayProvider,
469
+ PaymentResultStatus,
470
+ } from "@inflow_pay/sdk";
471
+ import type { PaymentResult, SaveCardResult, PaymentError } from "@inflow_pay/sdk";
472
+
473
+ // React components and hooks
474
+ import {
475
+ InflowPayProvider,
476
+ CardElement,
477
+ useInflowPay,
478
+ PaymentResultStatus,
479
+ } from "@inflow_pay/sdk/react";
480
+ import type { PaymentResult, SaveCardResult } from "@inflow_pay/sdk/react";
362
481
  ```
363
482
 
483
+ `useInflowPay()` returns the shared `PaymentSDK` instance from `InflowPayProvider` (e.g. for advanced use).
484
+
364
485
  ## Features
365
486
 
366
487
  - ✅ Dynamic height adjustment