WordPress Payment Implementation

1. Overview

This document describes the technical implementation of the event-payment-plugin-woocommerce plugin, which handles payment processing and order synchronisation with the admin-service.

2. Plugin Architecture

2.1. Trait-Based Composition

The plugin uses a trait-based architecture where EPAWooHandler is the main class that composes multiple traits:

class EPAWooHandler {
    use EPAAdmin;                    // Settings page
    use EPAConnTest;                 // API connectivity validation
    use EPACustomEndpoint;           // REST API registration
    use EPAOnPaymentProcessed;       // Post-purchase sync
    use EPAOnStatusUpdate;           // Order status changes
    use EPAWooCustomTemplates;       // Template overrides
    use MBox_TribeEvent;             // Event selector metabox
    use EPAOnOrderCancel;            // Cancellation handling
    use EPAFallbackGuzzleConn;       // HTTP fallback
}

2.2. Hook Registration

The plugin registers hooks for the complete WooCommerce order lifecycle:

Hook Priority Handler

woocommerce_before_pay_action

99

EpaUpdateOrderBillingBeforePayment()

woocommerce_thankyou

10

EpaOnPaymentProcessed()

woocommerce_order_status_pending

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_processing

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_completed

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_on-hold

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_cancelled

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_refunded

10

EPAOnOrderStatusUpdate()

woocommerce_order_status_failed

10

EPAOnOrderStatusUpdate()

3. REST Endpoint Implementation

3.1. Endpoint Registration

add_action('rest_api_init', function () {
    register_rest_route('payment-api/v1', '/order/event/create/', [
        'methods'  => 'POST',
        'callback' => [$this, 'handleOrderCreation'],
        'permission_callback' => '__return_true'
    ]);
});

3.2. Payload Validation

The endpoint performs multi-layer validation:

  1. API key validation - Checks payload contains valid API key

  2. Payload type detection - Determines if standard event or membership

  3. Required field validation - Verifies all required fields present

  4. Product validation - Confirms products exist in WooCommerce

private function validatePayload($payload) {
    if (empty($payload['apiKey']) ||
        $payload['apiKey'] !== get_option('epa_callback_api_key')) {
        return new WP_Error('unauthorized', 'Invalid API key', ['status' => 401]);
    }
    // Additional validation...
}

3.3. Order Creation Flow

function handleStandardEventData($payload) {
    // 1. Create WooCommerce order
    $order = wc_create_order(['status' => 'draft']);

    // 2. Store admin order reference
    $order->update_meta_data('epa_admin_portal_order_id', $payload['orderId']);
    $order->update_meta_data('event_type', 'standard');

    // 3. Add line items for each participant
    foreach ($payload['participants'] as $participant) {
        $product = wc_get_product($participant['productId']);
        $itemId = $order->add_product($product, 1);

        // Add participant metadata
        wc_add_order_item_meta($itemId, 'Entrant ID', $participant['id']);
        wc_add_order_item_meta($itemId, 'Entrant Name', $participant['name']);
        // ...
    }

    // 4. Calculate totals
    $order->set_total($payload['total'] - $payload['discount']);
    $order->save();

    return [
        'redirectURL' => $order->get_checkout_payment_url(),
        'orderNumber' => $order->get_id()
    ];
}

4. Status Synchronisation

4.1. API Client Integration

The plugin uses a generated PHP API client from the OpenAPI specification:

class AdminServiceClient {
    private static $instance = null;
    private $orderApi;
    private $membershipApi;

    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getOrderResourceApi() {
        if ($this->orderApi === null) {
            $config = Configuration::getDefaultConfiguration()
                ->setApiKey('X-API-KEY', get_option('epa_admin_api_key'))
                ->setHost(get_option('epa_admin_api_url'));

            $this->orderApi = new OrderResourceExApi(
                new GuzzleHttp\Client(['timeout' => 15]),
                $config
            );
        }
        return $this->orderApi;
    }
}

4.2. DTO Mapping

function createParticipantOrderDTO($order, $status) {
    $dto = new ParticipantOrderDTO();
    $dto->setId($order->get_meta('epa_admin_portal_order_id'));
    $dto->setStatus($this->mapOrderStatus($order->get_status()));
    $dto->setPaymentReference($order->get_id());

    // Extract participant IDs from line items
    $participantIds = [];
    foreach ($order->get_items() as $item) {
        $entrantId = wc_get_order_item_meta($item->get_id(), 'Entrant ID');
        if ($entrantId) {
            $participantIds[] = $entrantId;
        }
    }
    $dto->setParticipantIds($participantIds);

    return $dto;
}

5. Order Metadata Structure

5.1. Order Level

Meta Key Description

epa_admin_portal_order_id

Links to admin-service order

epa_order_synced

Flag to prevent duplicate syncs

epa_order_sync_response

Last API response (debugging)

event_type

standard or membership

event_id

Event ID from admin-service

event_name

Event name

event_date

Event date

5.2. Line Item Level (Events)

Meta Key Description

Entrant ID

Participant ID

Entrant Name

Full name

Entrant DOB

Date of birth

Category

Age category

Race Type(s)

HTML list of races

5.3. Line Item Level (Memberships)

Meta Key Description

_membership_id

Member ID(s)

Type

Membership type name

Period

Membership period

Name

Member name

6. Error Handling

6.1. Logging Strategy

if (defined('EPA_DEBUG') && EPA_DEBUG) {
    $logger = wc_get_logger();
    $logger->info('Order sync initiated', [
        'source' => 'event-payment-api',
        'order_id' => $order->get_id(),
        'admin_order_id' => $adminOrderId
    ]);
}

Logs are written to:

  • WooCommerce logs: wp-content/uploads/wc-logs/

  • Plugin debug log: event-payment-plugin/debug.log

6.2. Order Notes

All API interactions are recorded as order notes for admin visibility:

$order->add_order_note(sprintf(
    'Admin-service sync: %s (Status: %s)',
    $response->success ? 'Success' : 'Failed',
    $order->get_status()
));

7. User Management

7.1. Auto-Creation

function createOrLinkUser($email, $order) {
    $user = get_user_by('email', $email);

    if (!$user) {
        $userId = wp_create_user(
            sanitize_email($email),
            wp_generate_password(),
            $email
        );
        $user = get_user_by('ID', $userId);
    }

    $order->set_customer_id($user->ID);
    $order->save();

    return $user;
}

7.2. Auto-Login

After successful payment, if the order email matches an existing account:

if (is_user_logged_in() === false) {
    $user = get_user_by('email', $order->get_billing_email());
    if ($user) {
        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID);
    }
}