Povito Checkout for WooCommerce#
The plugin turns Povito Checkout into a WooCommerce payment method: ZainCash, FIB, Qi Card, FastPay, cards and cash on delivery, on Povito's hosted page, with the shopper returned to your store.
Requires WordPress 6.3+, WooCommerce 8.0+, PHP 8.1+. Works with the classic shortcode checkout and the Cart/Checkout blocks, and with High-Performance Order Storage. It is built on the PHP SDK, which it bundles.
How it confirms a payment#
- The shopper picks Povito Checkout and places the order.
- The plugin creates a checkout session and redirects them to the hosted page.
- They pay.
- Povito sends a signed webhook, and the shopper lands back on your order-received page.
- Either way the plugin reads the session back from the API, and only then marks the order paid.
Neither the webhook nor the return URL marks an order paid
A webhook is a prompt to GET /checkout/sessions/{id}. Landing on the return
URL proves only that a browser followed a redirect. The plugin marks an order
paid on payment_status = paid and a captured amount and currency equal
to the order's; anything else puts the order on hold with a note.
If the webhook never arrives — a firewall in front of the site, a queue backing up — the thank-you page reads the session itself, and a scheduled job reads it again at 5 minutes, 30 minutes and 2 hours. An order is not lost because a delivery was.
Install#
Upload the zip at Plugins → Add New → Upload Plugin, then activate.
Building from the source in the Povito-Checkout monorepo instead:
cd plugins/woocommerce/povito-checkout
./bin/build-zip.sh # → build/povito-checkout-0.1.0.zipThe script runs composer install --no-dev --optimize-autoloader first — that
step is what puts the SDK and its autoloader into the package. Without it the
plugin refuses to load and says so in an admin notice.
Configure#
WooCommerce → Settings → Payments → Povito Checkout.
| Setting | What it is for |
|---|---|
| Enable/Disable | Offer the gateway at checkout |
| Test mode | Use the test key and the simulated gateway |
| Title / Description (English, Arabic, Kurdish) | What the shopper reads. A language left empty falls back to English; the page follows the site's locale |
| Test / Live secret key | povito_ck_test_… and povito_ck_live_… |
| Test / Live webhook secret | whsec_…, shown once when you create the endpoint |
| Methods to offer | Empty means every method enabled on your account. With a key configured, the list is fetched from GET /payment_methods, so you pick from what exists |
| Order status after payment | Or leave it to WooCommerce |
| Debug log | WooCommerce → Status → Logs, source povito-checkout |
Keys are per mode. The plugin uses the test pair when test mode is on and the live pair when it is off, and never mixes them — see keys and environments.
The webhook endpoint#
Register this URL in the Povito portal, once for test and once for live:
https://your-store.example/wp-json/povito-checkout/v1/webhookThe settings screen prints the exact URL for your site. Copy the signing secret the portal gives you into the matching field; it is shown once.
The route is open to the world by design: the Povito-Signature header is the
authentication. It is verified against the raw request bytes with a
constant-time compare and a five-minute tolerance before anything else happens,
and an unsigned or stale request gets 401 without the body being read.
A blocked REST API is survivable
If your host blocks /wp-json/, or you are developing behind NAT, orders are
still confirmed on the return page and by the scheduled check. The webhook
makes confirmation instant, not possible.
Test with a povito_ck_test_ key#
- Turn Test mode on and paste the test secret key.
- Create a test webhook endpoint for the URL above; paste its secret into Test webhook secret.
- Buy something in your own shop. On the hosted page use the test phone
+9647500000000with OTP000000, then press Succeed. - The order should be paid before you finish reading the thank-you page, with two notes on it: the session created, and the payment confirmed.
Worth trying while you are there, because these are the paths that go wrong in production:
| Do this | Expect |
|---|---|
| Press Fail on the hosted page | Order stays unpaid with a note; the shopper is told they can pay again |
| Press Cancel | The same, from the other direction |
| Close the tab after paying | The webhook marks the order paid without the browser |
| Put rubbish in the webhook secret and pay | The delivery is rejected 401, and the thank-you page still confirms the order |
| Refund from the order screen | A refund is created; cash on delivery says it cannot be refunded |
An admin notice sits on your WooCommerce screens for as long as test mode is on, so nobody ships an order believing they were paid. More on the simulated gateway in testing.
Go live#
- Finish verification in the portal and issue a live secret key.
- Create a live webhook endpoint for the same URL; paste its secret into Live webhook secret.
- Add your store's domain to the merchant's allowed return hosts — an
unregistered
success_urlis refused at create time with422 return_host_not_allowed. - Turn Test mode off.
- Buy something real and cheap, then refund it.
Money and line items#
Amounts are integers in the currency's minor unit, and IQD has no minor
unit: 45,000 dinars is 45000. The plugin converts the order total with the
SDK's Money helper, so a store in IQD and a store in USD both come out right.
The breakdown sent to the hosted page matches what WooCommerce showed the
shopper: products at their pre-discount subtotal, then shipping, then fees,
then the coupon as one discount line. The API requires that these reconcile
to the total. Where per-line rounding leaves a dinar over — three items at
3,333.33 in a currency with no fractions — the remainder becomes an explicit
Rounding line rather than a failed checkout. If the breakdown is off by more
than rounding could explain, the plugin sends a single line for the order total
instead of guessing.
Refunds#
Refund from the WooCommerce order screen as usual and the plugin calls the API.
- Cash on delivery, and any rail without a refund API, answer
422 method_not_refundable— refund those by other means. - An accepted refund comes back
requestedorprocessingand settles asynchronously. WooCommerce records it straight away; the order gets a note when Povito confirms it. - You cannot refund more than was captured.
The rules are the same as everywhere else: refunds.
Order metadata#
Stored on every order the plugin touches, and shown on the order screen:
| Key | What |
|---|---|
_povito_session_id |
The checkout session |
_povito_attempt_id |
The payment attempt that succeeded |
_povito_method / _povito_gateway |
How they paid |
_povito_gateway_reference |
The vendor's own reference; also the WooCommerce transaction id |
_povito_captured_amount |
Minor units, the ceiling for refunds |
_povito_mode |
live or test |
Uninstalling removes the settings, the caches and the scheduled job. It leaves this metadata alone: it is the record of how an order was paid, and a shop that loses it cannot answer a chargeback.
Hooks#
| Hook | Use |
|---|---|
povito_checkout_session_params (filter) |
Last word on the body sent to the API |
povito_checkout_api_base_url (filter) |
Point at another environment |
povito_checkout_api_timeout (filter) |
Seconds per attempt, default 15 |
povito_checkout_icon (filter) |
The icon shown at checkout |
add_filter( 'povito_checkout_session_params', function ( array $params, WC_Order $order ): array {
$params['metadata']['crm_id'] = (string) $order->get_meta( '_crm_id' );
return $params;
}, 10, 2 );Languages#
Text domain povito-checkout, with Arabic (ar) and Kurdish Sorani (ckb)
catalogues. WordPress calls Sorani ckb; the plugin routes both ckb and ku
to Povito's Kurdish hosted page. The gateway's own title and description are
settings rather than translations, because that wording is the merchant's.
Troubleshooting#
| Symptom | Cause |
|---|---|
| The gateway is missing at checkout | No secret key for the current mode. The plugin hides itself rather than failing at the last click; an admin notice says so |
| "Povito Checkout is missing its dependencies" | The plugin was copied without vendor/. Run composer install --no-dev, or install the packaged zip |
| Orders confirm only on the thank-you page | The webhook is not reaching the site, or its secret is wrong for this mode. Deliveries and their status codes are listed in the portal |
422 return_host_not_allowed in the log |
The store's domain is not on the merchant's allowed return hosts |
| An order went on hold with an amount note | The captured amount did not equal the order total. Nothing is marked paid until a human looks |
Turn on Debug log and read WooCommerce → Status → Logs, source
povito-checkout. Keys and secrets are masked there, and request and response
bodies are never logged — they carry the shopper's phone number and address.
The error codes are in errors.