PHPackages                             equidna/stag-herd - 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. [Payment Processing](/categories/payments)
4. /
5. equidna/stag-herd

ActiveLibrary[Payment Processing](/categories/payments)

equidna/stag-herd
=================

Multi-provider payment webhook verification and processing for Laravel.

198PHPCI failing

Since May 20Pushed 2w agoCompare

[ Source](https://github.com/EquidnaMX/stag-herd)[ Packagist](https://packagist.org/packages/equidna/stag-herd)[ RSS](/packages/equidna-stag-herd/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (2)Used By (0)

StagHerd Payment Processing
===========================

[](#stagherd-payment-processing)

Multi-provider payment webhook verification and processing for Laravel applications.

Features
--------

[](#features)

- **Multi-Provider Support**: PayPal, Stripe (Google Pay), Mercado Pago, Openpay, Clip, Conekta
- **Webhook Verification**: Cryptographic signature validation for all providers
- **Idempotency**: Prevents duplicate webhook processing
- **Event-Driven**: Dispatches Laravel events for payment state changes
- **Configurable**: Extensible handler system via configuration
- **Decoupled**: Clean contracts for integration with any Laravel application

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

[](#installation)

```
composer require equidna/stag-herd
```

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

[](#configuration)

Publish the configuration file:

```
php artisan vendor:publish --tag=stag-herd-config
```

### Environment Variables

[](#environment-variables)

Use `.env.example` as the package template and see `docs/environment.md` for local, sandbox, and production rollout notes. Add the relevant values to the host application's `.env`:

```
STAG_HERD_ROUTE_PREFIX=stag-herd
STAG_HERD_PAYMENT_MODEL=App\Models\Payment

# Enable/Disable Payment Methods
STRIPE_ENABLED=true
PAYPAL_ENABLED=true
MERCADOPAGO_ENABLED=false
CONEKTA_ENABLED=false
OPENPAY_ENABLED=false
CLIP_ENABLED=false

# Webhook Secrets
STRIPE_WEBHOOK_SECRET=whsec_...
PAYPAL_WEBHOOK_ID=...
MERCADOPAGO_WEBHOOK_SECRET=...
CONEKTA_WEBHOOK_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
OPENPAY_WEBHOOK_SECRET=...

# API Credentials
STRIPE_SECRET=...
MERCADOPAGO_ACCESS_TOKEN=...
CONEKTA_API_SECRET=...
OPENPAY_MERCHANT_ID=...
OPENPAY_PRIVATE_KEY=...
OPENPAY_SANDBOX=true

# PayPal Configuration
PAYPAL_SANDBOX=true
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...

# Clip Configuration
CLIP_API_KEY=...
CLIP_API_BASE_URL=https://api-gw.payclip.com
CLIP_CURRENCY=MXN
CLIP_SUCCESS_URL=https://your.app/payments/success
CLIP_ERROR_URL=https://your.app/payments/error
CLIP_DEFAULT_URL=https://your.app/payments/cancel
CLIP_WEBHOOK_URL=https://your.app/stag-herd/clip
```

### Payment Model

[](#payment-model)

Configure your Payment Eloquent model in `config/stag-herd.php`:

```
'payment_model' => 'App\Models\Finance\Payment',
```

### Custom Payment Methods

[](#custom-payment-methods)

Payment methods from the package are registered automatically. To add custom handlers, configure them in `config/stag-herd.php`:

```
'custom_methods' => [
    'CLIENT_CREDIT' => [
        'handler' => 'App\Classes\Payment\Handlers\ClientCreditHandler',
        'description' => 'Linea de crédito cliente',
        'enabled' => true,
    ],
    'GIFT' => [
        'handler' => 'App\Classes\Payment\Handlers\GiftHandler',
        'description' => 'Regalo',
        'enabled' => true,
    ],
],
```

Usage
-----

[](#usage)

### Requesting a Payment

[](#requesting-a-payment)

```
use Equidna\StagHerd\Payment\Payment;

$payment = Payment::request(
    amount: 100.00,
    method: 'PAYPAL',
    order: $order, // Must implement PayableOrder contract
    method_data: (object) ['return_url' => 'https://...']
);

// Access payment link
$paymentLink = $payment->getPaymentModel()->link;
```

Method Data
-----------

[](#method-data)

Below are the expected `method_data` fields per provider when creating a payment.

### Mercado Pago

[](#mercado-pago)

```
method_data: (object) [
    'token' => '',
    'payment_method_id' => 'visa',
    'payer_email' => 'buyer@email.com', // optional if order has client email
    'installments' => 1,
    'issuer_id' => null,
]
```

### Conekta

[](#conekta)

```
// OXXO
method_data: (object) [
    'payment_method' => 'oxxo_cash', // or 'oxxo'
    'payer_email' => 'buyer@email.com',
    'payer_name' => 'Buyer Name',
]

// Card
method_data: (object) [
    'payment_method' => 'card',
    'token' => '',
    'payer_email' => 'buyer@email.com',
    'payer_name' => 'Buyer Name',
]
```

### PayPal

[](#paypal)

```
// Request (order creation)
method_data: (object) [
    'return_url' => 'https://your.app/return',
    'cancel_url' => 'https://your.app/cancel',
]

// After approval (webhook/validation), the package stores:
// method_data.capture_id = ''
// This capture_id is used for refunds.
```

Maintenance &amp; Cleanup
-------------------------

[](#maintenance--cleanup)

Run the scheduled cleanup to remove orphan payments and close stale pending records:

```
php artisan stag-herd:payments:clean
```

- The package schedules this command daily at 03:00 (cron `0 3 * * *`) when `STAG_HERD_CLEANUP_ENABLED=true`.
- Use `--revalidate` to force revalidation of recent pending payments; `--skip-revalidate` suppresses revalidation even if enabled in config.
- Configure behavior via `config/stag-herd.php` (`cleanup` section): cron expression, lookback window for revalidation, and stale pending age (default 14 days).

### Implementing Contracts

[](#implementing-contracts)

Your application must implement the required contracts:

#### PayableOrder

[](#payableorder)

```
use Equidna\StagHerd\Contracts\PayableOrder;

class Order implements PayableOrder
{
    public function getID(): int|string { return $this->id_order; }
    public function getClient(): PayableClient { return $this->client; }
    public function getStatus(): string { return $this->status; }
    public function processPayment($payment): void { /* Update order */ }
    public static function fromID(int|string $id): static { /* Load order */ }
}
```

#### PayableClient

[](#payableclient)

```
use Equidna\StagHerd\Contracts\PayableClient;

class Client implements PayableClient
{
    public function getID(): int|string { return $this->id_client; }
    public function getName(): string { return $this->name; }
    public function getEmail(): string { return $this->email; }
}
```

#### PaymentContextProvider (Optional)

[](#paymentcontextprovider-optional)

Bind a custom context provider if you need to control `uri_registrar` and `registrar_type` values.

```
use Equidna\StagHerd\Contracts\PaymentContextProvider;

app()->bind(PaymentContextProvider::class, function () {
    return new class implements PaymentContextProvider {
        public function getExecutorUri(): ?string { return request()->fullUrl(); }
        public function getExecutorType(): ?string { return 'http'; }
    };
});
```

### Custom Payment Handlers

[](#custom-payment-handlers)

Extend `PaymentHandler` for custom payment methods:

```
namespace App\Payment\Handlers;

use Equidna\StagHerd\Data\PaymentResult;
use Equidna\StagHerd\Payment\Handlers\PaymentHandler;

class CustomHandler extends PaymentHandler
{
    public const PAYMENT_METHOD = 'CUSTOM_METHOD';

    public function requestPayment(): PaymentResult
    {
        // Custom logic
        return PaymentResult::pending(
            method_id: 'custom-' . uniqid(),
            reason: 'Always PENDING'
        );
    }

    protected function validatePayment($paymentModel): PaymentResult
    {
        parent::validatePayment($paymentModel);

        // Validation logic
        return PaymentResult::success(
            result: 'APPROVED',
            method_id: $paymentModel->method_id
        );
    }
}
```

### Gateway Contracts

[](#gateway-contracts)

Provider adapters are bound behind contracts so host applications can replace them for tests, sandbox wrappers, tracing, or custom retry policies:

```
use Equidna\StagHerd\Contracts\ClipGateway;
use App\Payments\InstrumentedClipGateway;

app()->bind(ClipGateway::class, InstrumentedClipGateway::class);
```

Available gateway contracts:

- `ClipGateway`
- `ConektaGateway`
- `MercadoPagoGateway`
- `OpenpayGateway`
- `PayPalGateway`
- `StripeGateway`

### Payment State Machine

[](#payment-state-machine)

`Payment::applyResult()` enforces payment state transitions centrally:

- `PENDING` can resolve to `APPROVED`, `REJECTED`, `DECLINED`, `CANCELED`, `REFUNDED`, or `CHARGEBACK`.
- `APPROVED` can move to `REFUNDED`, `CHARGEBACK`, or `CANCELED`.
- `REJECTED`, `DECLINED`, `CANCELED`, `REFUNDED`, and `CHARGEBACK` are terminal except for idempotent repeats of the same status.

Invalid transitions are logged and ignored, for example `REJECTED -> APPROVED`.

Webhook Routes
--------------

[](#webhook-routes)

Webhooks are automatically registered with the configured prefix (default: `stag-herd`):

- `POST /stag-herd/paypal`
- `POST /stag-herd/mercadopago`
- `POST /stag-herd/conekta`
- `POST /stag-herd/openpay`
- `POST /stag-herd/googlepay`
- `POST /stag-herd/clip`

Webhooks
--------

[](#webhooks)

This package verifies webhook signatures for each provider using their official schemes. Ensure you send the raw request body to the verifier and configure secrets.

### Stripe

[](#stripe)

- Header: `Stripe-Signature: t=,v1=`
- Signature: `HMAC-SHA256(".", STRIPE_WEBHOOK_SECRET)`
- Tolerance: default 300 seconds; events outside tolerance are rejected.
- Idempotency: the event `id` is deduplicated to prevent reprocessing.

### Mercado Pago

[](#mercado-pago-1)

- Headers: `X-Signature`, `X-Request-Id`
- Signature parts: `ts=,v1=`
- Manifest: `id:;request-id:;ts:;`
- Signature: `HMAC-SHA256(manifest, MERCADOPAGO_WEBHOOK_SECRET)`
- `data.id` is taken from query/body JSON `data.id` or `id`.

### Conekta

[](#conekta-1)

- Header: `Digest: ` (the implementation also accepts the `sha-256=` prefix).
- Signature: RSA SHA-256 verification of the raw UTF-8 body using the webhook public key generated by Conekta.
- Public key source: `config('stag-herd.conekta.webhook_public_key')` (populate from `CONEKTA_WEBHOOK_PUBLIC_KEY`).
- Event id: body JSON `id` when present.

### Clip

[](#clip)

- Clip Checkout webhook notifications are reconciled by `payment_request_id`.
- The webhook payload is not trusted as payment state. The package re-queries Clip Checkout (`GET /checkout/{payment_request_id}`) before applying `APPROVED`, `REJECTED`, `CANCELED`, or `PENDING`.
- Configure `CLIP_WEBHOOK_URL` or let the package use the `stag-herd.clip` route when creating Checkout links.

### Openpay

[](#openpay)

- Header: `Verification-Signature: t=,v1=` (or `Signature-Digest`)
- Signature: `HMAC-SHA256(".", OPENPAY_WEBHOOK_SECRET)`
- Event id: body JSON `event_id` or `id`.

### Idempotency

[](#idempotency)

- `WebhookVerifier::checkIdempotency(eventId, provider, ttl)` returns true for duplicates and stores new events via cache.
- Configure a persistent cache backend in production to retain deduplication keys.

### Examples

[](#examples)

```
POST /webhook HTTP/1.1
Stripe-Signature: t=1710000000,v1=abcdef...
Content-Type: application/json

{"id":"evt_123","type":"charge.succeeded"}
```

```
POST /webhook HTTP/1.1
X-Signature: ts=1710000000,v1=abcdef...
X-Request-Id: req-123
Content-Type: application/json

{"data":{"id":"123"}}
```

```
POST /webhook HTTP/1.1
Digest: sha-256=abc123base64=
Content-Type: application/json

{"id":"evt_123","type":"charge.paid"}
```

```
POST /webhook HTTP/1.1
Verification-Signature: t=1710000000,v1=abcdef...
Content-Type: application/json

{"event_id":"evt_op","id":"op_123"}
```

Events
------

[](#events)

Listen to payment events in your application:

```
use Equidna\StagHerd\Events\PaymentApproved;
use Equidna\StagHerd\Events\PaymentCanceled;
use Equidna\StagHerd\Events\PaymentChargeback;
use Equidna\StagHerd\Events\PaymentLinkCreated;
use Equidna\StagHerd\Events\PaymentRefunded;
use Equidna\StagHerd\Events\PaymentRejected;
use Illuminate\Support\Facades\Event;

Event::listen(PaymentApproved::class, function (PaymentApproved $event) {
    $payment = $event->payment;
    // Handle approved payment
});

Event::listen(PaymentLinkCreated::class, function (PaymentLinkCreated $event) {
    $payment = $event->payment;
    $link = $event->link;
    // Notify client with payment link
});
```

Testing
-------

[](#testing)

```
composer test
```

For provider sandbox validation, follow `docs/sandbox-test-matrix.md`.

Static Analysis
---------------

[](#static-analysis)

```
composer phpstan
```

License
-------

[](#license)

MIT License. See LICENSE file for details.

###  Health Score

25

—

LowBetter than 35% of packages

Maintenance63

Regular maintenance activity

Popularity15

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity13

Early-stage or recently created project

 Bus Factor1

Top contributor holds 90.9% of commits — single point of failure

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.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/984800?v=4)[gruelas](/maintainers/gruelas)[@gruelas](https://github.com/gruelas)

---

Top Contributors

[![gruelasjr](https://avatars.githubusercontent.com/u/40619710?v=4)](https://github.com/gruelasjr "gruelasjr (10 commits)")[![Chenminsuyi](https://avatars.githubusercontent.com/u/104664610?v=4)](https://github.com/Chenminsuyi "Chenminsuyi (1 commits)")

### Embed Badge

![Health badge](/badges/equidna-stag-herd/health.svg)

```
[![Health](https://phpackages.com/badges/equidna-stag-herd/health.svg)](https://phpackages.com/packages/equidna-stag-herd)
```

###  Alternatives

[msilabs/bkash

bKash Payment Gateway API for Laravel Framework.

181.2k](/packages/msilabs-bkash)

PHPackages © 2026

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