public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __('Enable/Disable', 'woocommerce'), 'type' => 'checkbox', 'label' => __('Enable Selcom Payments', 'woocommerce'), 'default' => 'yes' ), 'title' => array( 'title' => __('Title', 'woocommerce'), 'type' => 'text', 'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'), 'default' => __('Pay via mobile phone or card', 'woocommerce'), 'desc_tip' => true, ), 'description' => array( 'title' => __('Customer Message', 'woocommerce'), 'type' => 'textarea', 'default' => 'Pay directly from your mobile phone or card', 'description' => 'This controls the description which the user sees during checkout.', ), 'merchant_id' => array( 'title' => __('Vendor/Merchant ID', 'woocommerce'), 'type' => 'text', 'description' => __('Your merchant id received from Selcom.', $this->id), 'default' => '', 'desc_tip' => true, ), 'api_key' => array( 'title' => __('API Key', 'woocommerce'), 'type' => 'text', 'description' => __('Your API Key received from Selcom.', $this->id), 'default' => '', 'desc_tip' => true, ), 'secret_key' => array( 'title' => __('Secret Key', 'woocommerce'), 'type' => 'text', 'description' => __('Your Secret Key received from Selcom.', $this->id), 'default' => '', 'desc_tip' => true, ), 'api_url' => array( 'title' => __('API URL', 'woocommerce'), 'type' => 'text', 'description' => __('Base URL for Selcom API (e.g., https://apigw.selcommobile.com). Contact Selcom support to confirm.', $this->id), 'default' => 'https://apigw.selcommobile.com', 'desc_tip' => true, ), 'prefix' => array( 'title' => __('Unique Vendor Prefix', 'woocommerce'), 'type' => 'text', 'description' => __('Vendor prefix for auto-generated transaction IDs.', $this->id), 'default' => 'YAT', 'desc_tip' => true, ) ); } public function process_payment($order_id) { global $woocommerce; // Get an instance of the WC_Order object $order = new WC_Order($order_id); // Selcom API prerequisites $apiKey = $this->api_key; $apiSecret = $this->api_secret; $baseUrl = rtrim($this->api_url, '/'); // Ensure no trailing slash $vendorPrefix = $this->prefix; date_default_timezone_set('Africa/Dar_es_Salaam'); // Validate required fields if (empty($apiKey) || empty($apiSecret) || empty($this->vendor) || empty($baseUrl)) { wc_add_notice(__('Error: Selcom API credentials are not configured correctly.', 'woocommerce'), 'error'); return array('result' => 'failure'); } // Test API URL reachability with endpoint $testUrl = $baseUrl . '/checkout/create-order'; error_log('Selcom: Testing API URL: ' . $testUrl); $headers = array( 'Authorization' => 'SELCOM ' . base64_encode($apiKey), 'Accept' => 'application/json', ); $testResponse = wp_remote_get($testUrl, array( 'timeout' => 5, 'headers' => $headers, )); if (is_wp_error($testResponse)) { $error_message = $testResponse->get_error_message(); error_log('Selcom: API URL test failed: ' . $error_message); wc_add_notice(__('Error: Cannot connect to Selcom API (' . $error_message . '). Please verify the API URL or contact Selcom support at support@selcom.net.', 'woocommerce'), 'error'); return array('result' => 'failure'); } $httpCode = wp_remote_retrieve_response_code($testResponse); if ($httpCode >= 400) { $response_body = wp_remote_retrieve_body($testResponse); error_log('Selcom: API URL test failed: HTTP ' . $httpCode . ' | Response: ' . $response_body); wc_add_notice(__('Error: Selcom API returned an error (HTTP ' . $httpCode . '). Please verify the API URL or contact Selcom support at support@selcom.net.', 'woocommerce'), 'error'); return array('result' => 'failure'); } // Log raw phone number error_log('Selcom: Raw billing phone from order: ' . $order->get_billing_phone()); // Order array $minOrder = array( "vendor" => $this->vendor, "order_id" => $order->get_id(), "buyer_email" => $order->get_billing_email() ?: 'no-email@selcom.com', // Fallback for guest checkout "buyer_name" => trim($order->get_billing_first_name() . " " . $order->get_billing_last_name()), "buyer_phone" => '', // Will be set after normalization "amount" => (float) $order->get_total(), "webhook" => base64_encode(get_site_url() . '/wp-json/selcom-push/v2/selcomstat'), "currency" => "TZS", "no_of_items" => (int) $woocommerce->cart->cart_contents_count, "cancel_url" => base64_encode(get_site_url() . '/checkout'), "redirect_url" => base64_encode($this->get_return_url($order)), "payment_methods" => "ALL", "billing.firstname" => $order->get_billing_first_name() ?: 'Guest', "billing.lastname" => $order->get_billing_last_name() ?: 'User', "billing.address_1" => $order->get_billing_address_1() ?: 'Unknown', "billing.city" => $order->get_billing_city() ?: 'Dar es Salaam', "billing.state_or_region" => $order->get_billing_state() ?: 'Dar es Salaam', "billing.postcode_or_pobox" => $order->get_billing_postcode() ?: '94103', "billing.country" => $order->get_billing_country() ?: 'TZ', "billing.phone" => $order->get_billing_phone(), ); // Normalize phone number $raw_phone = $order->get_billing_phone(); $normalized_phone = preg_replace('/[^0-9]/', '', $raw_phone); // Remove non-digits if (substr($normalized_phone, 0, 1) === '0') { $normalized_phone = '255' . substr($normalized_phone, 1); // Convert 07xx to 2557xx } elseif (substr($normalized_phone, 0, 3) === '+255') { $normalized_phone = substr($normalized_phone, 1); // Convert +255 to 255 } $minOrder['buyer_phone'] = $normalized_phone; // Log webhook URL error_log('Selcom: Webhook URL: ' . base64_decode($minOrder['webhook'])); // Validate order data if (empty($minOrder['buyer_phone']) || !preg_match('/^255[0-9]{9}$/', $minOrder['buyer_phone'])) { error_log('Selcom: Invalid phone number provided: ' . $raw_phone); wc_add_notice(__('Error: Invalid or missing billing phone number. Please enter a valid Tanzanian phone number (e.g., 255123456789).', 'woocommerce'), 'error'); return array('result' => 'failure'); } // Send Order Request error_log('Selcom: Sending order request to: ' . $testUrl); $orderResponse = sendOrder($minOrder, $apiKey, $apiSecret, $baseUrl); // Log the full API response for debugging error_log('Selcom API Response: ' . print_r($orderResponse, true)); // Based on the order response, continue implementing USSD Push if (!isset($orderResponse->result)) { $order->update_status('failed', __('Order failed: Invalid API response.', 'woocommerce')); wc_add_notice(__('Error: Selcom API returned an invalid response.', 'woocommerce'), 'error'); return array('result' => 'failure'); } if ($orderResponse->result == 'FAIL') { $order->update_status('failed', __('Order not created: ' . ($orderResponse->message ?? 'Unknown error'), 'woocommerce')); wc_add_notice(__('Error: ', 'woocommerce') . ($orderResponse->message ?? 'Order could not be created. Please try again or contact support.'), 'error'); return array('result' => 'failure'); } elseif ($orderResponse->result == 'SUCCESS') { $data1 = (array) $orderResponse->data; $data1 = (object) $data1[0]; $payment_gateway_url = base64_decode($data1->payment_gateway_url); $order->update_status('pending', __('Order has been received, waiting for payment.', 'woocommerce')); wc_add_notice(__('Update: ', 'woocommerce') . 'Order has been created, waiting for payment completion.', 'success'); return array( 'result' => 'success', 'redirect' => $payment_gateway_url ); } elseif ($orderResponse->result == 'INPROGRESS' || $orderResponse->result == 'AMBIGOUS') { // Handle INPROGRESS or AMBIGOUS by scheduling a status check $order->update_status('pending', __('Order in progress, awaiting confirmation.', 'woocommerce')); wc_add_notice(__('Update: ', 'woocommerce') . 'Order is being processed, please wait.', 'success'); // Schedule a status check (requires WP-Cron) wp_schedule_single_event(time() + 180, 'selcom_check_order_status', array($order->get_id(), $baseUrl, $apiKey, $apiSecret)); return array( 'result' => 'success', 'redirect' => $this->get_return_url($order) ); } else { $order->update_status('failed', __('Order failed: ' . ($orderResponse->message ?? 'Unknown error'), 'woocommerce')); wc_add_notice(__('Error: ', 'woocommerce') . ($orderResponse->message ?? 'Something went wrong with the order. Please try again.'), 'error'); return array('result' => 'failure'); } }