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

732 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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';
}
}
?>