npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.**

[![npm version](https://badge.fury.io/js/@patalink%2Freact-payment-modal.svg)](https://badge.fury.io/js/@patalink%2Freact-payment-modal)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![React Version](https://img.shields.io/badge/react-18.0+-blue.svg)](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-modal

Step 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-modal

yarn

yarn add @patalink/react-payment-modal

pnpm

pnpm add @patalink/react-payment-modal

Peer 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?

  1. Sign up at patalink.me
  2. Go to SettingsAPI Keys
  3. Click Generate API Key and Generate Encryption Key
  4. 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 options
  • fixedAmount: 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. 🚀