React hooks and components for accepting payments with the Klasha payment gateway.
Two things were wrong with 0.0.x, and both are fatal:
-
The script it loaded no longer exists. 0.0.x fetched
https://klastatic.fra1.digitaloceanspaces.com/{test,prod}/js/klasha-integration.js. That bucket has been deleted; both URLs now return a 404.window.KlashaClientis therefore never defined and no payment can start at all. Klasha's current script ishttps://js.klasha.com/pay.js, which 1.0.0 loads. -
isTestModewas never sent to the gateway. The Klasha client takes the test flag as its 9th constructor argument; 0.0.x stopped at 8. Inside the Klasha script the flag then read asundefined, so the base URL, the iframe and the redirect URL all took the production branch. This matters now: the currentpay.jsserves one URL for both environments and decides which gateway to use from that argument alone, so repointing 0.0.x at the new script without adding it would route sandbox traffic to production. 1.0.0 passes it, coerced to a real boolean.To be clear about the past: while the old CDN still existed, 0.0.x picked
/test/js/or/prod/js/correctly based onisTestMode, and that old script was environment-specific by URL. There is no reason to believe your historical sandbox payments went to production, and nothing to reconcile.
1.0.0 also fixes a silently dropped phone number, duplicate DOM ids, a load race
that reported "ready" before the Klasha script had run, and console.log calls
that printed your merchantKey to the browser console. See
Migrating from 0.0.x.
- React 16.8 or later (17, 18 and 19 are supported)
- Node 18+ to build from source
0.0.x declared support for React 15, but the library is built on hooks and has never worked below 16.8.
npm install react-klashayarn add react-klashaimport {KlashaButton} from 'react-klasha';
export default function Checkout() {
return (
<KlashaButton
text="Make Payment"
className="payButton"
merchantKey="YOUR_MERCHANT_KEY"
businessId="YOUR_BUSINESS_ID"
amount={1000}
sourceCurrency="USD"
destinationCurrency="NGN"
tx_ref={`tx-${Date.now()}`}
fullname="John Doe"
email="john@example.com"
phone="+2348143108254"
paymentDescription="Order #1234"
isTestMode
callBack={(response) => console.log('payment complete', response)}
/>
);
}There is a single script — https://js.klasha.com/pay.js — for both test and
live traffic. The environment is chosen entirely by isTestMode, which this
library forwards as the 9th constructor argument:
isTestMode |
Gateway used |
|---|---|
true |
Klasha test environment |
false (default) |
Klasha live environment |
Use your test merchant key with isTestMode, and your live key without it.
import {useKlashaPayment} from 'react-klasha';
function DonateButton() {
const config = {
merchantKey: 'YOUR_MERCHANT_KEY',
businessId: 'YOUR_BUSINESS_ID',
amount: 1000,
sourceCurrency: 'USD',
destinationCurrency: 'NGN',
tx_ref: `tx-${Date.now()}`,
fullname: 'John Doe',
email: 'john@example.com',
phone: '+2348143108254',
isTestMode: true,
};
const initializePayment = useKlashaPayment(config);
const callBack = (response) => console.log('payment complete', response);
return <button onClick={() => initializePayment(callBack)}>Donate</button>;
}initializePayment throws if it is called before the Klasha script has
loaded, or if the script failed to load. Gate your button on the script state:
import {useKlashaScript, useKlashaPayment} from 'react-klasha';
function PayButton({config, callBack}) {
const [ready, failed] = useKlashaScript(config.isTestMode);
const initializePayment = useKlashaPayment(config);
if (failed) return <p>Payment is unavailable right now.</p>;
return (
<button disabled={!ready} onClick={() => initializePayment(callBack)}>
{ready ? 'Pay' : 'Loading…'}
</button>
);
}useKlashaScript returns [loaded, error]. loaded only turns true once
pay.js itself has executed — never merely because a load was started.
import {KlashaConsumer} from 'react-klasha';
<KlashaConsumer {...config} callBack={callBack}>
{({initializePayment}) => <button onClick={initializePayment}>Pay with Klasha</button>}
</KlashaConsumer>;KlashaConsumer forwards refs. The ref is handed to the render prop:
<KlashaConsumer {...config} ref={myRef}>
{({initializePayment, ref}) => (
<button ref={ref} onClick={initializePayment}>
Pay
</button>
)}
</KlashaConsumer>;Wrap a subtree so nested components share one payment configuration. Nested
components read initializePayment and callBack from context.
import {KlashaProvider} from 'react-klasha';
<KlashaProvider {...config} callBack={callBack}>
<Checkout />
</KlashaProvider>;Accepted by useKlashaPayment, KlashaButton, KlashaConsumer and KlashaProvider.
| Option | Type | Required | Description |
|---|---|---|---|
merchantKey |
string |
✅ | Merchant API key from the Klasha dashboard |
businessId |
string | number |
✅ | Merchant business id |
amount |
number |
✅ | Amount to charge |
isTestMode |
boolean |
Use the Klasha test environment. Defaults to false |
|
sourceCurrency |
string |
Currency the customer is charged in. Defaults to NGN |
|
destinationCurrency |
string |
Currency the merchant is settled in. Defaults to NGN |
|
tx_ref |
string |
Your unique transaction reference. Generated when omitted | |
fullname |
string |
Customer's full name | |
email |
string |
Customer's email | |
phone |
string |
Customer's phone number | |
phone_number |
string |
Deprecated alias of phone |
|
callbackUrl |
string |
URL the gateway calls when the transaction completes | |
paymentDescription |
string |
Description of what is being paid for | |
metadata |
object |
Extra data to keep alongside the transaction | |
kit |
KlashaKitOptions |
Extra fields forwarded to the Klasha client | |
containerId |
string |
Render the checkout into a specific element. One is created per component when omitted |
kit is passed straight to the Klasha client. The keys pay.js actually reads
are businessId, currency, redirect_url, email, phone, productType,
amount, sourceAmount, callBack and tx_ref.
You rarely need to set any of them — this library fills in currency, email,
fullname, phone, amount, tx_ref and callBack from the top-level options
and builds a fresh kit for every payment (the Klasha script mutates the
object it is given, so a shared one leaks state between transactions).
const config = {
// …payment options…
kit: {
productType: 'digital',
sourceAmount: 12,
},
};
kit.phoneis the key the gateway reads. 0.0.x set onlykit.phone_number, whichpay.jsnever looks at, so the customer's phone number was silently dropped from the exchange-rate request. 1.0.0 sets both.
Everything above, plus:
| Prop | Type | Description |
|---|---|---|
text |
string |
Button label |
children |
ReactNode |
Used when text is absent |
className |
string |
CSS class |
disabled |
boolean |
Disable the button |
type |
'button', 'submit' or 'reset' |
Defaults to 'button' |
callBack |
Function |
Called when the payment flow completes |
import {
useKlashaPayment,
useKlashaScript,
useKlashaContainer,
KlashaButton,
KlashaConsumer,
KlashaProvider,
ensureKlashaContainer,
removeKlashaContainer,
getKlashaScriptUrl,
KLASHA_SDK_URL,
} from 'react-klasha';TypeScript types KlashaOptionsProps, KlashaKitOptions, KlashaClientConstructor,
KlashaClientInstance and KlashaRenderProps are exported too.
| Change | Impact |
|---|---|
Loads https://js.klasha.com/pay.js |
The old CDN bucket was deleted and returns 404, so 0.0.x cannot start a payment at all. |
isTestMode is forwarded to the gateway |
Test-mode integrations no longer hit the live environment. Use your test merchant key when isTestMode is set. |
| jQuery is no longer loaded | pay.js does not need it. If you relied on this library to provide window.$, load jQuery yourself. |
useKlashaScript reports loaded only once pay.js has executed |
It used to resolve on whichever of jQuery/Klasha loaded first, so loaded was frequently true while window.KlashaClient was undefined. |
Script error no longer reports loaded: true |
useKlashaScript returns [false, true] on failure instead of [true, true]. |
initializePayment throws instead of silently doing nothing when the script is not ready |
Gate the call on useKlashaScript, or catch the error. |
| The checkout container has a unique per-component id | The hardcoded <div id="ktest"> was appended on every effect run. Pass containerId if you need a specific element. |
kit.phone is populated |
The phone number used to be dropped. kit.phone_number is still set for compatibility. |
tx_ref is generated when omitted |
It is interpolated into the redirect URL and must not be empty. |
console.log of the options (including merchantKey) removed |
Nothing is written to the console any more. |
KlashaConsumer ref actually reaches the render prop |
It was always undefined before. |
Peer range is now react@^16.8 || ^17 || ^18 || ^19 |
React 15 was never actually supported. |
react-dom peer dependency removed |
The library never imported it. |
Build output is dist/index.cjs + dist/index.mjs with an exports map |
Deep imports into dist/ are no longer supported. |
npm install
npm run typecheck
npm test
npm run buildSee the Klasha documentation for the full list of options and for how to verify a transaction server-side.
Always verify a payment on your server before fulfilling an order. Never trust the browser callback alone.
- Fork it
- Create your feature branch:
git checkout -b feature-name - Commit your changes:
git commit -am 'Some commit message' - Push to the branch:
git push origin feature-name - Submit a pull request 😉
Star the repo, and share it! Don't forget to follow me on twitter.
Thanks! Adekanbi Dansteve.
MIT — see LICENSE.md.
