@smarthivelabs-devs/payments-react
v1.2.0
Published
React hooks and components for SmartHive Payments checkout integration
Downloads
478
Readme
@smarthivelabs-devs/payments-react
Official React SDK for SmartHive Payments — hooks and components for embedding checkout in any React web app.
Installation
npm install @smarthivelabs-devs/payments-reactPeer dependencies (install separately):
npm install react react-domHow it works
Your frontend never holds an API key. The React SDK talks to your own backend, which talks to SmartHive using @smarthivelabs-devs/payments. Your backend creates the checkout session and exposes a verify endpoint; the React SDK handles the UI state machine.
Browser → Your Backend → SmartHive APISetup — SmartHiveProvider
Wrap your app (or just the payment section) with SmartHiveProvider. Pass the base URL of your own backend.
import { SmartHiveProvider } from '@smarthivelabs-devs/payments-react';
export default function App() {
return (
<SmartHiveProvider apiBaseUrl="https://yourapp.com">
<CheckoutPage />
</SmartHiveProvider>
);
}| Prop | Type | Required | Description |
|---|---|---|---|
| apiBaseUrl | string | Yes | Base URL of your own backend (e.g. https://yourapp.com) |
| sessionEndpoint | string | No | Override default session creation path. Default: /api/payments/checkout/session |
| verifyEndpoint | string | No | Override default verify path. Default: /api/payments/verify/:reference |
useCheckout — Full Checkout Flow
The all-in-one hook. Creates a session, opens the checkout URL, then polls for completion.
import { useCheckout } from '@smarthivelabs-devs/payments-react';
function CheckoutPage() {
const { createCheckout, status, error, checkoutUrl, reset } = useCheckout({
onSuccess: (data) => {
console.log('Paid!', data.reference);
router.push('/order-complete');
},
onFailed: (data) => {
console.log('Payment failed', data.reference);
},
onCancelled: () => {
console.log('Customer cancelled');
},
});
return (
<div>
<button
onClick={() =>
createCheckout({
amount: 5000, // GHS 50.00 in pesewas
currency: 'GHS',
countryCode: 'GH',
customerEmail: '[email protected]',
customerName: 'Kwame Mensah',
})
}
disabled={status === 'creating' || status === 'pending'}
>
{status === 'creating' ? 'Creating...' : 'Pay GHS 50'}
</button>
{status === 'success' && <p>Payment complete!</p>}
{status === 'failed' && <p>Payment failed. <button onClick={reset}>Try again</button></p>}
{error && <p>Error: {error.message}</p>}
</div>
);
}Status values
| Status | Meaning |
|---|---|
| idle | No checkout in progress |
| creating | Calling your backend to create a session |
| redirecting | Opening the checkout URL |
| pending | Checkout URL opened — waiting for payment |
| success | Payment confirmed |
| failed | Payment failed |
| error | An unexpected error occurred |
useCheckout options
| Option | Type | Default | Description |
|---|---|---|---|
| backendEndpoint | string | From context | Override the session creation endpoint |
| verifyEndpoint | string | From context | Override the verify endpoint |
| openMode | 'redirect' \| 'new-tab' | 'redirect' | How to open the checkout URL |
| pollInterval | number | 3000 | Status polling interval in ms |
| maxAttempts | number | 60 | Max polling attempts before timeout |
| onSuccess | (data) => void | — | Called on successful payment |
| onFailed | (data) => void | — | Called on failed payment |
| onCancelled | () => void | — | Called when customer cancels |
| onTimeout | () => void | — | Called when polling times out |
useCheckoutSession — Session Only
Creates the checkout session and returns the URL without opening it — useful when you want to control the redirect yourself.
Important:
createSessiononly creates a pre-payment container. No payment transaction exists yet.data.sessionIdis a session ID (chs_xxx), not a payment reference. The payment reference (txn_xxx) is only created when the customer completes payment on the hosted checkout page.To get the payment reference after checkout, read the
?reference=query parameter from yourreturnUrl(e.g.https://yourapp.com/order/complete?reference=txn_xxx) or listen for thepayment.successwebhook event.
import { useCheckoutSession } from '@smarthivelabs-devs/payments-react';
function MyButton() {
const { createSession, status, sessionData, error } = useCheckoutSession({
onCreated: (data) => {
// data.checkoutUrl — redirect the customer here
// data.sessionId — session ID (chs_xxx), NOT a payment reference
// There is no payment reference yet; it is created when the customer pays.
window.open(data.checkoutUrl, '_blank');
},
});
return (
<button
onClick={() =>
createSession({
amount: 10000,
currency: 'GHS',
countryCode: 'GH',
customerEmail: '[email protected]',
})
}
disabled={status === 'creating'}
>
{status === 'creating' ? 'Please wait...' : 'Buy Now'}
</button>
);
}usePaymentStatus — Polling Hook
Poll for payment status after the customer returns from the checkout page. Use this if you handle the checkout redirect yourself.
Where does
referencecome from? Three options:
callerReference— passcallerReference: 'ORDER-001'tocreateSessionon your backend. After the customer pays, verify withsmarthive.payments.verify('ORDER-001'). Simplest approach if you have your own order ID.- returnUrl query param — SmartHive appends
?reference=txn_xxxto yourreturnUrlafter payment. Extract it and pass to your verify endpoint.- Webhook —
event.data.referencefrom yourpayment.successwebhook event.Do not pass the session ID (
chs_xxx) fromcreateSession— it will always return 404.returnUrl: 'https://yourapp.com/order/complete' → after payment: 'https://yourapp.com/order/complete?reference=txn_xxx&status=success'
import { usePaymentStatus } from '@smarthivelabs-devs/payments-react';
// Extract the reference from the returnUrl query string:
// const reference = new URLSearchParams(window.location.search).get('reference');
function OrderConfirmation({ reference }: { reference: string }) {
const { status, data, isLoading, error, stop } = usePaymentStatus(reference, {
pollInterval: 3000,
maxAttempts: 40,
onSuccess: (data) => {
console.log('Confirmed:', data.paidAt);
},
onFailed: (data) => {
console.log('Failed payment for', data.reference);
},
onTimeout: () => {
console.log('Giving up polling after max attempts');
},
});
if (isLoading && status === 'pending') return <p>Verifying payment...</p>;
if (status === 'success') return <p>Payment confirmed! Reference: {data?.reference}</p>;
if (status === 'failed') return <p>Payment failed.</p>;
if (error) return <p>Error: {error.message} <button onClick={stop}>Stop</button></p>;
return <p>Awaiting payment...</p>;
}Pass null or undefined as the reference to keep the hook idle:
const { status } = usePaymentStatus(hasReference ? reference : null, options);CheckoutButton — Drop-in Component
A ready-made button that wraps useCheckout. Fully customizable.
import { CheckoutButton } from '@smarthivelabs-devs/payments-react';
function ProductPage() {
return (
<CheckoutButton
params={{
amount: 5000,
currency: 'GHS',
countryCode: 'GH',
customerEmail: '[email protected]',
}}
label="Pay GHS 50"
loadingLabel="Opening checkout..."
className="my-pay-button"
onSuccess={(data) => router.push(`/order/${data.reference}`)}
onFailed={() => alert('Payment failed. Please try again.')}
/>
);
}CheckoutButton props
| Prop | Type | Default | Description |
|---|---|---|---|
| params | CheckoutParams | Required | Payment parameters |
| label | string | 'Pay Now' | Button text |
| loadingLabel | string | 'Processing...' | Text while loading |
| className | string | — | CSS class |
| style | CSSProperties | — | Inline style |
| disabled | boolean | — | Additional disable condition |
| onSuccess | (data) => void | — | Called on success |
| onFailed | (data) => void | — | Called on failure |
| onCancelled | () => void | — | Called on cancel |
PaymentStatusDisplay — Drop-in Status Component
Renders status UI automatically — spinner, success message, or error.
import { PaymentStatusDisplay } from '@smarthivelabs-devs/payments-react';
function OrderPage({ reference }: { reference: string }) {
return (
<PaymentStatusDisplay
reference={reference}
onSuccess={(data) => console.log('Paid at', data.paidAt)}
renderSuccess={(data) => <SuccessBanner reference={data.reference} />}
renderFailed={() => <p>Payment failed. Please try again.</p>}
renderPending={() => <p>Verifying your payment...</p>}
/>
);
}Coupons
useCouponValidate — Dry-run validation hook
Preview a coupon's discount before the customer commits to checkout. This calls the validate endpoint and never increments usage.
import { useCouponValidate } from '@smarthivelabs-devs/payments-react';
function CheckoutWithCoupon() {
const [applied, setApplied] = useState<{
code: string;
discountAmountMinor: number;
finalAmountMinor: number;
} | null>(null);
const { validate, result, loading, error, reset } = useCouponValidate({
apiKey: process.env.NEXT_PUBLIC_SMARTHIVE_API_KEY!,
baseUrl: process.env.NEXT_PUBLIC_PAYMENTS_BACKEND_URL!,
});
const handleApply = async (code: string) => {
const discount = await validate(code, {
amountMinor: 5000, // GHS 50.00
currency: 'GHS',
applicableTo: 'checkout',
});
if (discount) {
setApplied({ code, discountAmountMinor: discount.discountAmountMinor, finalAmountMinor: discount.finalAmountMinor });
}
};
return (
<div>
{applied ? (
<p>Save {applied.discountAmountMinor / 100} — pay {applied.finalAmountMinor / 100}</p>
) : (
<button onClick={() => handleApply('SUMMER20')}>Apply SUMMER20</button>
)}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
);
}useCouponValidate options
| Option | Type | Required | Description |
|---|---|---|---|
| apiKey | string | Yes | Your public-facing API key |
| baseUrl | string | No | Backend base URL (default: window.location.origin) |
Return values
| Field | Type | Description |
|---|---|---|
| validate | (code, params) => Promise<CouponValidateResult \| null> | Call to dry-run a coupon. Returns the discount breakdown or null on error. |
| result | CouponValidateResult \| null | The latest successful result |
| loading | boolean | True while the request is in-flight |
| error | string \| null | Error message if validation failed |
| reset | () => void | Clear result and error |
CouponInput — Drop-in component
Pre-built input + Apply button. Handles validation state, success display, and removal in one component.
import { CouponInput } from '@smarthivelabs-devs/payments-react';
function CheckoutForm() {
const [coupon, setCoupon] = useState<{
code: string;
discount: number;
final: number;
} | null>(null);
return (
<form>
<p>Total: GHS {coupon ? (coupon.final / 100).toFixed(2) : '50.00'}</p>
<CouponInput
amountMinor={5000}
currency="GHS"
apiKey={process.env.NEXT_PUBLIC_SMARTHIVE_API_KEY!}
baseUrl={process.env.NEXT_PUBLIC_PAYMENTS_BACKEND_URL!}
onApply={(code, discountAmountMinor, finalAmountMinor) => {
setCoupon({ code, discount: discountAmountMinor, final: finalAmountMinor });
}}
onRemove={() => setCoupon(null)}
/>
{/* Renders: [________] [Apply] */}
{/* When valid: ✓ SUMMER20 — GHS 10.00 off [Remove] */}
</form>
);
}CouponInput props
| Prop | Type | Required | Description |
|---|---|---|---|
| amountMinor | number | Yes | Order amount in minor units (used for dry-run preview) |
| currency | string | Yes | ISO currency code (e.g. GHS) |
| apiKey | string | Yes | Your public-facing API key |
| baseUrl | string | No | Backend base URL |
| onApply | (code, discountAmountMinor, finalAmountMinor) => void | Yes | Called when a valid coupon is applied |
| onRemove | () => void | Yes | Called when the coupon is removed |
| customerId | string | No | Customer ID (forwarded for per-customer limit checks) |
| customerEmail | string | No | Customer email (forwarded for per-customer limit checks) |
| className | string | No | CSS class for the container |
Applying the coupon at checkout
After the customer validates a coupon, pass couponCode in the session params. The backend computes the discount and stores it — the customer pays the discounted total.
Also set allowCouponInput: true so the hosted checkout page shows a coupon entry field. Without this flag the coupon UI is hidden on the checkout page even if the merchant has active coupons.
const { createCheckout } = useCheckout({ onSuccess: (data) => console.log(data) });
createCheckout({
amount: 5000,
currency: 'GHS',
countryCode: 'GH',
couponCode: 'SUMMER20', // ← pre-apply; backend applies discount server-side
allowCouponInput: true, // ← show coupon input field on the hosted checkout page
customerEmail: '[email protected]',
});couponCode and allowCouponInput are also accepted by useCheckoutSession, CheckoutButton (via params), and your backend's createSession call.
Backend Requirements
Your backend needs two endpoints:
1. Session creation endpoint
POST /api/payments/checkout/session (default path)
// Express example using @smarthivelabs-devs/payments
import { SmartHivePayments } from '@smarthivelabs-devs/payments';
const smarthive = new SmartHivePayments({
apiKey: process.env.SMARTHIVE_PAYMENTS_API_KEY!,
baseUrl: process.env.SMARTHIVE_PAYMENTS_BASE_URL!,
});
app.post('/api/payments/checkout/session', async (req, res) => {
const { amount, currency, countryCode, customerEmail, customerName } = req.body;
const session = await smarthive.checkout.createSession({
amount: String(amount),
currency,
countryCode,
customerEmail,
customerName,
platform: 'web',
returnUrl: `${process.env.FRONTEND_URL}/order/complete`,
callbackUrl: `${process.env.BACKEND_URL}/webhooks/payments`,
callerReference: req.body.callerReference, // optional — your own order ID
mode: 'sandbox', // or 'live'
});
// If you passed callerReference, you can later call:
// smarthive.payments.verify('ORDER-001') instead of waiting for ?reference=txn_xxx
res.json(session);
});2. Verify endpoint
GET /api/payments/verify/:reference (default path)
This endpoint is called after payment is complete — it verifies a
txn_xxxpayment reference extracted from thereturnUrlquery string or a webhook event. It will return 404 if called before the customer pays (e.g. immediately aftercreateSession).
app.get('/api/payments/verify/:reference', async (req, res) => {
// req.params.reference must be a payment reference (txn_xxx),
// not a session ID (chs_xxx). Extract it from ?reference= in your returnUrl.
const payment = await smarthive.payments.verify(req.params.reference);
res.json(payment);
});TypeScript
All types are exported:
import type {
UseCheckoutParams,
UseCheckoutResult,
CheckoutState,
UseCheckoutSessionResult,
CreateCheckoutSessionParams,
CheckoutSessionData,
UsePaymentStatusOptions,
UsePaymentStatusResult,
PaymentStatusData,
CheckoutButtonProps,
SmartHiveProviderProps,
// Coupon types
UseCouponValidateOptions,
UseCouponValidateResult,
CouponValidateParams,
CouponValidateResult,
CouponInputProps,
} from '@smarthivelabs-devs/payments-react';Full Example — Ecommerce Checkout
import { SmartHiveProvider, CheckoutButton } from '@smarthivelabs-devs/payments-react';
export default function App() {
return (
<SmartHiveProvider apiBaseUrl="https://api.yourshop.com">
<ProductPage />
</SmartHiveProvider>
);
}
function ProductPage() {
const router = useRouter();
return (
<div>
<h1>Premium Plan — GHS 200/month</h1>
<CheckoutButton
params={{ amount: 20000, currency: 'GHS', countryCode: 'GH' }}
label="Subscribe Now"
onSuccess={(data) => router.push(`/dashboard?ref=${data.reference}`)}
onFailed={() => router.push('/checkout/failed')}
/>
</div>
);
}Sandbox & Live Mode
The React SDK passes mode in every checkout/subscription request to your backend. Your backend validates that mode matches the API key prefix it holds (sk_test_ → 'sandbox', sk_live_ → 'live').
Always pass mode explicitly. Omitting it defaults to 'sandbox' — safe during development, but must be 'live' in production:
const { openCheckout } = useCheckout({
onSuccess: (data) => console.log(data),
});
// In production — derive from env so it's always in sync with the server key
openCheckout({
amount: 5000,
currency: 'GHS',
mode: process.env.NEXT_PUBLIC_PAYMENT_MODE as 'sandbox' | 'live',
// ...
});For SubscriptionProvider / useMultiPlanAccess, the apiKey you pass is your own backend's public key — not the SmartHive API key. The SmartHive key (sk_test_... / sk_live_...) lives server-side only and is never exposed to the browser.
Support
Contact Smart Hive Labs or open an issue in the workspace repository.
