GeneratorsWooCommercePayment Gateway

Payment Gateway Generator

A WC_Payment_Gateway subclass — offline (mark on-hold, confirm by hand) or a redirect-to-processor stub with a webhook — using the exact result/redirect contract process_payment() must return.

class-acme-gateway-gateway.php
Saved just now
The gateway
Acme_Gateway_Gateway
Instructions
Show instructions after checkout
Printed on the order-received page and in the order emails, the way Cheque/BACS do it in core.
Marks the order on-hold until you confirm payment by hand — the shape of Cheque/BACS.
<?php
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Plugin Name:       Acme Bank Transfer
 * Description:       Registers the acme_gateway payment gateway.
 * Version:           1.0.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 * Text Domain:       acme
 */

defined( 'ABSPATH' ) || exit;

/**
 * Declare the class once WooCommerce's own gateway base class exists.
 */
function acme_gateway_init() {
	if ( class_exists( 'Acme_Gateway_Gateway' ) ) {
		return;
	}

	class Acme_Gateway_Gateway extends WC_Payment_Gateway {

	public function __construct() {
		$this->id                 = 'acme_gateway';
		$this->icon               = '';
		$this->has_fields         = false;
		$this->method_title       = __( 'Acme Bank Transfer', 'acme' );
		$this->method_description = __( 'Accepts manual bank transfers, confirmed by the store owner.', 'acme' );
		$this->supports           = array( 'products' );

		$this->init_form_fields();
		$this->init_settings();

		$this->title       = $this->get_option( 'title' );
		$this->description = $this->get_option( 'description' );
		$this->enabled     = $this->get_option( 'enabled' );
		$this->instructions = $this->get_option( 'instructions' );
		add_action( 'woocommerce_thankyou_' . $this->id, array( $this, 'thankyou_page' ) );
		add_action( 'woocommerce_email_before_order_table', array( $this, 'email_instructions' ), 10, 3 );

		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
	}

	/**
	 * The settings fields, keyed by option name.
	 */
	public function init_form_fields() {
		$this->form_fields = array(
		'enabled' => array(
			'title'   => __( 'Enable/Disable', 'acme' ),
			'type'    => 'checkbox',
			'label'   => __( 'Enable this payment method', 'acme' ),
			'default' => 'no',
		),

		'title' => array(
			'title'       => __( 'Title', 'acme' ),
			'type'        => 'text',
			'description' => __( 'Shown to the customer during checkout.', 'acme' ),
			'default'     => __( 'Pay by bank transfer', 'acme' ),
			'desc_tip'    => true,
		),

		'description' => array(
			'title'   => __( 'Description', 'acme' ),
			'type'    => 'textarea',
			'default' => __( 'Make your payment directly into our bank account. Your order will ship once funds have cleared.', 'acme' ),
		),

		'instructions' => array(
			'title'       => __( 'Instructions', 'acme' ),
			'type'        => 'textarea',
			'description' => __( 'Shown on the order-received page and in the order emails.', 'acme' ),
			'default'     => __( 'Our bank details are: Acme Ltd, Sort code 00-00-00, Account 00000000. Use your order number as the payment reference.', 'acme' ),
			'desc_tip'    => true,
		),
		);
	}

	/**
	 * Handle the order once the customer confirms payment.
	 *
	 * @param int $order_id Order ID.
	 * @return array
	 */
	public function process_payment( $order_id ) {
		$order = wc_get_order( $order_id );

		$order->update_status( 'on-hold', __( 'Awaiting manual payment confirmation.', 'acme' ) );

		wc_reduce_stock_levels( $order_id );
		WC()->cart->empty_cart();

		return array(
			'result'   => 'success',
			'redirect' => $this->get_return_url( $order ),
		);
	}

	/**
	 * Print the instructions on the order-received page.
	 */
	public function thankyou_page() {
		if ( $this->instructions ) {
			echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) );
		}
	}

	/**
	 * Print the instructions in the order emails, while the order is on-hold.
	 *
	 * @param WC_Order $order         The order.
	 * @param bool     $sent_to_admin Whether this copy goes to the store admin.
	 * @param bool     $plain_text    Whether the email is plain text.
	 */
	public function email_instructions( $order, $sent_to_admin, $plain_text = false ) {
		if ( $this->instructions && ! $sent_to_admin && 'acme_gateway' === $order->get_payment_method() && $order->has_status( 'on-hold' ) ) {
			echo wp_kses_post( wpautop( wptexturize( $this->instructions ) ) ) . PHP_EOL;
		}
	}
	}
}
add_action( 'plugins_loaded', 'acme_gateway_init' );

/**
 * Register it so it appears under WooCommerce → Settings → Payments.
 *
 * @param array $gateways Existing gateways.
 * @return array
 */
function acme_add_gateway( $gateways ) {
	$gateways[] = 'Acme_Gateway_Gateway';

	return $gateways;
}
add_filter( 'woocommerce_payment_gateways', 'acme_add_gateway' );

About this generator

Payment Gateway Generator Online

A WooCommerce payment gateway boilerplate that is actually complete: a WC_Payment_Gateway subclass with its settings fields, a process_payment() that returns the result array WooCommerce requires, and the woocommerce_payment_gateways registration. Choose an offline gateway — bank transfer, pay on collection, invoice — or a redirect gateway that sends the shopper to a processor and comes back through a webhook.

The Reference tab covers the four things that decide whether a gateway works in production: the exact process_payment() return shape, when to use update_status() versus payment_complete(), why stock is reduced with wc_reduce_stock_levels(), and how the woocommerce_api_{class} webhook endpoint is dispatched. Free to use, no account, nothing uploaded.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why this payment gateway boilerplate beats the tutorial you were about to copy

Most gateway tutorials stop at the settings screen. The parts that break in production are further down: a process_payment() that returns the wrong shape leaves the shopper on a spinning Place Order button with no error; a hand-built redirect URL skips get_return_url() and lands them on a page that does not know the order; calling payment_complete() for money that has not arrived marks unpaid orders as paid. This generator writes those parts correctly and names the trade-offs in the code.

A process_payment() that returns the right array

Both modes end with array( 'result' => 'success', 'redirect' => ... ). Offline mode redirects through $this->get_return_url( $order ), which applies the woocommerce_get_return_url filter and honours force-SSL and multisite settings that a hand-built URL will not.

Offline mode does the whole offline dance

It sets the order to on-hold with a note, calls wc_reduce_stock_levels( $order_id ) — the current, non-deprecated way — and empties the cart. Turn on instructions and you also get thankyou_page() and an email_instructions() callback that only prints while the order is still on hold and only to the customer.

Redirect mode ships a real webhook entry point

The constructor registers woocommerce_api_ plus the lowercased class name, which is what WooCommerce dispatches when a request hits ?wc-api=YourClassName. The handler loads the order, returns a 400 if it cannot, and marks the point where you must verify the processor’s signature before trusting the payload.

Settings fields wired to the Settings API

Enable/Disable, Title and Description in both modes; Instructions in offline mode; Test mode plus a password-type API key field in redirect mode. process_admin_options() is bound to woocommerce_update_options_payment_gateways_ plus the gateway id so the screen saves.

Declared safely, registered once

The class is defined inside a plugins_loaded callback behind a class_exists() guard, so the file cannot fatal when WooCommerce is inactive, and the gateway is appended to the woocommerce_payment_gateways array rather than replacing it.

Checks on the fields that end up on every order

A missing gateway id is an error because it becomes the payment_method value stored on every order placed through it. A missing checkout title or admin method title is an error too, and instructions that are enabled but empty are flagged as a warning.

How does the Payment Gateway Generator work?

Four steps. Choose the flavour, name the gateway, fill in the mode-specific detail, then export.

  1. 01

    Choose offline or redirect

    Offline builds a manual-confirmation gateway that puts the order on hold. Redirect builds one that sends the shopper to an external processor and confirms the payment through a webhook.

  2. 02

    Name the gateway

    Set the gateway id, the title the shopper sees next to the radio button at checkout, the method title and description shown in WooCommerce → Settings → Payments, and the checkout description under the title. The class name is derived from your prefix and shown as you type.

  3. 03

    Fill in the mode-specific parts

    Offline: write the payment instructions and decide whether they appear on the order-received page and in emails. Redirect: decide whether test mode is on by default, which adds the sandbox endpoint branch and the API key setting.

  4. 04

    Clear the checks, then export

    Resolve anything flagged, then copy or download the plugin file. Activate it and the gateway appears under WooCommerce → Settings → Payments, disabled until the store owner enables it.

Worked example — the offline process_payment()

The heart of an offline gateway, exactly as the tool emits it. The order goes on hold, stock comes down, the cart is cleared, and the shopper is sent to the order-received page.

public function process_payment( $order_id ) {
	$order = wc_get_order( $order_id );

	$order->update_status( 'on-hold', __( 'Awaiting manual payment confirmation.', 'acme' ) );

	wc_reduce_stock_levels( $order_id );
	WC()->cart->empty_cart();

	return array(
		'result'   => 'success',
		'redirect' => $this->get_return_url( $order ),
	);
}

update_status( 'on-hold' ) is correct here precisely because the money has not arrived yet. payment_complete() is reserved for a gateway that has verified the payment — it also fires woocommerce_payment_complete, which other plugins act on, so calling it for an unconfirmed offline payment triggers fulfilment that should not have started.

WooCommerce payment gateways — frequently asked questions

What developers ask when writing a gateway rather than installing one.

What must process_payment() return?

An array with a result key of success or failure and a redirect key holding the URL to send the shopper to. Anything else — returning nothing, returning true, echoing output — leaves the Place Order button spinning with no error, because the checkout AJAX response cannot be parsed. Build the success URL with $this->get_return_url( $order ) rather than by hand.

Why does my custom payment gateway not appear at checkout?

Work through four things in order: the class must be added to the array returned by the woocommerce_payment_gateways filter; the gateway must be enabled in WooCommerce → Settings → Payments, since the generated default is no; is_available() must return true, which by default requires the gateway to be enabled and the cart total to be payable; and the gateway must support the products in the cart through its $this->supports array.

When should I call payment_complete() instead of update_status()?

Call payment_complete() only when you have confirmed that the money actually arrived — typically inside a verified webhook. It moves the order to processing or completed depending on the products, records the transaction, reduces stock and fires woocommerce_payment_complete, which other extensions listen for. For a payment you are still waiting on, use update_status( 'on-hold', $note ) as the offline mode does.

How do I handle the webhook or IPN callback from a processor?

Register an action on woocommerce_api_ followed by the lowercased class name in the constructor. WooCommerce dispatches it whenever a request hits ?wc-api=YourClassName, before the theme loads. Inside the handler, verify the request against the processor’s signature or a shared secret *before* touching the order — an unverified handler that calls payment_complete() will mark any order paid for anyone who guesses the URL.

Can I use one gateway class in both test and live mode?

Yes, and that is what redirect mode generates: a testmode checkbox setting read into $this->testmode in the constructor, which then selects the sandbox or production endpoint when building the redirect URL. Keep separate API key settings for each environment if your processor issues different credentials, and never log the key.

Does a custom gateway work with the Checkout block?

The PHP class still handles the payment, but the block-based checkout does not render classic gateway markup. A gateway with no fields, like the offline one this generator produces, needs a small block integration registering it as a payment method so it appears in the block UI. A gateway with has_fields set to true needs a JavaScript component for its input fields.

Where do I paste the code from the Payment Gateway generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other WooCommerce generators in the library, plus the WordPress tools most often used alongside this one.