Take payments
Take a payment
Create a charge, send the customer to checkout, and confirm what happened.
A charge is one attempt to collect one amount from one customer. It creates a checkout on the business's provider and hands you a URL to send the customer to. Everything else on this page is about being sure what happened afterwards.
Create the charge
PHP
<?php
use Nordaxiz\HarbourConnect\Harbour;
$checkout = Harbour::charge(12_000_00, ['email' => 'funke@example.ng', 'name' => 'Funke Adeyemi'], [
'reference' => 'order-1043', // yours; must be unique per payment attempt
'description' => 'Aso-ebi order, 12 yards',
'return_url' => 'https://shop.example.ng/thanks', // or a deep link: myapp://harbour/return
'provider' => 'auto', // auto | paystack | flutterwave | opay
'metadata' => ['order_id' => '1043', 'channel' => 'web'],
]);
$checkout->reference; // "HB-88413" ← store this against the order
$checkout->checkoutUrl; // "https://pay.harbour.africa/c/ct_…"
$checkout->checkoutToken; // "ct_…" for harbour.js or an in-app browser
$checkout->status; // "open"
The customer can be an email string, an array with email, name and phone, or a
Customer object. Only the amount and an email are required.
The same call, fluently:
PHP
<?php
$charge = $harbour->charge()
->amount(84_000_00)
->customer('adaeze@example.ng')
->reference('order-2001')
->description('Deposit, Lekki job')
->returnUrl('https://shop.example.ng/thanks')
->provider('paystack')
->metadata(['job_id' => '2001'])
->idempotencyKey('order-2001-attempt-1')
->create();
Send the customer
PHP
<?php
// 303 keeps the browser from re-posting the form if the customer presses back
header('Location: ' . $checkout->checkoutUrl, true, 303);
exit;
The checkout page picks the provider by the business's routing rule and hands off to that provider's own hosted
page, so card details never reach Harbour and never reach you. When the customer is done they return to your
return_url with ?reference=HB-88413&status=succeeded.
If you would rather not leave your page, harbour.js opens the same checkout in a popup.
Verify server side
Treat the return URL as "the customer says they are done", nothing more. Ask Harbour what actually happened:
PHP
<?php
use Nordaxiz\HarbourConnect\Harbour;
$checkout = Harbour::verify($_GET['reference'] ?? '');
if (!$checkout->isPaid()) {
return $this->render('order/pending', ['status' => $checkout->status]);
}
// never fulfil on the amount the customer's browser tells you
if ($checkout->merchantReference !== $order->reference || $checkout->amountKobo !== $order->total_kobo) {
throw new RuntimeException('Payment does not match this order.');
}
$order->markPaid(); // safe to call twice: the webhook may have run already
verify() asks the provider directly when the payment is not final yet, so it is safe to call the moment
the customer lands. Once paid, $checkout->transaction holds the full Transaction.
Verification is a fallback, not the mechanism: a customer who closes the tab never comes back, and only the webhook will tell you. Build both.
Your reference and theirs
| Reference | Who makes it | Use it for |
|---|---|---|
HB-88413$checkout->reference | Harbour | Verifying, refunding, support. Store it on the order. |
order-1043$checkout->merchantReference | You, in reference | Matching a payment back to your order, in webhooks and exports. |
txn_… | Harbour | The ledger row's own id, in the API and webhooks. |
PHP
<?php
// when you have your own reference but not Harbour's (a lost redirect, a support ticket)
$checkout = Harbour::charges()->findByMerchantReference('order-1043'); // null when nothing matches
Choosing a provider
provider takes auto (the default), paystack, flutterwave or opay.
auto uses the business's primary connection, which they set in Providers. Name a provider
only when you have a reason to: a recovery attempt through the other one, or a channel only one of them supports.
The whole thing
A complete, defensive version of the create step, in the shape most apps end up with:
PHP
<?php
use Nordaxiz\HarbourConnect\Exception\HarbourException;
use Nordaxiz\HarbourConnect\Exception\ValidationException;
use Nordaxiz\HarbourConnect\Harbour;
Harbour::configure((string) getenv('HARBOUR_SECRET_KEY'));
try {
$checkout = Harbour::charge($order->total_kobo, $order->customer_email, [
'reference' => $order->reference,
'description' => $order->summary(),
'return_url' => route('orders.thanks'),
'idempotency_key' => 'order-' . $order->id . '-attempt-' . $order->attempts,
]);
} catch (ValidationException $e) {
return back()->withErrors($e->getFieldErrors()); // ['amount_kobo' => 'plain language']
} catch (HarbourException $e) {
report($e); // 502 provider_unavailable, 429, network…
return back()->with('error', 'Payments are having a moment. Try again in a minute.');
}
$order->update(['harbour_reference' => $checkout->reference]);
return redirect()->away($checkout->checkoutUrl, 303);
The idempotency_key makes a retry of that request safe: same key and same body returns the original
checkout instead of charging twice. See Idempotency and retries.