Angular component, directive and service for accepting Klasha payments.
angular-klasha@0.0.1 cannot process a payment at all, and never warns you.
-
Its checkout script no longer exists. 0.0.x loaded
https://klastatic.fra1.digitaloceanspaces.com/{test,prod}/js/klasha-integration.js. That DigitalOcean Spaces bucket has been deleted and both URLs now returnNoSuchBucket.window.KlashaClientis therefore never defined and the checkout silently never opens. -
The 9th
isTestModeargument is missing.KlashaClienttakesisTestModeas its 9th constructor argument, and on the currentpay.jsit is the only thing that selects the sandbox. 0.0.x passes eight. That matters now, because the current script serves one URL for both environments — repointing without adding the argument would send sandbox traffic to production. 1.0.0 passes it, coerced to a real boolean.For the avoidance of doubt about the past: while the old CDN existed, 0.0.x selected
/test/js/or/prod/js/correctly fromisTestMode, and that script was environment-specific by URL. There is no evidence historical sandbox payments reached production. -
The customer phone number was dropped. 0.0.x set
kit.phone_number; the Klasha script only ever readskit.phone. -
Duplicate DOM ids. 0.0.x appended a
<div id="ktest">from the service constructor on every injection, so more than one payment button meant more than one element with the same id. -
It loaded jQuery from the Google CDN for no reason — the Klasha script does not use jQuery.
-
It also
console.log'd on construction and shipped a stale vendored copy of the dead integration script inside the published package.
1.0.0 fixes all of the above: it loads the current script
https://js.klasha.com/pay.js, forwards isTestMode as a real boolean 9th
argument, sets kit.phone, gives every instance its own container element, and
loads no jQuery.
There is no safe way to stay on 0.0.x. Upgrade.
| Angular | 16, 17, 18, 19 or 20 (@angular/common and @angular/core) |
| Environment | Browser. The checkout script cannot run during server-side rendering; call pay() from a browser-only code path. |
Published as Ivy partial-compilation output, so it works with both NgModule-based and standalone applications.
npm install --save angular-klashaNo script tag, no jQuery and no extra setup: https://js.klasha.com/pay.js is
injected lazily the first time a payment is started.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideKlasha } from 'angular-klasha';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideKlasha({
merchantKey: 'YOUR_MERCHANT_KEY',
businessId: '1',
isTestMode: true, // false (or omitted) targets the LIVE gateway
}),
],
});forRoot() still works exactly as it did in 0.0.x, so existing code does not
have to change:
import { NgModule } from '@angular/core';
import { AngularKlashaModule } from 'angular-klasha';
@NgModule({
imports: [
AngularKlashaModule.forRoot('YOUR_MERCHANT_KEY', '1', true),
// a config object also works:
// AngularKlashaModule.forRoot({ merchantKey: 'YOUR_MERCHANT_KEY', businessId: '1', isTestMode: true }),
],
})
export class AppModule {}
isTestModehere is what gets forwarded toKlashaClientas its 9th argument. Leaving it out means live payments.
Both are standalone, so you can import either one directly into a
standalone component — or get both by importing AngularKlashaModule.
Renders a <button> that opens the Klasha checkout when clicked.
import { Component } from '@angular/core';
import { AngularKlashaComponent, KlashaOptions } from 'angular-klasha';
@Component({
selector: 'app-checkout',
standalone: true,
imports: [AngularKlashaComponent],
templateUrl: './checkout.component.html',
})
export class CheckoutComponent {
options: KlashaOptions = this.buildOptions();
paymentInit(): void {
console.log('Payment initialised');
}
paymentDone(response: unknown): void {
console.log('Payment done', response);
// Build a fresh options object for the next payment: `tx_ref` must be
// unique and the Klasha script mutates the `kit` object it is given.
this.options = this.buildOptions();
}
private buildOptions(): KlashaOptions {
return {
merchantKey: 'YOUR_MERCHANT_KEY',
businessId: '1',
amount: 10000,
email: 'customer@example.com',
fullname: 'Some Customer',
phone: '+2348000000000',
sourceCurrency: 'NGN',
destinationCurrency: 'NGN',
tx_ref: `klasha-${Date.now()}`,
paymentDescription: 'Order #1234',
isTestMode: true,
};
}
}<angular-klasha
[klashaOptions]="options"
[class]="'btn btn-primary'"
(paymentInit)="paymentInit()"
(callBack)="paymentDone($event)"
>
Pay with Klasha
</angular-klasha>Same behaviour, applied to your own element so you keep full control of the markup:
import { Component } from '@angular/core';
import { AngularKlashaDirective } from 'angular-klasha';
@Component({
selector: 'app-checkout',
standalone: true,
imports: [AngularKlashaDirective],
templateUrl: './checkout.component.html',
})
export class CheckoutComponent { /* … same class body as above … */ }<button
type="button"
angular-klasha
[klashaOptions]="options"
(paymentInit)="paymentInit()"
(callBack)="paymentDone($event)"
>
Pay with Klasha
</button>Every field of KlashaOptions is also available as its own input:
<button
type="button"
angular-klasha
[merchantKey]="'YOUR_MERCHANT_KEY'"
[businessId]="'1'"
[amount]="10000"
[email]="'customer@example.com'"
[fullname]="'Some Customer'"
[phone]="'+2348000000000'"
[tx_ref]="txRef"
[isTestMode]="true"
(paymentInit)="paymentInit()"
(callBack)="paymentDone($event)"
>
Pay with Klasha
</button>merchantKey, businessId and isTestMode set on the component/directive
take priority over the values given to provideKlasha() / forRoot().
(callBack)is required. Without a subscriber,pay()refuses to open the checkout and returnsangular-klasha: Insert a callBack output like so (callBack)='PaymentComplete($event)' to check payment status.
KlashaOptions — merchantKey, businessId, amount and email are
required; everything else is optional.
| Option | Type | Description |
|---|---|---|
merchantKey |
string |
Your Klasha merchant key. Falls back to the globally provided one. |
businessId |
string |
Your Klasha business id. Falls back to the globally provided one. |
amount |
number |
Amount to charge. |
email |
string |
Customer email address. |
fullname |
string |
Customer full name. |
phone |
string |
Customer phone number. This is the field the Klasha script reads. |
phone_number |
string |
Deprecated alias of phone, still accepted. |
sourceCurrency |
string |
Currency you price in. Defaults to NGN. |
destinationCurrency |
string |
Currency the customer is charged in. Defaults to NGN. |
tx_ref |
string |
Unique transaction reference. Generated for you when omitted. |
callbackUrl |
string |
URL the customer is redirected to after paying. |
paymentDescription |
string |
Human readable description of the payment. |
isTestMode |
boolean |
true = Klasha sandbox, false/omitted = live gateway. |
metadata |
unknown |
Free-form data kept on the instance; not sent to Klasha. |
kit |
KlashaKitOptions |
Advanced: extra fields merged into the payload handed to the Klasha script. |
You rarely need to set kit yourself — the service builds it from the options
above. It ends up containing:
| Field | Source |
|---|---|
currency |
sourceCurrency |
phone |
phone (or the deprecated phone_number) |
phone_number |
the same value, kept only for backwards compatibility |
email |
email |
fullname |
fullname |
tx_ref |
tx_ref, generated when omitted |
productType |
kit.productType (or the deprecated kit.paymentType) |
amount |
kit.amount, defaulting to amount |
sourceAmount |
kit.sourceAmount, when you supply one |
callBack |
wired to the (callBack) output for you |
The Klasha script mutates the
kitobject it is given (it assignsbusinessId,currencyandredirect_urlonto it).angular-klashabuilds a brand newkitfor every payment, so never share one between payments.
| Output | Fires |
|---|---|
(paymentInit) |
Immediately before the Klasha checkout is opened. |
(callBack) |
With the Klasha response once the payment flow finishes. Required. |
The Klasha script renders into a container element looked up by id. Each
component/directive instance gets its own id (klasha-container-1,
klasha-container-2, …): the element is created on the first payment, reused
on every subsequent one, and removed on destroy. 0.0.x hardcoded ktest for
every instance and appended a new element each time.
If you need the id — for styling, say — read it off the instance:
@ViewChild(AngularKlashaDirective) klasha!: AngularKlashaDirective;
// this.klasha.containerIdAngularKlashaService is providedIn: 'root' and can be used without the
component or the directive:
import { inject } from '@angular/core';
import { AngularKlashaService } from 'angular-klasha';
const klasha = inject(AngularKlashaService);
async function pay(): Promise<void> {
const options = klasha.getKlashaOptions({
merchantKey: 'YOUR_MERCHANT_KEY',
businessId: '1',
amount: 10000,
email: 'customer@example.com',
fullname: 'Some Customer',
phone: '+2348000000000',
isTestMode: true,
});
options.kit.callBack = (response) => console.log('done', response);
const containerId = 'my-klasha-container';
await klasha.loadScript();
klasha.launch(options, containerId); // creates the container if needed
}| Member | Description |
|---|---|
loadScript() |
Injects https://js.klasha.com/pay.js once and resolves when window.KlashaClient is available. |
getKlashaOptions(options) |
Resolves defaults and builds a fresh kit. |
checkInput(options) |
Returns '' when the options are usable, otherwise an error message. |
launch(resolvedOptions, containerId) |
Constructs KlashaClient (with all nine arguments) and calls init(). |
ensureContainer(id) / removeContainer(id) |
Container lifecycle. |
makeId(length?) |
Random transaction reference generator. |
Also exported: KLASHA_SCRIPT_URL, nextKlashaContainerId(), and the
MERCHANT_KEY, BUSINESS_ID and IS_TEST_MODE injection tokens.
| 0.0.x | 1.0.0 |
|---|---|
AngularKlashaModule.forRoot(key, biz, isTest) |
Unchanged — still works. |
<angular-klasha> / [angular-klasha] |
Unchanged; now standalone as well. |
(callBack), (paymentInit) |
Unchanged. |
phone_number |
Still accepted; prefer phone. |
KlashaOptions required almost every field |
Only merchantKey, businessId, amount and email are required. |
tx_ref had to be supplied |
Generated when omitted. |
Container was always ktest |
Per-instance id, available as .containerId. |
isTestMode was ignored by the Klasha script |
Forwarded as the 9th constructor argument. |
PrivateKlashaOptions |
Renamed ResolvedKlashaOptions (old name kept as a deprecated alias). |
Nothing that worked before has been removed.
You cannot complete a real payment without live merchant credentials. Mock
window.KlashaClient and assert on the constructor arguments — argument index
8 is isTestMode:
const calls: unknown[][] = [];
(window as any).KlashaClient = function (this: any, ...args: unknown[]) {
calls.push(args);
this.init = () => undefined;
};
// … trigger a payment …
expect(calls[0].length).toBe(9);
expect(calls[0][8]).toBe(true); // isTestMode
expect((calls[0][7] as any).phone).toBe('+2348000000000');Fork the repository and open a pull request — contributions are welcome.
Star the GitHub repository, and share it. Don't forget to follow me on twitter!
Thanks! Dansteve Adekanbi.
The MIT License (MIT). Please see the License File for more information.