PHPackages                             esanj/discount-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. esanj/discount-client

ActiveLibrary

esanj/discount-client
=====================

Laravel client package for the Esanj Discount Microservice (coupons &amp; gift cards)

v0.0.1(yesterday)01↑2900%MITPHP ^8.2|^8.3|^8.4

Since Jul 19Compare

[ Source](https://github.com/eSanjDev/ms-package-discount)[ Packagist](https://packagist.org/packages/esanj/discount-client)[ RSS](/packages/esanj-discount-client/feed)WikiDiscussions Synced today

READMEChangelogDependencies (6)Versions (2)Used By (0)

Esanj Discount Client
=====================

[](#esanj-discount-client)

A Laravel client package for the **Esanj Discount Microservice** (coupons &amp; gift cards). It authenticates with client credentials through the [`esanj/auth-bridge`](../ms-package-accounting-bridge) package, then lets you create and apply **discount codes (coupons)** and **gift cards** — with typed DTOs, typed responses and structured error handling.

Installation
------------

[](#installation)

```
composer update esanj/discount-client
```

The service provider and the `Discount`, `Coupon` and `GiftCard` facades are auto-discovered.

Configuration
-------------

[](#configuration)

Publish the config (optional — it is merged automatically):

```
php artisan vendor:publish --tag=discount-config
```

Set the environment variables:

```
# Discount microservice
DISCOUNT_SERVICE_URL=https://discount.esanj.io
DISCOUNT_CLIENT_ID=your-service-client-id
DISCOUNT_CLIENT_SECRET=your-service-client-secret

# Optional
DISCOUNT_SCOPE=*
DISCOUNT_TIMEOUT=30
DISCOUNT_RETRY_ATTEMPTS=3
DISCOUNT_RETRY_SLEEP_MS=1000
DISCOUNT_LOG_CHANNEL=

# Required by esanj/auth-bridge — the OAuth server that issues the token
ACCOUNTING_BRIDGE_BASE_URL=https://accounting.esanj.io
```

> The access token is issued by the OAuth server configured for `esanj/auth-bridge`(`ACCOUNTING_BRIDGE_BASE_URL`) using this service's `DISCOUNT_CLIENT_ID` / `DISCOUNT_CLIENT_SECRET`, then sent as a short-lived `Bearer` token to the Discount service. Tokens are cached and refreshed automatically by auth-bridge.

Usage
-----

[](#usage)

### Dependency injection (recommended)

[](#dependency-injection-recommended)

```
use Esanj\DiscountClient\Contracts\DiscountClientInterface;

class CheckoutController
{
    public function __construct(private DiscountClientInterface $discount) {}

    public function apply()
    {
        $usage = $this->discount->coupons()->validate('WELCOME', new CouponValidationData(
            userId: 42, amount: 1_000_000, currency: 'IRR',
        ));
    }
}
```

You can also inject `CouponClientInterface` / `GiftCardClientInterface` directly.

### Facades

[](#facades)

```
use Esanj\DiscountClient\Facades\Coupon;
use Esanj\DiscountClient\Facades\GiftCard;
use Esanj\DiscountClient\Facades\Discount;   // Discount::coupons() / Discount::giftCards()
```

### Applying a coupon (validate → redeem → confirm / cancel)

[](#applying-a-coupon-validate--redeem--confirm--cancel)

```
use Esanj\DiscountClient\Facades\Coupon;
use Esanj\DiscountClient\DTOs\CouponValidationData;

$order = new CouponValidationData(userId: 42, amount: 1_000_000, currency: 'IRR', productId: 193);

// 1) Validate against the order — computes the discount and reserves a usage.
$usage = Coupon::validate('WELCOME', $order);
$usage->usageId;          // keep this for the next steps
$usage->discountAmount;   // e.g. 150000
$usage->finalAmount;      // e.g. 850000

// 2) Redeem — holds the coupon for this user while payment is in progress.
Coupon::redeem('WELCOME', $usage->usageId, $order);

// 3a) Confirm once the order is paid (commits the coupon)…
Coupon::confirm('WELCOME', $usage->usageId);

// 3b) …or cancel to release the reservation.
Coupon::cancel('WELCOME', $usage->usageId);
```

### Applying a gift card (validate → redeem)

[](#applying-a-gift-card-validate--redeem)

```
use Esanj\DiscountClient\Facades\GiftCard;

$usage = GiftCard::validate('GC-8H2K', userId: 42);
$usage->amount;    // gift card balance/value
$usage->currency;

GiftCard::redeem('GC-8H2K', $usage->usageId, userId: 42);
```

### Creating coupons and gift cards

[](#creating-coupons-and-gift-cards)

```
use Esanj\DiscountClient\Facades\Coupon;
use Esanj\DiscountClient\Facades\GiftCard;
use Esanj\DiscountClient\DTOs\CouponData;
use Esanj\DiscountClient\DTOs\CouponProductData;
use Esanj\DiscountClient\DTOs\GiftCardData;
use Esanj\DiscountClient\Enums\CouponAmountType;

$coupon = Coupon::create(new CouponData(
    name: 'Welcome 15%',
    amountType: CouponAmountType::Percentage,
    amount: 15,                       // 15% off…
    currency: ['IRR'],
    maxAmount: 200_000,               // …capped at 200,000
    usageLimit: 1000,
    usageLimitPerUser: 1,
    products: [new CouponProductData(serviceId: 3, productId: 193)],
    expiredAt: '2026-12-31 23:59:59',
));
$coupon->code;                        // generated when you don't pass one

$giftCard = GiftCard::create(new GiftCardData(
    name: 'Yalda 500k',
    amount: 500_000,
    currency: 'IRR',
    usageLimit: 1,
));
```

Error Handling
--------------

[](#error-handling)

Every failure throws a subclass of `Esanj\DiscountClient\Exceptions\DiscountException`:

ExceptionWhen`DiscountAuthenticationException`Client credentials missing/invalid; the OAuth server rejected the token request.`DiscountApiException`The Discount service returned an error response. Carries `statusCode`, `responseBody`, `getErrorCode()`, `getErrors()`.```
use Esanj\DiscountClient\Exceptions\DiscountApiException;
use Esanj\DiscountClient\Exceptions\DiscountAuthenticationException;

try {
    $usage = Coupon::validate('WELCOME', $order);
} catch (DiscountApiException $e) {
    if ($e->isBusinessRuleFailure()) {           // 400 — a coupon/gift-card rule rejected it
        match ($e->getErrorCode()) {
            'COUPON_EXPIRED'             => /* … */,
            'COUPON_MAX_USAGE_REACHED'   => /* … */,
            'MINIMUM_ORDER_AMOUNT_NOT_MET' => /* … */,
            default                      => report($e),
        };
    } elseif ($e->isNotFound())        { /* unknown code */ }
    elseif ($e->isValidationError())   { $errors = $e->getErrors(); }   // 422
    else                               { report($e); }
} catch (DiscountAuthenticationException $e) {
    // credentials / OAuth problem
}
```

Server (5xx), connection and rejected-token (401/403) failures are retried automatically according to the `retry` config; a rejected token is invalidated and re-fetched before the retry.

API surface
-----------

[](#api-surface)

MethodHTTPEndpoint`coupons()->create(CouponData)`POST`/api/v1/service/coupons``coupons()->show(string $code)`GET`/api/v1/service/coupons/{code}``coupons()->validate(string $code, CouponValidationData)`POST`/api/v1/service/coupons/{code}/validate``coupons()->redeem(string $code, string $usageId, CouponValidationData)`POST`/api/v1/service/coupons/{code}/redeem``coupons()->confirm(string $code, string $usageId)`POST`/api/v1/service/coupons/{code}/confirm``coupons()->cancel(string $code, string $usageId)`POST`/api/v1/service/coupons/{code}/cancel``giftCards()->create(GiftCardData)`POST`/api/v1/service/gift-cards``giftCards()->show(string $code)`GET`/api/v1/service/gift-cards/{code}``giftCards()->validate(string $code, int $userId)`POST`/api/v1/service/gift-cards/{code}/validate``giftCards()->redeem(string $code, string $usageId, int $userId)`POST`/api/v1/service/gift-cards/{code}/redeem`See [`docs/GUIDE.md`](docs/GUIDE.md) for a full walkthrough.

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c908dfdf092a072c4bc678b161b053e20d00f90e9e9ba2c18ecddef6b848f1a4?d=identicon)[esanj](/maintainers/esanj)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/esanj-discount-client/health.svg)

```
[![Health](https://phpackages.com/badges/esanj-discount-client/health.svg)](https://phpackages.com/packages/esanj-discount-client)
```

###  Alternatives

[craftcms/cms

Craft CMS

3.6k3.6M3.2k](/packages/craftcms-cms)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M133](/packages/roots-acorn)[illuminate/http

The Illuminate Http package.

11937.9M7.4k](/packages/illuminate-http)[spatie/laravel-export

Create a static site bundle from a Laravel app

674146.0k6](/packages/spatie-laravel-export)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

293.1k](/packages/eslazarev-wildberries-sdk)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1235.9k21](/packages/fleetbase-core-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
