blob-solutions/laravel-vcr-am

Official Laravel adapter for the VCR.AM Virtual Cash Register PHP SDK — ServiceProvider, Facade, config publishing, and Artisan commands.

Maintainers

Package info

github.com/blob-am/laravel-vcr-am

Homepage

Issues

pkg:composer/blob-solutions/laravel-vcr-am

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

v0.6.0 2026-07-10 11:10 UTC

This package is auto-updated.

Last update: 2026-07-10 11:11:42 UTC


README

Packagist Version PHP Version Require License CI

A thin Laravel adapter around blob-solutions/vcr-am-sdk. Wires the SDK's VcrClient into Laravel's container, publishes a config file, exposes a facade, and registers an Artisan health-check command.

The adapter contains zero business logic — every API call goes straight through to the SDK. If the SDK supports it, the adapter exposes it.

Requirements

  • PHP 8.2 or newer
  • Laravel 11.x or 12.x
  • A VCR.AM account and API key — sign up at vcr.am

Installation

composer require blob-solutions/laravel-vcr-am

The service provider and facade are auto-discovered.

Add your API key to .env:

VCR_AM_API_KEY=your-api-key-here

That's it — the package is ready to use.

Configuration

To customise the configuration (e.g. point at a staging environment), publish the config file:

php artisan vendor:publish --tag=vcr-am-config

This creates config/vcr-am.php:

return [
    'api_key' => env('VCR_AM_API_KEY'),
    'base_url' => env('VCR_AM_BASE_URL'), // null = SDK default (https://vcr.am/api/v1)
];

Usage

Via the facade

use BlobSolutions\LaravelVcrAm\Facades\VcrAm;
use BlobSolutions\VcrAm\Input\RegisterSaleInput;

$response = VcrAm::registerSale(new RegisterSaleInput(/* ... */));

Via dependency injection

use BlobSolutions\VcrAm\VcrClient;

class CheckoutController
{
    public function __construct(private readonly VcrClient $vcr) {}

    public function __invoke(): RedirectResponse
    {
        $response = $this->vcr->registerSale(/* ... */);
        // ...
    }
}

VcrClient is bound as a singleton, so the same instance is reused for the lifetime of every request.

Via the container

$client = app(VcrClient::class);

Health check

To verify connectivity from a running app or as part of your deployment pipeline:

php artisan vcr-am:health
VCR.AM API reachable.
  VCR    : "My Shop" (CRN 1234567890123)
  Mode   : production
  Entity : My Shop LLC (TIN 01234567)

Mode is production or sandbox — useful at a glance for confirming which VCR your API key is wired to. Add --sandbox to point the check at the parallel sandbox client (see Testing against a sandbox VCR below).

Exit code is 0 on success, 1 on failure (with the SDK's error message printed).

Testing

There are two distinct testing strategies. Pick whichever fits the seam you're testing:

You want… Use
Unit tests that never touch the network VcrAm::fake()
Smoke tests / staging against a real VCR Sandbox channel

Unit tests: VcrAm::fake()

Modelled on Laravel's Http::fake(). After calling VcrAm::fake(), every resolution of VcrClient (via the facade, app(VcrClient::class), or constructor DI) returns a fake that records every request and rejects every un-stubbed endpoint with a clear RuntimeException — so SDK calls the test didn't expect surface as test failures, not as silent passes.

use BlobSolutions\LaravelVcrAm\Facades\VcrAm;

it('fiscalises checkout via VCR.AM', function (): void {
    VcrAm::fake([
        'POST /sales' => [
            'urlId' => 'abc',
            'saleId' => 42,
            'crn' => '1234567890123',
            'srcReceiptId' => 100,
            'fiscal' => 'F-1',
        ],
    ]);

    $this->postJson('/checkout', [/* ... */])->assertSuccessful();

    VcrAm::assertSentCount(1);
    VcrAm::assertSent(
        'POST /sales',
        fn (?array $body) => ($body['cashier']['deskId'] ?? null) === 'desk-1',
    );
});

Stub forms:

// Plain array — turned into a 200 JSON response.
VcrAm::fake([
    'POST /sales' => ['urlId' => 'X', /* ... */],
]);

// Closure — receives the captured PSR-7 request, returns either a
// ResponseInterface or a plain array.
VcrAm::fake([
    'POST /sales' => function (Psr\Http\Message\RequestInterface $request) {
        return ['urlId' => 'dyn', /* ... */];
    },
]);

// Throw a server-side error to test how your code handles failures.
use Nyholm\Psr7\Response;

VcrAm::fake([
    'POST /sales' => fn () => new Response(
        400,
        ['Content-Type' => 'application/json'],
        '{"code":"VALIDATION","message":"price must be positive"}',
    ),
]);

Assertions:

VcrAm::assertSent('POST /sales');                          // any matching request
VcrAm::assertSent('POST /sales', fn (?array $body) => /* ... */);  // narrow on body
VcrAm::assertNotSent('POST /prepayments');                 // negative assertion
VcrAm::assertNothingSent();                                // no SDK calls at all
VcrAm::assertSentCount(2);                                 // exact request count

The matcher syntax is "METHOD /path". Method is case-insensitive, path is exact. The SDK base path (/api/v1) is stripped automatically so stubs read like the endpoints documented in the SDK source (POST /sales, not POST /api/v1/sales).

Testing against a sandbox VCR

The SDK has no operating-mode flag of its own — sandbox is determined entirely by the VCR an API key authenticates against. To run real HTTP calls during integration testing without touching production, create a sandbox VCR at vcr.am, generate an API key for it, and point this package at it.

Quickest setup — overwrite the default key in your testing environment:

# .env.testing
VCR_AM_API_KEY=sk_sandbox_...

Or run two clients side-by-side — useful when a single app needs both for different flows (e.g. real receipts in production paths, sandbox receipts in an E2E suite that runs against the same deploy):

# .env
VCR_AM_API_KEY=sk_live_...
VCR_AM_SANDBOX_API_KEY=sk_sandbox_...
VcrAm::registerSale($input);             // hits the production VCR
VcrAm::sandbox()->registerSale($input);  // hits the sandbox VCR

The sandbox VcrClient is also resolvable through the container:

use BlobSolutions\LaravelVcrAm\VcrAmServiceProvider;
use BlobSolutions\VcrAm\VcrClient;

$sandbox = app(VcrAmServiceProvider::SANDBOX_BINDING); // returns VcrClient

Calling VcrAm::sandbox() without VCR_AM_SANDBOX_API_KEY set throws a RuntimeException — deliberately, so production code never silently downgrades to the default key.

Health check, sandbox edition

vcr-am:health also accepts --sandbox and reports the sandbox VCR's identity:

php artisan vcr-am:health --sandbox
VCR.AM API reachable.
  VCR    : "My Shop (sandbox)" (CRN not activated)
  Mode   : sandbox
  Entity : My Shop LLC (TIN 01234567)

Logging

The package forwards Laravel's PSR-3 logger into the SDK automatically. Every request and response is logged at the level configured in your config/logging.php. To filter only VCR.AM log entries, give the SDK its own channel:

// config/logging.php
'channels' => [
    'vcr-am' => [
        'driver' => 'daily',
        'path' => storage_path('logs/vcr-am.log'),
        'days' => 14,
    ],
],
// AppServiceProvider::register()
$this->app->bind(\Psr\Log\LoggerInterface::class, fn () => Log::channel('vcr-am'));

HTTP client override

The SDK ships with Guzzle 7 by default. To swap it (e.g. for testing or to use a shared HTTP client with custom retry policies), bind a PSR-18 implementation in your AppServiceProvider:

$this->app->bind(\Psr\Http\Client\ClientInterface::class, fn () => new MyHttpClient());

The adapter detects this binding and passes it into the SDK constructor.

Documentation for SDK methods

See the SDK README for every endpoint, every input DTO, and every response type. The Laravel adapter is a 1:1 wrapper — anything documented for VcrClient works through the facade or container binding.

Status

Pre-release. API tracks the SDK's 0.x releases. Pin tightly until 1.0.

License

ISC