842 lines
29 KiB
PHP
842 lines
29 KiB
PHP
<?php
|
||
/**
|
||
* Moneris 支付网关类
|
||
*
|
||
* 处理 Moneris 支付集成和订阅支付
|
||
*/
|
||
|
||
if (!defined('ABSPATH')) {
|
||
exit;
|
||
}
|
||
|
||
/**
|
||
* Moneris 支付网关类
|
||
*/
|
||
class Yoone_Moneris_Gateway extends WC_Payment_Gateway implements Interface_Yoone_Payment_Gateway {
|
||
|
||
/**
|
||
* 网关ID
|
||
*/
|
||
public $id = 'yoone_moneris';
|
||
|
||
/**
|
||
* 支持的功能
|
||
*/
|
||
public $supports = array(
|
||
'products',
|
||
'subscriptions',
|
||
'subscription_cancellation',
|
||
'subscription_suspension',
|
||
'subscription_reactivation',
|
||
'subscription_amount_changes',
|
||
'subscription_date_changes',
|
||
'multiple_subscriptions',
|
||
'refunds',
|
||
'pre-orders'
|
||
);
|
||
|
||
/**
|
||
* 测试模式
|
||
*/
|
||
protected $testmode;
|
||
|
||
/**
|
||
* API 凭据
|
||
*/
|
||
protected $store_id;
|
||
protected $api_token;
|
||
protected $processing_country;
|
||
|
||
/**
|
||
* 构造函数
|
||
*/
|
||
public function __construct() {
|
||
$this->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 '<p class="testmode-info">' . __('测试模式已启用。您可以使用测试卡号:4242424242424242', 'yoone-subscriptions') . '</p>';
|
||
}
|
||
|
||
echo '<fieldset id="wc-' . esc_attr($this->id) . '-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">';
|
||
|
||
// 如果支持令牌化,显示保存的支付方式
|
||
if ($this->supports('tokenization') && is_checkout()) {
|
||
$this->tokenization_script();
|
||
$this->saved_payment_methods();
|
||
}
|
||
|
||
echo '<div class="wc-credit-card-form-card-number">
|
||
<label for="' . esc_attr($this->id) . '-card-number">' . __('卡号', 'yoone-subscriptions') . ' <span class="required">*</span></label>
|
||
<input id="' . esc_attr($this->id) . '-card-number" class="input-text wc-credit-card-form-card-number" type="text" maxlength="20" autocomplete="cc-number" placeholder="•••• •••• •••• ••••" name="' . esc_attr($this->id) . '-card-number" />
|
||
</div>';
|
||
|
||
echo '<div class="wc-credit-card-form-card-expiry">
|
||
<label for="' . esc_attr($this->id) . '-card-expiry">' . __('到期日期 (MM/YY)', 'yoone-subscriptions') . ' <span class="required">*</span></label>
|
||
<input id="' . esc_attr($this->id) . '-card-expiry" class="input-text wc-credit-card-form-card-expiry" type="text" autocomplete="cc-exp" placeholder="MM / YY" name="' . esc_attr($this->id) . '-card-expiry" />
|
||
</div>';
|
||
|
||
echo '<div class="wc-credit-card-form-card-cvc">
|
||
<label for="' . esc_attr($this->id) . '-card-cvc">' . __('CVV', 'yoone-subscriptions') . ' <span class="required">*</span></label>
|
||
<input id="' . esc_attr($this->id) . '-card-cvc" class="input-text wc-credit-card-form-card-cvc" type="text" autocomplete="cc-csc" placeholder="CVV" name="' . esc_attr($this->id) . '-card-cvc" />
|
||
</div>';
|
||
|
||
// 保存支付方式选项
|
||
if ($this->supports('tokenization') && is_checkout() && !is_add_payment_method_page()) {
|
||
echo '<div class="wc-credit-card-form-save-payment-method">
|
||
<input id="wc-' . esc_attr($this->id) . '-new-payment-method" name="wc-' . esc_attr($this->id) . '-new-payment-method" type="checkbox" value="true" style="width:auto;" />
|
||
<label for="wc-' . esc_attr($this->id) . '-new-payment-method" style="display:inline;">' . __('保存支付方式以便将来使用', 'yoone-subscriptions') . '</label>
|
||
</div>';
|
||
}
|
||
|
||
echo '<div class="clear"></div></fieldset>';
|
||
}
|
||
|
||
/**
|
||
* 验证字段
|
||
*/
|
||
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 version="1.0"?>';
|
||
$xml .= '<request>';
|
||
$xml .= '<store_id>' . htmlspecialchars($data['store_id']) . '</store_id>';
|
||
$xml .= '<api_token>' . htmlspecialchars($data['api_token']) . '</api_token>';
|
||
$xml .= '<' . $endpoint . '>';
|
||
|
||
foreach ($data as $key => $value) {
|
||
if ($key !== 'store_id' && $key !== 'api_token') {
|
||
$xml .= '<' . $key . '>' . htmlspecialchars($value) . '</' . $key . '>';
|
||
}
|
||
}
|
||
|
||
$xml .= '</' . $endpoint . '>';
|
||
$xml .= '</request>';
|
||
|
||
return $xml;
|
||
}
|
||
|
||
/**
|
||
* 解析 XML 响应
|
||
*/
|
||
protected function parse_xml_response($xml_string) {
|
||
$xml = simplexml_load_string($xml_string);
|
||
|
||
if (!$xml) {
|
||
return array(
|
||
'success' => false,
|
||
'message' => __('无效的响应格式', 'yoone-subscriptions')
|
||
);
|
||
}
|
||
|
||
$response_code = (string) $xml->receipt->ResponseCode;
|
||
$success = $response_code !== null && intval($response_code) < 50;
|
||
|
||
$result = array(
|
||
'success' => $success,
|
||
'response_code' => $response_code,
|
||
'message' => (string) $xml->receipt->Message,
|
||
'transaction_id' => (string) $xml->receipt->TransID,
|
||
'reference_num' => (string) $xml->receipt->ReferenceNum,
|
||
'data_key' => (string) $xml->receipt->DataKey
|
||
);
|
||
|
||
return $result;
|
||
}
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| 辅助方法
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
/**
|
||
* 获取信用卡数据
|
||
*/
|
||
protected function get_card_data() {
|
||
if (empty($_POST[$this->id . '-card-number'])) {
|
||
return false;
|
||
}
|
||
|
||
$card_number = str_replace(' ', '', $_POST[$this->id . '-card-number']);
|
||
$card_expiry = $_POST[$this->id . '-card-expiry'];
|
||
$card_cvc = $_POST[$this->id . '-card-cvc'];
|
||
|
||
// 解析到期日期
|
||
$expiry_parts = explode('/', str_replace(' ', '', $card_expiry));
|
||
if (count($expiry_parts) !== 2) {
|
||
return false;
|
||
}
|
||
|
||
$exp_month = str_pad($expiry_parts[0], 2, '0', STR_PAD_LEFT);
|
||
$exp_year = strlen($expiry_parts[1]) === 2 ? '20' . $expiry_parts[1] : $expiry_parts[1];
|
||
$exp_year = substr($exp_year, -2); // Moneris 需要 2 位年份
|
||
|
||
return array(
|
||
'number' => $card_number,
|
||
'exp_month' => $exp_month,
|
||
'exp_year' => $exp_year,
|
||
'cvc' => $card_cvc
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 保存支付令牌
|
||
*/
|
||
protected function save_payment_token($customer_id, $data_key, $card_data) {
|
||
global $wpdb;
|
||
|
||
// 获取卡片信息
|
||
$lookup_response = $this->moneris_res_lookup_full($data_key);
|
||
|
||
$card_type = '';
|
||
$last_four = '';
|
||
|
||
if ($lookup_response && $lookup_response['success']) {
|
||
// 从响应中提取卡片信息
|
||
$card_type = $this->get_card_type($card_data['number']);
|
||
$last_four = substr($card_data['number'], -4);
|
||
}
|
||
|
||
return $wpdb->insert(
|
||
$wpdb->prefix . 'yoone_payment_tokens',
|
||
array(
|
||
'customer_id' => $customer_id,
|
||
'gateway_id' => $this->id,
|
||
'token' => $data_key,
|
||
'token_type' => 'credit_card',
|
||
'card_type' => $card_type,
|
||
'last_four' => $last_four,
|
||
'expiry_month' => $card_data['exp_month'],
|
||
'expiry_year' => '20' . $card_data['exp_year'],
|
||
'is_default' => 0,
|
||
'created_at' => current_time('mysql'),
|
||
'updated_at' => current_time('mysql')
|
||
),
|
||
array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s')
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取卡片类型
|
||
*/
|
||
protected function get_card_type($card_number) {
|
||
$card_number = str_replace(' ', '', $card_number);
|
||
|
||
if (preg_match('/^4/', $card_number)) {
|
||
return 'visa';
|
||
} elseif (preg_match('/^5[1-5]/', $card_number)) {
|
||
return 'mastercard';
|
||
} elseif (preg_match('/^3[47]/', $card_number)) {
|
||
return 'amex';
|
||
}
|
||
|
||
return 'unknown';
|
||
}
|
||
|
||
/**
|
||
* 检查订单是否包含订阅
|
||
*/
|
||
protected function order_contains_subscription($order) {
|
||
foreach ($order->get_items() as $item) {
|
||
$product = $item->get_product();
|
||
if ($product && $product->get_meta('_yoone_subscription_enabled') === 'yes') {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 从订单获取订阅
|
||
*/
|
||
protected function get_subscription_from_order($order) {
|
||
global $wpdb;
|
||
|
||
$subscription_id = $wpdb->get_var($wpdb->prepare("
|
||
SELECT id FROM {$wpdb->prefix}yoone_subscriptions
|
||
WHERE parent_order_id = %d
|
||
LIMIT 1
|
||
", $order->get_id()));
|
||
|
||
return $subscription_id ? new Yoone_Subscription($subscription_id) : null;
|
||
}
|
||
|
||
/**
|
||
* 计划订阅支付
|
||
*/
|
||
public function scheduled_subscription_payment($amount_to_charge, $renewal_order) {
|
||
$subscription = $this->get_subscription_from_order($renewal_order);
|
||
|
||
if ($subscription) {
|
||
$result = $this->process_recurring_subscription_payment($subscription, $amount_to_charge);
|
||
|
||
if ($result) {
|
||
$renewal_order->payment_complete();
|
||
} else {
|
||
$renewal_order->update_status('failed');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 检查是否可用
|
||
*/
|
||
public function is_available() {
|
||
return parent::is_available() && !empty($this->store_id) && !empty($this->api_token);
|
||
}
|
||
} |