@patalink/react-payment-modal
v1.1.3
Published
```markdown # @patalink/react-payment-modal - React Payment Component
Readme
# @patalink/react-payment-modal - React Payment Component
**A complete, customizable payment modal for React applications. Accept MTN Mobile Money, Airtel, and card payments with one component.**
[](https://badge.fury.io/js/@patalink%2Freact-payment-modal)
[](https://opensource.org/licenses/MIT)
[](https://reactjs.org/)
---
## 📖 Table of Contents
- [Quick Start](#rocket-quick-start)
- [Installation](#package-installation)
- [Props](#props-属性)
- [Usage Examples](#usage-examples)
- [Events](#events-事件监听)
- [Styling](#styling-自定义样式)
- [TypeScript](#typescript)
- [FAQ](#faq)
---
## 🚀 Quick Start
Add a complete payment modal to your React app in 3 simple steps:
### Step 1: Install the package
```bash
npm install @patalink/react-payment-modalStep 2: Add the component
import { PaymentModal } from '@patalink/react-payment-modal';
function App() {
return (
<PaymentModal
apiKey="pk_live_abc123..."
encryptionKey="xK8j3Hd9Fq2Wm5R..."
baseUrl="https://patalink.me"
/>
);
}Step 3: That's it! 🎉
The payment button appears. Users click it, select amount, choose payment method, and pay. You get notified via the onSuccess callback.
📦 Installation
npm
npm install @patalink/react-payment-modalyarn
yarn add @patalink/react-payment-modalpnpm
pnpm add @patalink/react-payment-modalPeer Dependencies
This package requires the following peer dependencies:
npm install react react-dom lucide-react zod⚙️ Props
Required Props
| Prop | Type | Description | Example |
|------|------|-------------|---------|
| apiKey | string | Your API key from dashboard | "pk_live_abc123..." |
| encryptionKey | string | Your encryption key from dashboard | "xK8j3Hd9Fq2Wm5R..." |
| baseUrl | string | API endpoint URL | "https://patalink.me" |
Optional Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| amounts | number[] | [2000, 5000, 10000] | Preset amount options |
| fixedAmount | number | undefined | Fixed amount (skips amount selection) |
| customText | string | "Support Me" | Button text |
| customColor | string | "#16a34a" | Button background color |
| customTextColor | string | "#ffffff" | Button text color |
| buttonRoundness | string | "rounded-xl" | Button border radius |
| font | string | "inherit" | Font family for modal |
| themeColor | string | "#16a34a" | Modal theme/accent color |
| themeTextColor | string | "#ffffff" | Text color on themed elements |
| creatorName | string | "Creator" | Name shown on success screen |
| hideAmountSelector | boolean | false | Hide amount selector |
| logoUrl | string | undefined | Logo image URL |
| companyName | string | undefined | Company name displayed in modal |
| modalBackgroundColor | string | "#ffffff" | Modal background color |
| onSuccess | (data, error) => void | undefined | Callback when API responds |
💡 Usage Examples
Basic Donation Button
import { PaymentModal } from '@patalink/react-payment-modal';
function DonationPage() {
return (
<PaymentModal
apiKey={process.env.NEXT_PUBLIC_PATALINK_API_KEY}
encryptionKey={process.env.PATALINK_ENCRYPTION_KEY}
baseUrl="https://patalink.me"
amounts={[1000, 5000, 10000, 25000, 50000]}
customText="Donate Now"
customColor="#EF4444"
themeColor="#EF4444"
creatorName="Hope for Children"
/>
);
}Event Ticket (Fixed Amount)
import { PaymentModal } from '@patalink/react-payment-modal';
function TicketPage() {
const handlePaymentSuccess = (data, error) => {
if (error) {
console.error('Payment failed:', error);
} else {
console.log('Payment successful:', data);
// Redirect to confirmation page
router.push(`/confirmation?txn=${data.transactionId}`);
}
};
return (
<PaymentModal
apiKey={process.env.NEXT_PUBLIC_PATALINK_API_KEY}
encryptionKey={process.env.PATALINK_ENCRYPTION_KEY}
baseUrl="https://patalink.me"
fixedAmount={25000}
customText="Buy Ticket - 25,000 RWF"
customColor="#8B5CF6"
themeColor="#8B5CF6"
creatorName="Summer Music Festival"
logoUrl="/festival-logo.png"
companyName="VIBEHUB"
onSuccess={handlePaymentSuccess}
/>
);
}Complete E-commerce Checkout
import { useState } from 'react';
import { PaymentModal } from '@patalink/react-payment-modal';
function CheckoutPage() {
const [cartTotal, setCartTotal] = useState(150000);
const [isProcessing, setIsProcessing] = useState(false);
const handlePaymentComplete = (data, error) => {
setIsProcessing(false);
if (error) {
alert(`Payment failed: ${error.message}`);
} else {
// Clear cart, show success, redirect
localStorage.removeItem('cart');
window.location.href = `/order-confirmation?id=${data.transactionId}`;
}
};
return (
<div className="checkout-container">
<h2>Order Summary</h2>
<p>Total: {cartTotal.toLocaleString()} RWF</p>
<PaymentModal
apiKey={process.env.NEXT_PUBLIC_PATALINK_API_KEY}
encryptionKey={process.env.PATALINK_ENCRYPTION_KEY}
baseUrl="https://patalink.me"
fixedAmount={cartTotal}
customText={`Pay ${cartTotal.toLocaleString()} RWF`}
customColor="#10B981"
themeColor="#10B981"
creatorName="Your Store"
onSuccess={handlePaymentComplete}
/>
</div>
);
}Dark Mode Integration
import { PaymentModal } from '@patalink/react-payment-modal';
function DarkModeApp() {
const [isDarkMode, setIsDarkMode] = useState(true);
return (
<div className={isDarkMode ? 'dark' : 'light'}>
<PaymentModal
apiKey={process.env.NEXT_PUBLIC_PATALINK_API_KEY}
encryptionKey={process.env.PATALINK_ENCRYPTION_KEY}
baseUrl="https://patalink.me"
amounts={[5000, 10000, 25000]}
customText="Support"
customColor="#6366F1"
themeColor="#6366F1"
modalBackgroundColor={isDarkMode ? '#1A1A1A' : '#FFFFFF'}
themeTextColor="#FFFFFF"
creatorName="Content Creator"
/>
</div>
);
}Multiple Price Tiers
import { PaymentModal } from '@patalink/react-payment-modal';
function ProductPage() {
const [selectedTier, setSelectedTier] = useState(null);
const tiers = {
basic: 10000,
pro: 50000,
enterprise: 250000
};
return (
<div>
<div className="pricing-tiers">
{Object.entries(tiers).map(([name, price]) => (
<button key={name} onClick={() => setSelectedTier(price)}>
{name} - {price.toLocaleString()} RWF
</button>
))}
</div>
{selectedTier && (
<PaymentModal
apiKey={process.env.NEXT_PUBLIC_PATALINK_API_KEY}
encryptionKey={process.env.PATALINK_ENCRYPTION_KEY}
baseUrl="https://patalink.me"
fixedAmount={selectedTier}
customText={`Subscribe - ${selectedTier.toLocaleString()} RWF`}
customColor="#8B5CF6"
themeColor="#8B5CF6"
creatorName="Premium Service"
onSuccess={(data, error) => {
if (!error) {
// Activate subscription
activateSubscription(data.transactionId);
}
}}
/>
)}
</div>
);
}🎯 Events
onSuccess Callback
The onSuccess callback fires immediately when the API responds, not after polling completes.
<PaymentModal
apiKey="your_api_key"
encryptionKey="your_encryption_key"
baseUrl="https://patalink.me"
onSuccess={(data, error) => {
if (error) {
// Handle error
console.error('Payment failed:', error.message);
showErrorToast(error.message);
} else {
// Handle success
console.log('Payment created:', data);
console.log('Transaction ID:', data.transactionId);
console.log('Status:', data.status);
// data can be:
// - { status: 'pending', transactionId: '...' } - waiting for user to approve
// - { status: 'success', transactionId: '...' } - instant success
// - { redirectUrl: '...' } - for card payments, redirect user
if (data.redirectUrl) {
window.location.href = data.redirectUrl;
}
}
}}
/>🎨 Styling
Center the Button
/* CSS to center the payment button */
.payment-button-wrapper {
display: flex;
justify-content: center;
width: 100%;
}
/* Or target the component directly */
[data-payment-modal] button {
margin: 0 auto;
width: auto !important;
min-width: 200px;
}Custom Button Styling
/* Override button styles using the custom props */
.payment-modal-button {
font-size: 18px !important;
font-weight: 600 !important;
padding: 14px 32px !important;
border-radius: 50px !important;
transition: transform 0.2s !important;
}
.payment-modal-button:hover {
transform: scale(1.02) !important;
}🔷 TypeScript
Full TypeScript support with complete type definitions.
import { PaymentModal, PaymentModalProps, PaymentSuccessData } from '@patalink/react-payment-modal';
const App: React.FC = () => {
const config: PaymentModalProps = {
apiKey: process.env.NEXT_PUBLIC_PATALINK_API_KEY!,
encryptionKey: process.env.PATALINK_ENCRYPTION_KEY!,
baseUrl: 'https://patalink.me',
amounts: [1000, 5000, 10000],
fixedAmount: 25000,
customText: 'Donate Now',
customColor: '#EF4444',
themeColor: '#EF4444',
creatorName: 'Charity Organization',
onSuccess: (data: PaymentSuccessData, error?: any) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Success:', data.transactionId);
}
}
};
return <PaymentModal {...config} />;
};❓ FAQ
Do I need a backend?
Yes. The modal sends encrypted payment data to your backend API. You need to implement the /api/pay endpoint that processes payments.
How do I get my API and encryption keys?
- Sign up at patalink.me
- Go to Settings → API Keys
- Click Generate API Key and Generate Encryption Key
- Copy both keys to your environment variables
What payment methods are supported?
- MTN Mobile Money
- Airtel Money
- Credit/Debit Cards (via PesaPal)
Can I use this without encryption key?
No. Both apiKey and encryptionKey are required for secure transactions.
Is the encryption automatic?
Yes. The component automatically encrypts all payment data before sending to your API.
What's the difference between amounts and fixedAmount?
amounts: Shows amount selection step with preset optionsfixedAmount: Skips amount selection, uses this amount directly
Can I test without real money?
Yes. Use your test keys from the dashboard (they start with pk_test_).
How do I handle the payment response?
Use the onSuccess callback. It fires immediately with the API response, giving you the transactionId and status.
Does it work with Next.js?
Yes. Use it with Next.js client components:
'use client';
import { PaymentModal } from '@patalink/react-payment-modal';Can I customize the look and feel?
Absolutely. Use the styling props (customColor, themeColor, modalBackgroundColor, etc.) or override CSS.
📞 Support
📄 License
MIT © PataLink
Add payments to your React app with one component. No payment gateway headaches. 🚀
