Testing & sandbox
Before you point an integration at real money, you want three things confirmed: the config is complete, the credentials authenticate, and a charge survives the full roundtrip. Parakit ships three commands for exactly that.
# 1. Config + connectivity, safe to run anywhere
php artisan parakit:doctor
# 2. A real sandbox charge, end to end
php artisan parakit:transactions:test-charge fib --amount=1000 --currency=IQD
# 3. A simulated webhook posted to your local app
php artisan parakit:webhooks:simulate fib --transaction-id=pid_1 --status=paidSee Artisan commands for every option of every command.
parakit:doctor — config and connectivity
parakit:doctor checks the default gateway unless you select another one. For drivers it knows, it verifies the required config keys are present, and for FIB it forces a fresh token fetch to prove the credentials authenticate:
php artisan parakit:doctor # default gateway
php artisan parakit:doctor --gateway=fib # one gateway
php artisan parakit:doctor --all # every declared gatewayUnused gateway templates are ignored in the default mode, so an application using only one provider is not failed for credentials it does not need.
It makes no charge and moves no money, so it is safe to run anywhere — including CI. It exits non-zero when any check fails, so a pipeline step against sandbox credentials catches a broken config before deploy:
# In a CI pipeline
- run: php artisan parakit:doctorTIP
The FIB check clears the cached token first, so a credential rotation cannot be masked by a stale token. A custom driver type registered via Payment::extend() has no built-in field check — doctor reports it as unverified and asks you to confirm the config manually.
parakit:transactions:test-charge — a real sandbox roundtrip
parakit:transactions:test-charge builds a PaymentRequest with a random reference and runs a genuine charge() against the gateway you name:
php artisan parakit:transactions:test-charge fib --amount=1000 --currency=IQD--amount defaults to 1000 and --currency to IQD. On success it prints the gateway transaction id, plus any redirect URL, readable code, or deep link the gateway returned:
OK: 6f1c2a90-...
readable: 1234 5678This is a real API call to whatever base_url the config points at. Run it against sandbox credentials. The random reference (test_ plus 8 hex chars) is fine for sandbox use but is not collision-safe at production volume.
WARNING
test-charge charges for real. If the config holds production credentials it hits the production gateway. Confirm base_url and credentials are sandbox before you run it.
parakit:webhooks:simulate — a local webhook
You usually cannot receive a real gateway callback on localhost. parakit:webhooks:simulate closes that gap by POSTing a webhook to your own route, so your verification and listeners run for real:
php artisan parakit:webhooks:simulate fib \
--transaction-id=pid_1 --status=paidIt posts to {route_prefix}/{gateway} and prints the HTTP status and body it got back. NassWallet is posted to {route_prefix}/nasswallet/callback, the route that gateway actually calls.
The command builds a faithful, correctly-formed payload for every driver:
fib— an{ id, status }form body, the shape a FIB callback delivers.zaincash— an HS256 JWT signed with the merchant'sapi_key, wrapping aSTATUS_CHANGEDenvelope, exactly as ZainCash v2 sends it.nass— an{ orderId, responseCode }form body.nasswallet— a JSON{ "data": { ... } }envelope keyed byInitTransactionId.fastpay— an{ order_id, status }form body.
--status defaults to paid; --reference and --transaction-id are optional.
TIP
FIB, NassPay, NassWallet, and FastPay all ignore the callback body and re-fetch status server-to-server, so the transaction id you pass must be one the sandbox recognises. ZainCash instead verifies the JWT signature against the configured api_key — that config must be set for the simulated webhook to pass.
Faking events in feature tests
For application tests, you do not need a gateway at all. Fake the events and assert your own code reacts:
use Froshly\Parakit\Events\PaymentSucceeded;
use Illuminate\Support\Facades\Event;
Event::fake([PaymentSucceeded::class]);
// ... run the code that should mark an order paid ...
Event::assertDispatched(PaymentSucceeded::class);See Events for the full list and each event's payload.
Sandbox endpoints
Every gateway's default base_url points at the provider's staging environment (fib.stage.fib.iq, pg-api-uat.zaincash.iq, and so on — see Configuration). Switch to production URLs only when you flip to live credentials.