@nestbolt/yallapaysudan
v0.1.0
Published
YallaPaySudan payment gateway integration for NestJS — one-time payments, subscriptions, payment status, and webhook handling.
Downloads
134
Maintainers
Readme
This package provides a YallaPaySudan payment gateway integration for NestJS that handles one-time payments, subscriptions, payment status checks, and secure webhook processing.
Once installed, using it is as simple as:
const payment = await this.yallaPay.createPayment(100, "order-123", {
description: "Premium plan",
});
// Redirect user to payment.paymentUrlTable of Contents
- Installation
- Quick Start
- Module Configuration
- Creating Payments
- Creating Subscriptions
- Checking Payment Status
- Webhook Handling
- Configuration Options
- Using the Service Directly
- Testing
- Changelog
- Contributing
- Security
- Credits
- License
Installation
Install the package via npm:
npm install @nestbolt/yallapaysudanOr via yarn:
yarn add @nestbolt/yallapaysudanOr via pnpm:
pnpm add @nestbolt/yallapaysudanPeer Dependencies
This package requires the following peer dependencies, which you likely already have in a NestJS project:
@nestjs/common ^10.0.0 || ^11.0.0
@nestjs/core ^10.0.0 || ^11.0.0
reflect-metadata ^0.1.13 || ^0.2.0Optional peer dependencies:
@nestjs/event-emitter ^2.0.0 || ^3.0.0 # For webhook event handlingQuick Start
1. Register the module in AppModule
import { YallaPaySudanModule } from "@nestbolt/yallapaysudan";
@Module({
imports: [
YallaPaySudanModule.forRoot({
authorizationToken: process.env.YALLAPAY_AUTHORIZATION_TOKEN,
secretKey: process.env.YALLAPAY_SECRET_KEY,
redirectUrlSuccess: "https://yoursite.com/payment/success",
redirectUrlFailed: "https://yoursite.com/payment/failed",
}),
],
})
export class AppModule {}2. Inject the service and create a payment
import { YallaPaySudanService } from "@nestbolt/yallapaysudan";
@Injectable()
export class PaymentService {
constructor(private readonly yallaPay: YallaPaySudanService) {}
async checkout(orderId: string, amount: number) {
const payment = await this.yallaPay.createPayment(amount, orderId, {
description: "Order payment",
});
return { redirectUrl: payment.paymentUrl };
}
}3. Handle webhook notifications
import { OnEvent } from "@nestjs/event-emitter";
import { WebhookReceivedEvent } from "@nestbolt/yallapaysudan";
@Injectable()
export class PaymentListener {
@OnEvent("yallapaysudan.webhook.received")
handlePaymentWebhook(event: WebhookReceivedEvent) {
if (event.isSuccessful()) {
// Update order status
console.log(`Payment ${event.clientReferenceId} succeeded`);
}
}
}Module Configuration
Static Configuration (forRoot)
YallaPaySudanModule.forRoot({
authorizationToken: "your-api-token",
secretKey: "your-webhook-secret",
baseUrl: "https://gateway.yallapaysudan.com/api/v1", // optional
redirectUrlSuccess: "https://yoursite.com/success", // optional
redirectUrlFailed: "https://yoursite.com/failed", // optional
commissionPaidByCustomer: false, // optional
webhook: {
path: "yallapay/webhook", // optional, default: "yallapay/webhook"
toleranceMinutes: 5, // optional, default: 5
enabled: true, // optional, default: true
},
});Async Configuration (forRootAsync)
Use forRootAsync with ConfigService for environment-based configuration:
import { ConfigModule, ConfigService } from "@nestjs/config";
YallaPaySudanModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
authorizationToken: config.getOrThrow("YALLAPAY_AUTHORIZATION_TOKEN"),
secretKey: config.getOrThrow("YALLAPAY_SECRET_KEY"),
redirectUrlSuccess: config.get("YALLAPAY_REDIRECT_SUCCESS"),
redirectUrlFailed: config.get("YALLAPAY_REDIRECT_FAILED"),
commissionPaidByCustomer: config.get("YALLAPAY_COMMISSION_BY_CUSTOMER") === "true",
}),
});The module is registered as global, so you only need to import it once in your root module.
Creating Payments
Generate a one-time payment link:
const payment = await this.yallaPay.createPayment(
150, // amount
"order-abc-123", // your unique reference ID
{
description: "Premium subscription",
paymentSuccessfulRedirectUrl: "https://yoursite.com/thank-you", // override default
paymentFailedRedirectUrl: "https://yoursite.com/retry", // override default
commissionPaidByCustomer: true, // override default
},
);
// Redirect the customer to:
console.log(payment.paymentUrl);Creating Subscriptions
Generate a recurring subscription payment link:
const subscription = await this.yallaPay.createSubscription(
50, // amount per cycle
"sub-001", // your unique reference ID
"MONTH", // interval: "DAY" | "MONTH" | "YEAR"
1, // every 1 month
{
description: "Monthly plan",
totalCycles: 12, // 12 months, omit for indefinite
},
);
console.log(subscription.paymentUrl);Checking Payment Status
Check the status of a payment at any time:
const status = await this.yallaPay.getPaymentStatus(
"order-abc-123", // your client reference ID
"2025-12-05", // transaction date (YYYY-MM-DD)
);
console.log(status.status);
// "SUCCESSFUL" | "FAILED" | "CANCELLED" | "REVOKED" | "EXPIRED"
console.log(status.amount);
console.log(status.paymentDate);Webhook Handling
Built-in Controller
The module automatically registers a webhook endpoint at POST /yallapay/webhook (configurable). It:
- Validates the
YallaPay-Signatureheader using HMAC-SHA-256 - Checks the
YallaPay-TimeStampfor replay attack prevention - Parses and validates the webhook payload
- Emits a
yallapaysudan.webhook.receivedevent (if@nestjs/event-emitteris installed)
Listening to Events
Install @nestjs/event-emitter and register the module:
npm install @nestjs/event-emitterimport { EventEmitterModule } from "@nestjs/event-emitter";
@Module({
imports: [
EventEmitterModule.forRoot(),
YallaPaySudanModule.forRoot({
/* ... */
}),
],
})
export class AppModule {}Then listen to webhook events:
import { OnEvent } from "@nestjs/event-emitter";
import { WebhookReceivedEvent } from "@nestbolt/yallapaysudan";
@Injectable()
export class PaymentListener {
@OnEvent("yallapaysudan.webhook.received")
handleWebhook(event: WebhookReceivedEvent) {
if (event.isSuccessful()) {
// Mark order as paid
} else if (event.isFailed()) {
// Handle failure
} else if (event.isCancelled()) {
// Handle cancellation
}
}
}Custom Webhook Controller
If you need more control, disable the built-in controller and use the service directly:
// Disable built-in controller
YallaPaySudanModule.forRoot({
// ...
webhook: { enabled: false },
});import { Controller, Post, Req, Res, HttpStatus, UseGuards } from "@nestjs/common";
import { YallaPaySudanService, WebhookSignatureGuard } from "@nestbolt/yallapaysudan";
@Controller("my-webhook")
export class MyWebhookController {
constructor(private readonly yallaPay: YallaPaySudanService) {}
@Post()
@UseGuards(WebhookSignatureGuard)
async handle(@Req() req: any, @Res() res: any) {
const { clientReferenceId, status } = req.body;
// Your custom logic
res.status(HttpStatus.OK).json({ received: true });
}
}Configuration Options
| Option | Type | Default | Description |
| -------------------------- | --------- | ------------------------------------------ | ---------------------------------------------------------- |
| authorizationToken | string | — | Required. Bearer token from merchant dashboard |
| secretKey | string | — | Required. HMAC-SHA-256 secret for webhook verification |
| baseUrl | string | https://gateway.yallapaysudan.com/api/v1 | API base URL |
| redirectUrlSuccess | string | "" | Default success redirect URL |
| redirectUrlFailed | string | "" | Default failure redirect URL |
| commissionPaidByCustomer | boolean | false | Whether customer pays the commission |
| webhook.path | string | "yallapay/webhook" | Webhook endpoint path |
| webhook.toleranceMinutes | number | 5 | Max webhook timestamp age (replay protection) |
| webhook.enabled | boolean | true | Register the built-in webhook controller |
Using the Service Directly
Inject YallaPaySudanService anywhere in your application:
import { YallaPaySudanService } from "@nestbolt/yallapaysudan";
@Injectable()
export class MyService {
constructor(private readonly yallaPay: YallaPaySudanService) {}
}Service Methods
| Method | Returns | Description |
| ---------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------- |
| createPayment(amount, clientReferenceId, options?) | Promise<PaymentResponse> | Generate a one-time payment link |
| createSubscription(amount, clientReferenceId, interval, intervalCycle, options?) | Promise<PaymentResponse> | Generate a subscription payment link |
| getPaymentStatus(clientReferenceId, transactionDate) | Promise<PaymentStatusResponse> | Check payment status |
| verifyWebhookSignature(rawBody, signature, timestamp) | boolean | Verify webhook HMAC-SHA-256 signature |
Testing
npm testRun tests in watch mode:
npm run test:watchGenerate coverage report:
npm run test:covChangelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please report them via GitHub Issues with the security label instead of using the public issue tracker.
Credits
- YallaPaySudan — Payment gateway provider
- amolood/yallapaysudan-laravel — Original Laravel package author
License
The MIT License (MIT). Please see License File for more information.
