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

430 lines
15 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;
/**
* UniPayment支付
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
*/
class UniPayment
{
// 插件配置参数
private $config;
// 订单信息存储缓存key
private $pay_data_cache_key = 'payment_unipayment_pay_data_';
// 返回地址存储缓存key
private $respond_url_cache_key = 'payment_unipayment_respond_url_cache_key_';
/**
* 构造方法
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数(支付配置参数)]
*/
public function __construct($params = [])
{
$this->config = $params;
}
/**
* 配置信息
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
*/
public function Config()
{
// 基础信息
$base = [
'name' => 'UniPayment', // 插件名称
'version' => '1.0.0', // 插件版本
'apply_version' => '不限', // 适用系统版本描述
'apply_terminal'=> ['pc', 'h5'], // 适用终端 默认全部 ['pc', 'h5', 'ios', 'android', 'alipay', 'weixin', 'baidu', 'toutiao']
'desc' => '适配PC+H5、使用 UniPayment 接受和管理加密货币付款<a href="https://www.unipayment.io/" target="_blank">立即申请</a>', // 插件描述支持html
'author' => 'Devil', // 开发者
'author_url' => 'http://shopxo.net/', // 开发者主页
];
// 配置信息
$element = [
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'app_id',
'placeholder' => 'AppId',
'title' => 'AppId',
'is_required' => 0,
'message' => '请填写AppId',
],
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'client_id',
'placeholder' => 'ClientID',
'title' => 'ClientID',
'is_required' => 0,
'message' => '请填写ClientID',
],
[
'element' => 'input',
'type' => 'text',
'default' => '',
'name' => 'client_secret',
'placeholder' => 'ClientSecret',
'title' => 'ClientSecret',
'is_required' => 0,
'message' => '请填写ClientSecret',
],
[
'element' => 'select',
'title' => '价格货币(默认 USD',
'message' => '请选择价格货币',
'name' => 'price_currency',
'is_multiple' => 0,
'element_data' => [
['value'=>'BTC', 'name'=>'Bitcoin'],
['value'=>'SGD', 'name'=>'Singapore Dollar'],
['value'=>'ETH', 'name'=>'Ethereum'],
['value'=>'USDT', 'name'=>'USDT'],
['value'=>'USDC', 'name'=>'USDC'],
['value'=>'CNY', 'name'=>'Yuan Renminbi'],
['value'=>'USD', 'name'=>'US Dollar'],
['value'=>'HKD', 'name'=>'Hong Kong Dollar'],
],
],
[
'element' => 'select',
'title' => '支付货币(默认 USDT',
'message' => '请选择支付货币',
'name' => 'pay_currency',
'is_multiple' => 0,
'element_data' => [
['value'=>'BTC', 'name'=>'Bitcoin'],
['value'=>'SGD', 'name'=>'Singapore Dollar'],
['value'=>'ETH', 'name'=>'Ethereum'],
['value'=>'USDT', 'name'=>'USDT'],
['value'=>'USDC', 'name'=>'USDC'],
['value'=>'CNY', 'name'=>'Yuan Renminbi'],
['value'=>'USD', 'name'=>'US Dollar'],
['value'=>'HKD', 'name'=>'Hong Kong Dollar'],
],
],
];
return [
'base' => $base,
'element' => $element,
];
}
/**
* 支付入口
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数]
*/
public function Pay($params = [])
{
// 参数
if(empty($params))
{
return DataReturn('参数不能为空', -1);
}
// 配置信息
if(empty($this->config) || empty($this->config['app_id']) || empty($this->config['client_id']) || empty($this->config['client_secret']))
{
return DataReturn('支付缺少配置', -1);
}
// 是否存在返回地址
$respond_url = (!empty($params['params']) && !empty($params['params']['respond_url'])) ? base64_decode(urldecode($params['params']['respond_url'])) : null;
MyCache($this->respond_url_cache_key.$params['order_no'], $respond_url, 3600);
// 请求数据
$parameter = [
'app_id' => $this->config['app_id'],
'title' => ChinesePinyin($params['name'], true),
'lang' => 'en',
'price_amount' => $params['total_price'],
'price_currency' => empty($this->config['price_currency']) ? 'USD' : $this->config['price_currency'],
'pay_currency' => empty($this->config['pay_currency']) ? 'USDT' : $this->config['pay_currency'],
'notify_url' => $params['notify_url'],
'redirect_url' => $params['call_back_url'],
'order_id' => $params['order_no'],
];
// 支付请求记录
PayLogService::PayLogRequestRecord($params['order_no'], ['request_params'=>$parameter]);
// 请求接口
$ret = $this->HttpRequest('invoices', $parameter);
if($ret['code'] != 0)
{
return $ret;
}
if(!empty($ret['data']['data']) && !empty($ret['data']['data']['invoice_url']))
{
// 订单信息存储缓存
$key = $this->pay_data_cache_key.$parameter['order_id'];
MyCache($key, [
'params' => $params,
'data' => $parameter,
'respond' => $ret['data'],
'client_type' => APPLICATION_CLIENT_TYPE,
], 3600);
// 直接返回支付url地址
return DataReturn('success', 0, $ret['data']['data']['invoice_url']);
}
return DataReturn('支付单创建失败', -1);
}
/**
* 同步回调处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数]
*/
public function Respond($params = [])
{
// 回调处理
$ret = $this->RespondHandle($params);
if($ret['code'] != 0)
{
return $ret;
}
// 是否自定义返回地址
$key = $this->respond_url_cache_key.$ret['data']['out_trade_no'];
$respond_url = MyCache($key);
if(!empty($respond_url))
{
MyCache($key, null);
MyRedirect($respond_url, true);
}
// 返回回调处理数据
return $ret;
}
/**
* 异步回调处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数]
*/
public function Notify($params = [])
{
return $this->RespondHandle($params);
}
/**
* 回调数据处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数]
*/
public function RespondHandle($params = [])
{
// 回调订单id
$order_id = empty($params['orderId']) ? (empty($params['order_id']) ? '' : $params['order_id']) : $params['orderId'];
if(empty($order_id))
{
return DataReturn('回调订单id为空', -1);
}
$pay_data = MyCache($this->pay_data_cache_key.$order_id);
if(empty($pay_data))
{
return DataReturn('支付数据失效、请重新发起支付', -1);
}
// 创建事件则存储缓存
if(!empty($params['event']))
{
$pay_data['invoice_created_data'] = $params;
MyCache($this->pay_data_cache_key.$order_id, $pay_data);
}
// 仅一个参数则使用创建存储的数据作为参数
if(count($params) == 1 && !empty($pay_data['invoice_created_data']))
{
$params = $pay_data['invoice_created_data'];
}
// 事件
$event = empty($params['event']) ? '' : $params['event'];
// 非完成和则不处理
if(!in_array($event, ['invoice_markedComplete', 'invoice_completed']))
{
return DataReturn('未支付、事件('.$event.')', -1);
}
// 验证订单
$ret = $this->HttpRequest('ipn', $params);
if($ret['code'] != 0)
{
return $ret;
}
// 返回数据固定基础参数
$data = $params;
$data['trade_no'] = isset($params['invoice_id']) ? $params['invoice_id'] : $pay_data['respond']['data']['invoice_id']; // 支付平台 - 订单号
$data['buyer_user'] = ''; // 支付平台 - 用户
$data['out_trade_no'] = $order_id; // 本系统发起支付的 - 订单号
$data['subject'] = $pay_data['data']['title']; // 本系统发起支付的 - 商品名称
$data['pay_price'] = isset($params['price_amount']) ? $params['price_amount'] : $pay_data['respond']['data']['price_amount']; // 本系统发起支付的 - 总价
return DataReturn('支付成功', 0, $data);
}
/**
* 支付回调处理
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [array] $params [输入参数]
*/
public function Refund($params = [])
{
return DataReturn('UniPayment不支持接口退款、请到UniPayment商户平台处理', -1);
}
/**
* 网络请求
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [string] $query [请求方法]
* @param [string] $data [请求数据]
* @param [int] $second [超时时间、秒]
*/
private function HttpRequest($query, $data, $second = 30)
{
// url地址
$url = $this->RequestUrl($query);
// 数据为数组则转json
$data = is_array($data) ? json_encode($data) : $data;
// 头信息
$header = [
'Content-Length: '.strlen($data),
'Accept: application/json',
'Content-Type: application/json',
'Authorization: Hmac '.$this->RequestSign($url, $data),
];
// 初始化curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
$result = curl_exec($ch);
if($result)
{
curl_close($ch);
$result = json_decode($result, true);
if(empty($result))
{
return DataReturn('返回数据有误:'.$result, -1);
}
$code = isset($result['Code']) ? $result['Code'] : $result['code'];
if($code != 'OK')
{
$msg = empty($result['Msg']) ? $result['msg'] : $result['Msg'];
return DataReturn('返回失败:'.$msg.'('.$code.')', -1, $code);
}
return DataReturn('success', 0, $result);
}
$error = curl_errno($ch);
curl_close($ch);
return DataReturn('curl出错错误码:'.$error, -1);
}
/**
* 请求签名
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
* @param [string] $url [请求url]
* @param [string] $body [请求内容]
* @param [string] $method [请求类型]
*/
protected function RequestSign($url, $body, $method = 'POST')
{
$request_url = urlencode(strtolower($url));
$content_string = '';
if($body !== '')
{
$hashed_body = md5($body, true);
$content_string = base64_encode($hashed_body);
}
$time = time();
$nonce = str_replace('-', '', uniqid(32));
$signature_raw_data = $this->config['client_id'] . $method = 'POST' . $request_url . $time . $nonce
. $content_string;
$signature = hash_hmac('sha256', $signature_raw_data, $this->config['client_secret'], true);
return $this->config['client_id'] . ':' . base64_encode($signature) . ':' . $nonce . ':' . $time;
}
/**
* 请求地址
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2023-01-27
* @desc description
*/
public function RequestUrl($query)
{
return in_array(substr($query, 0, 6), ['https:', 'http:/']) ? $query : 'https://api.unipayment.io/v1.0/'.$query;
}
}
?>