Angular — quickstart
A complete, runnable Angular checkout. We’ll use the standalone pattern (Angular 17+ default). Module-based projects work the same way; only the registration step differs (see Configuration).
What we’re building
Section titled “What we’re building”A single page with a “Pay $12.50” button. Tapping it asks your
backend to mint a Pdirect session, then mounts the
<pdirect-pay-checkout> component and walks the customer through a
wallet payment.
1 — Mint a session from your backend
Section titled “1 — Mint a session from your backend”The merchant backend mints a session bound to
(amount, currency, customer_reference) and returns the
session_token to your Angular app. Node example:
import crypto from "node:crypto";
export async function startCheckout(req, res) { const r = await fetch( "https://app.api.gtwy.pdirect.com/api/v1/internal/sessions/create", { method: "POST", headers: { Authorization: `Bearer ${process.env.PDIRECT_MERCHANT_KEY_ID}:${process.env.PDIRECT_MERCHANT_SECRET}`, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ amount: "12.50", currency: "usd", customer_reference: "cust_abc123", ttl_seconds: 1800, }), }, ); res.json(await r.json());}The Angular app never sees merchant_secret. Treat the result
as ephemeral — a fresh session per checkout.
2 — Bootstrap with HTTP and config
Section titled “2 — Bootstrap with HTTP and config”import { bootstrapApplication } from "@angular/platform-browser";import { provideHttpClient } from "@angular/common/http";import { PDIRECT_ENV_CONFIG, PDIRECT_PAY_CONFIG,} from "@mms/pdirect-pay";import { AppComponent } from "./app/app.component";
bootstrapApplication(AppComponent, { providers: [ provideHttpClient(), { provide: PDIRECT_ENV_CONFIG, useValue: { baseUrl: "https://app.api.gtwy.pdirect.com", }, }, { provide: PDIRECT_PAY_CONFIG, useValue: { isProduction: false, acceptLanguage: "en" }, }, ],}).catch((err) => console.error(err));The session_token is set per-checkout on the component below, not
at bootstrap.
3 — A checkout component
Section titled “3 — A checkout component”import { Component, signal, inject } from "@angular/core";import { HttpClient } from "@angular/common/http";import { firstValueFrom } from "rxjs";import { PdirectPayCheckoutComponent, PdirectPayConfig, PdirectPaymentBody, PdirectPayOnResponse, PdirectPayOnError, PdirectPayThemeConfig,} from "@mms/pdirect-pay";
@Component({ selector: "app-checkout", standalone: true, imports: [PdirectPayCheckoutComponent], template: ` @if (sessionToken()) { <pdirect-pay-checkout [configs]="configs()" [paymentBody]="paymentBody" [themeConfig]="themeConfig" (response)="onSuccess($event)" (error)="onFailure($event)" (closed)="onClose()" /> } @else { <button type="button" class="pay-button" [disabled]="busy()" (click)="onStart()"> Pay $12.50 </button> } `, styles: [` .pay-button { padding: 0.75rem 1.5rem; border-radius: 9999px; border: none; background: #2563eb; color: white; font-weight: 600; cursor: pointer; } .pay-button[disabled] { opacity: 0.6; cursor: progress; } `],})export class CheckoutComponent { private http = inject(HttpClient);
sessionToken = signal<string | null>(null); busy = signal(false);
configs = () => ({ // `token` carries the session_token your backend minted. // The SDK sends it as Authorization: Bearer <session_token>. token: this.sessionToken()!, isProduction: false, acceptLanguage: "en", } satisfies PdirectPayConfig);
paymentBody: PdirectPaymentBody = { // amount / currency / customerReference are bound to the session // — leave them unset and the SDK omits them on the wire. paymentMethod: "wallet", userInfo: "full", paymentMethodInfo: "full", feeCoveredBy: "buyer", deliveryBehaviour: "direct_delivery", notifyUrl: "https://merchant.example.com/pdirect/webhook", firstName: "John", lastName: "Doe", email: "john.doe@example.com", phoneNumber: "+243998857000", walletNumber: "3005710720108954", walletPin: "048698", };
themeConfig: PdirectPayThemeConfig = { primaryColor: "#2563eb", secondaryColor: "#1e40af", tertiaryColor: "#3bfbda", };
async onStart() { this.busy.set(true); try { const { session_token } = await firstValueFrom( this.http.post<{ session_token: string }>( "/api/checkout/start", {}, ), ); this.sessionToken.set(session_token); } finally { this.busy.set(false); } }
onSuccess(r: PdirectPayOnResponse) { console.log("payment ok:", r.transactionId, r.paymentStatus); this.sessionToken.set(null); }
onFailure(e: PdirectPayOnError) { console.warn("payment failed:", e.errorCode, e.message); this.sessionToken.set(null); }
onClose() { this.sessionToken.set(null); }}4 — Mount it
Section titled “4 — Mount it”import { Component } from "@angular/core";import { CheckoutComponent } from "./checkout/checkout.component";
@Component({ selector: "app-root", standalone: true, imports: [CheckoutComponent], template: `<main><app-checkout /></main>`, styles: [`main { display: grid; place-items: center; min-height: 100vh; }`],})export class AppComponent {}5 — Run it
Section titled “5 — Run it”ng serve# → http://localhost:4200Click Pay $12.50. The component asks your backend for a
session_token, then mounts the <pdirect-pay-checkout> widget; in
sandbox the wallet flow resolves to approved within a few seconds
and onSuccess fires with a transactionId.
Direct service alternative
Section titled “Direct service alternative”Skip the widget when you need a custom UI:
import { Component, OnInit, inject } from "@angular/core";import { PdirectPaymentService, PdirectPayApiResponseCode, PdirectPaymentBody,} from "@mms/pdirect-pay";
@Component({ /* ... */ })export class CustomCheckoutComponent implements OnInit { private payments = inject(PdirectPaymentService);
ngOnInit() { // setAppKey is kept as the configuration helper for backwards // compat — the string you pass is the per-checkout session_token // your backend minted server-to-server. this.payments.setAppKey(sessionTokenFromYourBackend); this.payments.setAcceptLanguage("en");
const body: PdirectPaymentBody = { // amount / currency / customerReference are bound to the // session — omit them. paymentMethod: "wallet", userInfo: "full", paymentMethodInfo: "full", // ... };
this.payments.createPayment(body).subscribe((res) => { if (res.isSuccess) { console.log("transaction:", res.data?.transactionId); } else { console.warn("failed:", res.apiResponseCode, res.message); } }); }}PdirectPaymentService returns RxJS Observable<PdirectHttpResponse<T>>.
The wrapper exposes isSuccess, statusCode, apiResponseCode,
and the parsed data. See Error handling.
Going beyond wallet
Section titled “Going beyond wallet”The same pattern applies to every payment method — only the
paymentMethod and method-specific fields change.
- Card payments with 3DS
- Mobile money status polling
- Payment methods — field reference
See also
Section titled “See also”- Configuration — modules, tokens, theme
- API reference — every public symbol
- Error handling