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

366 lines
13 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\AppMiniUserService;
use app\service\PluginsDataConfigService;
use app\plugins\wallet\service\BaseService;
use app\plugins\wallet\service\CashPaymentService;
/**
* 钱包 - 钱包余额提现到微信服务层
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2020-09-04
* @desc description
*/
class CashWeixinService
{
// 配置信息
public static $config;
/**
* 初始化
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2025-07-05
* @desc description
*/
public static function Init()
{
// 数据配置
self::$config = PluginsDataConfigService::DataConfigData('wallet');
// 请求参数
$p = [
[
'checked_type' => 'empty',
'key_name' => 'weixin_merchant_id',
'error_msg' => '商户号未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_api_key_v3',
'error_msg' => 'api安全密钥v3未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_transfer_scene_id',
'error_msg' => '转账场景未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_serial_no',
'error_msg' => '商户证书序列号未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_private_key',
'error_msg' => '商户私钥证书未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_out_pub_id',
'error_msg' => '微信平台支付公钥ID未配置',
],
[
'checked_type' => 'empty',
'key_name' => 'weixin_out_pub_key',
'error_msg' => '微信平台支付公钥证书未配置',
],
];
$ret = ParamsChecked(self::$config, $p);
if($ret !== true)
{
return DataReturn($ret, -1);
}
return DataReturn('success', 0);
}
/**
* 微信转账创建
* @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 = [])
{
// 初始化
$ret = self::Init();
if($ret['code'] != 0)
{
return $ret;
}
if(empty($cash['bank_accounts']))
{
return DataReturn('微信openid为空', -1);
}
// appid
$appid = ($cash['accounts_other_type'] == 'web') ? (isset(self::$config['weixin_appid']) ? self::$config['weixin_appid'] : '') : AppMiniUserService::AppMiniConfig('common_app_mini_weixin_appid');
if(empty($appid))
{
return DataReturn('配置appid为空', -1);
}
// 转账数据
$pay_no = $cash['cash_no'].GetNumberCode(6);
$data = [
'appid' => $appid,
'out_bill_no' => $pay_no,
'transfer_scene_id' => self::$config['weixin_transfer_scene_id'],
'openid' => $cash['bank_accounts'],
'user_name' => ($cash['money'] < 0.3) ? '' : self::ContentEncrypt($cash['bank_username']),
'transfer_amount' => (int) (($cash['money']*1000)/10),
'transfer_remark' => '用户提现',
'transfer_scene_report_infos' => self::TransferSceneReportInfos(self::$config['weixin_transfer_scene_id']),
];
// 先释放原来为处理的数据
CashPaymentService::CashPaymentRelease($cash);
// 转账数据添加
$insert = CashPaymentService::CashPaymentInsert($cash, $pay_no, $data, $params);
if($insert['code'] != 0)
{
return $insert;
}
$start_time = time();
// 请求接口
$ret = self::WeixinRequest('/v3/fund-app/mch-transfer/transfer-bills', $data, 'POST', false);
// 回调处理
$status = 3;
$reason = '异常错误';
$out_order_no = '';
if($ret['code'] == 0)
{
$response = json_decode($ret['data'], true);
// ACCEPTED 已受理, PROCESSING 转账中, SUCCESS 已完成, FAIL 失败
if(isset($response['state']) && in_array($response['state'], ['ACCEPTED', 'PROCESSING', 'SUCCESS']))
{
$status = 2;
$reason = '';
// 待用户收款
// WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
// TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
} else if(isset($response['state']) && in_array($response['state'], ['WAIT_USER_CONFIRM', 'TRANSFERING']))
{
$status = 1;
$reason = '';
} else {
$reason = empty($response['message']) ? $ret['data'] : $response['message'];
if(!empty($response['code']))
{
$reason .= '('.$response['code'].')';
}
}
$out_order_no = isset($response['transfer_bill_no']) ? $response['transfer_bill_no'] : '';
} else {
$reason = $ret['msg'];
}
// 转账数据回调
return CashPaymentService::CashPaymentResponse($cash, $insert['data'], $start_time, $ret['data'], $status, $reason, $out_order_no);
}
/**
* 转账场景数据
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2025-07-04
* @desc description
* @param [string] $transfer_scene_id [转账场景id]
*/
public static function TransferSceneReportInfos($transfer_scene_id)
{
$arr = BaseService::ConstData('weixin_transfer_scene_report_list');
return (isset($arr[$transfer_scene_id]) && isset($arr[$transfer_scene_id]['data'])) ? $arr[$transfer_scene_id]['data'] : [];
}
/**
* 支付刷新
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-19
* @desc description
* @param [array] $cash [提现数据]
* @param [array] $cash_log [提现日志数据]
* @param [array] $params [输入参数]
*/
public static function PayRefresh($cash, $cash_log, $params = [])
{
// 初始化
$ret = self::Init();
if($ret['code'] != 0)
{
return $ret;
}
if(empty($cash_log['out_order_no']))
{
return DataReturn('没有支付平台单号', -1);
}
// 请求接口
$ret = self::WeixinRequest('/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/'.$cash_log['out_order_no'], '', 'GET', false);
if($ret['code'] != 0)
{
return $ret;
}
// 回调处理
$status = 3;
$reason = '异常错误';
$out_order_no = '';
$response = json_decode($ret['data'], true);
// ACCEPTED 已受理, PROCESSING 转账中, SUCCESS 已完成, FAIL 失败
if(isset($response['state']) && in_array($response['state'], ['ACCEPTED', 'PROCESSING', 'SUCCESS']))
{
$status = 2;
$reason = '';
// 待用户收款
// WAIT_USER_CONFIRM: 待收款用户确认,可拉起微信收款确认页面进行收款确认
// TRANSFERING: 转账中,可拉起微信收款确认页面再次重试确认收款
} else if(isset($response['state']) && in_array($response['state'], ['WAIT_USER_CONFIRM', 'TRANSFERING']))
{
$status = 1;
$reason = '';
} else {
$reason = empty($response['message']) ? (empty($response['fail_reason']) ? $ret['data'] : $response['fail_reason']) : $response['message'];
if(!empty($response['code']))
{
$reason .= '('.$response['code'].')';
}
}
$out_order_no = isset($response['transfer_bill_no']) ? $response['transfer_bill_no'] : '';
// 转账数据回调
return CashPaymentService::CashPaymentRefresh($cash, $cash_log['id'], $status, $reason, $out_order_no);
}
/**
* 商户证书私钥
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-20
* @desc description
*/
public static function PrivateKey()
{
$result = '';
if(stripos(self::$config['weixin_private_key'], '-----') === false)
{
$result = "-----BEGIN PRIVATE KEY-----\n";
$result .= wordwrap(self::$config['weixin_private_key'], 64, "\n", true);
$result .= "\n-----END PRIVATE KEY-----";
} else {
$result = self::$config['weixin_private_key'];
}
return $result;
}
/**
* 商户证书公钥
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-20
* @desc description
*/
public static function WeixinPublicKey()
{
$result = '';
if(stripos(self::$config['weixin_out_pub_key'], '-----') === false)
{
$result = "-----BEGIN PUBLIC KEY-----\n";
$result .= wordwrap(self::$config['weixin_out_pub_key'], 64, "\n", true);
$result .= "\n-----END PUBLIC KEY-----";
} else {
$result = self::$config['weixin_out_pub_key'];
}
return $result;
}
/**
* 内容加密
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-20
* @desc description
* @param [string] $str [需要加密的内容字符串]
*/
public static function ContentEncrypt($str)
{
if(openssl_public_encrypt($str, $encrypted, self::WeixinPublicKey(), OPENSSL_PKCS1_OAEP_PADDING))
{
return base64_encode($encrypted);
}
return '';
}
/**
* 接口请求
* @author Devil
* @blog http://gong.gg/
* @version 1.0.0
* @date 2024-09-20
* @desc description
* @param [string] $path [接口路径地址]
* @param [array] $body [请求数据]
* @param [string] $method [请求类型]
* @param [boolean] $is_json [是否json格式返回原数据]
*/
public static function WeixinRequest($path, $body = '', $method = 'GET', $is_json = true)
{
// 请求签名+token
$timestamp = time();
$nonce = strtoupper(RandomString(32));
$body_json = empty($body) ? '' : json_encode($body);
$message = $method."\n".$path."\n".$timestamp."\n".$nonce."\n".$body_json."\n";
openssl_sign($message, $raw_sign, self::PrivateKey(), OPENSSL_ALGO_SHA256);
$sign = base64_encode($raw_sign);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', self::$config['weixin_merchant_id'], $nonce, $timestamp, self::$config['weixin_serial_no'], $sign);
// 头信息
$header = [
'Authorization: '.$schema.' '.$token,
'Accept: application/json',
'User-Agent: */*',
'Wechatpay-Serial: '.self::ContentEncrypt(self::$config['weixin_out_pub_id']),
'wechatpay-serial: '.self::$config['weixin_out_pub_id'],
];
// 请求接口
$url = 'https://api.mch.weixin.qq.com';
$ret = CurlPost($url.$path, $body, 1, 30, $method, $header);
if(!empty($ret['data']) && $is_json)
{
$ret['data'] = json_decode($ret['data'], true);
}
return $ret;
}
}
?>