WP Manifestindependent plugin directory
manifest / ecommerce / woocommerce-payment-gateway

SimulatedPay Gateway for WooCommerce

A production-style WooCommerce payment gateway featuring secure API integration, signed webhooks, duplicate-payment prevention and automated testing.

by Aderonmu Akintunde Emmanuel · github.com/emmanuel0635/woocommerce-payment-gateway · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/emmanuel0635/woocommerce-payment-gateway/archive/refs/heads/main.zip

A portfolio-grade WooCommerce redirect payment plugin that connects a store to a bundled simulated payment provider. It demonstrates the same major boundaries used by a real provider integration: payment creation over HTTP, hosted checkout redirection, signed server-to-server webhooks, order reconciliation, replay protection, and structured operational logs.

What the project demonstrates

  • PHP 8.1+ and object-oriented PHP
  • WooCommerce WC_Payment_Gateway
  • WordPress actions, filters, REST routes, Admin Post handlers, sanitisation, and escaping
  • redirect payment flow
  • sandbox and live configuration
  • provider API client with timeouts and safe error handling
  • HMAC-SHA256 webhook signatures with timestamp replay protection
  • event idempotency using a unique database record
  • duplicate paid-transaction blocking
  • amount, currency, reference, payment-ID, and order validation
  • WooCommerce order status updates and payment_complete()
  • structured WooCommerce logging with redaction
  • classic checkout and Checkout Block registration
  • PHPUnit tests and a PHP-version GitHub Actions matrix
  • Docker-based local WordPress environment

Architecture

Customer
   |
   | 1. Places WooCommerce order
   v
WooCommerce Gateway
   |
   | 2. POST /payments + Idempotency-Key
   v
Simulated Provider API
   |
   | 3. Returns payment_id + hosted payment_url
   v
Hosted Sandbox Checkout
   |
   | 4. Customer chooses success / delayed / failed
   v
Simulated Provider
   |
   | 5. Signed webhook: t=timestamp,v1=HMAC
   v
WooCommerce Webhook Handler
   |
   | 6. Verify signature, acquire event lease, reconcile exact values
   v
WooCommerce Order

The browser redirect never marks an order paid. Only a verified webhook may complete payment.

Repository structure

.
├── assets/js/blocks.js
├── src/
│   ├── ApiClient.php
│   ├── BlocksSupport.php
│   ├── Config.php
│   ├── EventStoreInterface.php
│   ├── Gateway.php
│   ├── Logger.php
│   ├── MockProvider.php
│   ├── PaymentStatus.php
│   ├── PaymentValidator.php
│   ├── Plugin.php
│   ├── Reference.php
│   ├── Signature.php
│   ├── WebhookEventStore.php
│   └── WebhookHandler.php
├── tests/
├── .github/workflows/tests.yml
├── composer.json
├── docker-compose.yml
└── woocommerce-payment-gateway.php

Local setup with Docker

Requirements

  • Docker Desktop
  • Git
  • Composer for running tests locally

Start WordPress

cp .env.example .env
docker compose up -d

Open http://localhost:8080, finish WordPress installation, then:

The container sets WSP_INTERNAL_BASE_URL to http://127.0.0.1 for server-to-server calls. Do not define that constant on a normal hosted installation unless you understand the network routing.

  1. Install and activate WooCommerce.
  2. Activate SimulatedPay Gateway for WooCommerce.
  3. Go to WooCommerce → Settings → Payments → SimulatedPay.
  4. Enable the gateway.
  5. Keep mode set to Sandbox.
  6. Leave the sandbox base URL blank.
  7. Open the gateway settings and keep the automatically generated sandbox credentials.
  8. Create a product and place a test order.

The bundled provider offers three buttons:

  • successful payment;
  • delayed/pending payment;
  • failed payment.

Live-mode contract

A real provider used with this plugin must accept:

POST {base_url}/payments
Authorization: Bearer YOUR_API_KEY
Idempotency-Key: order-123-...
Content-Type: application/json

Example request:

{
  "order_id": 123,
  "merchant_reference": "wsp_123_opaque-digest",
  "amount": "25000.00",
  "currency": "NGN",
  "callback_url": "https://store.example/checkout/order-received/...",
  "webhook_url": "https://store.example/wc-api/wsp_webhook/",
  "description": "Payment for order 123"
}

Expected response:

{
  "payment_id": "pay_abc123",
  "payment_url": "https://provider.example/pay/pay_abc123"
}

Webhook contract

Webhook URL:

https://your-store.example/wc-api/wsp_webhook/

Header:

X-WSP-Signature: t=1710000000,v1=hex_hmac_sha256

The signed value is:

timestamp + "." + raw_request_body

Example event:

{
  "event_id": "evt_abc123",
  "type": "payment.status_changed",
  "payment_id": "pay_abc123",
  "transaction_id": "txn_abc123",
  "order_id": 123,
  "merchant_reference": "wsp_123_opaque-digest",
  "amount": "25000.00",
  "currency": "NGN",
  "status": "paid",
  "occurred_at": "2026-07-15T09:00:00Z"
}

Supported statuses are paid, pending, failed, and cancelled.

Duplicate-payment and replay protection

The project uses two separate protections:

  1. Duplicate event protection: every event_id is inserted into {prefix}wsp_webhook_events, where it is the primary key. Completed events are ignored, while a previously failed or abandoned processing lease can be retried safely.
  2. Duplicate payment protection: if an order is already paid, a repeated event with the same transaction ID is ignored, while a different paid transaction ID is blocked and recorded in the order notes and logs.

Payment creation also sends an Idempotency-Key, and the bundled provider reuses the original payment session for a repeated key.

Order status behaviour

Provider status WooCommerce action
paid Calls $order->payment_complete($transactionId)
pending Changes an unpaid order to on-hold
failed Changes an unpaid order to failed
cancelled Changes an unpaid order to cancelled

Paid orders are never downgraded by a later failed, pending, or cancelled event.

Logging

Logs are written through wc_get_logger() with source wsp-gateway.

View them under:

WooCommerce → Status → Logs

Secret-like fields are redacted. Full webhook bodies and customer details are not logged.

Tests

Install development dependencies and run:

composer install
composer test

The unit suite covers:

  • valid signatures;
  • tampered payloads;
  • wrong secrets;
  • expired signatures;
  • malformed signature headers;
  • supported and unsupported payment statuses;
  • amount, currency, reference, and payment-ID reconciliation;
  • deterministic opaque merchant references.

The GitHub Actions workflow runs syntax checks and PHPUnit on PHP 8.1, 8.2, 8.3, and 8.4. The Docker configuration defines an internal loopback URL so the bundled provider can call WordPress reliably while the browser continues using localhost:8080.

Roadmap

  • add WordPress/WooCommerce integration tests using the official test environment;
  • add refunds and partial refunds;
  • add webhook key rotation with multiple active secrets;
  • implement asynchronous webhook retry delivery in the simulated provider;
  • add an admin payment timeline metabox;
  • add metrics for API latency, webhook latency, and payment-conversion rate;
  • add end-to-end browser tests using Playwright;
  • add screenshots and a short demonstration video;
  • deploy a public demo store containing only fake products and test data.

Security boundaries

  • Sandbox API and webhook credentials are generated during activation rather than shipped as public defaults.
  • The bundled provider accepts callback and webhook URLs only on the exact WordPress site origin; the webhook path must match the plugin endpoint.
  • External provider requests use WordPress safe HTTP APIs. The Docker-only internal URL is used only when the site owner explicitly defines WSP_INTERNAL_BASE_URL.
  • Payment amounts are normalized as exact decimal strings and never compared with binary floating-point arithmetic.
  • Webhook records use processing states so temporary failures can be retried without processing completed events twice.
  • No card details are collected or stored.

Security limitations of the bundled provider

The bundled provider exists for demonstrations and automated/manual testing. It is not a PCI-compliant processor, does not collect card details, and must never be used for real money. Live mode is intended to point to a separate HTTPS payment service.

License

GPL-2.0-or-later.