Take payments

Checkout in the browser and in apps

harbour.js for a popup checkout, and what to do inside a mobile app.

harbour.js opens the same hosted checkout in a popup, so the customer never leaves your page. It is served by Harbour, not by the SDK, and it takes a public key. Card details are still entered on the provider's own page: the popup hands off to them.

With a public key

HTML
<script src="https://pay.harbour.africa/v1/harbour.js"></script>
<script>
  document.getElementById('pay').addEventListener('click', function () {
    Harbour.checkout({
      key: 'hb_pk_live_your_public_key',
      amount: 1200000,                      // kobo
      email: 'funke@example.ng',
      reference: 'order-1043',              // your reference, optional
      onSuccess: function (result) {
        // result.reference === "HB-88413": hand it to your server and verify there
        fetch('/orders/1043/confirm', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ reference: result.reference })
        }).then(function () { window.location = '/thanks'; });
      },
      onClose: function () { /* the customer closed the popup */ },
      onError: function (err) { console.error(err.message); }
    });
  });
</script>

Restrict the public key to your own domains in Settings → Developers so it cannot be used from someone else's site.

PHP
SDK language
  • PHP
  • Node.jsComing soon
  • PythonComing soon
  • JavaComing soon
  • GoComing soon
  • .NETComing soon
Any language can call the API today
Your confirm endpoint
<?php

// POST /orders/1043/confirm — the browser is not a source of truth
$checkout = Harbour::verify($request->json('reference'));

if ($checkout->isPaid() && $checkout->amountKobo === $order->total_kobo) {
    $order->markPaid();
}

With a checkout token

If you would rather create the charge on your server (so the amount is never in the page), pass the checkout token instead of the public key. Checkout::inlinePayload() returns exactly what harbour.js needs:

PHP
SDK language
  • PHP
  • Node.jsComing soon
  • PythonComing soon
  • JavaComing soon
  • GoComing soon
  • .NETComing soon
Any language can call the API today
<?php $payload = $checkout->inlinePayload(); ?>
<script src="https://pay.harbour.africa/v1/harbour.js"></script>
<script>
  Harbour.checkout(Object.assign(
    <?= json_encode($payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>,
    { onSuccess: confirmOnServer }
  ));
</script>

The payload carries token, amount, email and reference — no key, and nothing a customer can usefully edit.

When popups are blocked

harbour.js falls back to a full-page redirect on its own. The customer comes back to the charge's return_url with ?reference=HB-…&status=succeeded, so your return page must handle that case too. With the public-key form, set a return URL by creating the charge server side first, or handle onClose by reloading the order page.

Inside a mobile app

Do not embed the checkout in a WebView: providers reject some card flows there, and 3-D Secure can break. Open checkout_url in the system browser component instead — Chrome Custom Tabs on Android, SFSafariViewController on iOS — with return_url set to your deep link.

PHP
SDK language
  • PHP
  • Node.jsComing soon
  • PythonComing soon
  • JavaComing soon
  • GoComing soon
  • .NETComing soon
Any language can call the API today
<?php

// your server, when the app asks to start a payment
$checkout = Harbour::charge($order->total_kobo, $user->email, [
    'reference'  => $order->reference,
    'return_url' => 'myapp://harbour/return',     // your app's deep link
]);

return response()->json([
    'checkout_url' => $checkout->checkoutUrl,     // open this in a Custom Tab / SFSafariViewController
    'reference'    => $checkout->reference,
]);

Harbour appends ?reference= and &status= to the deep link. When your app is woken by it, call your own server to verify; the app never talks to Harbour with a secret key.