npm package: @nestbolt/yallapaysudan
Description: YallaPaySudan payment gateway integration for NestJS — one-time payments, subscriptions, payment status, and webhook handling.
Latest version: 0.1.0

<p align="center">
    <h1 align="center">@nestbolt/yallapaysudan</h1>
    <p align="center">YallaPaySudan payment gateway integration for NestJS.</p>
</p>

<p align="center">
    <a href="https://www.npmjs.com/package/@nestbolt/yallapaysudan"><img src="https://img.shields.io/npm/v/@nestbolt/yallapaysudan.svg?style=flat-square" alt="npm version"></a>
    <a href="https://www.npmjs.com/package/@nestbolt/yallapaysudan"><img src="https://img.shields.io/npm/dt/@nestbolt/yallapaysudan.svg?style=flat-square" alt="npm downloads"></a>
    <a href="https://github.com/nestbolt/yallapaysudan/actions"><img src="https://img.shields.io/github/actions/workflow/status/nestbolt/yallapaysudan/tests.yml?branch=main&style=flat-square&label=tests" alt="tests"></a>
    <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square" alt="license"></a>
</p>

<hr>

This package provides a **YallaPaySudan payment gateway integration** for [NestJS](https://nestjs.com) that handles one-time payments, subscriptions, payment status checks, and secure webhook processing.

Once installed, using it is as simple as:

```typescript
const payment = await this.yallaPay.createPayment(100, "order-123", {
  description: "Premium plan",
});
// Redirect user to payment.paymentUrl
```

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Module Configuration](#module-configuration)
  - [Static Configuration (forRoot)](#static-configuration-forroot)
  - [Async Configuration (forRootAsync)](#async-configuration-forrootasync)
- [Creating Payments](#creating-payments)
- [Creating Subscriptions](#creating-subscriptions)
- [Checking Payment Status](#checking-payment-status)
- [Webhook Handling](#webhook-handling)
  - [Built-in Controller](#built-in-controller)
  - [Listening to Events](#listening-to-events)
  - [Custom Webhook Controller](#custom-webhook-controller)
- [Configuration Options](#configuration-options)
- [Using the Service Directly](#using-the-service-directly)
- [Testing](#testing)
- [Changelog](#changelog)
- [Contributing](#contributing)
- [Security](#security)
- [Credits](#credits)
- [License](#license)

## Installation

Install the package via npm:

```bash
npm install @nestbolt/yallapaysudan
```

Or via yarn:

```bash
yarn add @nestbolt/yallapaysudan
```

Or via pnpm:

```bash
pnpm add @nestbolt/yallapaysudan
```

### Peer 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.0
```

**Optional peer dependencies:**

```
@nestjs/event-emitter  ^2.0.0 || ^3.0.0   # For webhook event handling
```

## Quick Start

### 1. Register the module in `AppModule`

```typescript
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

```typescript
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

```typescript
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)

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

1. Validates the `YallaPay-Signature` header using HMAC-SHA-256
2. Checks the `YallaPay-TimeStamp` for replay attack prevention
3. Parses and validates the webhook payload
4. Emits a `yallapaysudan.webhook.received` event (if `@nestjs/event-emitter` is installed)

### Listening to Events

Install `@nestjs/event-emitter` and register the module:

```bash
npm install @nestjs/event-emitter
```

```typescript
import { EventEmitterModule } from "@nestjs/event-emitter";

@Module({
  imports: [
    EventEmitterModule.forRoot(),
    YallaPaySudanModule.forRoot({
      /* ... */
    }),
  ],
})
export class AppModule {}
```

Then listen to webhook events:

```typescript
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:

```typescript
// Disable built-in controller
YallaPaySudanModule.forRoot({
  // ...
  webhook: { enabled: false },
});
```

```typescript
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:

```typescript
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

```bash
npm test
```

Run tests in watch mode:

```bash
npm run test:watch
```

Generate coverage report:

```bash
npm run test:cov
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

## Security

If you discover any security-related issues, please report them via [GitHub Issues](https://github.com/nestbolt/yallapaysudan/issues) with the **security** label instead of using the public issue tracker.

## Credits

- [YallaPaySudan](https://yallapaysudan.com) — Payment gateway provider
- [amolood/yallapaysudan-laravel](https://github.com/amolood/yallapaysudan-laravel) — Original Laravel package author

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.