PHPackages                             bhaidar/laravel-checkeeper - 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. bhaidar/laravel-checkeeper

ActiveLibrary[Payment Processing](/categories/payments)

bhaidar/laravel-checkeeper
==========================

Laravel package for Checkeeper API v3 - Check mailing and PDF generation services

v0.6.0(4mo ago)024MITPHPPHP ^8.2

Since Jan 28Pushed 4mo agoCompare

[ Source](https://github.com/bhaidar/laravel-checkeeper)[ Packagist](https://packagist.org/packages/bhaidar/laravel-checkeeper)[ RSS](/packages/bhaidar-laravel-checkeeper/feed)WikiDiscussions main Synced today

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

Laravel Checkeeper
==================

[](#laravel-checkeeper)

[![Latest Version on Packagist](https://camo.githubusercontent.com/74a17f55e2e54235142d009e690d93ce7154fd4edc35fe1b0a33703a9ee35f60/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f626861696461722f6c61726176656c2d636865636b65657065722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bhaidar/laravel-checkeeper)[![Total Downloads](https://camo.githubusercontent.com/a5d4765e7ff776145070bf0cd82f595c2f0a6c5807c63eb30d756c81dcbfc127/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f626861696461722f6c61726176656c2d636865636b65657065722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/bhaidar/laravel-checkeeper)

A comprehensive Laravel package for the Checkeeper API v3. Send physical checks via USPS, UPS, or FedEx, or generate PDFs for self-printing.

Features
--------

[](#features)

- **Full API Coverage** - Complete implementation of Checkeeper API v3
- **Type-Safe** - Readonly DTOs with typed properties throughout
- **Fluent Queries** - Powerful filter builder for searching checks
- **Webhook Support** - Automatic signature verification and event dispatching
- **Queue Integration** - Async check creation
- **Event-Driven** - Laravel events for check operations
- **Well Tested** - Comprehensive Pest test suite
- **Laravel 11+** - Built for modern Laravel applications

Table of Contents
-----------------

[](#table-of-contents)

- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [How It Works](#how-it-works)
- [Creating Checks](#creating-checks)
    - [Basic Check Creation](#basic-check-creation)
    - [Using Type-Safe DTOs](#using-type-safe-dtos)
    - [Delivery Methods](#delivery-methods)
    - [Bulk Check Creation](#bulk-check-creation)
    - [Async Check Creation](#async-check-creation)
- [Querying Checks](#querying-checks)
    - [List All Checks](#list-all-checks)
    - [Filtering Checks](#filtering-checks)
    - [Check Status](#check-status)
    - [Tracking Events](#tracking-events)
    - [Cancel Check](#cancel-check)
    - [Download Check Images](#download-check-images)
- [Webhooks](#webhooks)
    - [Webhook Setup](#webhook-setup)
    - [Listening to Events](#listening-to-events)
    - [Example Listeners](#example-listeners)
    - [Webhook Security](#webhook-security)
- [Team &amp; Templates](#team--templates)
- [Events](#events)
- [Exception Handling](#exception-handling)
- [Complete Application Example](#complete-application-example)
- [Testing Your Integration](#testing-your-integration)
- [API Reference](#api-reference)
- [Contributing](#contributing)
- [License](#license)

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11.0+

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

[](#installation)

Install via Composer:

```
composer require bhaidar/laravel-checkeeper
```

Publish the configuration file:

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

Add your API credentials to `.env`:

```
CHECKEEPER_API_KEY=your-api-key-here
CHECKEEPER_WEBHOOK_SECRET=your-webhook-secret
```

Optional queue configuration:

```
CHECKEEPER_QUEUE_ENABLED=true
CHECKEEPER_QUEUE_CONNECTION=redis
CHECKEEPER_QUEUE_NAME=checkeeper
```

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

[](#configuration)

The package configuration file is located at `config/checkeeper.php`:

```
return [
    // API authentication
    'api_key' => env('CHECKEEPER_API_KEY'),

    // API endpoint
    'base_url' => env('CHECKEEPER_BASE_URL', 'https://api.checkeeper.com/v3'),

    // HTTP timeout (seconds)
    'timeout' => 30,

    // Webhook configuration
    'webhooks' => [
        'enabled' => true,
        'secret' => env('CHECKEEPER_WEBHOOK_SECRET'),
        'route' => 'checkeeper/webhook',
        'middleware' => ['api'],
    ],

    // Queue configuration
    'queue' => [
        'enabled' => true,
        'connection' => env('CHECKEEPER_QUEUE_CONNECTION', 'default'),
        'queue' => env('CHECKEEPER_QUEUE_NAME', 'checkeeper'),
    ],
];
```

How It Works
------------

[](#how-it-works)

The Laravel Checkeeper package provides a clean, Laravel-friendly interface to the Checkeeper API. Here's how the components work together:

### Architecture Overview

[](#architecture-overview)

```
Your Application
    |
Checkeeper Facade
    |
CheckkeeperClient (HTTP Client)
    |
Resources (Check, Team, Template)
    |
Checkeeper API
    |
Check Mailing Service

```

### Core Components

[](#core-components)

1. **Facade (`Checkeeper`)** - Main entry point for all operations
2. **Resources** - Organized API endpoints (Checks, Team, Templates)
3. **DTOs** - Type-safe data transfer objects
4. **Events** - Laravel events for check operations and webhooks
5. **Jobs** - Queue jobs for async operations
6. **Filter Builder** - Fluent query builder for searching checks

### Request Flow

[](#request-flow)

```
// 1. Create check data (DTO or array)
$checkData = new CheckData(...);

// 2. Optionally specify delivery method
$delivery = new DeliveryData(method: DeliveryMethod::UspsFirstClass);

// 3. Send to Checkeeper via Facade
$result = Checkeeper::checks()->create($checkData, $delivery);

// 4. Package handles HTTP request with retry logic
// 5. Returns typed CheckStatusData response
echo $result->id; // "check-abc123"

// 6. Webhooks notify you of status changes
// 7. Events fired for your listeners
```

Creating Checks
---------------

[](#creating-checks)

### Basic Check Creation

[](#basic-check-creation)

Create a check using a simple array:

```
use Bhaidar\Checkeeper\Facades\Checkeeper;

$result = Checkeeper::checks()->create([
    'bank' => [
        'routing' => '123456789',
        'account' => '987654321',
    ],
    'payer' => [
        'line1' => 'My Company Inc',
        'line2' => '123 Business St',
        'line3' => 'Suite 100',
        'line4' => 'New York, NY 10001',
    ],
    'payee' => [
        'line1' => 'Vendor Services LLC',
        'line2' => '456 Supplier Ave',
        'line3' => 'Los Angeles, CA 90210',
    ],
    'signer' => [
        'type' => 'text',
        'value' => 'Jane Smith',
    ],
    'amount' => 50000,              // $500.00 (amount in cents)
    'number' => 1001,
    'date' => '2024-02-29',
    'memo' => 'Invoice #12345',
    'nonce' => 'unique-id-12345',   // prevents duplicates
]);

// Result contains check ID and status
echo $result->id;            // "check-abc123"
echo $result->status->value; // "processing"
```

### Using Type-Safe DTOs

[](#using-type-safe-dtos)

**Recommended approach** for type safety and IDE autocomplete:

```
use Bhaidar\Checkeeper\Facades\Checkeeper;
use Bhaidar\Checkeeper\DataTransferObjects\CheckData;
use Bhaidar\Checkeeper\DataTransferObjects\BankData;
use Bhaidar\Checkeeper\DataTransferObjects\PayerData;
use Bhaidar\Checkeeper\DataTransferObjects\PayeeData;
use Bhaidar\Checkeeper\DataTransferObjects\SignerData;
use Bhaidar\Checkeeper\Enums\SignerType;
use Illuminate\Support\Str;

$checkData = new CheckData(
    bank: new BankData(
        routing: '123456789',
        account: '987654321'
    ),
    payer: new PayerData(
        line1: 'My Company Inc',
        line2: '123 Business St',
        line3: 'Suite 100',
        line4: 'New York, NY 10001'
    ),
    payee: new PayeeData(
        line1: 'Vendor Services LLC',
        line2: '456 Supplier Ave',
        line3: 'Los Angeles, CA 90210'
    ),
    signer: new SignerData(
        type: SignerType::Text,
        value: 'Jane Smith'
    ),
    amount: 50000,
    number: 1001,
    memo: 'Invoice #12345',
    nonce: 'payment-' . Str::uuid()
);

$result = Checkeeper::checks()->create($checkData);
```

**Adding a Company Logo:**

```
use Illuminate\Support\Facades\Storage;

$payer = new PayerData(
    line1: 'My Company Inc',
    line2: '123 Business St',
    logo: base64_encode(Storage::get('company-logo.png'))
);
```

**Using Image Signature:**

```
$signer = new SignerData(
    type: SignerType::Png,
    value: base64_encode(Storage::get('ceo-signature.png'))
);
```

### Delivery Methods

[](#delivery-methods)

Delivery is specified **separately** from check data and passed as the second argument to `create()` or `createBulk()`. This matches the Checkeeper API payload structure where `delivery` is a sibling to `checks`:

```
{
    "checks": [{ ... }],
    "delivery": { "method": "usps.first_class" }
}
```

```
use Bhaidar\Checkeeper\DataTransferObjects\DeliveryData;
use Bhaidar\Checkeeper\DataTransferObjects\AddressData;
use Bhaidar\Checkeeper\Enums\DeliveryMethod;

// First class mail
$delivery = new DeliveryData(
    method: DeliveryMethod::UspsFirstClass
);

$result = Checkeeper::checks()->create($checkData, $delivery);

// Priority mail
$delivery = new DeliveryData(
    method: DeliveryMethod::UspsPriority
);

$result = Checkeeper::checks()->create($checkData, $delivery);

// Overnight shipping (bundled to one address)
$delivery = new DeliveryData(
    method: DeliveryMethod::UpsNextDay,
    bundleAddress: new AddressData(
        name: 'John Doe',
        line1: '789 Main St',
        city: 'Chicago',
        state: 'IL',
        zip: '60601',
        country: 'US',
        phone: '555-123-4567'
    )
);

$result = Checkeeper::checks()->create($checkData, $delivery);

// PDF return (no mailing, get PDF to print yourself)
$delivery = new DeliveryData(
    method: DeliveryMethod::Pdf
);

$result = Checkeeper::checks()->create($checkData, $delivery);
```

**No delivery specified** defaults to the team's configured delivery method:

```
$result = Checkeeper::checks()->create($checkData);
```

**Available Delivery Methods:**

EnumValueDescription`DeliveryMethod::UspsFirstClass``usps.first_class`USPS First Class Mail`DeliveryMethod::UspsPriority``usps.priority`USPS Priority Mail`DeliveryMethod::UpsTwoDay``ups.two_day`UPS 2-Day Shipping`DeliveryMethod::UpsNextDay``ups.next_day`UPS Next Day Air`DeliveryMethod::FedexTwoDay``fedex.two_day`FedEx 2-Day`DeliveryMethod::FedexOvernight``fedex.overnight`FedEx Overnight`DeliveryMethod::Pdf``pdf`Return PDF only### Bulk Check Creation

[](#bulk-check-creation)

Create multiple checks in a single API request. Delivery applies to **all** checks in the batch:

```
$checks = [
    [
        'bank' => ['routing' => '123456789', 'account' => '111111'],
        'payer' => ['line1' => 'My Company'],
        'payee' => ['line1' => 'Vendor A'],
        'signer' => ['type' => 'text', 'value' => 'Jane Smith'],
        'amount' => 25000,
        'number' => 1001,
        'nonce' => 'check-001',
    ],
    [
        'bank' => ['routing' => '123456789', 'account' => '111111'],
        'payer' => ['line1' => 'My Company'],
        'payee' => ['line1' => 'Vendor B'],
        'signer' => ['type' => 'text', 'value' => 'Jane Smith'],
        'amount' => 35000,
        'number' => 1002,
        'nonce' => 'check-002',
    ],
];

// Without delivery (uses team default)
$result = Checkeeper::checks()->createBulk($checks);

// With delivery
$delivery = new DeliveryData(method: DeliveryMethod::UspsPriority);
$result = Checkeeper::checks()->createBulk($checks, $delivery);

// Result contains:
echo "Created: " . count($result['checks']); // New checks
echo "Duplicates: " . count($result['existing']); // Checks with duplicate nonce
echo "Total credits: " . $result['total_credits']; // Cost in credits
```

**Using DTOs for bulk:**

```
$checksData = [
    new CheckData(/* ... */),
    new CheckData(/* ... */),
    new CheckData(/* ... */),
];

$delivery = new DeliveryData(method: DeliveryMethod::UspsFirstClass);
$result = Checkeeper::checks()->createBulk($checksData, $delivery);
```

### Async Check Creation

[](#async-check-creation)

**Recommended for production** to avoid blocking your application:

```
use Bhaidar\Checkeeper\Jobs\CreateCheckJob;

// Dispatch to queue
CreateCheckJob::dispatch($checkData);

// With delay
CreateCheckJob::dispatch($checkData)->delay(now()->addMinutes(5));

// On specific queue
CreateCheckJob::dispatch($checkData)->onQueue('payments');

// With callback URL (for your own tracking)
CreateCheckJob::dispatch($checkData, callbackUrl: 'https://yourapp.com/check-created');
```

**Listen for completion:**

```
// app/Providers/EventServiceProvider.php

use Bhaidar\Checkeeper\Events\CheckCreated;
use App\Listeners\NotifyAccountingOfCheckCreation;

protected $listen = [
    CheckCreated::class => [
        NotifyAccountingOfCheckCreation::class,
    ],
];
```

Querying Checks
---------------

[](#querying-checks)

### List All Checks

[](#list-all-checks)

```
$checks = Checkeeper::checks()->list();

foreach ($checks as $check) {
    echo "{$check->id}: {$check->status->value}\n";
}
```

### Filtering Checks

[](#filtering-checks)

Use the fluent filter builder for powerful queries:

```
use Bhaidar\Checkeeper\Enums\CheckStatus;

$checks = Checkeeper::checks()
    ->filter()
    ->whereEquals('status', CheckStatus::Delivered->value)
    ->whereGreaterThan('amount', 10000)
    ->whereBetween('date', '2024-01-01', '2024-12-31')
    ->whereContains('memo', 'Invoice')
    ->sortBy('created', 'desc')
    ->get();
```

**Available Filter Methods:**

```
// Equality
->whereEquals('field', 'value')
->whereNotEquals('field', 'value')

// Comparison
->whereLessThan('amount', 10000)
->whereLessThanOrEqual('amount', 10000)
->whereGreaterThan('amount', 10000)
->whereGreaterThanOrEqual('amount', 10000)

// Lists
->whereIn('status', ['delivered', 'printed'])
->whereNotIn('status', ['cancelled'])

// Text search
->whereContains('memo', 'Invoice')

// Ranges
->whereBetween('date', '2024-01-01', '2024-12-31')

// Sorting
->sortBy('created', 'desc')
->sortBy('amount', 'asc')
```

**Filterable Fields:**

- `id`, `request_id`, `template_id`
- `status`, `ship_method`, `test`
- `number`, `date`, `amount`, `memo`, `note`
- `payer_line1`, `payer_line2`, `payer_line3`, `payer_line4`
- `payee_line1`, `payee_line2`, `payee_line3`, `payee_line4`
- `meta`, `created`, `updated`

**Raw filter array:**

```
$checks = Checkeeper::checks()->list([
    'filters[status][$eq]' => 'delivered',
    'filters[amount][$gt]' => 10000,
    'sort' => 'created:desc',
]);
```

### Check Status

[](#check-status)

Get current status of a check:

```
$status = Checkeeper::checks()->status('check-abc123');

echo $status->id;              // "check-abc123"
echo $status->status->value;   // "delivered"
echo $status->created;         // "2024-02-29 10:00:00"
echo $status->updated;         // "2024-03-02 14:30:00"
echo $status->trackingUrl;     // "https://tools.usps.com/..."
```

**Available Check Statuses:**

StatusDescription`Processing`Check is being prepared`Ready`Check is ready for printing`Printed`Check has been printed`Mailed`Check has been sent`Delivered`Check delivered to recipient`Cancelled`Check was cancelled`Returned`Check returned to sender### Tracking Events

[](#tracking-events)

Get detailed tracking information:

```
$events = Checkeeper::checks()->tracking('check-abc123');

foreach ($events as $event) {
    echo "{$event->event}: {$event->eventDate}\n";
    if ($event->location) {
        echo "  Location: {$event->location->city}, {$event->location->state}\n";
    }
}
```

### Cancel Check

[](#cancel-check)

Cancel a check before it's printed or mailed:

```
$cancelled = Checkeeper::checks()->cancel('check-abc123');

if ($cancelled) {
    echo "Check cancelled successfully";
}
```

**Note:** Checks can only be cancelled if they haven't been printed yet.

### Download Check Images

[](#download-check-images)

Download check images as JPG or PDF:

```
use Illuminate\Support\Facades\Storage;

// Get JPG image
$imageData = Checkeeper::checks()->image('check-abc123', 'jpg');
Storage::put('checks/check-abc123.jpg', $imageData);

// Get PDF
$pdfData = Checkeeper::checks()->image('check-abc123', 'pdf');
Storage::put('checks/check-abc123.pdf', $pdfData);

// Download in controller
public function download($checkId)
{
    $pdfData = Checkeeper::checks()->image($checkId, 'pdf');

    return response()->streamDownload(function () use ($pdfData) {
        echo $pdfData;
    }, "check-{$checkId}.pdf");
}
```

**Get voucher image:**

```
$voucherData = Checkeeper::checks()->voucherImage('check-abc123');
Storage::put('vouchers/voucher-abc123.jpg', $voucherData);
```

Webhooks
--------

[](#webhooks)

Webhooks allow Checkeeper to notify your application in real-time when check statuses change.

### Webhook Setup

[](#webhook-setup)

1. **The webhook route is auto-registered** at `/checkeeper/webhook`
2. **Configure the URL in Checkeeper dashboard**: `https://yourapp.com/checkeeper/webhook`
3. **Set webhook secret** in `.env`: ```
    CHECKEEPER_WEBHOOK_SECRET=your-webhook-secret-from-dashboard
    ```

The package automatically:

- Verifies webhook signatures using HMAC SHA256
- Dispatches `WebhookReceived` event
- Returns 200 OK immediately

### Listening to Events

[](#listening-to-events)

Register listeners in your `EventServiceProvider`:

```
use Bhaidar\Checkeeper\Events\WebhookReceived;
use Bhaidar\Checkeeper\Events\CheckCreated;
use Bhaidar\Checkeeper\Events\CheckCancelled;

protected $listen = [
    WebhookReceived::class => [
        LogWebhookActivity::class,
        ProcessCheckStatusUpdate::class,
    ],

    CheckCreated::class => [
        SendCheckCreatedNotification::class,
        UpdateInvoiceStatus::class,
    ],

    CheckCancelled::class => [
        RefundPayment::class,
        NotifyAccounting::class,
    ],
];
```

### Example Listeners

[](#example-listeners)

#### Update Invoice When Check Delivered

[](#update-invoice-when-check-delivered)

```
namespace App\Listeners;

use Bhaidar\Checkeeper\Events\WebhookReceived;
use App\Models\Invoice;

class MarkInvoiceAsPaid
{
    public function handle(WebhookReceived $event): void
    {
        $payload = $event->payload;

        if ($payload['event'] !== 'check.delivered') {
            return;
        }

        $invoice = Invoice::where('check_id', $payload['check_id'])->first();

        if (! $invoice) {
            return;
        }

        $invoice->update([
            'status' => 'paid',
            'paid_at' => $payload['delivered_at'],
        ]);

        $invoice->customer->notify(new CheckDeliveredNotification($invoice));
    }
}
```

#### Log All Webhook Activity

[](#log-all-webhook-activity)

```
namespace App\Listeners;

use Bhaidar\Checkeeper\Events\WebhookReceived;
use Illuminate\Support\Facades\Log;

class LogWebhookActivity
{
    public function handle(WebhookReceived $event): void
    {
        Log::info('Checkeeper webhook received', [
            'event' => $event->payload['event'] ?? 'unknown',
            'check_id' => $event->payload['check_id'] ?? null,
            'received_at' => $event->receivedAt,
        ]);
    }
}
```

### Webhook Security

[](#webhook-security)

The package automatically verifies webhook signatures using the `VerifyWebhookSignature` middleware:

1. Extracts `X-Checkeeper-Signature` header
2. Validates using HMAC SHA256 with your webhook secret
3. Rejects invalid signatures with 401 Unauthorized

**No additional configuration needed** - just ensure your webhook secret is set in `.env`.

**Disable webhooks** if needed:

```
// config/checkeeper.php
'webhooks' => [
    'enabled' => false,
],
```

Team &amp; Templates
--------------------

[](#team--templates)

### Get Team Information

[](#get-team-information)

```
$info = Checkeeper::team()->info();

echo $info['name'];
echo $info['credits'];
```

### List Available Templates

[](#list-available-templates)

```
$templates = Checkeeper::templates()->list();

foreach ($templates as $template) {
    echo "{$template['id']}: {$template['name']}\n";
}
```

**Use a template when creating checks:**

```
$checkData = new CheckData(
    // ... other fields
    templateId: 'template-123'
);
```

Events
------

[](#events)

The package dispatches Laravel events for key operations:

### CheckCreated

[](#checkcreated)

Fired after successful check creation.

```
use Bhaidar\Checkeeper\Events\CheckCreated;

class SendCheckCreatedEmail
{
    public function handle(CheckCreated $event): void
    {
        $check = $event->check;       // CheckStatusData
        $metadata = $event->metadata;  // array

        Mail::to($recipient)->send(new CheckCreatedMail($check));
    }
}
```

### CheckCancelled

[](#checkcancelled)

Fired after check cancellation.

```
use Bhaidar\Checkeeper\Events\CheckCancelled;

class RefundCancelledCheck
{
    public function handle(CheckCancelled $event): void
    {
        $checkId = $event->checkId;

        Refund::create(['check_id' => $checkId]);
    }
}
```

### WebhookReceived

[](#webhookreceived)

Fired when webhook is received and verified.

```
use Bhaidar\Checkeeper\Events\WebhookReceived;

class ProcessWebhookPayload
{
    public function handle(WebhookReceived $event): void
    {
        $payload = $event->payload;       // array
        $signature = $event->signature;   // string
        $receivedAt = $event->receivedAt; // string (ISO 8601)

        match ($payload['event']) {
            'check.printed' => $this->handlePrinted($payload),
            'check.mailed' => $this->handleMailed($payload),
            'check.delivered' => $this->handleDelivered($payload),
            default => null,
        };
    }
}
```

Exception Handling
------------------

[](#exception-handling)

The package throws typed exceptions for different error scenarios:

```
use Bhaidar\Checkeeper\Exceptions\AuthenticationException;
use Bhaidar\Checkeeper\Exceptions\ValidationException;
use Bhaidar\Checkeeper\Exceptions\NotFoundException;
use Bhaidar\Checkeeper\Exceptions\CheckkeeperException;

try {
    $result = Checkeeper::checks()->create($checkData);
} catch (AuthenticationException $e) {
    // Invalid API key (401/403)
    Log::error('Checkeeper auth failed', ['error' => $e->getMessage()]);

} catch (ValidationException $e) {
    // Invalid check data (422)
    return back()->withErrors($e->errors);

} catch (NotFoundException $e) {
    // Check not found (404)
    abort(404, 'Check not found');

} catch (CheckkeeperException $e) {
    // Other API errors
    Log::error('Checkeeper error', [
        'status' => $e->statusCode,
        'message' => $e->getMessage(),
        'errors' => $e->errors,
    ]);
}
```

Complete Application Example
----------------------------

[](#complete-application-example)

Here's a complete example of a vendor payment system:

```
use Bhaidar\Checkeeper\Facades\Checkeeper;
use Bhaidar\Checkeeper\DataTransferObjects\CheckData;
use Bhaidar\Checkeeper\DataTransferObjects\BankData;
use Bhaidar\Checkeeper\DataTransferObjects\PayerData;
use Bhaidar\Checkeeper\DataTransferObjects\PayeeData;
use Bhaidar\Checkeeper\DataTransferObjects\SignerData;
use Bhaidar\Checkeeper\DataTransferObjects\DeliveryData;
use Bhaidar\Checkeeper\Enums\SignerType;
use Bhaidar\Checkeeper\Enums\DeliveryMethod;
use Bhaidar\Checkeeper\Events\WebhookReceived;
use Bhaidar\Checkeeper\Jobs\CreateCheckJob;

// Step 1: Create payment record
$payment = Payment::create([
    'invoice_id' => $invoice->id,
    'vendor_id' => $vendor->id,
    'amount' => 50000, // $500.00
    'status' => 'pending',
]);

// Step 2: Prepare check data
$checkData = new CheckData(
    bank: new BankData(
        routing: config('company.bank_routing'),
        account: config('company.bank_account')
    ),
    payer: new PayerData(
        line1: config('company.name'),
        line2: config('company.address'),
        logo: base64_encode(Storage::get('company-logo.png'))
    ),
    payee: new PayeeData(
        line1: $vendor->name,
        line2: $vendor->address
    ),
    signer: new SignerData(
        type: SignerType::Png,
        value: base64_encode(Storage::get('ceo-signature.png'))
    ),
    amount: $payment->amount,
    number: $payment->check_number,
    memo: "Invoice #{$invoice->number}",
    nonce: "payment-{$payment->id}"
);

// Step 3: Create check with delivery method
$delivery = new DeliveryData(method: DeliveryMethod::UspsFirstClass);
$result = Checkeeper::checks()->create($checkData, $delivery);

$payment->update([
    'check_id' => $result->id,
    'status' => 'sent_to_printer',
]);

// Or queue it for async processing
CreateCheckJob::dispatch($checkData);

// Step 4: Listen for webhook events
// app/Listeners/UpdatePaymentStatus.php
class UpdatePaymentStatus
{
    public function handle(WebhookReceived $event): void
    {
        $payload = $event->payload;
        $checkId = $payload['check_id'];

        $payment = Payment::where('check_id', $checkId)->first();

        if (! $payment) {
            return;
        }

        match ($payload['event']) {
            'check.printed' => $payment->update(['status' => 'printed']),
            'check.mailed' => $payment->update(['status' => 'mailed']),
            'check.delivered' => $payment->update([
                'status' => 'delivered',
                'delivered_at' => $payload['delivered_at'],
            ]),
            'check.returned' => $payment->update(['status' => 'returned']),
            default => null,
        };

        $payment->vendor->notify(new CheckStatusUpdated($payment));
    }
}

// Step 5: Display status to user
// resources/views/payments/show.blade.php
```

```

    Payment #{{ $payment->id }}

        {{ ucfirst($payment->status) }}

    @if($payment->check_id)

            Tracking
            @foreach($tracking as $event)

                    {{ $event->event }}
                    {{ $event->eventDate }}
                    {{ $event->location->city ?? '' }}

            @endforeach

            Download Check Image

    @endif

```

Testing Your Integration
------------------------

[](#testing-your-integration)

Use HTTP fakes to test your Checkeeper integration:

```
use Bhaidar\Checkeeper\Facades\Checkeeper;
use Illuminate\Support\Facades\Http;

test('can create vendor payment', function () {
    Http::fake([
        'api.checkeeper.com/*' => Http::response([
            'data' => [
                'checks' => [
                    ['id' => 'check-123', 'status' => 'processing']
                ],
                'total_credits' => 1,
            ],
        ], 201),
    ]);

    $vendor = Vendor::factory()->create();
    $invoice = Invoice::factory()->create(['vendor_id' => $vendor->id]);

    $payment = Payment::create([
        'invoice_id' => $invoice->id,
        'vendor_id' => $vendor->id,
        'amount' => 50000,
    ]);

    CreateCheckJob::dispatchSync($checkData);

    expect($payment->fresh())
        ->check_id->toBe('check-123')
        ->status->toBe('sent_to_printer');
});

test('updates payment status on webhook', function () {
    $payment = Payment::factory()->create(['check_id' => 'check-123']);

    $payload = json_encode([
        'event' => 'check.delivered',
        'check_id' => 'check-123',
        'delivered_at' => now()->toIso8601String(),
    ]);

    $signature = hash_hmac('sha256', $payload, config('checkeeper.webhooks.secret'));

    $this->postJson('/checkeeper/webhook', json_decode($payload, true), [
        'X-Checkeeper-Signature' => $signature,
    ])->assertOk();

    expect($payment->fresh()->status)->toBe('delivered');
});
```

API Reference
-------------

[](#api-reference)

### Check Operations

[](#check-operations)

```
// List checks (returns Collection of CheckStatusData)
Checkeeper::checks()->list(array $filters = []): Collection

// Create single check (delivery is separate from check data)
Checkeeper::checks()->create(CheckData|array $data, ?DeliveryData $delivery = null): CheckStatusData

// Create multiple checks (delivery applies to all checks)
Checkeeper::checks()->createBulk(array $checks, ?DeliveryData $delivery = null): array

// Get check status
Checkeeper::checks()->status(string $id): CheckStatusData

// Get tracking events
Checkeeper::checks()->tracking(string $id): Collection

// Cancel check
Checkeeper::checks()->cancel(string $id): bool

// Get check image (jpg or pdf)
Checkeeper::checks()->image(string $id, string $type = 'jpg'): string

// Add attachment
Checkeeper::checks()->attachment(string $id, string $file): bool

// Get voucher image
Checkeeper::checks()->voucherImage(string $id): string

// Fluent filter builder
Checkeeper::checks()->filter(): CheckFilterBuilder
```

### Team Operations

[](#team-operations)

```
Checkeeper::team()->info(): array
```

### Template Operations

[](#template-operations)

```
Checkeeper::templates()->list(): Collection
```

### Available DTOs

[](#available-dtos)

DTODescription`CheckData`Check payload (bank, payer, payee, signer, amount, etc.)`BankData`Bank routing and account numbers`PayerData`Payer/company info (lines 1-4, logo)`PayeeData`Payee/recipient info (lines 1-4)`SignerData`Signature (text name or image)`AddressData`Full address (name, lines, city, state, zip, country, phone)`DeliveryData`Delivery method and optional bundle address`CheckStatusData`API response (id, status, created, updated, trackingUrl)`TrackingEventData`Tracking event (event, subevent, eventDate, location)### Available Enums

[](#available-enums)

EnumValues`DeliveryMethod``UspsFirstClass`, `UspsPriority`, `UpsTwoDay`, `UpsNextDay`, `FedexTwoDay`, `FedexOvernight`, `Pdf``CheckStatus``Processing`, `Ready`, `Printed`, `Mailed`, `Delivered`, `Cancelled`, `Returned``SignerType``Text`, `Png`, `Gif`, `Jpg`Testing
-------

[](#testing)

Run the package test suite:

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG.md](CHANGELOG.md) for recent changes.

Contributing
------------

[](#contributing)

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Bilal Haidar](https://github.com/bhaidar)
- [All Contributors](../../contributors)

License
-------

[](#license)

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

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance74

Regular maintenance activity

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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.

###  Release Activity

Cadence

Every ~2 days

Total

9

Last Release

142d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6f82767012ddd622f7b8c33f76c60e4bbd71d765cf729ab0dad362aa74633fd4?d=identicon)[bhaidar](/maintainers/bhaidar)

---

Top Contributors

[![bhaidar](https://avatars.githubusercontent.com/u/1163421?v=4)](https://github.com/bhaidar "bhaidar (11 commits)")

---

Tags

laravelcheckpaymentmailingcheckeeper

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/bhaidar-laravel-checkeeper/health.svg)

```
[![Health](https://phpackages.com/badges/bhaidar-laravel-checkeeper/health.svg)](https://phpackages.com/packages/bhaidar-laravel-checkeeper)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

77022.3M151](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58171.6k14](/packages/api-platform-laravel)[defstudio/telegraph

A laravel facade to interact with Telegram Bots

816333.8k3](/packages/defstudio-telegraph)[illuminate/auth

The Illuminate Auth package.

10528.2M1.2k](/packages/illuminate-auth)[illuminate/routing

The Illuminate Routing package.

1419.2M3.0k](/packages/illuminate-routing)

PHPackages © 2026

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