Xgenious/ docs

Payment Flow & Testing

Payment Flow

1. Gateway Registration (boot time)

Plugin registers via $this->ctx->registry->register('gateway', ...) with:

  • id — Gateway identifier used in checkout forms
  • label — Display name
  • ipn_route — Named route for webhook
  • settings_prefix — Prefix for static_options keys
  • enabled_option — Option key that enables/disables gateway

2. Checkout Selection (user-facing)

Core's PaymentGatewayList builds the gateway list from built-in + plugin-declared gateways. Each gateway appears only if get_static_option('{prefix}_gateway') is non-empty.

3. Payment Initiation (core side)

When user selects a plugin gateway:

  1. Core validates gateway is in allowed list
  2. Creates pending record (WalletHistory/Order/Subscription)
  3. Builds payment_args array with amount, track ID, IPN URL, etc.
  4. Fires payment.charge filter via plugin_charge_payment()
  5. Plugin returns a redirect Response or null

4. Charge Handling (plugin side)

Plugin hooks into payment.charge:

  • Checks if the gateway matches its ID
  • Builds provider-specific payment URL
  • Returns redirect Response to hosted payment page

5. IPN/Callback (provider calls back)

Provider POSTs to your IPN endpoint. Plugin:

  1. Verifies authenticity (HMAC signature, API key)
  2. Parses provider data
  3. Calls plugin_apply_filters('payment.complete', ...) to delegate to core

6. Core Completion

PaymentCompletionService::complete() handles:

Payment TypeModelAction
client-walletWalletHistoryCredit wallet, fire wallet.deposit.completed
orderOrderMark paid, fire order.paid
subscriptionUserSubscriptionActivate, fire subscription.activated
promotionPlugin modelDelegates to payment.complete.promotion filter

Core Bridge Functions

FunctionPurpose
plugin_gateways()Get all registered gateways
plugin_gateway_declared($id)Check if gateway is registered
plugin_gateway_ipn_route($id)Get IPN route name
plugin_charge_payment($gateway, $args)Fire payment.charge filter
plugin_apply_filters($hook, $value, ...)Fire any filter
plugin_do_action($hook, ...$args)Fire any action
get_static_option($key, $default)Read setting
update_static_option($key, $value)Write setting

Architectural Invariants

  1. Core never references plugins by name — all wiring via registry and filters
  2. IPN endpoints are public — no CSRF, no session (use routes/api.php)
  3. Core owns completion — plugins verify authenticity, core handles business logic
  4. Completion is idempotent — duplicate webhooks are safe (checks payment_status != 'complete')
  5. Gateway presence depends solely on {prefix}_gateway static option
  6. No per-gateway core edits — payment.charge fallback handles all plugin gateways
  7. Settings live in static_options — no new tables required for gateway config
  8. payment_type context tells core what to complete: client-wallet, order, subscription, promotion

Testing

Manual Testing

  1. Enable gateway in admin settings
  2. Set test mode and test API keys
  3. Create a wallet deposit or order
  4. Select gateway at checkout
  5. Complete payment on provider's sandbox
  6. Verify IPN received and wallet/order updated

Webhook Testing

Use tools like ngrok to expose local IPN endpoint:

ngrok http 80

Configure provider to send webhooks to the ngrok URL.

Reference: Existing Gateway Plugins

PluginLocationPattern
SePayplugins/sepay-payment-gateway/Custom checkout page, QR code, polling
CoinPaymentsplugins/coin-payment-gateway/Uses XgPaymentGateway library
YooMoneyplugins/yoomoney-payment-gateway/Uses XgPaymentGateway library

Last updated: September 2026

Still stuck?
Our support team is ready to help you get set up.
Get support