agentsclimarketplace

Wc payment gateway

Skill Lonsdale201/wp-agent-skills/woocommerce/wc-payment-gateway

Build a secure WooCommerce core payment gateway. Covers `WC_Payment_Gateway` registration/settings, synchronous captured versus authorized/pending outcomes, `payment_complete()` versus status updates, refunds, core support flags, customer-safe errors, logging, signed and idempotent webhooks, callback URLs, tokenization boundaries, and Checkout Block compatibility. Use when creating or auditing a gateway or debugging charged orders, stale carts, duplicate webhooks, or unsafe provider errors.From its SKILL.md

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill wc-payment-gateway

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.4 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it

WooCommerce payment gateway

A provider response is not automatically a paid order. Model captured, authorization-only, asynchronous pending, failed, refunded, and duplicate callback states explicitly.

Register the gateway

add_filter( 'woocommerce_payment_gateways', static function ( array $gateways ): array {
    $gateways[] = MyPlugin\Payment\Gateway::class;
    return $gateways;
} );

Register a class name so WooCommerce controls instantiation.

Minimal gateway class

namespace MyPlugin\Payment;

final class Gateway extends \WC_Payment_Gateway {
    public function __construct() {
        $this->id                 = 'myplugin_gateway';
        $this->method_title       = __( 'My Provider', 'myplugin' );
        $this->method_description = __( 'Accept payments through My Provider.', 'myplugin' );
        $this->has_fields         = false;
        $this->supports           = array( 'products', 'refunds' );

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

        $this->enabled     = $this->get_option( 'enabled', 'no' );
        $this->title       = $this->get_option( 'title', __( 'Card payment', 'myplugin' ) );
        $this->description = $this->get_option( 'description', '' );

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

    public function init_form_fields(): void {
        $this->form_fields = array(
            'enabled' => array(
                'title'   => __( 'Enable', 'myplugin' ),
                'type'    => 'checkbox',
                'label'   => __( 'Enable this payment method', 'myplugin' ),
                'default' => 'no',
            ),
            'title' => array(
                'title'   => __( 'Title', 'myplugin' ),
                'type'    => 'text',
                'default' => __( 'Card payment', 'myplugin' ),
            ),
            'api_key' => array(
                'title' => __( 'API key', 'myplugin' ),
                'type'  => 'password',
            ),
        );
    }

    public function process_payment( $order_id ) {
        $order = wc_get_order( $order_id );
        if ( ! $order instanceof \WC_Order ) {
            return array( 'result' => 'failure' );
        }

        try {
            $payment = $this->provider()->create_payment( array(
                'amount'          => wc_add_number_precision( $order->get_total(), false ),
                'currency'        => $order->get_currency(),
                'idempotency_key' => 'wc-' . $order->get_order_key(),
                'metadata'        => array( 'order_id' => $order->get_id() ),
            ) );
        } catch ( \Throwable $error ) {
            wc_get_logger()->error(
                'Payment provider request failed.',
                array(
                    'source'          => 'myplugin-gateway',
                    'order_id'        => $order->get_id(),
                    'exception_class' => get_class( $error ),
                )
            );
            wc_add_notice( __( 'The payment could not be processed. Please try again.', 'myplugin' ), 'error' );
            return array( 'result' => 'failure' );
        }

        $order->update_meta_data( '_myplugin_provider_payment_id', $payment->id );

        if ( 'captured' === $payment->status ) {
            if ( ! $order->payment_complete( $payment->transaction_id ) ) {
                return array( 'result' => 'failure' );
            }
        } elseif ( 'authorized' === $payment->status ) {
            // Authorization reserves funds; it is not necessarily a capture.
            $order->set_transaction_id( $payment->transaction_id );
            $order->update_status( 'on-hold', __( 'Payment authorized; capture pending.', 'myplugin' ) );
        } elseif ( 'pending' === $payment->status ) {
            $order->update_status( 'on-hold', __( 'Awaiting provider confirmation.', 'myplugin' ) );
        } else {
            $order->update_status( 'failed', __( 'The provider declined the payment.', 'myplugin' ) );
            wc_add_notice( __( 'The payment was declined. Try another payment method.', 'myplugin' ), 'error' );
            return array( 'result' => 'failure' );
        }

        if ( WC()->cart ) {
            WC()->cart->empty_cart();
        }

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

Use WooCommerce decimal helpers according to the provider's minor-unit contract; do not blindly multiply floats by 100. Store only provider identifiers and non-sensitive state on the order.

Payment completion contract

Call $order->payment_complete( $transaction_id ) only at the provider's "money captured/settled enough to fulfill" state. It sets transaction/date paid data, clears order_awaiting_payment, chooses processing/completed, saves, adds a note, and fires woocommerce_payment_complete.

update_status() still runs normal status transition hooks, emails, and stock handlers for the chosen status. What it does not provide is the complete paid-domain contract: transaction assignment, awaiting-payment cleanup, and woocommerce_payment_complete.

Authorization-only must not be treated as capture unless the provider contract explicitly defines it as the fulfillable paid state.

Core support flags

Declare only behavior the gateway actually implements:

FlagRequirement
productsNormal product checkout
refundsWorking process_refund() returning true or WP_Error
tokenizationSafe saved-method selection/storage integration
add_payment_methodWorking My Account add-method flow
default_credit_card_formLegacy core card form support

Do not advertise support solely to make UI appear. Extension-defined flags belong in the extension-specific integration and tests, not a core gateway scaffold.

Refunds

process_refund( $order_id, $amount, $reason ) must validate the order and transaction, use a provider idempotency key, log redacted failures, and return a generic WP_Error to admin. Never put raw provider payloads, API responses, or secrets into order notes.

WooCommerce creates the local refund around the gateway flow; do not independently duplicate refund records unless the caller contract requires it.

Webhooks

A webhook endpoint is public by design and authenticated by provider signature, not by a WordPress nonce. Preferred flow:

  1. Read the raw request body exactly once.
  2. Verify timestamped signature against the raw bytes before JSON decoding.
  3. Reject stale timestamps and malformed event types.
  4. Resolve the order by a previously stored provider payment/reference ID; never trust an arbitrary submitted order ID alone.
  5. Compare currency and amount using the provider's integer/decimal contract.
  6. Atomically claim provider event ID in an owned table with a unique key.
  7. Return 2xx for already-processed valid events.
  8. Apply only allowed state transitions; call payment_complete() only for capture/success.
  9. Log redacted identifiers and add concise private order notes.

Register a namespaced WP REST route with permission_callback => __return_true only because signature verification occurs inside the callback. Alternatively, WooCommerce's callback surface still supports:

$callback_url = WC()->api_request_url( 'myplugin_gateway' );
add_action( 'woocommerce_api_myplugin_gateway', array( $handler, 'handle' ) );

Do not call the callback mechanism deprecated in 10.9.4; it remains used by core. WP REST is usually preferable for explicit methods, schemas, and response handling.

Checkout Block boundary

A PHP WC_Payment_Gateway class supports classic checkout server behavior. Checkout Block additionally needs an AbstractPaymentMethodType PHP adapter and a JavaScript registerPaymentMethod() registration. Client paymentMethodData can reach the existing process_payment() through Woo's legacy Store API bridge, or an advanced integration can own processing through PaymentContext/PaymentResult. Store API payment requirements only filter eligibility. A gateway appearing in classic checkout does not prove Block compatibility; use wc-checkout-block-payment-method for the complete implementation and event lifecycle.

Security rules

  • Never show $error->getMessage() or a provider response to customers.
  • Never log API keys, signatures, PAN, CVV, full request bodies, or bearer tokens.
  • Use provider idempotency keys for charge, capture, refund, and webhook events.
  • Verify webhook signature, amount, currency, provider reference, order state, and event uniqueness.
  • Use generic customer errors and detailed but redacted server logs.
  • Keep provider API calls out of constructors and availability checks.
  • Test synchronous, pending, authorized, captured, duplicate, out-of-order, timeout, refund, and replay paths.

Cross-references

  • wc-order-lifecycle-and-items for status and stock side effects.
  • wc-payment-tokens for saved methods.
  • wc-checkout-block-payment-method for PHP/JavaScript Checkout Block registration and payment processing.
  • wc-store-api for shopper checkout identity and payment requirements.
  • wc-hpos-compatibility for order storage.

References

  • Gateway contract: includes/abstracts/abstract-wc-payment-gateway.php.
  • Registration: includes/class-wc-payment-gateways.php.
  • Paid lifecycle: includes/class-wc-order.php.
  • Callback URL builder: includes/class-woocommerce.php.
  • Official documentation: https://woocommerce.com/document/payment-gateway-api/
  • Verified source paths:
    • wp-content/plugins/woocommerce/includes/abstracts/abstract-wc-settings-api.php
    • wp-content/plugins/woocommerce/src/Enums/PaymentGatewayFeature.php

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.