Skip to content

WordPress and WooCommerce

One plugin, nine integrations, and the hooks to build your own.

The Wajub plugin turns a WordPress site into a payment surface. It detects the e-commerce, donation, LMS, membership and form plugins already installed, registers itself as a payment method inside each of them, and falls back to shortcodes for everything it does not have a native gateway for. One set of keys, one webhook, every integration.

What the plugin does

Nine integrations ship a real gateway. The plugin loads each one only when it detects the matching plugin, so an install with WooCommerce alone never loads the GiveWP or LMS code.

IntegrationDetected throughCheckoutRefunds from WordPress
WooCommerceWooCommerce classRedirect, inline, overlay, BlocksYes
Easy Digital DownloadsEDD_VERSIONRedirectYes, on status change
GiveWP 3.xgivewp_register_payment_gatewayRedirectYes
GiveWP below 2.18GIVE_VERSIONRedirectYes, on status change
CharitableCHARITABLE_VERSIONRedirectYes
Tutor LMSTUTOR_VERSIONNative Ecommerce checkoutYes
LifterLMSLLMS_PLUGIN_FILERedirectYes
LearnDashLEARNDASH_VERSIONShortcode buttonNo order model to refund
MemberPressMEPR_VERSIONRedirectYes
Gravity FormsGFForms classRedirectYes

Five more appear in the admin with a shortcode badge. They have no gateway API the plugin can hook into, so you drop [wajub_pay] on the page instead: WPForms, Bookly, The Events Calendar, WP Simple Pay and Amelia. WP Crowdfunding runs through WooCommerce, so enabling the WooCommerce integration covers it.

Requirements

ComponentMinimumNote
WordPress6.2Tested through 7.0
PHP8.1The plugin shows an admin notice and stops below this
WooCommerce8.0Only if you use it. Tested through 11.0
HTTPSRequiredThe webhook and the return URL both need it
A Wajub accountRequiredSandbox keys are enough to start

The plugin declares itself compatible with both WooCommerce features that break older gateways, High Performance Order Storage and the Blocks cart and checkout, so you do not have to stay on the legacy order tables to use it.

Install the plugin

The plugin is distributed as a zip rather than through the WordPress.org directory, so there is no search box install and no automatic update. Get the current build from your Wajub contact.

  1. 1

    Upload the zip

    In WordPress admin, go to Plugins, Add New Plugin, Upload Plugin, pick the zip and install it. Activate when the upload finishes.

  2. 2

    Or install it from the command line

    WP-CLI takes the zip path or URL directly.

  3. 3

    Or drop the folder over SFTP

    Unzip locally and upload the folder to wp-content/plugins/, then activate it from the plugins screen.

WP-CLI covers the same install in one line, and the second command confirms the plugin is active under the right folder name.

WP-CLI
wp plugin install ./wajub.zip --activate

wp plugin list --name=wajub --fields=name,status,version

Activation does three things. It creates three pages, Payment Successful, Payment Failed and Payment Processing, each holding one [wajub_pay_message] shortcode. It creates the wp_wajub_transactions table. It schedules a rewrite rules flush so the REST routes answer immediately.

The three pages are ordinary pages. Edit them, style them, move them into your theme's design. The plugin only needs their ids, which it stores in wajub_success_page_id, wajub_failure_page_id and wajub_callback_page_id.

Connect your Wajub account

Go to Wajub, Settings. The plugin keeps two sets of credentials, sandbox and live, and a switch that decides which set every request uses.

OptionHoldsUsed when
wajub_test_modeSandbox switch, on by defaultAlways, it picks the pair below
wajub_secret_key_testsk_test. keyTest mode is on
wajub_webhook_secret_testwhsec_test_ secretTest mode is on
wajub_secret_keysk. keyTest mode is off
wajub_webhook_secretwhsec_ secretTest mode is off
wajub_debug_logDebug logging switchWrites to the WooCommerce logger, or error_log

A Wajub secret key is a prefix, a dot, then 96 characters. Live keys start with sk., sandbox keys with sk_test.. Copy them from the dashboard. The plugin never asks for a publishable key: the browser gets a session token created by your server, never a key.

Nothing reads an environment variable on its own, but WordPress gives you the hook for it. The core pre_option_ filter short circuits any option read, so you can keep secrets out of the database entirely.

Keys from the environment, in a must-use plugin
<?php
// wp-content/mu-plugins/wajub-keys.php

foreach ([
    'wajub_secret_key' => 'WAJUB_SECRET_KEY',
    'wajub_secret_key_test' => 'WAJUB_SECRET_KEY_TEST',
    'wajub_webhook_secret' => 'WAJUB_WEBHOOK_SECRET',
    'wajub_webhook_secret_test' => 'WAJUB_WEBHOOK_SECRET_TEST',
] as $option => $env) {
    add_filter("pre_option_{$option}", static function () use ($env) {
        $value = getenv($env);

        return $value === false || $value === '' ? false : $value;
    });
}

Returning false lets WordPress read the stored option as usual, so the fields in the admin still work on a machine where the variables are not set.

The dashboard at Wajub calls GET /payments once per page load to show whether the keys work. Connected means the key is valid, Error means the API refused it, Not configured means the field is empty.

Register the webhook

The webhook is not optional. It is the only thing that confirms a payment when the customer closes the tab, loses signal after approving on their phone, or pays through a channel that settles minutes later. Every integration in this plugin relies on it.

Copy the URL from Wajub, Settings, or build it yourself.

WhatValue
URLhttps://your-site.com/wp-json/wajub/v1/webhook
MethodPOST
Signature headerX-Wajub-Signature: v1=<hmac>
Timestamp headerX-Wajub-Timestamp
Signed payloadThe timestamp, a dot, then the raw body
AlgorithmHMAC SHA-256 with your webhook secret
Tolerance300 seconds

Paste it in the Wajub dashboard under webhook endpoints, copy the signing secret it gives you back into the matching field in Wajub, Settings, and save.

You can prove the endpoint works without leaving your terminal. Sign a payload the same way Wajub does and post it.

Send a signed test event
SECRET='whsec_test_your_secret_here'
BODY='{"id":"evt_local_test","event":"payment.succeeded","data":{"reference":"wc_42_1757000000","status":"succeeded","amount":25000,"currency":"XAF","metadata":{"source":"woocommerce","source_id":"42"}}}'
TS=$(date +%s)

SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d" " -f1)

curl -i -X POST https://your-site.com/wp-json/wajub/v1/webhook \
  -H "Content-Type: application/json" \
  -H "X-Wajub-Timestamp: $TS" \
  -H "X-Wajub-Signature: v1=$SIG" \
  -d "$BODY"

A valid signature returns {"received":true}. A bad one returns 403. Replaying the same id returns {"received":true} without doing the work twice, because the handler stores every event id it has seen for a week.

Two details worth knowing. The handler accepts both type and event as the event name key, which matters because the Wajub wire format uses event. And it routes work by metadata.source and metadata.source_id, the two fields every integration in this plugin writes when it creates the payment. That pair is what tells the webhook which order, donation or entry to complete.

WooCommerce

WooCommerce is the deepest integration. It gets three checkout modes, Blocks support, refunds in both directions, dispute notices on the order, and a retry path that rebuilds the cart.

Enable the gateway

Go to WooCommerce, Settings, Payments and enable Wajub. The gateway hides itself from the checkout until a secret key is saved, so if it does not appear there, the keys are missing.

Pick a checkout mode

ModeWhat the SDK callsWhere the customer pays
redirectauthorization_urlOn the Wajub hosted checkout, then back to your thank you page
inlinewajub.mount()On your checkout page, in an embedded frame
overlaywajub.open()On your checkout page, in a modal

Redirect is the default and the one to start with. It has the fewest moving parts and it is the only mode that still works with JavaScript disabled.

Inline and overlay load Wajub.js from https://js.wajub.com and keep the customer on the checkout page. Two extra settings apply to them: a locale, and a theme of system, light or dark.

The mode labels in the settings screen are stale

The dropdown still says the inline and overlay modes embed on the order pay page. Since 1.2.0 they embed on the checkout page itself and the customer never navigates. The help text under the field is the accurate one.

How inline and overlay stay on the page

Classic checkout and Blocks checkout take different routes to the same result, and both fall back to a working page if the JavaScript never runs.

On classic checkout, WooCommerce fires checkout_place_order_success on the checkout form with the AJAX result, just before it reads result.redirect and navigates. The plugin listens, rewrites that redirect to a same document fragment, and mounts the payment widget in place.

On Blocks checkout, onCheckoutSuccess observers are awaited before WooCommerce decides where to go, so the plugin returns a promise that stays pending while the customer pays and resolves with the real destination.

If either script fails to load, WooCommerce follows the original redirect to the order pay page, where the same widget is rendered server side. The customer still pays, they just take one navigation to get there.

While the widget is open, the page polls wajub/v1/embed-status every three seconds for up to ten minutes. That endpoint asks the API for the real payment status, completes the order when it reads a success, and hands back a retry URL when it reads a failure.

Refunds

Refunds work in both directions.

A refund started in WooCommerce calls POST /refunds and marks the Wajub refund id on the order so the confirming webhook does not record it twice. A refund started in the Wajub dashboard arrives as a refund.succeeded webhook and creates the matching WooCommerce refund with refund_payment set to false, because the money has already moved.

The guard that makes partial refunds safe is the _wajub_refund_<id> order meta. A webhook whose refund id is already marked is skipped, and an amount larger than what is still refundable is rejected rather than clamped.

The order meta the plugin writes

Meta keyHolds
_wajub_referenceYour reference, wc_<order id>_<timestamp>
_wajub_payment_idThe Wajub payment id, trx_…
_wajub_session_idThe authorization token used by inline and overlay
_wajub_modeThe mode the order was created with
_wajub_dispute_statusThe last dispute event seen for this order
_wajub_refund_<id>Marks one Wajub refund as already recorded

Read them with $order->get_meta() in your own code. The reference is the one to log, because it is what appears in the Wajub dashboard and in every webhook.

Disputes

A dispute.created, dispute.won or dispute.lost event finds the order by reference or payment id, writes _wajub_dispute_status, adds an order note with the reason and amount, and emails the site admin with the response deadline. Intermediate dispute events are recorded on the order without an email.

Retry after a failure

When a payment fails or the confirmation times out, the customer gets a retry link. Following it cancels the pending order, refills the cart with the same products and variations, and sends them back to checkout with a notice explaining what happened. The link carries a nonce, so it cannot be shared or replayed.

Donations with GiveWP

Both generations of GiveWP are covered, and the plugin picks the right one at load time.

GiveWP 3 and up register through givewp_register_payment_gateway under the id wajub-givewp. Enable it in Donations, Settings, Payment Gateways. Donors are redirected to the hosted checkout and come back through a secure gateway route that verifies the donation, sets the donation status and sends them to your success page.

GiveWP below 2.18 uses the older give_payment_gateways filter and a give-listener callback. Nothing to configure differently.

Wajub statuses map onto Give statuses rather than collapsing into a single failure:

Wajub statusGiveWP donation status
succeededComplete
canceled, cancelled, expiredCancelled
abandonedAbandoned
failed, rejectedFailed
refunded, partially-refundedRefunded

Refunding a donation from the GiveWP admin calls the Wajub API. Recurring donations are not supported.

GiveWP is redirect only

The donation form redirects to the hosted checkout. If you want an inline or overlay donation experience, build the page with the [wajub_pay type="donation"] shortcode instead, which supports all three modes.

Donations with Charitable

The gateway extends Charitable_Gateway and registers itself automatically. It reads the keys from Wajub, Settings, so the Charitable gateway settings screen has nothing to fill in.

Donors are redirected to the hosted checkout. On return, the plugin verifies the payment against the API, checks that the stored reference and the source_id both match the donation, and only then marks it complete and sends the donor to the receipt page. Refunds from the Charitable admin call POST /refunds once and mark the donation so a second attempt is a no-op.

Easy Digital Downloads

Wajub appears at the EDD checkout as Wajub (Mobile Money, Card). There is no card form to render, so the gateway suppresses it.

The flow creates a pending EDD payment first, then the Wajub payment, then redirects. On return the plugin verifies against the API and either publishes the payment with a note carrying the reference, or marks it failed with the reason.

Refunds are driven by EDD itself. Moving a payment to refunded or partially_refunded calls the Wajub API once, and a _wajub_refund_api_done meta flag stops a second call.

Courses with Tutor LMS, LifterLMS and LearnDash

The three LMS plugins get three different levels of integration, because they expose three different amounts of surface.

Tutor LMS gets a native gateway. It plugs into Tutor Ecommerce through tutor_payment_gateways, so Wajub appears in the normal Tutor checkout and enrollment happens through Tutor's own order flow. Refunds issued from Tutor call the API. If Tutor Ecommerce is switched off, a [wajub_pay] button appears on the course page instead.

LifterLMS gets a real gateway too, extending LLMS_Payment_Gateway and registered through lifterlms_payment_gateways. A paid order redirects to the hosted checkout, and the confirming webhook records the transaction on the LifterLMS order and enrolls the student.

LearnDash has no order model this plugin can drive, so it gets a payment button appended to the course payment buttons, and access is granted on the wajub_payment_complete hook. Nothing is refundable from WordPress here, so refund from the Wajub dashboard instead.

Memberships with MemberPress

The gateway registers through mepr-gateway-paths and handles one-time membership payments. A purchase redirects to the hosted checkout, and the return callback verifies the payment, completes the MemberPress transaction and sends the receipt notice.

Recurring billing is not implemented. A membership with a subscription price will not be charged again automatically.

Gravity Forms

The add-on builds on Gravity Forms' payment add-on framework, so it behaves like every other payment feed you have configured: create a feed on the form, map the amount and the email, and Wajub handles the rest.

Submissions redirect to the hosted checkout. The return callback verifies the payment against the API, checks the stored reference and the source_id against the entry, then marks the entry Paid and fires gform_post_payment_status so your own feeds and notifications run.

Refunding from the Gravity Forms entry calls the Wajub API once, guarded by a wajub_refund_api_done entry meta.

Any other plugin, through shortcodes

Three shortcodes cover every plugin without a gateway, and every custom page you build yourself. They all go through one public REST route that creates the payment server side, so no key ever reaches the browser.

A payment button

[wajub_pay] renders a small form asking for an email and an optional name, then starts the payment in the mode you chose.

amountnumberoptional
Amount in the major unit. 25000 XAF is twenty five thousand francs.
currencystringoptionaldefault : XAF
Three letter code. Anything else falls back to XAF.
moderedirect | inline | overlayoptionaldefault : redirect
Anything else falls back to redirect.
typepayment | donationoptionaldefault : payment
A donation renders an amount field the donor fills in.
descriptionstringoptional
Shown on the checkout and stored on the payment. Trimmed to 500 characters.
product_idnumberoptional
A WooCommerce product id. Its price and name override amount and description.
content_idstringoptional
Marks the payment as unlocking a piece of content.
button_textstringoptionaldefault : Pay with Wajub
The submit button label.
classstringoptional
One extra CSS class on the wrapper.
Three ways to place the button
[wajub_pay amount="25000" currency="XAF" description="Consultation" mode="redirect"]

[wajub_pay product_id="482" mode="overlay" button_text="Buy now"]

[wajub_pay type="donation" currency="XAF" mode="inline" button_text="Support us"]

Locked content

[wajub_pay_content] wraps content that only appears once the reader has paid. It renders the same form until then, defaulting to the overlay mode so the page does not move under them.

A paywalled section
[wajub_pay_content content_id="masterclass-2026" amount="15000" currency="XAF"]
The full recording, the slides, and the worksheet live here.
[/wajub_pay_content]

Access is remembered two ways: on the user account when the buyer is logged in, and on a transient keyed by the content id and the buyer's email for thirty days when they are not. A logged out buyer who changes browser will have to pay again, so this is the right tool for a digital download or a single article, not for a membership. MemberPress or LifterLMS handle that properly.

The result message

[wajub_pay_message] renders the success, failure or processing message. It is what the three pages created at activation contain, and it prints nothing at all unless the URL carries a reference parameter, so it stays invisible on a direct visit.

Shortcodes inside a theme template

Outside the editor, run them through do_shortcode().

In a template file
<?php
$amount = (float) get_post_meta(get_the_ID(), 'ticket_price', true);

if ($amount > 0) {
    echo do_shortcode(sprintf(
        '[wajub_pay amount="%s" currency="XAF" description="%s" mode="overlay"]',
        esc_attr((string) $amount),
        esc_attr(get_the_title())
    ));
}

Build your own integration

Everything above is built on the same three pieces you can use directly: an API client, an HMAC callback token, and a set of hooks fired from the webhook handler.

The hooks

HookArgumentsFires when
wajub_payment_complete$reference, $data, $source, $sourceIdA payment succeeded
wajub_payment_failed$reference, $data, $source, $sourceIdA payment failed, was cancelled, abandoned, rejected or expired
wajub_refund_complete$paymentRef, $data, 'woocommerce', $orderIdA refund succeeded on a WooCommerce order
wajub_dispute_received$eventType, $dataAny dispute event
wajub_webhook_received$eventType, $dataEvery verified event, after routing
wajub_webhook_missing_secret$rawPayloadAn event arrived with no secret configured
wajub_webhook_invalid_signature$rawPayloadAn event failed signature or timestamp checks
wajub_default_phone_country_code'237'A nine digit phone number needs a country code

The last one is a filter. The rest are actions. $source and $sourceId come straight from the metadata you set when creating the payment, which is what makes your own source name work.

Fulfil your own record

Set your own metadata.source when you create the payment, then act on it. The hook runs inside the webhook, which is the only place a payment is confirmed rather than assumed.

Marking your own booking as paid
<?php

add_action('wajub_payment_complete', static function (
    string $reference,
    array $data,
    string $source,
    $sourceId
): void {
    if ($source !== 'bookings' || empty($sourceId)) {
        return;
    }

    $booking = get_post((int) $sourceId);
    if (! $booking || $booking->post_type !== 'booking') {
        return;
    }

    update_post_meta($booking->ID, 'payment_status', 'paid');
    update_post_meta($booking->ID, 'wajub_reference', $reference);
    update_post_meta($booking->ID, 'paid_amount', (float) ($data['amount'] ?? 0));

    do_action('bookings_confirmed', $booking->ID);
}, 10, 4);

Create a payment from your own code

Client::getInstance() gives you a configured client using whichever key set the test mode switch selects. Amounts are in the major unit, and the client rounds them for zero decimal currencies on its own.

Starting a payment and redirecting
<?php

use WajubPay\API\Client;
use WajubPay\Security\CallbackToken;

function bookings_start_payment(int $bookingId, float $amount, string $email): string
{
    $callback = add_query_arg([
        'bookings' => 'wajub_return',
        'booking_id' => $bookingId,
        '_wajub_token' => CallbackToken::generate($bookingId),
    ], home_url('/'));

    $result = Client::getInstance()->createPayment([
        'amount' => $amount,
        'currency' => 'XAF',
        'description' => sprintf('Booking #%d', $bookingId),
        'reference' => 'booking_' . $bookingId . '_' . time(),
        'callback' => $callback,
        'customer' => ['email' => $email],
        'metadata' => [
            'source' => 'bookings',
            'source_id' => (string) $bookingId,
        ],
    ]);

    $parsed = Client::parsePaymentResponse($result);
    update_post_meta($bookingId, 'wajub_reference', $parsed['reference']);

    return $parsed['authorization_url'];
}

parsePaymentResponse() flattens the API envelope into id, reference, session_id, authorization_url and status. Send the customer to authorization_url for a redirect checkout, or hand session_id to wajub.mount() for an embedded one.

The callback URL is where the customer lands on the way back. It is a browser redirect, so treat it as a hint about which page to show and never as proof of payment. Verify the token, then verify the payment.

Handling the return safely
<?php

use WajubPay\API\Client;
use WajubPay\Security\CallbackToken;

add_action('init', static function (): void {
    $action = isset($_GET['bookings']) ? sanitize_text_field(wp_unslash($_GET['bookings'])) : '';
    if ($action !== 'wajub_return') {
        return;
    }

    $bookingId = absint($_GET['booking_id'] ?? 0);
    $token = sanitize_text_field(wp_unslash($_GET['_wajub_token'] ?? ''));

    if (! $bookingId || ! CallbackToken::verify($bookingId, $token)) {
        wp_safe_redirect(home_url('/'));
        exit;
    }

    $reference = (string) get_post_meta($bookingId, 'wajub_reference', true);
    $status = 'pending';

    try {
        $payment = Client::getInstance()->retrievePayment($reference);
        $status = (string) (Client::extractTransaction($payment)['status'] ?? 'pending');
    } catch (\Throwable) {
        // The webhook is still the source of truth. Show a waiting page.
    }

    wp_safe_redirect(Client::isSucceededStatus($status)
        ? home_url('/booking-confirmed/')
        : home_url('/booking-pending/'));
    exit;
});

CallbackToken::generate() is an HMAC over the id, optionally salted with a second value such as an order key, signed with your webhook secret. It is what stops a visitor from confirming someone else's booking by editing the id in the URL. Always pass the same second argument to verify() that you passed to generate().

Change the default phone country code

A nine digit phone number with no country code is assumed to be Cameroonian. Change it once for the whole site.

A Senegalese default
<?php

add_filter('wajub_default_phone_country_code', static fn (): string => '221');

What the API client gives you

MethodDoes
createPayment(array $params)POST /payments, returns the raw envelope
retrievePayment(string $reference)GET /payments/{reference}
cancelPayment(string $reference)DELETE /payments/{reference}
createRefund(string $paymentId, ?float $amount, ?string $reason, string $currency)POST /refunds
refundByStoredIds(string $paymentId, string $reference, …)Resolves the trx_ id first, then refunds
testConnection()One cheap GET /payments call, returns a boolean
isConfigured(), isTestMode()Reads the saved settings

Every request carries the raw secret key in Authorization with no bearer prefix, a 30 second timeout, and an Idempotency-Key on writes. It retries twice on a network error, a 429 or a 5xx. Failures throw a typed exception: AuthenticationException, PermissionException, NotFoundException, RateLimitException, InvalidRequestException or ApiConnectionException, all extending ApiException with readonly errorCode, httpStatus and body properties.

The REST routes

Four routes, all under wajub/v1. All of them are publicly reachable by design, and each one carries its own proof.

RouteMethodGuarded by
/webhookPOSTHMAC signature and a 300 second timestamp window
/callbackGETNothing, it only redirects. The status is re-read from the API
/embed-statusGETA nonce tied to the order id, plus the order key
/create-paymentPOSTA WordPress nonce, plus 20 requests per minute per IP

There is also a legacy webhook path, POST /?wajub=webhook, kept for sites configured before the REST route existed. It verifies the same signature. Use the REST route for anything new.

The transactions table

Every webhook writes a row in wp_wajub_transactions, whatever integration it belongs to. It is the one place where a WooCommerce order, a GiveWP donation and a shortcode payment sit side by side. Wajub, Transactions renders it.

ColumnHolds
referenceYour reference, unique
trxrefThe Wajub side reference
amount, currencyAs sent by the API
statusThe last status seen
source, source_idThe metadata pair that routed the event
customer_emailFrom the payment
environmenttest or live, from the switch at the time of writing
metadata, payloadJSON
created_at, updated_atTimestamps

The dashboard totals succeeded volume grouped by currency rather than summing everything into one number, so a site taking both XAF and GHS sees two figures instead of one meaningless one.

Uninstalling the plugin drops this table and deletes every wajub_ option. Deactivating does not. Export anything you need before you delete the plugin.

Go live

  1. 1

    Test the whole path in sandbox

    Place a real order with a sandbox Mobile Money number and watch the order reach a paid state without touching the admin. Test a cancellation too, and a refund.

  2. 2

    Confirm the webhook is firing

    The order should complete even if you close the tab immediately after approving. If it only completes when you return to the site, the webhook is not configured and you are relying on the callback.

  3. 3

    Add the live credentials

    Paste the live secret key and the live webhook secret, and register the same URL as a live webhook endpoint in the dashboard. Live and sandbox endpoints are separate.

  4. 4

    Turn the test switch off

    Uncheck test mode in Wajub, Settings. Confirm the dashboard reads Live and Connected.

  5. 5

    Take one real payment

    A small one, on a real phone, then refund it. It is the only test that exercises the live keys, the live webhook and your fulfilment together.

Account activation and the KYC that unlocks live keys are on Going live.

Troubleshooting

SymptomCauseFix
Wajub is missing from the WooCommerce checkoutNo secret key saved for the current environmentThe gateway hides itself until one is. Fill the field for the environment the switch selects
Orders stay on pending after a successful paymentThe webhook secret field is empty, or the endpoint is not registeredEvery delivery is refused with 403 until the secret matches. Send the signed test request above
Real money moved while in test modeThe sandbox key field was empty and the client fell back to the live keyFill both fields, or clear the live one until you go live
The shortcode form returns an invalid request errorAn expired nonce served from a full page cacheExclude the page from the cache, or shorten the cache lifetime below 24 hours
The Gravity Forms feed does not appearThe plugin folder is not named wajubRename it to wajub and reactivate
Inline checkout redirects to a separate pay pageThe embed script did not loadCheck the console for a blocked request to js.wajub.com. The redirect is the intended fallback
An amount is a hundred times too largeMinor units, out of habitWajub amounts are in the major unit. Send 25000 for twenty five thousand francs, not 2500000
The admin dashboard is slowIt calls the API once per page load to show the connection stateExpected. It is one request, and only on Wajub admin screens
Nothing is loggedDebug logging is offEnable it in settings, or define WP_DEBUG. Output goes to the WooCommerce logger under the wajub source when WooCommerce is active, otherwise to error_log

For anything else, turn on debug logging, reproduce, and send the log with the transaction reference to support. Never send a key.

What did you think of this content?