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

@ng-archefusion/angular

v1.1.2

Published

Official Angular SDK for the Archefusion payment orchestration platform

Readme

@ng-archefusion/angular

Official Angular SDK for Archefusion — a payment orchestration platform that intelligently routes transactions through the best available payment gateway (Paystack, Flutterwave, Monnify, Interswitch, and more) using a single integration.


Table of Contents


Requirements

| Dependency | Version | |------------|---------| | @angular/core | >= 12.0.0 | | @angular/common | >= 12.0.0 | | rxjs | >= 7.0.0 |

Node.js >= 18.0.0 is required only for server-side webhook verification (Angular Universal / Express). Browser-only apps have no Node.js requirement.


Installation

npm install @ng-archefusion/angular

Architecture

Archefusion API keys are secret keys — there is no separate public/publishable key for browser use. Putting the key in your Angular app means it will be visible in the JavaScript bundle.

The correct flow for a browser SPA is:

Browser (Angular) → Your backend API → Archefusion API

Your Angular app calls your own backend, which holds the secret key and calls Archefusion. The backend returns only what the frontend needs (e.g. the checkout redirect URL).

If you use Angular Universal (SSR), you can call Archefusion directly from the server-rendered side — the key never reaches the browser.


API Key Setup

  1. Log in to your Archefusion merchant dashboard.
  2. Navigate to Settings → API Keys.
  3. Copy your Secret Key — it starts with af_live_ (production) or af_test_ (test mode).

Store it as a server-side environment variable. Never hardcode it or put it in environment.ts.

# .env (server only — never commit this file)
ARCHEFUSION_API_KEY=af_live_YOUR_KEY_HERE
ARCHEFUSION_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET_HERE

Register the SDK

Registration is only needed if you are calling Archefusion directly from the server (Angular Universal). Browser SPAs that proxy through their own backend do not need this.

Standalone app (Angular 14+)

// app.config.ts
import { provideHttpClient } from '@angular/common/http';
import { provideArchefusion } from '@ng-archefusion/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideArchefusion({ apiKey: process.env['ARCHEFUSION_API_KEY'] }),
  ],
};

NgModule app

// app.module.ts
import { HttpClientModule } from '@angular/common/http';
import { ArchefusionModule } from '@ng-archefusion/angular';

@NgModule({
  imports: [
    HttpClientModule,
    ArchefusionModule.forRoot({ apiKey: process.env['ARCHEFUSION_API_KEY'] }),
  ],
})
export class AppModule {}

Usage

Browser SPA — call your own backend

Your Angular service calls your own API endpoint. No API key in the browser.

// src/app/shared/services/payment.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment';

@Injectable({ providedIn: 'root' })
export class PaymentService {
  constructor(private http: HttpClient) {}

  initiate(payload: { orderId: string; amount: number; currency: string; email?: string }) {
    return this.http.post<{ redirectUrl: string }>(
      `${environment.apiBaseUrl}/api/payments/initiate`,
      payload
    );
  }

  verify(paymentId: string) {
    return this.http.post<{ status: string }>(
      `${environment.apiBaseUrl}/api/payments/${paymentId}/verify`,
      {}
    );
  }
}

Your backend receives the request, calls Archefusion with the secret key, and returns the redirect URL:

// backend route (Express / NestJS)
import { PaymentsService } from '@ng-archefusion/angular';

async function initiatePayment(req, res) {
  const result = await firstValueFrom(
    paymentsService.initiate({
      merchantOrderId: req.body.orderId,
      amount: req.body.amount,
      currency: req.body.currency,
      customer: { email: req.body.email },
      redirectUrl: req.body.redirectUrl,
    })
  );
  res.json({ redirectUrl: result.data.recommendedGateway.session.redirectUrl });
}

Then in your component:

pay(): void {
  this.paymentService.initiate({ orderId: `order-${crypto.randomUUID()}`, amount: 5000, currency: 'NGN', email: '[email protected]' })
    .subscribe({
      next: (res) => window.location.href = res.redirectUrl,
      error: (err) => console.error(err),
    });
}

Angular Universal (SSR) — call Archefusion server-side

Inject PaymentsService directly in a server-side service using PLATFORM_ID to prevent browser execution:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { PaymentsService, InitiatePaymentRequest } from '@ng-archefusion/angular';

@Injectable({ providedIn: 'root' })
export class SsrPaymentService {
  constructor(
    private payments: PaymentsService,
    @Inject(PLATFORM_ID) private platformId: object,
  ) {}

  initiate(payload: InitiatePaymentRequest) {
    if (!isPlatformServer(this.platformId)) {
      throw new Error('Payment initiation must run server-side only.');
    }
    return this.payments.initiate(payload);
  }
}

Verify a payment

After the customer pays, Archefusion redirects them to your redirectUrl with ?paymentId=xxx appended. Always verify server-side before delivering value — do not trust the status query parameter.

// callback.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PaymentService } from '@app/shared/services/payment.service';

@Component({
  selector: 'app-payment-callback',
  standalone: true,
  template: `
    @if (loading) { <p>Verifying payment...</p> }
    @else if (status === 'SUCCESS') { <p>Payment successful!</p> }
    @else { <p>Payment did not complete. Status: {{ status }}</p> }
  `,
})
export class CallbackComponent implements OnInit {
  loading = true;
  status = '';

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private paymentService: PaymentService,
  ) {}

  ngOnInit(): void {
    const paymentId = this.route.snapshot.queryParamMap.get('paymentId');
    if (!paymentId) { this.router.navigate(['/']); return; }

    this.paymentService.verify(paymentId).subscribe({
      next: (res) => { this.status = res.status; this.loading = false; },
      error: () => { this.status = 'ERROR'; this.loading = false; },
    });
  }
}

Payment statuses:

| Status | Meaning | |--------|---------| | INITIATED | Payment created, customer not yet redirected. | | PENDING | Customer at checkout, payment not yet completed. | | SUCCESS | Payment completed. Safe to deliver value. | | FAILED | Payment failed or was declined. | | REFUNDED | Payment was refunded. |


Handle webhooks

Webhooks deliver real-time payment events to your server even if the customer never returns to your app. Use WebhookService to verify the HMAC-SHA256 signature.

Must run server-side (Angular Universal / Express). Never put your webhook secret in browser code.

// server/webhook.handler.ts (Express)
import express from 'express';
import { WebhookService } from '@ng-archefusion/angular';

const app = express();

app.post(
  '/webhooks/archefusion',
  express.raw({ type: 'application/json' }), // raw body — do NOT parse as JSON first
  async (req, res) => {
    const webhookService = new WebhookService({ apiKey: '' }); // only secret is used here

    const result = await webhookService.verify(
      req.headers,
      req.body.toString(),
      process.env['ARCHEFUSION_WEBHOOK_SECRET']
    );

    if (!result.valid) return res.status(400).send('Invalid signature');

    switch (result.event!.type) {
      case 'payment.succeeded': await fulfillOrder(result.event!.data.paymentId); break;
      case 'payment.failed':    await notifyCustomer(result.event!.data.paymentId); break;
      case 'refund.successful': await processRefund(result.event!.data.paymentId); break;
    }

    res.status(200).send('OK');
  }
);

Supported events:

| Event | Fires when | |-------|-----------| | payment.created | A payment is initiated. | | payment.updated | A payment status changes. | | payment.succeeded | A payment completes successfully. | | payment.failed | A payment fails for any reason. | | refund.created | A refund is initiated. | | refund.successful | A refund completes successfully. | | refund.failed | A refund fails. |

Signature verification rules:

  • Always pass the raw request body — do not JSON-parse and re-stringify.
  • Respond 200 OK immediately after signature verification; process asynchronously.
  • Make your handler idempotent — Archefusion retries failed deliveries. Guard against duplicate processing using the event id.

Auth Interceptor Safety

If your app has an HTTP interceptor that attaches a user JWT to every outgoing request, it will break any direct SDK calls by overwriting the Authorization header and potentially logging the user out on gateway 401 responses.

Add a gateway URL check to skip both behaviours:

// auth.interceptor.ts
const isArchefusionGateway = req.url.includes('api-gateway.archefusion.com');

// Skip JWT injection for gateway requests
if (!isArchefusionGateway && token && !AuthUtils.isTokenExpired(token)) {
  clonedReq = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
}

// Skip logout for gateway 401s
catchError((error: HttpErrorResponse) => {
  if (error.status === 401 && !isArchefusionGateway) {
    authService.handleUnauthorized();
  }
  return throwError(() => error);
})

If you follow the browser-proxy pattern (your Angular app calls your own backend, not Archefusion directly), your interceptor will never see Archefusion requests and you can skip this entirely.


Angular Version Compatibility

| Angular version | Support | |----------------|---------| | 12, 13 | Supported — NgModule setup; constructor injection in your services | | 14, 15, 16 | Supported — standalone or NgModule | | 17+ | Supported — standalone or NgModule |

The SDK uses constructor-based dependency injection throughout. There are no DI conflicts across Angular versions.

Angular 12 / 13: inject() field initializers are not available. Use constructor injection when consuming the SDK:

@Injectable({ providedIn: 'root' })
export class MyService {
  constructor(private payments: PaymentsService) {}
}

Error Reference

| HTTP Status | Cause | Fix | |-------------|-------|-----| | 400 | Invalid payload — missing required field or bad currency code | Log err.error for details. Check initiate() payload. | | 401 | Invalid or missing API key | Check the key. If using an interceptor, ensure it is not overwriting the Authorization header. | | 403 | API key lacks permission | Check key permissions in the dashboard. | | 404 | paymentId not found | Confirm the ID is correct and belongs to your account. | | 422 | No eligible gateways for this currency/method | Go to Settings → Gateways and confirm at least one active gateway matches your request. | | 429 | Rate limited | Slow down requests or contact support. | | 500 | Archefusion server error | Retry with exponential backoff. |


API Reference

PaymentsService

initiate(payload: InitiatePaymentRequest): Observable<InitiatePaymentResponse>

| Param | Type | Required | Description | |-------|------|----------|-------------| | merchantOrderId | string | Yes | Unique order ID. Must be unique per transaction. | | amount | number | Yes | Amount in the main currency unit (e.g. 1000 = ₦1,000). | | currency | string | Yes | ISO 4217 code — NGN, GHS, USD, etc. | | paymentMethod | 'CARD' \| 'TRANSFER' | No | Helps smart routing pick the right gateway. | | redirectUrl | string | No | Where to send the customer after payment. | | customer.email | string | No | Required by some gateways. Always recommended. | | customer.phone | string | No | Required by some gateways (e.g. Interswitch). | | customerCountry | string | No | ISO 3166-1 alpha-3 (e.g. NGA). Improves cross-border routing. | | metadata | object | No | Any extra data. Returned in webhook events. |

verify(paymentId: string): Observable<VerifyPaymentResponse>

Verifies the outcome of a payment using the paymentId from initiate() or the redirect URL query param.


WebhookService

verify(headers, rawBody, webhookSecret?): Promise<WebhookVerificationResult>

| Param | Description | |-------|-------------| | headers | Raw request headers. | | rawBody | Unparsed request body string. | | webhookSecret | Your Archefusion webhook secret. |

Returns { valid: boolean, event?: WebhookEvent, error?: string }.


provideArchefusion(config) / ArchefusionModule.forRoot(config)

| Option | Type | Required | Description | |--------|------|----------|-------------| | apiKey | string | Yes | Your Archefusion secret API key. | | baseUrl | string | No | Override the API base URL. Defaults to https://dev.api-gateway.archefusion.com. |


TypeScript Types

import type {
  ArchefusionConfig,
  InitiatePaymentRequest,
  InitiatePaymentResponse,
  VerifyPaymentResponse,
  Payment,
  PaymentStatus,
  PaymentMethod,
  WebhookEvent,
  WebhookEventType,
  WebhookHeaders,
  WebhookVerificationResult,
  PaymentWebhookEvent,
  RefundWebhookEvent,
} from '@ng-archefusion/angular';

Security Checklist

  • [ ] API key is a server-side environment variable — never in environment.ts or the browser bundle.
  • [ ] Payment initiation and verification go through your backend — your Angular frontend calls your own API, not Archefusion directly.
  • [ ] Test key in development (af_test_...), live key in production server environment only.
  • [ ] Auth interceptor skips gateway requests — see Auth Interceptor Safety.
  • [ ] Webhook secret is server-side only — never in browser code.
  • [ ] Payments verified server-side before delivering value. Never trust the status query param on the redirect URL.
  • [ ] Webhook handler is idempotent — guard against duplicate events using the event id.
  • [ ] Raw body passed to webhookService.verify() — do not parse and re-stringify.
  • [ ] Rotate your API key from the dashboard immediately if it is ever exposed.

Troubleshooting

401 from the gateway logs the user out

Cause: Auth interceptor catches the gateway 401 and calls signOut(). Fix: Add the isArchefusionGateway guard — see Auth Interceptor Safety.


Payment 401 even though the API key looks correct

Cause: Auth interceptor is overwriting the Authorization header with the user's JWT. Fix: Same as above.


422 — No eligible gateways

Cause: No active gateway on your account matches the currency / paymentMethod you sent. Fix: Go to Settings → Gateways, confirm a gateway is connected and supports your currency and method. Try omitting paymentMethod to let routing decide.


merchantOrderId already exists

Cause: The same merchantOrderId was sent twice. It must be globally unique. Fix: Generate a unique ID per transaction:

merchantOrderId: `order-${crypto.randomUUID()}`

Redirect URL not working after payment

Cause: The redirectUrl route doesn't exist in your app, or an auth guard is blocking unauthenticated access. Fix: Confirm the route is registered in app.routes.ts and that the callback path is whitelisted in any auth guard.


Links