vr-shopxo-plugin/shopxo/app/plugins/wallet/service/CashAlipayService.php

411 lines
14 KiB
PHP

<?php
// +----------------------------------------------------------------------
// | ShopXO 国内领先企业级B2C免费开源电商系统
// +----------------------------------------------------------------------
// | Copyright (c) 2011~2099 http://shopxo.net All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( https://opensource.org/licenses/mit-license.php )
// +----------------------------------------------------------------------
// | Author: Devil
// +----------------------------------------------------------------------
namespace app\plugins\wallet\service;
use think\facade\Db;
use app\service\PluginsDataConfigService;
use app\plugins\wallet\service\CashPaymentService;
/**
* 钱包 - 钱包余额提现到支付宝服务层
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2020-09-04
* @desc description
*/
class CashAlipayService
{
// 配置信息
public static $config;
/**
* 支付宝转账创建
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-19
* @desc description
* @param [array] $cash [提现数据]
* @param [array] $params [输入参数]
*/
public static function TransferCreate($cash, $params = [])
{
// 数据配置
self::$config = PluginsDataConfigService::DataConfigData('wallet');
// 请求参数
$p = [
[
'checked_type' => 'empty',
'key_name' => 'alipay_appid',
'error_msg' => 'appid未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'alipay_cert_content',
'error_msg' => '应用证书未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'alipay_out_root_cert_content',
'error_msg' => '支付宝ROOT证书未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'alipay_out_cert_content',
'error_msg' => '支付宝证书未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'alipay_rsa_private',
'error_msg' => '应用私钥未配置',
],
];
$ret = ParamsChecked(self::$config, $p);
if($ret !== true)
{
return DataReturn($ret, -1);
}
if(empty($cash['bank_accounts']))
{
return DataReturn('支付宝账户为空', -1);
}
if(empty($cash['bank_username']))
{
return DataReturn('支付宝账户姓名为空', -1);
}
// 转账数据
$data = [
'app_id' => self::$config['alipay_appid'],
'method' => 'alipay.fund.trans.uni.transfer',
'format' => 'JSON',
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'app_cert_sn' => self::GetCertSNFromContent(self::$config['alipay_cert_content']),
'alipay_root_cert_sn' => self::GetRootCertSNFromContent(self::$config['alipay_out_root_cert_content']),
];
$pay_no = $cash['cash_no'].GetNumberCode(6);
$biz_content = [
'out_biz_no' => $pay_no,
'trans_amount' => $cash['money'],
'biz_scene' => 'DIRECT_TRANSFER',
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
'order_title' => '钱包提现',
'payee_info' => [
'identity' => $cash['bank_accounts'],
'identity_type' => 'ALIPAY_LOGON_ID',
'name' => $cash['bank_username'],
],
'business_params'=> '{"payer_show_name_use_alias":"true"}',
];
$data['biz_content'] = json_encode($biz_content, JSON_UNESCAPED_UNICODE);
// 生成签名参数+签名
$data['sign'] = self::MyRsaSign(self::GetSignContent($data));
// 先释放原来为处理的数据
CashPaymentService::CashPaymentRelease($cash);
// 转账数据添加
$insert = CashPaymentService::CashPaymentInsert($cash, $pay_no, $data, $params);
if($insert['code'] != 0)
{
return $insert;
}
$start_time = time();
// 执行请求
$ret = self::HttpRequest('https://openapi.alipay.com/gateway.do', $data);
$key = str_replace('.', '_', $data['method']).'_response';
// 回调处理
$status = 3;
$reason = '异常错误';
$out_order_no = '';
if(isset($ret[$key]['code']) && $ret[$key]['code'] == 10000)
{
// 验证签名
if(self::SyncRsaVerify($ret, $key))
{
$status = 2;
$reason = '';
$out_order_no = $ret[$key]['order_id'];
} else {
$reason = '回调签名验证错误';
}
} else {
$reason = $ret[$key]['sub_msg'].'['.$ret[$key]['sub_code'].']';
}
// 转账数据回调
return CashPaymentService::CashPaymentResponse($cash, $insert['data'], $start_time, $ret, $status, $reason, $out_order_no);
}
/**
* 获取签名内容
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2019-03-15
* @desc description
* @param [array] $params [需要签名的参数]
*/
public static function GetSignContent($params)
{
ksort($params);
$string = "";
$i = 0;
foreach($params as $k => $v)
{
if(!empty($v) && "@" != substr($v, 0, 1))
{
if ($i == 0) {
$string .= "$k" . "=" . "$v";
} else {
$string .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
unset($k, $v);
return $string;
}
/**
* 签名字符串
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2017-09-24T08:38:28+0800
* @param [string] $prestr [需要签名的字符串]
* @return [string] [签名结果]
*/
public static function MyRsaSign($prestr)
{
if(stripos(self::$config['alipay_rsa_private'], '-----') === false)
{
$res = "-----BEGIN RSA PRIVATE KEY-----\n";
$res .= wordwrap(self::$config['alipay_rsa_private'], 64, "\n", true);
$res .= "\n-----END RSA PRIVATE KEY-----";
} else {
$res = self::$config['alipay_rsa_private'];
}
return openssl_sign($prestr, $sign, $res, OPENSSL_ALGO_SHA256) ? base64_encode($sign) : null;
}
/**
* 从证书内容中提取序列号
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2021-09-15
* @desc description
* @param [string] $cert_content [证书内容发]
* @return [string] [序列号]
*/
public static function GetCertSNFromContent($cert_content)
{
$ssl = openssl_x509_parse($cert_content);
return md5(self::ArrayToString(array_reverse($ssl['issuer'])).$ssl['serialNumber']);
}
/**
* 数组转字符串
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2021-09-15
* @desc description
* @param [array] $array [数组]
* @return [string] [字符串]
*/
public static function ArrayToString($array)
{
$string = [];
if ($array && is_array($array))
{
foreach($array as $key=>$value)
{
$string[] = $key.'='.$value;
}
}
return implode(',', $string);
}
/**
* 提取根证书序列号
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2021-09-15
* @desc description
* @param [string] $cert_content [证书]
* @return [string] [序列号]
*/
public static function GetRootCertSNFromContent($cert_content)
{
$array = explode("-----END CERTIFICATE-----", $cert_content);
$sn = null;
for($i = 0; $i < count($array) - 1; $i++)
{
$ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
if(strpos($ssl[$i]['serialNumber'],'0x') === 0){
$ssl[$i]['serialNumber'] = self::Hex2dec($ssl[$i]['serialNumberHex']);
}
if($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption")
{
if($sn == null)
{
$sn = md5(self::ArrayToString(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
} else {
$sn = $sn . "_" . md5(self::ArrayToString(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
}
}
}
return $sn;
}
/**
* 0x转高精度数字
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2021-09-15
* @desc description
* @param [int] $hex [数字]
* @return [int|string] [转换的数据]
*/
public static function Hex2dec($hex)
{
$dec = 0;
$len = strlen($hex);
for($i = 1; $i <= $len; $i++)
{
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
/**
* 从证书中提取公钥
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2021-10-11
* @desc description
* @return [string] [公钥]
*/
public static function GetPublicKey()
{
$pkey = openssl_pkey_get_public(self::$config['alipay_out_cert_content']);
$keyData = openssl_pkey_get_details($pkey);
$public_key = str_replace('-----BEGIN PUBLIC KEY-----', '', $keyData['key']);
$public_key = trim(str_replace('-----END PUBLIC KEY-----', '', $public_key));
return $public_key;
}
/**
* 同步返回签名验证
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2017-09-25T13:13:39+0800
* @param [array] $data [返回数据]
* @param [boolean] $key [数据key]
*/
public static function SyncRsaVerify($data, $key)
{
$string = json_encode($data[$key], JSON_UNESCAPED_UNICODE);
return self::OutRsaVerify($string, $data['sign']);
}
/**
* 支付宝验证签名
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2017-09-24T08:39:50+0800
* @param [string] $prestr [需要签名的字符串]
* @param [string] $sign [签名结果]
* @return [boolean] [正确true, 错误false]
*/
public static function OutRsaVerify($prestr, $sign)
{
$public_key = self::GetPublicKey();
if(stripos($public_key, '-----') === false)
{
$res = "-----BEGIN PUBLIC KEY-----\n";
$res .= wordwrap($public_key, 64, "\n", true);
$res .= "\n-----END PUBLIC KEY-----";
} else {
$res = $public_key;
}
$pkeyid = openssl_pkey_get_public($res);
$sign = base64_decode($sign);
if($pkeyid)
{
$verify = openssl_verify($prestr, $sign, $pkeyid, OPENSSL_ALGO_SHA256);
unset($pkeyid);
}
return (isset($verify) && $verify == 1) ? true : false;
}
/**
* 网络请求
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @datetime 2017-09-25T09:10:46+0800
* @param [string] $url [请求url]
* @param [array] $data [发送数据]
* @return [mixed] [请求返回数据]
*/
public static function HttpRequest($url, $data)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$body_string = '';
if(is_array($data) && 0 < count($data))
{
foreach($data as $k => $v)
{
$body_string .= $k.'='.urlencode($v).'&';
}
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body_string);
}
$headers = array('content-type: application/x-www-form-urlencoded;charset=UTF-8');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$reponse = curl_exec($ch);
if(curl_errno($ch))
{
return false;
} else {
$http_status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if(200 !== $http_status_code)
{
return false;
}
}
curl_close($ch);
return json_decode($reponse, true);
}
}
?>