inflowpay-js
v0.6.0
Published
InflowPay JavaScript SDK with React components
Maintainers
Readme
InflowPay React SDK
Accept card payments with a pre-built, secure payment form for React applications.
Installation
npm install inflowpay-js react react-domQuick Start
import { InflowPayProvider, CardElement } from 'inflowpay-js/react';
function App() {
const paymentId = 'pay_xxx'; // From your backend
return (
<InflowPayProvider config={{ apiKey: 'inflow_pub_xxx' }}>
<CardElement
paymentId={paymentId}
onComplete={(result) => {
if (result.status === 'CHECKOUT_SUCCESS') {
window.location.href = '/success';
}
}}
/>
</InflowPayProvider>
);
}That's it! The form handles card validation, tokenization, 3DS, and payment completion.
Features
- 🔒 PCI Compliant - Card data never touches your server
- 🎨 Customizable - Match your brand with custom styling
- 🔐 3DS Built-in - Automatic 3D Secure authentication
- 📱 Mobile Ready - Responsive on all devices
- 💪 TypeScript - Full type definitions included
How It Works
- Backend: Create a payment with billing info (your private key)
- Frontend: Wrap your app with
InflowPayProvider - Frontend: Add
CardElementcomponent with thepaymentId - User: Enters card details and submits
- SDK: Handles tokenization, 3DS, and payment completion
- Your app: Receives success/error via callback
Payment Status
Payments progress through these statuses:
| Status | Description | Success? |
|--------|-------------|----------|
| CHECKOUT_SUCCESS | Payment confirmed | ✅ Yes |
| PAYMENT_RECEIVED | Funds received | ✅ Yes |
| PAYMENT_SUCCESS | Complete | ✅ Yes |
| PAYMENT_FAILED | Failed | ❌ No |
Check for success:
if (result.status === 'CHECKOUT_SUCCESS' ||
result.status === 'PAYMENT_RECEIVED' ||
result.status === 'PAYMENT_SUCCESS') {
// Payment successful
}Customization
Styling
The CardElement supports comprehensive styling through the style prop. All styles are scoped to the card element using Shadow DOM, so they won't conflict with your page styles.
Basic Styling Example
<CardElement
paymentId={paymentId}
style={{
// Input container (the box wrapping all inputs)
inputContainer: {
backgroundColor: '#F5F5F5',
border: '1px solid #E0E0E0',
borderRadius: '8px',
},
// Individual input fields
input: {
border: '1px solid #CECECE',
borderRadius: '5px',
color: '#333333',
fontWeight: '400',
placeholder: {
color: '#999999',
},
},
// Global font family
fontFamily: "'Inter', sans-serif",
}}
/>Complete Styling Reference
style prop - CSSProperties
interface CSSProperties {
// Input container styles (the box wrapping all 3 input fields)
inputContainer?: {
backgroundColor?: string; // Background color of the container
border?: string; // Border (e.g., '1px solid #E0E0E0')
borderRadius?: string; // Border radius (e.g., '8px')
};
// Individual input field styles
input?: {
border?: string; // Input border (e.g., '1px solid #CECECE')
borderRadius?: string; // Input border radius (e.g., '5px')
color?: string; // Text color
fontWeight?: string; // Font weight (e.g., '400', '500', 'bold')
placeholder?: {
color?: string; // Placeholder text color
};
};
// Global font family (applies to entire card element)
fontFamily?: string; // Font family (e.g., "'Inter', sans-serif")
// Theme-specific overrides (optional)
// These automatically follow system preference via CSS media queries
light?: {
inputContainer?: { backgroundColor?: string; border?: string; borderRadius?: string; };
input?: { border?: string; borderRadius?: string; color?: string; fontWeight?: string; placeholder?: { color?: string; }; };
};
dark?: {
inputContainer?: { backgroundColor?: string; border?: string; borderRadius?: string; };
input?: { border?: string; borderRadius?: string; color?: string; fontWeight?: string; placeholder?: { color?: string; }; };
};
}Example with theme-specific styling:
<CardElement
paymentId={paymentId}
style={{
inputContainer: {
backgroundColor: '#F5F5F5',
border: '1px solid #E0E0E0',
borderRadius: '8px',
},
input: {
border: '1px solid #CECECE',
borderRadius: '5px',
color: '#333333',
fontWeight: '400',
placeholder: {
color: '#999999',
},
},
fontFamily: "'Inter', sans-serif",
// Light mode overrides
light: {
inputContainer: {
backgroundColor: '#FFFFFF',
border: '1px solid #E0E0E0',
},
input: {
color: '#000000',
placeholder: {
color: '#999999',
},
},
},
// Dark mode overrides
dark: {
inputContainer: {
backgroundColor: '#2d2d2d',
border: '1px solid #7A7A7A',
},
input: {
color: '#FFFFFF',
border: '1px solid #7A7A7A',
placeholder: {
color: '#959499',
},
},
},
}}
/>Note: Theme styles (light and dark) automatically follow system preference using CSS media queries. If only light is provided, dark mode uses defaults. If only dark is provided, light mode uses defaults.
buttonStyle prop - ButtonStyle | Partial
Two ways to style the button:
Option 1: Flat structure (simple CSS properties)
<CardElement
paymentId={paymentId}
buttonStyle={{
backgroundColor: '#007bff',
color: '#ffffff',
borderRadius: '6px',
padding: '12px 24px',
fontSize: '16px',
fontWeight: '500',
}}
/>Option 2: Structured with states
<CardElement
paymentId={paymentId}
buttonStyle={{
base: {
backgroundColor: '#007bff',
color: '#ffffff',
borderRadius: '6px',
padding: '12px 24px',
fontSize: '16px',
fontWeight: '500',
},
hover: {
backgroundColor: '#0056b3',
transform: 'translateY(-1px)',
},
disabled: {
backgroundColor: '#cccccc',
cursor: 'not-allowed',
},
loaderColor: '#ffffff', // Color of the loading spinner
}}
/>ButtonStyle interface:
interface ButtonStyle {
base?: Partial<CSSStyleDeclaration>; // Base button styles
hover?: Partial<CSSStyleDeclaration>; // Hover state styles
disabled?: Partial<CSSStyleDeclaration>; // Disabled state styles
loaderColor?: string; // Loading spinner color
}buttonText prop
Customize the button text:
<CardElement
buttonText="Complete Payment" // Default: "Complete Payment"
/>placeholders prop
Customize placeholder text for each input:
<CardElement
placeholders={{
cardNumber: '1234 5678 9012 3456', // Default: '4242 4242 4242 4242'
expiry: 'MM/YY', // Default: 'MM/YY'
cvc: '123', // Default: 'CVC'
}}
/>Callbacks
<CardElement
paymentId={paymentId}
onReady={() => {
// Card element is mounted and ready
console.log('Card form is ready');
}}
onChange={(state) => {
// Track validation state
console.log('Valid:', state.complete);
}}
onComplete={(result) => {
// Payment complete (success or failure)
if (result.error) {
alert(result.error.message);
}
}}
onError={(error) => {
// SDK-level errors (network, validation)
console.error(error);
}}
/>3D Secure
3DS is handled automatically. When required:
- SDK opens a modal with the authentication page
- User completes authentication
- Modal closes automatically
- Your
onCompletecallback receives the final result
Setup
Copy 3ds-callback.html to your public directory:
cp node_modules/inflowpay-js/templates/3ds-callback.html public/The file must be accessible at https://yourdomain.com/3ds-callback.html for the 3DS authentication flow to work.
That's it! The SDK automatically uses this URL for 3DS redirects.
What This File Does
- Receives the redirect from the 3DS authentication provider
- Shows a success/failure message to the user
- Sends the result back to the parent modal via postMessage
- Auto-closes after completion
Important notes:
- The file must be on the same origin as your application (same domain, protocol, and port)
- Do not modify the file unless you need custom branding
- The file is small (~4KB) and has no external dependencies
Local Development with 3DS
Important: To test 3DS locally, you MUST serve your application through a public HTTPS tunnel. Modern browsers block 3DS providers from redirecting to localhost due to Private Network Access security restrictions.
Setup:
- Serve your application on a local port (e.g., port 5173 for Vite):
npm run dev # Vite runs on port 5173- Tunnel that port to a public HTTPS URL:
# Option 1: localtunnel (simple, no signup)
npx localtunnel --port 5173
# Returns: https://random-subdomain.loca.lt
# Option 2: ngrok (more reliable, requires free account)
npx ngrok http 5173
# Returns: https://abc123.ngrok.io- Access your app via the tunnel URL (not localhost):
- ✅ Use:
https://random-subdomain.loca.lt - ❌ Don't use:
http://localhost:5173
The SDK will automatically use the tunnel URL for the 3DS callback (https://random-subdomain.loca.lt/3ds-callback.html), and the 3DS provider will be able to redirect to it successfully.
Why this is required:
- 3DS providers (Basis Theory, Stripe) run on public servers
- After authentication, they redirect to your callback URL
- Browsers block redirects from public → private (localhost) networks
- A tunnel creates a public HTTPS URL that forwards to your localhost
Error Handling
Errors are automatically mapped to user-friendly messages:
<CardElement
paymentId={paymentId}
onComplete={(result) => {
if (result.error) {
console.log(result.error.message); // User-friendly
console.log(result.error.retryable); // Can retry?
}
}}
/>Common errors:
card_declined- Card was declinedinsufficient_funds- Not enough fundsinvalid_card_number- Invalid cardTHREE_DS_FAILED- 3DS authentication failed
API Reference
InflowPayProvider
<InflowPayProvider config={SDKConfig}>
{children}
</InflowPayProvider>Config:
apiKey(required) - Your public key (inflow_pub_xxx)timeout(optional) - Request timeout in ms (default: 30000)debug(optional) - Enable logging (default: false)
CardElement
<CardElement
paymentId={string}
config?: SDKConfig // Optional if using InflowPayProvider
buttonText?: string
buttonStyle?: ButtonStyle | Partial<CSSStyleDeclaration>
style?: CSSProperties
placeholders?: {
cardNumber?: string
expiry?: string
cvc?: string
}
onReady?: () => void
onChange?: (state: CardElementState) => void
onComplete?: (result: PaymentResult) => void
onError?: (error: PaymentError) => void
/>useInflowPay Hook
Access the InflowPay instance from context:
import { useInflowPay } from 'inflowpay-js/react';
function CustomComponent() {
const inflow = useInflowPay();
// Use inflow methods if needed
const checkStatus = async () => {
const status = await inflow.getPaymentStatus('pay_xxx');
console.log(status);
};
return <button onClick={checkStatus}>Check Status</button>;
}TypeScript
Full type definitions included:
import { InflowPayProvider, CardElement } from 'inflowpay-js/react';
import type { PaymentResult, PaymentError } from 'inflowpay-js';
function App() {
return (
<InflowPayProvider config={{ apiKey: 'inflow_pub_xxx' }}>
<CardElement
paymentId="pay_xxx"
onComplete={(result: PaymentResult) => {
if (result.status === 'CHECKOUT_SUCCESS') {
// Success
}
}}
onError={(error: PaymentError) => {
console.error(error);
}}
/>
</InflowPayProvider>
);
}Backend Integration
Create payments on your backend with your private key:
// Your backend (Node.js example)
const response = await fetch('https://api.inflowpay.xyz/api/server/payment', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Inflow-Api-Key': 'inflow_priv_xxx' // Private key
},
body: JSON.stringify({
products: [{ name: 'Product', price: 4999, quantity: 1 }],
currency: 'EUR',
customerEmail: '[email protected]',
billingCountry: 'FR',
postalCode: '75001',
firstName: 'John',
lastName: 'Doe',
purchasingAsBusiness: false,
successUrl: 'https://yourdomain.com/success',
cancelUrl: 'https://yourdomain.com/cancel'
})
});
const { payment } = await response.json();
// Send payment.id to your frontendSee API documentation for full backend API reference.
Security
- ✅ Card data is tokenized before reaching your server (PCI compliant)
- ✅ Public keys can be safely exposed in browser
- ✅ Private keys should never be in frontend code
- ✅ All requests over HTTPS
Support
- Documentation: https://docs.inflowpay.com/v0/reference/createpayment
- Email: [email protected]
- GitHub: https://github.com/inflowpay/sdk
License
MIT - See LICENSE
