vr-shopxo-plugin/shopxo/extend/payment/OceanPayment.php

732 lines
30 KiB
PHP
Raw Normal View History

<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2099 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/mit-license.php )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace payment;
use app\service\PayLogService;
use app\service\ResourcesService;
use app\service\UserService;
use app\service\OrderService;
/**
* OceanPayment支付
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2018-09-19
* @desc description
*/
class OceanPayment
{
// 插件配置参数
private $config;
// 缓存key
private $cache_key = 'plugins_oceanpayment_pay_data_';
/**
* 构造方法
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2018-09-17
* @desc description
* @param [array] $params [输入参数(支付配置参数)]
*/
public function __construct($params = [])
{
$this->config = $params;
}
/**
* 配置信息
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2018-09-19
* @desc description
*/
public function Config()
{
// 基础信息
$base = [
'name' => 'OceanPayment', // 插件名称
'version' => '1.0.0', // 插件版本
'apply_version' => '不限', // 适用系统版本描述
'apply_terminal'=> ['pc','h5'], // 适用终端 默认全部 ['pc', 'h5', 'ios', 'android', 'alipay', 'weixin', 'baidu', 'toutiao']
'desc' => 'OceanPayment全球数字支付方案提供商适用PC+H5信用卡支付。 <a href="https://www.oceanpayment.com/?form=shopxo" target="_blank">立即申请</a>', // 插件描述支持html
'author' => 'Devil', // 开发者
'author_url' => 'http://shopxo.net/', // 开发者主页
];
// 配置信息
$element = [
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'account',
'placeholder' => 'account账户号',
'title' => 'account账户号',
'is_required' => 0,
'message' => '请填写account账户号',
],
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'terminal',
'placeholder' => 'terminal终端号',
'title' => 'terminal终端号',
'is_required' => 0,
'message' => '请填写terminal终端号',
],
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'secure_code',
'placeholder' => 'secureCode',
'title' => 'secureCode',
'is_required' => 0,
'message' => '请填写secureCode',
],
[
'element' => 'textarea',
'name' => 'key',
'placeholder' => 'Oceanpayment公钥',
'title' => 'Oceanpayment公钥',
'is_required' => 0,
'rows' => 4,
'message' => '请填写Oceanpayment公钥',
],
[
'element' => 'input',
'type' => 'text',
'default' => 'en_US',
'name' => 'lang',
'placeholder' => '语言',
'title' => '语言',
'is_required' => 0,
'message' => '请填写语言',
],
[
'element' => 'input',
'type' => 'text',
'default' => 'USD',
'name' => 'order_currency',
'placeholder' => '交易币种',
'title' => '交易币种',
'is_required' => 0,
'message' => '请填写交易币种',
],
[
'element' => 'input',
'type' => 'text',
'default' => 'US',
'name' => 'billing_country',
'placeholder' => '消费者的账单国家',
'title' => '消费者的账单国家',
'is_required' => 0,
'message' => '请填写消费者的账单国家',
],
[
'element' => 'input',
'type' => 'text',
'default' => 'N/A',
'name' => 'billing_state',
'placeholder' => '消费者的州(省、郡)',
'title' => '消费者的州(省、郡)',
'is_required' => 0,
'message' => '请填写消费者的州(省、郡)',
],
[
'element' => 'select',
'title' => '是否测试环境',
'message' => '请选择是否测试环境',
'name' => 'is_dev_env',
'is_multiple' => 0,
'element_data' => [
['value'=>0, 'name'=>'否'],
['value'=>1, 'name'=>'是'],
],
],
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'show_images',
'placeholder' => '自定义展示图片地址',
'title' => '自定义展示图片地址',
'is_required' => 0,
'message' => '请填写自定义展示图片地址',
],
[
'element' => 'input',
'type' => 'text',
'default' => '进入我的订单',
'name' => 'button_order_name',
'placeholder' => '进入我的订单按钮名称',
'title' => '进入我的订单按钮名称',
'is_required' => 0,
'message' => '请填写进入我的订单按钮名称',
],
[
'element' => 'input',
'type' => 'text',
'default' => '立即支付',
'name' => 'button_pay_name',
'placeholder' => '支付按钮名称',
'title' => '支付按钮名称',
'is_required' => 0,
'message' => '请填写支付按钮名称',
],
[
'element' => 'input',
'type' => 'text',
'default' => '支付金额:',
'name' => 'pay_first_name',
'placeholder' => '价格提示名称',
'title' => '价格提示名称',
'is_required' => 0,
'message' => '请填写价格提示名称',
],
];
return [
'base' => $base,
'element' => $element,
];
}
/**
* 支付入口
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2018-09-19
* @desc description
* @param [array] $params [输入参数]
*/
public function Pay($params = [])
{
// 参数
if(empty($params))
{
return DataReturn('参数不能为空', -1);
}
// 配置信息
if(empty($this->config) || empty($this->config['account']) || empty($this->config['terminal']) || empty($this->config['secure_code']) || empty($this->config['key']))
{
return DataReturn('支付缺少配置', -1);
}
// 订单地址
$address_data = (!empty($params['business_data']) && !empty($params['business_data'][0]) && !empty($params['business_data'][0]['address_data'])) ? $params['business_data'][0]['address_data'] : [];
$tel = empty($address_data) ? (empty($params['user']['mobile']) ? 'N/A' : $params['user']['mobile']) : $address_data['tel'];
$city = empty($address_data['city_name']) ? 'N/A' : $address_data['city_name'];
$address = empty($address_data['address']) ? 'N/A' : $address_data['address'];
// 支付数据
$parameter = [
'account' => $this->config['account'],
'terminal' => $this->config['terminal'],
'signValue' => '',
'key' => $this->config['key'],
'backUrl' => $params['call_back_url'],
'noticeUrl' => $params['notify_url'],
'methods' => 'Credit Card',
'order_number' => $params['order_no'],
'order_currency' => empty($this->config['order_currency']) ? 'USD' : $this->config['order_currency'],
'order_amount' => $params['total_price'],
'billing_firstName' => $params['user']['id'],
'billing_lastName' => $params['user']['user_name_view'],
'billing_email' => empty($params['user']['email']) ? $params['user']['id'].'@'.__MY_HOST__ : $params['user']['email'],
'billing_phone' => $tel,
'billing_country' => empty($this->config['billing_country']) ? 'US' : $this->config['billing_country'],
'billing_state' => empty($this->config['billing_state']) ? 'N/A' : $this->config['billing_state'],
'billing_city' => $city,
'billing_address' => $address,
'billing_zip' => 'N/A',
'billing_ip' => GetClientIP(),
'ship_firstName' => $params['user']['id'],
'ship_lastName' => $params['user']['user_name_view'],
'ship_email' => empty($params['user']['email']) ? $params['user']['id'].'@'.__MY_HOST__ : $params['user']['email'],
'ship_phone' => $tel,
'ship_country' => empty($this->config['billing_country']) ? 'US' : $this->config['billing_country'],
'ship_state' => empty($this->config['billing_state']) ? 'N/A' : $this->config['billing_state'],
'ship_city' => $city,
'ship_addr' => $address,
'ship_zip' => 'N/A',
'productSku' => $params['name'],
'productName' => $params['site_name'].'-'.$params['name'],
'productNum' => 1,
'productPrice' => $params['total_price'],
];
// 存储单号缓存
MyCache($this->cache_key.$params['user']['id'], $params['order_no'], 3600);
// 签名account+terminal+order_number+order_currency+order_amount+billing_firstName+billing_lastName+billing_email+secureCode
$parameter['signValue'] = hash('sha256', $parameter['account'].$parameter['terminal'].$parameter['order_number'].$parameter['order_currency'].$parameter['order_amount'].$parameter['billing_firstName'].$parameter['billing_lastName'].$parameter['billing_email'].$this->config['secure_code']);
// 支付请求记录
PayLogService::PayLogRequestRecord($params['order_no'], ['request_params'=>$parameter]);
die($this->PayHtml($parameter));
}
/**
* 支付代码
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2019-05-25T00:07:52+0800
* @param [array] $pay_data [支付信息]
*/
private function PayHtml($pay_data)
{
// 支付代码
$html = '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Oceanpayment Debit/Credit Card|One-Page Checkout</title>
<style type="text/css">
#payForm, .show-images {
width: 600px;
}
#payForm {
margin: 0 auto;
margin-top: 50px;
}
.header {
position: relative;
}
.logo {
max-height: 80px;
}
.price {
position: absolute;
right: 0;
bottom: 0;
color: #666;
}
.total-price {
color: #F44336;
font-size: 22px;
font-weight: bold;
}
.form-content {
border: 1px solid #f5f5f5;
border-radius: 10px;
padding: 50px;
margin-top: 20px;
}
.bottom {
text-align: right;
padding-right: 50px;
overflow: hidden;
}
.bottom .my-order {
text-decoration: none;
background-color: #fff;
border: 1px solid #ff5722;
color: #ff5722;
border-radius: 10px;
padding: 13px 0;
margin-right: 15px;
}
.bottom .pay-button {
background-color: #4caf50;
border: 1px solid #4caf50;
color: #fff;
border-radius: 10px;
padding: 16px 0;
cursor: pointer;
}
.bottom .my-order,
.bottom .pay-button {
display: block;
float: right;
width: 175px;
text-align: center;
font-size: 16px;
}
.show-images {
position: absolute;
left: auto;
bottom: 50px;
text-align: center;
}
.show-images img {
max-width: 100%;
max-height: 50px;
}
@media only screen and (max-width: 640px) {
#payForm, .show-images {
max-width: 100%;
}
.form-content {
padding: 50px 0;
}
}
</style>
</head>
<body>
<!--表单提交 -->
<form id="payForm">
<div class="header">
<img src="'.AttachmentPathViewHandle(MyC('home_site_logo')).'" alt="'.MyC('home_seo_site_title').'" class="logo" />
<span class="price">
<span>'.(empty($this->config['pay_first_name']) ? '支付金额:' : $this->config['pay_first_name']).'</span>
<string class="total-price">'.ResourcesService::CurrencyDataSymbol().$pay_data['order_amount'].'</string>
</span>
</div>
<div class="form-content">';
foreach($pay_data as $k=>$v)
{
$html .= '<input class="form-control" type="hidden" id="'.$k.'" name="'.$k.'" value="'.$v.'"/>';
}
$html .= '<!--加载Oceanpayment支付页面Div-->
<div id="oceanpayment-element"></div>
<div class="bottom">
<button type="button" class="pay-button" onclick="pay();" data-status="0">'.(empty($this->config['button_pay_name']) ? '立即支付' : $this->config['button_pay_name']).'</button>
<a href="'.MyUrl('index/order/index').'" class="my-order">'.(empty($this->config['button_order_name']) ? '进入我的订单' : $this->config['button_order_name']).'</a>
</div>
</div>';
if(!empty($this->config['show_images']))
{
$html .= '<div class="show-images">
<img src="'.$this->config['show_images'].'" />
</div>';
}
$html .= '</form>
</body>
</html>
<!-- 加载JS -->
<script src="https://secure.oceanpayment.com/pub/js/jquery/jq.js"></script>
<script src="https://secure.oceanpayment.com/pages/js/oceanpayment.js"></script>
<script>
// 初始化
$(function() {
Oceanpayment.init('.(isset($this->config['is_dev_env']) && $this->config['is_dev_env'] == 1 ? 'true' : '""').', "","'.(empty($this->config['lang']) ? 'en_US' : $this->config['lang']).'");
});
// 回调处理
var oceanpaymentCallBack = function(data) {
if(data.msg){
$(".pay-button").attr("data-status", 0);
console.log("pay reset");
// 处理卡信息错误
alert(data.msg);
}else{
// 处理下单返回
console.log(data);
// 加载xml文档
var xml_doc = (new DOMParser()).parseFromString(data, "text/xml");
// 将XML对象转换为JSON对象
var obj = {};
var response = xml_doc.getElementsByTagName("response")[0];
if(response == undefined) {
$(".pay-button").attr("data-status", 0);
console.log("pay reset");
alert("xml error");
return false;
}
for(var i = 0; i < response.children.length; i++) {
var child = response.children[i];
obj[child.nodeName] = child.textContent;
}
// 是否需要3d验证
if((obj.pay_url || null) != null) {
$(".pay-button").attr("data-status", 0);
console.log("pay reset");
window.location.href = obj.pay_url;
return false;
}
// 成功进入回调页面
var back_url = "'.$pay_data['backUrl'].'";
var params = Object.keys(obj).map(key => key + "=" + obj[key]).join("&");
var join = back_url.indexOf("?") == -1 ? "?" : "&";
var url = back_url+join+params;
window.location.href = url;
}
}
// 调用支付
function pay() {
var status = parseInt($(".pay-button").attr("data-status") || 0);
if(status == 1) {
console.log("progress");
return false;
}
console.log("pay...");
$(".pay-button").attr("data-status", 1);
// Jquery的serialize()方法
var formData = $("#payForm").serializeArray();
var obj = {}
for (var i in formData) {
obj[formData[i].name]=formData[i]["value"];
}
Oceanpayment.checkout(obj);
}
</script>';
return $html;
}
/**
* 支付回调处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2018-09-19
* @desc description
* @param [array] $params [输入参数]
*/
public function Respond($params = [])
{
// 接收参数
if(empty($params))
{
$params = MyInput();
}
// 同步返回
if(empty($params['order_number']))
{
// 用户信息
$user = UserService::LoginUserInfo();
if(empty($user))
{
return DataReturn('用户未登录', -1);
}
$order_no = MyCache($this->cache_key.$user['id']);
if(empty($order_no))
{
return DataReturn('支付单号为空', -1);
}
// 轮训5秒查询结果
$count = 5;
for($i=0; $i<$count; $i++)
{
// 暂停1秒钟等待异步通知处理
sleep(1);
// 查询订单状态
$ret = OrderService::OrderPayCheck(['order_no'=>$order_no, 'user'=>$user]);
if($ret['code'] == 0)
{
return $ret;
}
}
return DataReturn('支付失败,可能网络原因,请以订单状态为准', -1);
}
// 异步通知处理
$sign = strtoupper(hash('sha256', $this->config['account'].$this->config['terminal'].$params['order_number'].$params['order_currency'].$params['order_amount'].(empty($params['order_notes']) ? '' : $params['order_notes']).$params['card_number'].$params['payment_id'].$params['payment_authType'].$params['payment_status'].(empty($params['payment_details']) ? '' : $params['payment_details']).(empty($params['payment_risk']) ? '' : $params['payment_risk']).$this->config['secure_code']));
if($sign != strtoupper($params['signValue']))
{
return DataReturn('签名校验失败', -1);
}
$ret = DataReturn('支付失败', -1);
if(isset($params['payment_status']) && $params['payment_status'] == 1)
{
$ret = DataReturn('支付成功', 0, $this->ReturnData($params));
}
return $ret;
}
/**
* 返回数据统一格式
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2018-10-06T16:54:24+0800
* @param [array] $data [返回数据]
*/
private function ReturnData($data)
{
// 返回数据固定基础参数
$data['trade_no'] = $data['payment_id']; // 支付平台 - 订单号
$data['buyer_user'] = isset($data['pay_userId']) ? $data['pay_userId'] : ''; // 支付平台 - 用户
$data['out_trade_no'] = $data['order_number']; // 本系统发起支付的 - 订单号
$data['subject'] = isset($data['order_notes']) ? $data['order_notes'] : ''; // 本系统发起支付的 - 商品名称
$data['pay_price'] = $data['order_amount']; // 本系统发起支付的 - 总价
return $data;
}
/**
* 退款处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2019-05-28
* @desc description
* @param [array] $params [输入参数]
*/
public function Refund($params = [])
{
// 参数
$p = [
[
'checked_type' => 'empty',
'key_name' => 'order_no',
'error_msg' => '订单号不能为空',
],
[
'checked_type' => 'empty',
'key_name' => 'trade_no',
'error_msg' => '交易平台订单号不能为空',
],
[
'checked_type' => 'empty',
'key_name' => 'refund_price',
'error_msg' => '退款金额不能为空',
],
];
$ret = ParamsChecked($params, $p);
if($ret !== true)
{
return DataReturn($ret, -1);
}
// 退款原因
$refund_reason = empty($params['refund_reason']) ? $params['order_no'].'订单退款'.$params['refund_price'].'元' : $params['refund_reason'];
// 退款参数
$parameter = [
'account' => $this->config['account'],
'terminal' => $this->config['terminal'],
'refund_number' => $params['order_no'].GetNumberCode(),
'out_trade_no' => $params['order_no'],
'payment_id' => $params['trade_no'],
'refund_type' => ($params['refund_price'] < $params['pay_price']) ? 2 : 1,
'refund_amount' => $params['refund_price'],
'refund_description' => $refund_reason,
];
// 签名account+terminal+payment_id+refund_number+refund_type+refund_amount+refund_description+secureCode
$parameter['signValue'] = hash('sha256', $parameter['account'].$parameter['terminal'].$parameter['payment_id'].$parameter['refund_number'].$parameter['refund_type'].$parameter['refund_amount'].$parameter['refund_description'].$this->config['secure_code']);
// 请求接口
$ret = CurlPost($this->RequestUrl('service/applyRefund'), $parameter);
if($ret['code'] != 0)
{
return $ret;
}
// xml处理
$data = $this->XmlToArray($ret['data']);
if(isset($data['refund_results']) && $data['refund_results'] == '00')
{
// 统一返回格式
$data = [
'out_trade_no' => isset($data['order_number']) ? $data['order_number'] : '',
'trade_no' => isset($data['refund_id']) ? $data['refund_id'] : '',
'buyer_user' => '',
'refund_price' => $params['refund_price'],
'return_params' => $data,
'request_params' => $parameter,
];
return DataReturn('退款成功', 0, $data);
}
$msg = empty($data['refund_description']) ? '退款失败' : $data['refund_description'];
return DataReturn($msg, -1);
}
/**
* 请求url
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-07-03
* @desc description
* @param [string] $path [接口路径]
*/
public function RequestUrl($path)
{
$url = 'https://query.oceanpayment.com/';
if(isset($this->config['is_dev_env']) && $this->config['is_dev_env'] == 1)
{
$url = 'https://test-query.oceanpayment.com/';
}
return $url.$path;
}
/**
* xml转数组
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2019-01-07
* @desc description
* @param [string] $xml [xm数据]
*/
private function XmlToArray($xml)
{
if(!$this->XmlParser($xml))
{
return is_string($xml) ? $xml : '接口返回数据有误';
}
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
/**
* 判断字符串是否为xml格式
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2019-01-07
* @desc description
* @param [string] $string [字符串]
*/
function XmlParser($string)
{
$xml_parser = xml_parser_create();
if(!xml_parse($xml_parser, $string, true))
{
xml_parser_free($xml_parser);
return false;
} else {
return (json_decode(json_encode(simplexml_load_string($string)),true));
}
}
/**
* 自定义成功返回内容
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2020-07-01
* @desc description
*/
public function SuccessReturn()
{
return 'receive-ok';
}
/**
* 自定义失败返回内容
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2020-07-01
* @desc description
*/
public function ErrorReturn()
{
return 'receive-no';
}
}
?>