method_title = __('Yoone Moneris', 'yoone-subscriptions'); $this->method_description = __('通过 Moneris 处理信用卡支付和订阅', 'yoone-subscriptions'); $this->has_fields = true; // 加载设置 $this->init_form_fields(); $this->init_settings(); // 获取设置值 $this->title = $this->get_option('title'); $this->description = $this->get_option('description'); $this->testmode = 'yes' === $this->get_option('testmode'); $this->store_id = $this->get_option('store_id'); $this->api_token = $this->get_option('api_token'); $this->processing_country = $this->get_option('processing_country', 'CA'); // 钩子 add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options')); add_action('wp_enqueue_scripts', array($this, 'payment_scripts')); // 订阅钩子 add_action('woocommerce_scheduled_subscription_payment_' . $this->id, array($this, 'scheduled_subscription_payment'), 10, 2); add_action('wcs_resubscribe_order_created', array($this, 'delete_resubscribe_meta'), 10); // 支持令牌化 $this->supports[] = 'tokenization'; $this->supports[] = 'add_payment_method'; } /** * 初始化表单字段 */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __('启用/禁用', 'yoone-subscriptions'), 'type' => 'checkbox', 'label' => __('启用 Moneris 支付', 'yoone-subscriptions'), 'default' => 'no' ), 'title' => array( 'title' => __('标题', 'yoone-subscriptions'), 'type' => 'text', 'description' => __('用户在结账时看到的支付方式标题', 'yoone-subscriptions'), 'default' => __('信用卡', 'yoone-subscriptions'), 'desc_tip' => true, ), 'description' => array( 'title' => __('描述', 'yoone-subscriptions'), 'type' => 'textarea', 'description' => __('用户在结账时看到的支付方式描述', 'yoone-subscriptions'), 'default' => __('使用信用卡安全支付', 'yoone-subscriptions'), 'desc_tip' => true, ), 'testmode' => array( 'title' => __('测试模式', 'yoone-subscriptions'), 'type' => 'checkbox', 'label' => __('启用测试模式', 'yoone-subscriptions'), 'default' => 'yes', 'description' => __('在测试模式下,您可以使用测试卡号进行测试', 'yoone-subscriptions'), ), 'store_id' => array( 'title' => __('Store ID', 'yoone-subscriptions'), 'type' => 'text', 'description' => __('从 Moneris 获取的 Store ID', 'yoone-subscriptions'), 'default' => '', 'desc_tip' => true, ), 'api_token' => array( 'title' => __('API Token', 'yoone-subscriptions'), 'type' => 'password', 'description' => __('从 Moneris 获取的 API Token', 'yoone-subscriptions'), 'default' => '', 'desc_tip' => true, ), 'processing_country' => array( 'title' => __('处理国家', 'yoone-subscriptions'), 'type' => 'select', 'description' => __('选择处理支付的国家', 'yoone-subscriptions'), 'default' => 'CA', 'desc_tip' => true, 'options' => array( 'CA' => __('加拿大', 'yoone-subscriptions'), 'US' => __('美国', 'yoone-subscriptions'), ), ), ); } /** * 加载支付脚本 */ public function payment_scripts() { if (!is_cart() && !is_checkout() && !isset($_GET['pay_for_order'])) { return; } if ('no' === $this->enabled) { return; } if (empty($this->store_id) || empty($this->api_token)) { return; } wp_enqueue_script('yoone-moneris-js', YOONE_SUBSCRIPTIONS_PLUGIN_URL . 'assets/js/moneris-payment.js', array('jquery'), YOONE_SUBSCRIPTIONS_VERSION, true); wp_localize_script('yoone-moneris-js', 'yoone_moneris_params', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('yoone_moneris_nonce'), 'testmode' => $this->testmode, 'store_id' => $this->store_id, 'i18n' => array( 'invalid_card' => __('请输入有效的信用卡号', 'yoone-subscriptions'), 'invalid_cvv' => __('请输入有效的CVV', 'yoone-subscriptions'), 'invalid_date' => __('请输入有效的到期日期', 'yoone-subscriptions'), ) )); } /** * 支付字段 */ public function payment_fields() { if ($this->description) { echo wpautop(wptexturize($this->description)); } if ($this->testmode) { echo '
' . __('测试模式已启用。您可以使用测试卡号:4242424242424242', 'yoone-subscriptions') . '
'; } echo ''; } /** * 验证字段 */ public function validate_fields() { if (empty($_POST[$this->id . '-card-number'])) { wc_add_notice(__('请输入信用卡号', 'yoone-subscriptions'), 'error'); return false; } if (empty($_POST[$this->id . '-card-expiry'])) { wc_add_notice(__('请输入到期日期', 'yoone-subscriptions'), 'error'); return false; } if (empty($_POST[$this->id . '-card-cvc'])) { wc_add_notice(__('请输入CVV', 'yoone-subscriptions'), 'error'); return false; } return true; } /** * 处理支付 */ public function process_payment($order_id) { $order = wc_get_order($order_id); if (!$order) { return array( 'result' => 'failure', 'messages' => __('订单不存在', 'yoone-subscriptions') ); } // 检查是否包含订阅 $has_subscription = $this->order_contains_subscription($order); if ($has_subscription) { return $this->process_subscription_payment($order_id, $order->get_total()); } else { return $this->process_regular_payment($order); } } /** * 处理常规支付 */ protected function process_regular_payment($order) { $card_data = $this->get_card_data(); if (!$card_data) { return array( 'result' => 'failure', 'messages' => __('信用卡信息无效', 'yoone-subscriptions') ); } // 调用 Moneris Purchase API $response = $this->moneris_purchase($order, $card_data); if ($response && $response['success']) { // 支付成功 $order->payment_complete($response['transaction_id']); $order->add_order_note(sprintf(__('Moneris 支付完成。交易ID: %s', 'yoone-subscriptions'), $response['transaction_id'])); // 清空购物车 WC()->cart->empty_cart(); return array( 'result' => 'success', 'redirect' => $this->get_return_url($order) ); } else { $error_message = isset($response['message']) ? $response['message'] : __('支付处理失败', 'yoone-subscriptions'); wc_add_notice($error_message, 'error'); return array( 'result' => 'failure', 'messages' => $error_message ); } } /** * 处理订阅支付 */ public function process_subscription_payment($subscription_id, $amount) { // 获取订阅对象 if (is_numeric($subscription_id)) { $subscription = new Yoone_Subscription($subscription_id); } else { $order = wc_get_order($subscription_id); $subscription = $this->get_subscription_from_order($order); } if (!$subscription || !$subscription->get_id()) { return array( 'result' => 'failure', 'messages' => __('订阅不存在', 'yoone-subscriptions') ); } // 首次支付需要创建令牌 if (!$subscription->get_payment_token()) { return $this->process_initial_subscription_payment($subscription, $amount); } else { return $this->process_recurring_subscription_payment($subscription, $amount); } } /** * 处理首次订阅支付 */ protected function process_initial_subscription_payment($subscription, $amount) { $card_data = $this->get_card_data(); if (!$card_data) { return array( 'result' => 'failure', 'messages' => __('信用卡信息无效', 'yoone-subscriptions') ); } // 1. 添加信用卡到 Vault $vault_response = $this->moneris_res_add_cc($card_data); if (!$vault_response || !$vault_response['success']) { return array( 'result' => 'failure', 'messages' => __('创建支付令牌失败', 'yoone-subscriptions') ); } $data_key = $vault_response['data_key']; // 2. 使用令牌进行首次支付 $payment_response = $this->moneris_res_purchase_cc($subscription, $data_key, $amount); if ($payment_response && $payment_response['success']) { // 保存支付令牌到订阅 $subscription->set_payment_token($data_key); $subscription->save(); // 保存支付令牌到客户 $this->save_payment_token($subscription->get_customer_id(), $data_key, $card_data); // 激活订阅 $subscription->activate(); return array( 'result' => 'success', 'redirect' => wc_get_checkout_url() . '/order-received/' . $subscription->get_parent_order_id() . '/?key=' . wc_get_order($subscription->get_parent_order_id())->get_order_key() ); } else { // 删除创建的令牌 $this->moneris_res_delete($data_key); $error_message = isset($payment_response['message']) ? $payment_response['message'] : __('订阅支付失败', 'yoone-subscriptions'); return array( 'result' => 'failure', 'messages' => $error_message ); } } /** * 处理续费支付 */ protected function process_recurring_subscription_payment($subscription, $amount) { $data_key = $subscription->get_payment_token(); if (!$data_key) { return false; } // 使用保存的令牌进行支付 $response = $this->moneris_res_purchase_cc($subscription, $data_key, $amount); if ($response && $response['success']) { $subscription->add_log('payment_success', sprintf(__('续费支付成功。交易ID: %s', 'yoone-subscriptions'), $response['transaction_id'])); return true; } else { $error_message = isset($response['message']) ? $response['message'] : __('续费支付失败', 'yoone-subscriptions'); $subscription->add_log('payment_failed', $error_message); return false; } } /** * 创建支付令牌 */ public function create_payment_token($payment_data) { return $this->moneris_res_add_cc($payment_data); } /** * 删除支付令牌 */ public function delete_payment_token($token_id) { return $this->moneris_res_delete($token_id); } /** * 验证支付令牌 */ public function validate_payment_token($token_id) { $response = $this->moneris_res_lookup_full($token_id); return $response && $response['success']; } /** * 处理退款 */ public function process_refund($order_id, $amount = null, $reason = '') { $order = wc_get_order($order_id); if (!$order) { return false; } $transaction_id = $order->get_transaction_id(); if (!$transaction_id) { return new WP_Error('error', __('没有找到交易ID', 'yoone-subscriptions')); } $response = $this->moneris_refund($transaction_id, $amount, $reason); if ($response && $response['success']) { $order->add_order_note(sprintf(__('退款成功。金额: %s, 原因: %s', 'yoone-subscriptions'), wc_price($amount), $reason)); return true; } else { $error_message = isset($response['message']) ? $response['message'] : __('退款失败', 'yoone-subscriptions'); return new WP_Error('error', $error_message); } } /** * 预授权 */ public function process_preauth($order_id, $amount) { $order = wc_get_order($order_id); $card_data = $this->get_card_data(); return $this->moneris_preauth($order, $card_data, $amount); } /** * 完成预授权 */ public function complete_preauth($transaction_id, $amount) { return $this->moneris_completion($transaction_id, $amount); } /* |-------------------------------------------------------------------------- | Moneris API 调用 |-------------------------------------------------------------------------- */ /** * Moneris Purchase API */ protected function moneris_purchase($order, $card_data) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'order_id' => $order->get_id() . '-' . time(), 'amount' => number_format($order->get_total(), 2, '.', ''), 'pan' => $card_data['number'], 'expdate' => $card_data['exp_month'] . $card_data['exp_year'], 'cvd' => $card_data['cvc'], 'crypt_type' => '7' ); return $this->make_api_request('purchase', $data); } /** * Moneris Res Add CC API */ protected function moneris_res_add_cc($card_data) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'pan' => $card_data['number'], 'expdate' => $card_data['exp_month'] . $card_data['exp_year'], 'crypt_type' => '7' ); return $this->make_api_request('res_add_cc', $data); } /** * Moneris Res Purchase CC API */ protected function moneris_res_purchase_cc($subscription, $data_key, $amount) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'order_id' => 'sub-' . $subscription->get_id() . '-' . time(), 'amount' => number_format($amount, 2, '.', ''), 'data_key' => $data_key, 'crypt_type' => '1' ); return $this->make_api_request('res_purchase_cc', $data); } /** * Moneris Res Delete API */ protected function moneris_res_delete($data_key) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'data_key' => $data_key ); return $this->make_api_request('res_delete', $data); } /** * Moneris Res Lookup Full API */ protected function moneris_res_lookup_full($data_key) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'data_key' => $data_key ); return $this->make_api_request('res_lookup_full', $data); } /** * Moneris Refund API */ protected function moneris_refund($transaction_id, $amount, $reason = '') { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'order_id' => 'refund-' . $transaction_id . '-' . time(), 'amount' => number_format($amount, 2, '.', ''), 'txn_number' => $transaction_id, 'crypt_type' => '7' ); return $this->make_api_request('refund', $data); } /** * Moneris Preauth API */ protected function moneris_preauth($order, $card_data, $amount) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'order_id' => 'preauth-' . $order->get_id() . '-' . time(), 'amount' => number_format($amount, 2, '.', ''), 'pan' => $card_data['number'], 'expdate' => $card_data['exp_month'] . $card_data['exp_year'], 'cvd' => $card_data['cvc'], 'crypt_type' => '7' ); return $this->make_api_request('preauth', $data); } /** * Moneris Completion API */ protected function moneris_completion($transaction_id, $amount) { $data = array( 'store_id' => $this->store_id, 'api_token' => $this->api_token, 'order_id' => 'completion-' . $transaction_id . '-' . time(), 'comp_amount' => number_format($amount, 2, '.', ''), 'txn_number' => $transaction_id, 'crypt_type' => '7' ); return $this->make_api_request('completion', $data); } /** * 发起 API 请求 */ protected function make_api_request($endpoint, $data) { $url = $this->get_api_url(); $xml_data = $this->build_xml_request($endpoint, $data); $response = wp_remote_post($url, array( 'body' => $xml_data, 'headers' => array( 'Content-Type' => 'application/xml', 'User-Agent' => 'Yoone Subscriptions/' . YOONE_SUBSCRIPTIONS_VERSION ), 'timeout' => 30, 'sslverify' => !$this->testmode )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } return $this->parse_xml_response(wp_remote_retrieve_body($response)); } /** * 获取 API URL */ protected function get_api_url() { if ($this->testmode) { return $this->processing_country === 'US' ? 'https://esqa.moneris.com/gateway2/servlet/MpgRequest' : 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'; } else { return $this->processing_country === 'US' ? 'https://esp.moneris.com/gateway2/servlet/MpgRequest' : 'https://www3.moneris.com/gateway2/servlet/MpgRequest'; } } /** * 构建 XML 请求 */ protected function build_xml_request($endpoint, $data) { $xml = ''; $xml .= '