Skip to content

Repository files navigation

angular-klasha

Angular component, directive and service for accepting Klasha payments.

npm license


⚠️ Upgrade from 0.0.x immediately — 0.0.x is completely non-functional

angular-klasha@0.0.1 cannot process a payment at all, and never warns you.

  1. 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 return NoSuchBucket. window.KlashaClient is therefore never defined and the checkout silently never opens.

  2. The 9th isTestMode argument is missing. KlashaClient takes isTestMode as its 9th constructor argument, and on the current pay.js it 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 from isTestMode, and that script was environment-specific by URL. There is no evidence historical sandbox payments reached production.

  3. The customer phone number was dropped. 0.0.x set kit.phone_number; the Klasha script only ever reads kit.phone.

  4. 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.

  5. It loaded jQuery from the Google CDN for no reason — the Klasha script does not use jQuery.

  6. 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.


Requirements

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.

Install

npm install --save angular-klasha

No script tag, no jQuery and no extra setup: https://js.klasha.com/pay.js is injected lazily the first time a payment is started.

1. Configure your merchant credentials

Standalone applications

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
    }),
  ],
});

NgModule applications

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 {}

isTestMode here is what gets forwarded to KlashaClient as its 9th argument. Leaving it out means live payments.

2. Use the component or the directive

Both are standalone, so you can import either one directly into a standalone component — or get both by importing AngularKlashaModule.

AngularKlashaComponent

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>

AngularKlashaDirective

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>

Individual inputs instead of klashaOptions

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 returns angular-klasha: Insert a callBack output like so (callBack)='PaymentComplete($event)' to check payment status.

Options

KlashaOptionsmerchantKey, 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.

kit

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 kit object it is given (it assigns businessId, currency and redirect_url onto it). angular-klasha builds a brand new kit for every payment, so never share one between payments.

Outputs

Output Fires
(paymentInit) Immediately before the Klasha checkout is opened.
(callBack) With the Klasha response once the payment flow finishes. Required.

The checkout container

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.containerId

Programmatic use

AngularKlashaService 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.

Migrating from 0.0.x

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.

Testing your integration

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');

Contributing

Fork the repository and open a pull request — contributions are welcome.

How can I thank you?

Star the GitHub repository, and share it. Don't forget to follow me on twitter!

Thanks! Dansteve Adekanbi.

License

The MIT License (MIT). Please see the License File for more information.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages