portalpay-react
v1.0.1
Published
React hooks and components for PortalPay payment processing. Provides usePortalPay hook and PortalPayButton component with full TypeScript support. Compatible with React 18+ and Next.js.
Readme
PortalPay React
React hooks and components for PortalPay payment processing. Seamlessly integrate payments into your React applications.
Overview
portalpay-react provides React-specific bindings for the PortalPay SDK:
- 🎣 React Hooks -
useEmbedPayhook for state management - 🧩 Components - Pre-built checkout button component
- ⚡ Reactive - Automatic re-rendering on state changes
- 🔒 Type-Safe - Full TypeScript support
- 🧪 Mock Mode - Easy testing with mock provider
Installation
npm install portalpay-react
# or
yarn add portalpay-react
# or
pnpm add portalpay-reactPeer Dependencies
npm install react@^18.0.0 react-dom@^18.0.0Quick Start
1. Set up the Provider
Wrap your app with the PortalPay context:
import { PortalPayProvider } from 'portalpay-react';
function App() {
return (
<PortalPayProvider
config={{
publishableKey: 'pk_live_alexpay_xxxxxxxx',
currency: 'GHS',
narration: 'Order Payment',
callback: 'https://yoursite.com/success',
cancel: 'https://yoursite.com/cancel'
}}
>
<YourApp />
</PortalPayProvider>
);
}2. Use the Hook
import { usePortalPay } from 'portalpay-react';
function CheckoutPage() {
const {
state,
initPayment,
config,
error
} = usePortalPay({
amountSource: {
type: 'prop',
getter: () => 5000
}
});
const handlePayment = async () => {
try {
await initPayment();
// User will be redirected to checkout
} catch (err) {
console.error('Payment failed:', err);
}
};
return (
<div>
{state === 'fetching' && <p>Loading...</p>}
{state === 'error' && <p>Error: {error}</p>}
<button
onClick={handlePayment}
disabled={state !== 'ready'}
>
Pay GHS 50.00
</button>
</div>
);
}3. Or Use the Component
import { PortalPayButton } from 'portalpay-react';
function ProductPage() {
return (
<PortalPayButton
amount={5000}
currency="GHS"
narration="Product Purchase"
onSuccess={() => console.log('Payment initiated!')}
onError={(err) => console.error('Error:', err)}
>
Pay Now
</PortalPayButton>
);
}API Reference
PortalPayProvider
Context provider for PortalPay configuration.
import { PortalPayProvider } from 'portalpay-react';
<PortalPayProvider config={config}>
{children}
</PortalPayProvider>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| config | PortalPayConfig | ✅ | Configuration object |
Config Properties:
interface PortalPayConfig {
publishableKey: string; // Publishable key
currency?: string; // Currency code (GHS, NGN, USD)
method?: 'card' | 'mobile-money';
narration?: string; // Payment description
callback?: string; // Success URL
cancel?: string; // Cancel URL
forceMock?: boolean; // Test mode
}usePortalPay
Main hook for payment processing.
const result = usePortalPay(options);Options:
interface UsePortalPayOptions {
amountSource: AmountSource;
autoInitialize?: boolean; // Auto-fetch config on mount
}
type AmountSource =
| { type: 'prop'; getter: () => number | Promise<number> }
| { type: 'var'; varName: string }
| { type: 'dom'; selector: string };Return Value:
interface UsePortalPayResult {
state: 'idle' | 'fetching' | 'ready' | 'error' | 'retrying';
config: PortalPayConfig | null;
error: string | null;
initPayment: () => Promise<void>;
retry: () => Promise<void>;
}Example with DOM source:
function CartPage() {
const { state, initPayment } = usePortalPay({
amountSource: {
type: 'dom',
selector: '#cart-total'
}
});
return (
<div>
<span id="cart-total">5000</span>
<button
onClick={initPayment}
disabled={state !== 'ready'}
>
{state === 'fetching' ? 'Loading...' : 'Checkout'}
</button>
</div>
);
}PortalPayButton
Pre-built checkout button component.
<PortalPayButton
amount={5000}
currency="GHS"
narration="Order #123"
disabled={false}
className="custom-button"
onSuccess={() => {}}
onError={(error) => {}}
>
Pay GHS 50.00
</PortalPayButton>Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| amount | number | ✅ | Payment amount |
| currency | string | ❌ | Override currency |
| narration | string | ❌ | Override narration |
| disabled | boolean | ❌ | Disable button |
| className | string | ❌ | CSS class |
| onSuccess | () => void | ❌ | Success callback |
| onError | (error: Error) => void | ❌ | Error callback |
| children | ReactNode | ✅ | Button content |
Advanced Usage
Dynamic Amount
import { useState } from 'react';
import { usePortalPay } from 'portalpay-react';
function DynamicCheckout() {
const [quantity, setQuantity] = useState(1);
const unitPrice = 5000;
const { initPayment } = usePortalPay({
amountSource: {
type: 'prop',
getter: () => quantity * unitPrice
}
});
return (
<div>
<input
type="number"
value={quantity}
onChange={(e) => setQuantity(Number(e.target.value))}
/>
<button onClick={initPayment}>
Pay GHS {(quantity * unitPrice / 100).toFixed(2)}
</button>
</div>
);
}Mock Mode Testing
function TestCheckout() {
return (
<PortalPayProvider
config={{
publishableKey: 'pk_test_alexpay_xxxxxxxx',
forceMock: true,
checkoutUrl: '/checkout'
}}
>
<PortalPayButton amount={5000}>
Test Payment
</PortalPayButton>
</PortalPayProvider>
);
}Error Handling
import { usePortalPay } from 'portalpay-react';
function SafeCheckout() {
const { state, error, initPayment, retry } = usePortalPay({
amountSource: { type: 'prop', getter: () => 5000 }
});
if (state === 'error') {
return (
<div>
<p>⚠️ {error}</p>
<button onClick={retry}>Retry</button>
</div>
);
}
return (
<button onClick={initPayment} disabled={state !== 'ready'}>
{state === 'fetching' ? 'Initializing...' : 'Pay Now'}
</button>
);
}Custom Button with Hook
import { usePortalPay } from 'portalpay-react';
function CustomCheckoutButton() {
const { state, initPayment } = usePortalPay({
amountSource: { type: 'prop', getter: () => 10000 }
});
return (
<button
onClick={initPayment}
disabled={state !== 'ready'}
className="checkout-btn"
style={{
background: state === 'ready' ? '#1a1714' : '#ccc',
padding: '12px 24px',
borderRadius: '8px'
}}
>
{state === 'fetching' && <span className="spinner" />}
{state === 'ready' && 'Pay GHS 100.00'}
{state === 'error' && 'Try Again'}
</button>
);
}TypeScript Support
Full TypeScript definitions included:
import type {
PortalPayConfig,
UsePortalPayOptions,
UsePortalPayResult,
AmountSource
} from 'portalpay-react';Testing
npm test
# or
yarn testExamples
Next.js App Router
// app/checkout/page.tsx
'use client';
import { PortalPayProvider, PortalPayButton } from 'portalpay-react';
export default function CheckoutPage() {
return (
<PortalPayProvider
config={{
publishableKey: process.env.NEXT_PUBLIC_PORTALPAY_KEY!,
currency: 'GHS'
}}
>
<PortalPayButton amount={5000}>
Complete Purchase
</PortalPayButton>
</PortalPayProvider>
);
}Create React App
// src/App.tsx
import { PortalPayProvider, usePortalPay } from 'portalpay-react';
function PaymentForm() {
const { state, initPayment } = usePortalPay({
amountSource: { type: 'prop', getter: () => 5000 }
});
return (
<button onClick={initPayment} disabled={state !== 'ready'}>
Pay Now
</button>
);
}
function App() {
return (
<PortalPayProvider config={{ key: 'pk_live_...' }}>
<PaymentForm />
</PortalPayProvider>
);
}Browser Support
- ✅ Chrome 80+
- ✅ Firefox 75+
- ✅ Safari 13+
- ✅ Edge 80+
Related Packages
portalpay- Browser bundleportalpay-core- Core functionalityportalpay-vue- Vue integration
License
MIT © Vesicash
Support
- 📧 Email: [email protected]
- 🌐 Website: https://vesicash.com/portalpay
Build better payment experiences with React + PortalPay ⚛️
