Changeset 3463217
- Timestamp:
- 02/17/2026 08:41:28 AM (6 weeks ago)
- Location:
- stitch-express
- Files:
-
- 6 edited
- 1 copied
-
tags/1.3.4 (copied) (copied from stitch-express/trunk)
-
tags/1.3.4/includes/stitch-express-client.php (modified) (1 diff)
-
tags/1.3.4/readme.txt (modified) (3 diffs)
-
tags/1.3.4/stitch-express.php (modified) (8 diffs)
-
trunk/includes/stitch-express-client.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/stitch-express.php (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
stitch-express/tags/1.3.4/includes/stitch-express-client.php
r3442620 r3463217 38 38 39 39 class Stitch_Express_Client { 40 private const PLUGIN_VERSION = '1.3. 3';40 private const PLUGIN_VERSION = '1.3.4'; 41 41 private string $baseUrl = 'https://express.stitch.money'; 42 42 private string $client_id; -
stitch-express/tags/1.3.4/readme.txt
r3442620 r3463217 3 3 Tags: woocommerce, card, payments, south africa, apple pay, google pay, stitch, express, stitch express 4 4 Tested up to: 6.7 5 Stable tag: 1.3. 35 Stable tag: 1.3.4 6 6 License: GPLv3 7 7 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 83 83 84 84 == Changelog == 85 = 1.3.4 = 86 * Webhook improvements 87 85 88 = 1.3.3 = 86 89 * Replace Happy Pay with Stitch BNPL … … 161 164 162 165 == Upgrade Notice == 166 = 1.3.4 = 167 * Improved webhook performance and reliability 168 163 169 = 1.3.3 = 164 170 * Replace Happy Pay with Stitch BNPL -
stitch-express/tags/1.3.4/stitch-express.php
r3442620 r3463217 9 9 * Description: Use Stitch Express to easily and securely accept Card payment on your WooCommerce store. 10 10 * Plugin URI: https://wordpress.org/plugins/stitchexpress/ 11 * Version: 1.3. 311 * Version: 1.3.4 12 12 * Requires at least: 6.5 13 13 * Requires PHP: 8.0 … … 67 67 ] 68 68 ); 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 */ 71 84 function stitch_express_handle_callback(WP_REST_Request $request): WP_REST_Response { 72 85 $gateway = new Stitch_Express_Payment_Gateway(); … … 85 98 wc_add_notice('Order not found', 'error'); 86 99 87 return stitch_express_generate_ webhook_response(home_url());100 return stitch_express_generate_redirect_response(home_url()); 88 101 } 89 102 … … 96 109 wc_add_notice('Failed to check status of your payment. Please contact the store.', 'error'); 97 110 98 return stitch_express_generate_ webhook_response($order->get_view_order_url());111 return stitch_express_generate_redirect_response($order->get_view_order_url()); 99 112 } 100 113 … … 103 116 wc_add_notice('Payment not found', 'error'); 104 117 105 return stitch_express_generate_ webhook_response($order->get_view_order_url());118 return stitch_express_generate_redirect_response($order->get_view_order_url()); 106 119 } 107 120 … … 110 123 wc_add_notice('Payment not completed', 'error'); 111 124 112 return stitch_express_generate_ webhook_response($order->get_view_order_url());125 return stitch_express_generate_redirect_response($order->get_view_order_url()); 113 126 } 114 127 … … 126 139 $order->save(); 127 140 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()); 129 142 } 130 143 … … 132 145 $logger->info("Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']); 133 146 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 */ 153 function 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 263 function stitch_express_generate_redirect_response(string $url): WP_REST_Response { 138 264 return new WP_REST_Response( 139 265 null, -
stitch-express/trunk/includes/stitch-express-client.php
r3442620 r3463217 38 38 39 39 class Stitch_Express_Client { 40 private const PLUGIN_VERSION = '1.3. 3';40 private const PLUGIN_VERSION = '1.3.4'; 41 41 private string $baseUrl = 'https://express.stitch.money'; 42 42 private string $client_id; -
stitch-express/trunk/readme.txt
r3442620 r3463217 3 3 Tags: woocommerce, card, payments, south africa, apple pay, google pay, stitch, express, stitch express 4 4 Tested up to: 6.7 5 Stable tag: 1.3. 35 Stable tag: 1.3.4 6 6 License: GPLv3 7 7 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 83 83 84 84 == Changelog == 85 = 1.3.4 = 86 * Webhook improvements 87 85 88 = 1.3.3 = 86 89 * Replace Happy Pay with Stitch BNPL … … 161 164 162 165 == Upgrade Notice == 166 = 1.3.4 = 167 * Improved webhook performance and reliability 168 163 169 = 1.3.3 = 164 170 * Replace Happy Pay with Stitch BNPL -
stitch-express/trunk/stitch-express.php
r3442620 r3463217 9 9 * Description: Use Stitch Express to easily and securely accept Card payment on your WooCommerce store. 10 10 * Plugin URI: https://wordpress.org/plugins/stitchexpress/ 11 * Version: 1.3. 311 * Version: 1.3.4 12 12 * Requires at least: 6.5 13 13 * Requires PHP: 8.0 … … 67 67 ] 68 68 ); 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 */ 71 84 function stitch_express_handle_callback(WP_REST_Request $request): WP_REST_Response { 72 85 $gateway = new Stitch_Express_Payment_Gateway(); … … 85 98 wc_add_notice('Order not found', 'error'); 86 99 87 return stitch_express_generate_ webhook_response(home_url());100 return stitch_express_generate_redirect_response(home_url()); 88 101 } 89 102 … … 96 109 wc_add_notice('Failed to check status of your payment. Please contact the store.', 'error'); 97 110 98 return stitch_express_generate_ webhook_response($order->get_view_order_url());111 return stitch_express_generate_redirect_response($order->get_view_order_url()); 99 112 } 100 113 … … 103 116 wc_add_notice('Payment not found', 'error'); 104 117 105 return stitch_express_generate_ webhook_response($order->get_view_order_url());118 return stitch_express_generate_redirect_response($order->get_view_order_url()); 106 119 } 107 120 … … 110 123 wc_add_notice('Payment not completed', 'error'); 111 124 112 return stitch_express_generate_ webhook_response($order->get_view_order_url());125 return stitch_express_generate_redirect_response($order->get_view_order_url()); 113 126 } 114 127 … … 126 139 $order->save(); 127 140 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()); 129 142 } 130 143 … … 132 145 $logger->info("Order {$order_id} status is '{$order->get_status()}', skipping payment completion", ['source' => 'stitch-express']); 133 146 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 */ 153 function 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 263 function stitch_express_generate_redirect_response(string $url): WP_REST_Response { 138 264 return new WP_REST_Response( 139 265 null,
Note: See TracChangeset
for help on using the changeset viewer.