Plugin Directory

Changeset 3463217


Ignore:
Timestamp:
02/17/2026 08:41:28 AM (6 weeks ago)
Author:
stitchexpress
Message:

1.3.4

Location:
stitch-express
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • stitch-express/tags/1.3.4/includes/stitch-express-client.php

    r3442620 r3463217  
    3838
    3939class Stitch_Express_Client {
    40     private const PLUGIN_VERSION = '1.3.3';
     40    private const PLUGIN_VERSION = '1.3.4';
    4141    private string $baseUrl = 'https://express.stitch.money';
    4242    private string $client_id;
  • stitch-express/tags/1.3.4/readme.txt

    r3442620 r3463217  
    33Tags: woocommerce, card, payments, south africa, apple pay, google pay, stitch, express, stitch express
    44Tested up to: 6.7
    5 Stable tag: 1.3.3
     5Stable tag: 1.3.4
    66License: GPLv3
    77License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    8383
    8484== Changelog ==
     85= 1.3.4 =
     86* Webhook improvements
     87
    8588= 1.3.3 =
    8689* Replace Happy Pay with Stitch BNPL
     
    161164
    162165== Upgrade Notice ==
     166= 1.3.4 =
     167* Improved webhook performance and reliability
     168
    163169= 1.3.3 =
    164170* Replace Happy Pay with Stitch BNPL
  • stitch-express/tags/1.3.4/stitch-express.php

    r3442620 r3463217  
    99 * Description:          Use Stitch Express to easily and securely accept Card payment on your WooCommerce store.
    1010 * Plugin URI:           https://wordpress.org/plugins/stitchexpress/
    11  * Version:              1.3.3
     11 * Version:              1.3.4
    1212 * Requires at least:    6.5
    1313 * Requires PHP:         8.0
     
    6767        ]
    6868    );
    69 }
    70 
     69
     70    register_rest_route(
     71        'stitch-express/v1',
     72        '/webhook',
     73        [
     74            'methods' => 'POST',
     75            'callback' => 'stitch_express_handle_webhook',
     76            'permission_callback' => '__return_true',
     77        ]
     78    );
     79}
     80
     81/**
     82 * GET callback handler — used for user browser redirects.
     83 */
    7184function stitch_express_handle_callback(WP_REST_Request $request): WP_REST_Response {
    7285    $gateway = new Stitch_Express_Payment_Gateway();
     
    8598        wc_add_notice('Order not found', 'error');
    8699
    87         return stitch_express_generate_webhook_response(home_url());
     100        return stitch_express_generate_redirect_response(home_url());
    88101    }
    89102
     
    96109        wc_add_notice('Failed to check status of your payment. Please contact the store.', 'error');
    97110
    98         return stitch_express_generate_webhook_response($order->get_view_order_url());
     111        return stitch_express_generate_redirect_response($order->get_view_order_url());
    99112    }
    100113
     
    103116        wc_add_notice('Payment not found', 'error');
    104117
    105         return stitch_express_generate_webhook_response($order->get_view_order_url());
     118        return stitch_express_generate_redirect_response($order->get_view_order_url());
    106119    }
    107120
     
    110123        wc_add_notice('Payment not completed', 'error');
    111124
    112         return stitch_express_generate_webhook_response($order->get_view_order_url());
     125        return stitch_express_generate_redirect_response($order->get_view_order_url());
    113126    }
    114127
     
    126139        $order->save();
    127140
    128         return stitch_express_generate_webhook_response($order->get_checkout_order_received_url());
     141        return stitch_express_generate_redirect_response($order->get_checkout_order_received_url());
    129142    }
    130143
     
    132145    $logger->info("Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']);
    133146
    134     return stitch_express_generate_webhook_response($order->get_view_order_url());
    135 }
    136 
    137 function stitch_express_generate_webhook_response(string $url): WP_REST_Response {
     147    return stitch_express_generate_redirect_response($order->get_view_order_url());
     148}
     149
     150/**
     151 * POST webhook handler — used for server-to-server confirmation from Express.
     152 */
     153function stitch_express_handle_webhook(WP_REST_Request $request): WP_REST_Response {
     154    $gateway = new Stitch_Express_Payment_Gateway();
     155    $logger = wc_get_logger();
     156    $client_secret = $gateway->get_option('client_secret');
     157
     158    // Verify HMAC signature
     159    $signature = $request->get_header('x_stitch_express_signature');
     160    $raw_body = $request->get_body();
     161
     162    if (!$signature || !$client_secret) {
     163        $logger->error('Webhook received without signature or client secret not configured', ['source' => 'stitch-express']);
     164
     165        return new WP_REST_Response(['success' => false, 'error' => 'Unauthorized'], 401);
     166    }
     167
     168    $expected_signature = hash_hmac('sha256', $raw_body, $client_secret);
     169
     170    if (!hash_equals($expected_signature, $signature)) {
     171        $logger->error('Webhook HMAC signature verification failed', ['source' => 'stitch-express']);
     172
     173        return new WP_REST_Response(['success' => false, 'error' => 'Invalid signature'], 401);
     174    }
     175
     176    // Parse JSON body
     177    $body = json_decode($raw_body, true);
     178
     179    if (!$body || !isset($body['payment_id']) || !isset($body['reference'])) {
     180        $logger->error('Webhook received with invalid body', ['source' => 'stitch-express']);
     181
     182        return new WP_REST_Response(['success' => false, 'error' => 'Invalid request body'], 400);
     183    }
     184
     185    $payment_id = $body['payment_id'];
     186    $reference = $body['reference'];
     187    $split_string = explode('-', $reference);
     188
     189    if (count($split_string) < 2) {
     190        $logger->error("Webhook received with invalid reference format: {$reference}", ['source' => 'stitch-express']);
     191
     192        return new WP_REST_Response(['success' => false, 'error' => 'Invalid reference format'], 400);
     193    }
     194
     195    $order_id = $split_string[1];
     196    $order = wc_get_order($order_id);
     197
     198    if (!$order) {
     199        $logger->error("Webhook: Order not found for ID: {$order_id}", ['source' => 'stitch-express']);
     200
     201        return new WP_REST_Response(['success' => false, 'error' => 'Order not found'], 404);
     202    }
     203
     204    $stitch_express_client = new Stitch_Express_Client($gateway->get_option('client_id'), $gateway->get_option('client_secret'));
     205
     206    try {
     207        $payment_status = $stitch_express_client->get_payment_status($payment_id, $reference);
     208    } catch (Exception $exception) {
     209        $logger->error("Webhook: Failed to check payment status - {$exception->getMessage()}", ['source' => 'stitch-express']);
     210
     211        return new WP_REST_Response([
     212            'success' => false,
     213            'error' => 'Failed to check payment status',
     214            'order_id' => (string) $order_id,
     215            'order_status' => $order->get_status(),
     216        ], 500);
     217    }
     218
     219    if ($payment_status === null) {
     220        $logger->error("Webhook: Payment link not found for payment ID: {$payment_id} and reference: {$reference}", ['source' => 'stitch-express']);
     221
     222        return new WP_REST_Response([
     223            'success' => false,
     224            'error' => 'Payment not found',
     225            'order_id' => (string) $order_id,
     226            'order_status' => $order->get_status(),
     227        ], 404);
     228    }
     229
     230    if ($payment_status !== 'PAID') {
     231        $logger->warning("Webhook: Unpaid status ({$payment_status}) for payment {$payment_id}", ['source' => 'stitch-express']);
     232
     233        return new WP_REST_Response([
     234            'success' => false,
     235            'error' => "Payment not completed (status: {$payment_status})",
     236            'order_id' => (string) $order_id,
     237            'order_status' => $order->get_status(),
     238        ], 422);
     239    }
     240
     241    // Process payment if order is awaiting payment
     242    $is_awaiting_payment = in_array($order->get_status(), ['awaiting-payment', 'pending'], true);
     243
     244    if ($is_awaiting_payment) {
     245        $logger->info(
     246            "Webhook: Marking order {$order_id} with status '{$order->get_status()}' as complete. Stitch Payment ID: {$payment_id}",
     247            ['source' => 'stitch-express']
     248        );
     249
     250        $order->payment_complete($payment_id);
     251        $order->save();
     252    } else {
     253        $logger->info("Webhook: Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']);
     254    }
     255
     256    return new WP_REST_Response([
     257        'success' => true,
     258        'order_id' => (string) $order_id,
     259        'order_status' => $order->get_status(),
     260    ], 200);
     261}
     262
     263function stitch_express_generate_redirect_response(string $url): WP_REST_Response {
    138264    return new WP_REST_Response(
    139265        null,
  • stitch-express/trunk/includes/stitch-express-client.php

    r3442620 r3463217  
    3838
    3939class Stitch_Express_Client {
    40     private const PLUGIN_VERSION = '1.3.3';
     40    private const PLUGIN_VERSION = '1.3.4';
    4141    private string $baseUrl = 'https://express.stitch.money';
    4242    private string $client_id;
  • stitch-express/trunk/readme.txt

    r3442620 r3463217  
    33Tags: woocommerce, card, payments, south africa, apple pay, google pay, stitch, express, stitch express
    44Tested up to: 6.7
    5 Stable tag: 1.3.3
     5Stable tag: 1.3.4
    66License: GPLv3
    77License URI: https://www.gnu.org/licenses/gpl-3.0.html
     
    8383
    8484== Changelog ==
     85= 1.3.4 =
     86* Webhook improvements
     87
    8588= 1.3.3 =
    8689* Replace Happy Pay with Stitch BNPL
     
    161164
    162165== Upgrade Notice ==
     166= 1.3.4 =
     167* Improved webhook performance and reliability
     168
    163169= 1.3.3 =
    164170* Replace Happy Pay with Stitch BNPL
  • stitch-express/trunk/stitch-express.php

    r3442620 r3463217  
    99 * Description:          Use Stitch Express to easily and securely accept Card payment on your WooCommerce store.
    1010 * Plugin URI:           https://wordpress.org/plugins/stitchexpress/
    11  * Version:              1.3.3
     11 * Version:              1.3.4
    1212 * Requires at least:    6.5
    1313 * Requires PHP:         8.0
     
    6767        ]
    6868    );
    69 }
    70 
     69
     70    register_rest_route(
     71        'stitch-express/v1',
     72        '/webhook',
     73        [
     74            'methods' => 'POST',
     75            'callback' => 'stitch_express_handle_webhook',
     76            'permission_callback' => '__return_true',
     77        ]
     78    );
     79}
     80
     81/**
     82 * GET callback handler — used for user browser redirects.
     83 */
    7184function stitch_express_handle_callback(WP_REST_Request $request): WP_REST_Response {
    7285    $gateway = new Stitch_Express_Payment_Gateway();
     
    8598        wc_add_notice('Order not found', 'error');
    8699
    87         return stitch_express_generate_webhook_response(home_url());
     100        return stitch_express_generate_redirect_response(home_url());
    88101    }
    89102
     
    96109        wc_add_notice('Failed to check status of your payment. Please contact the store.', 'error');
    97110
    98         return stitch_express_generate_webhook_response($order->get_view_order_url());
     111        return stitch_express_generate_redirect_response($order->get_view_order_url());
    99112    }
    100113
     
    103116        wc_add_notice('Payment not found', 'error');
    104117
    105         return stitch_express_generate_webhook_response($order->get_view_order_url());
     118        return stitch_express_generate_redirect_response($order->get_view_order_url());
    106119    }
    107120
     
    110123        wc_add_notice('Payment not completed', 'error');
    111124
    112         return stitch_express_generate_webhook_response($order->get_view_order_url());
     125        return stitch_express_generate_redirect_response($order->get_view_order_url());
    113126    }
    114127
     
    126139        $order->save();
    127140
    128         return stitch_express_generate_webhook_response($order->get_checkout_order_received_url());
     141        return stitch_express_generate_redirect_response($order->get_checkout_order_received_url());
    129142    }
    130143
     
    132145    $logger->info("Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']);
    133146
    134     return stitch_express_generate_webhook_response($order->get_view_order_url());
    135 }
    136 
    137 function stitch_express_generate_webhook_response(string $url): WP_REST_Response {
     147    return stitch_express_generate_redirect_response($order->get_view_order_url());
     148}
     149
     150/**
     151 * POST webhook handler — used for server-to-server confirmation from Express.
     152 */
     153function stitch_express_handle_webhook(WP_REST_Request $request): WP_REST_Response {
     154    $gateway = new Stitch_Express_Payment_Gateway();
     155    $logger = wc_get_logger();
     156    $client_secret = $gateway->get_option('client_secret');
     157
     158    // Verify HMAC signature
     159    $signature = $request->get_header('x_stitch_express_signature');
     160    $raw_body = $request->get_body();
     161
     162    if (!$signature || !$client_secret) {
     163        $logger->error('Webhook received without signature or client secret not configured', ['source' => 'stitch-express']);
     164
     165        return new WP_REST_Response(['success' => false, 'error' => 'Unauthorized'], 401);
     166    }
     167
     168    $expected_signature = hash_hmac('sha256', $raw_body, $client_secret);
     169
     170    if (!hash_equals($expected_signature, $signature)) {
     171        $logger->error('Webhook HMAC signature verification failed', ['source' => 'stitch-express']);
     172
     173        return new WP_REST_Response(['success' => false, 'error' => 'Invalid signature'], 401);
     174    }
     175
     176    // Parse JSON body
     177    $body = json_decode($raw_body, true);
     178
     179    if (!$body || !isset($body['payment_id']) || !isset($body['reference'])) {
     180        $logger->error('Webhook received with invalid body', ['source' => 'stitch-express']);
     181
     182        return new WP_REST_Response(['success' => false, 'error' => 'Invalid request body'], 400);
     183    }
     184
     185    $payment_id = $body['payment_id'];
     186    $reference = $body['reference'];
     187    $split_string = explode('-', $reference);
     188
     189    if (count($split_string) < 2) {
     190        $logger->error("Webhook received with invalid reference format: {$reference}", ['source' => 'stitch-express']);
     191
     192        return new WP_REST_Response(['success' => false, 'error' => 'Invalid reference format'], 400);
     193    }
     194
     195    $order_id = $split_string[1];
     196    $order = wc_get_order($order_id);
     197
     198    if (!$order) {
     199        $logger->error("Webhook: Order not found for ID: {$order_id}", ['source' => 'stitch-express']);
     200
     201        return new WP_REST_Response(['success' => false, 'error' => 'Order not found'], 404);
     202    }
     203
     204    $stitch_express_client = new Stitch_Express_Client($gateway->get_option('client_id'), $gateway->get_option('client_secret'));
     205
     206    try {
     207        $payment_status = $stitch_express_client->get_payment_status($payment_id, $reference);
     208    } catch (Exception $exception) {
     209        $logger->error("Webhook: Failed to check payment status - {$exception->getMessage()}", ['source' => 'stitch-express']);
     210
     211        return new WP_REST_Response([
     212            'success' => false,
     213            'error' => 'Failed to check payment status',
     214            'order_id' => (string) $order_id,
     215            'order_status' => $order->get_status(),
     216        ], 500);
     217    }
     218
     219    if ($payment_status === null) {
     220        $logger->error("Webhook: Payment link not found for payment ID: {$payment_id} and reference: {$reference}", ['source' => 'stitch-express']);
     221
     222        return new WP_REST_Response([
     223            'success' => false,
     224            'error' => 'Payment not found',
     225            'order_id' => (string) $order_id,
     226            'order_status' => $order->get_status(),
     227        ], 404);
     228    }
     229
     230    if ($payment_status !== 'PAID') {
     231        $logger->warning("Webhook: Unpaid status ({$payment_status}) for payment {$payment_id}", ['source' => 'stitch-express']);
     232
     233        return new WP_REST_Response([
     234            'success' => false,
     235            'error' => "Payment not completed (status: {$payment_status})",
     236            'order_id' => (string) $order_id,
     237            'order_status' => $order->get_status(),
     238        ], 422);
     239    }
     240
     241    // Process payment if order is awaiting payment
     242    $is_awaiting_payment = in_array($order->get_status(), ['awaiting-payment', 'pending'], true);
     243
     244    if ($is_awaiting_payment) {
     245        $logger->info(
     246            "Webhook: Marking order {$order_id} with status '{$order->get_status()}' as complete. Stitch Payment ID: {$payment_id}",
     247            ['source' => 'stitch-express']
     248        );
     249
     250        $order->payment_complete($payment_id);
     251        $order->save();
     252    } else {
     253        $logger->info("Webhook: Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']);
     254    }
     255
     256    return new WP_REST_Response([
     257        'success' => true,
     258        'order_id' => (string) $order_id,
     259        'order_status' => $order->get_status(),
     260    ], 200);
     261}
     262
     263function stitch_express_generate_redirect_response(string $url): WP_REST_Response {
    138264    return new WP_REST_Response(
    139265        null,
Note: See TracChangeset for help on using the changeset viewer.