初始上传

This commit is contained in:
2026-04-04 17:27:12 +08:00
parent 4d80d28eb4
commit b7e11774ee
11191 changed files with 1588469 additions and 0 deletions

849
addon/wechatpay/model/V3.php Executable file
View File

@@ -0,0 +1,849 @@
<?php
/**
* Niushop商城系统 - 团队十年电商经验汇集巨献!
* =========================================================
* Copy right 2019-2029 杭州牛之云科技有限公司, 保留所有权利。
* ----------------------------------------------
* 官方网址: https://www.niushop.com
* =========================================================
*/
namespace addon\wechatpay\model;
use app\exception\ApiException;
use app\model\BaseModel;
use app\model\system\Cron;
use app\model\system\Pay as PayCommon;
use app\model\system\PayTransfer;
use app\model\upload\Upload;
use think\exception\HttpException;
use think\facade\Cache;
use think\facade\Log;
use WeChatPay\Builder;
use WeChatPay\ClientDecoratorInterface;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use GuzzleHttp\Middleware;
use Psr\Http\Message\ResponseInterface;
use addon\wechatpay\model\Config as WechatPayConfig;
/**
* 微信支付v3支付
* 版本 1.0.4
*/
class V3 extends BaseModel
{
/**
* 应用实例
* @var \WeChatPay\BuilderChainable
*/
private $app;
// 平台证书实例
private $plateform_certificate_instance;
// 平台证书序列号
private $plateform_certificate_serial;
/**
* 微信支付配置
*/
private $config;
public function __construct($config)
{
$this->config = $config;
$merchant_certificate_instance = PemUtil::loadCertificate(realpath($config['apiclient_cert']));
// 证书序列号
$merchant_certificate_serial = PemUtil::parseCertificateSerialNo($merchant_certificate_instance);
// var_dump($this->config);
//平台证书和序列号处理
//plateform_cert 是系统自动生成的 plateform_certificate 是直接上传的
if (empty($this->config['plateform_certificate_serial'])) {
// 检测平台证书是否存在
if (empty($config['plateform_cert']) || !is_file($config['plateform_cert'])) {
$create_res = $this->certificates();
if ($create_res['code'] != 0) throw new ApiException(-1, $create_res['message'] ?? "微信支付配置错误");
// 保存平台证书
$this->config['plateform_cert'] = $create_res['data']['cert_path'];
(new Config())->setPayConfig($this->config, $this->config['site_id']);
}
// 加载平台证书
$this->plateform_certificate_instance = PemUtil::loadCertificate(realpath($this->config['plateform_cert']));
// 平台证书序列号
$this->plateform_certificate_serial = PemUtil::parseCertificateSerialNo($this->plateform_certificate_instance);
} else {
// 加载平台证书
$this->plateform_certificate_instance = file_get_contents(realpath($this->config['plateform_certificate']));
// 平台证书序列号
$this->plateform_certificate_serial = $this->config['plateform_certificate_serial'];
// 具体业务有很多需要用这个字段,值与新字段保持相同
$this->config[ 'plateform_cert' ] = $this->config[ 'plateform_certificate' ];
}
$this->app = Builder::factory([
// 商户号
'mchid' => $config['mch_id'],
// 商户证书序列号
'serial' => $merchant_certificate_serial,
// 商户API私钥
'privateKey' => PemUtil::loadPrivateKey(realpath($config['apiclient_key'])),
'certs' => [
$this->plateform_certificate_serial => $this->plateform_certificate_instance
]
]);
}
/**
* 生成平台证书
*/
private function certificates()
{
try {
$merchant_certificate_instance = PemUtil::loadCertificate(realpath($this->config['apiclient_cert']));
// 证书序列号
$merchant_certificate_serial = PemUtil::parseCertificateSerialNo($merchant_certificate_instance);
$certs = ['any' => null];
$app = Builder::factory([
// 商户号
'mchid' => $this->config['mch_id'],
// 商户证书序列号
'serial' => $merchant_certificate_serial,
// 商户API私钥
'privateKey' => PemUtil::loadPrivateKey(realpath($this->config['apiclient_key'])),
'certs' => &$certs
]);
$stack = $app->getDriver()->select(ClientDecoratorInterface::JSON_BASED)->getConfig('handler');
$stack->after('verifier', Middleware::mapResponse(self::certsInjector($this->config['v3_pay_signkey'], $certs)), 'injector');
$stack->before('verifier', Middleware::mapResponse(self::certsRecorder((string)dirname($this->config['apiclient_key']), $certs)), 'recorder');
$param = [
'url' => '/v3/certificates',
'timestamp' => (string)Formatter::timestamp(),
'noncestr' => uniqid()
];
$resp = $app->chain("v3/certificates")
->get([
'headers' => [
'Authorization' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($param)),
Rsa::from('file://' . realpath($this->config['apiclient_key']))
)
]
]);
$result = json_decode($resp->getBody()->getContents(), true);
$file_path = dirname($this->config['apiclient_key']) . '/plateform_cert.pem';
return $this->success(['cert_path' => $file_path]);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
return $this->error($result, $result['message']);
} else {
return $this->error([], $e->getMessage());
}
}
}
private static function certsInjector(string $apiv3Key, array &$certs): callable
{
return static function (ResponseInterface $response) use ($apiv3Key, &$certs): ResponseInterface {
$body = (string)$response->getBody();
/** @var object{data:array<object{encrypt_certificate:object{serial_no:string,nonce:string,associated_data:string}}>} $json */
$json = \json_decode($body);
$data = \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : [];
\array_map(static function ($row) use ($apiv3Key, &$certs) {
$cert = $row->encrypt_certificate;
$certs[$row->serial_no] = AesGcm::decrypt($cert->ciphertext, $apiv3Key, $cert->nonce, $cert->associated_data);
}, $data);
return $response;
};
}
private static function certsRecorder(string $outputDir, array &$certs): callable
{
return static function (ResponseInterface $response) use ($outputDir, &$certs): ResponseInterface {
$body = (string)$response->getBody();
/** @var object{data:array<object{effective_time:string,expire_time:string:serial_no:string}>} $json */
$json = \json_decode($body);
$data = \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : [];
\array_walk($data, static function ($row, $index, $certs) use ($outputDir) {
$serialNo = $row->serial_no;
$outpath = $outputDir . \DIRECTORY_SEPARATOR . 'plateform_cert.pem';
\file_put_contents($outpath, $certs[$serialNo]);
}, $certs);
return $response;
};
}
/**
* 支付
* @param array $param
* @return array
*/
public function pay(array $param)
{
$self = $this;
$site_id = $param['site_id'];
$data = [
'json' => [
'appid' => $this->config['appid'],
'mchid' => $this->config['mch_id'],
'description' => str_sub($param["pay_body"], 15),
'out_trade_no' => $param["out_trade_no"],
'notify_url' => $param["notify_url"],
'amount' => [
'total' => round($param["pay_money"] * 100)
]
]
];
switch ($param["trade_type"]) {
case 'JSAPI':
$data['json']['payer'] = ['openid' => $param['openid']];
$data['trade_type'] = 'jsapi';
$data['callback'] = function ($result) use ($self) {
return success(0, '', [
"type" => "jsapi",
"data" => $self->jsskdConfig($result['prepay_id'])
]);
};
break;
case 'APPLET':
$data['json']['payer'] = ['openid' => $param['openid']];
$data['trade_type'] = 'jsapi';
$data['callback'] = function ($result) use ($self) {
return success(0, '', [
"type" => "jsapi",
"data" => $self->jsskdConfig($result['prepay_id'])
]);
};
break;
case 'NATIVE':
$data['trade_type'] = 'native';
$data['callback'] = function ($result) use ($site_id) {
$upload_model = new Upload($site_id);
$qrcode_result = $upload_model->qrcode($result['code_url']);
return success(0, '', [
"type" => "qrcode",
"qrcode" => $qrcode_result['data'] ?? ''
]);
};
break;
case 'MWEB':
$data['trade_type'] = 'h5';
$data['json']['scene_info'] = [
'payer_client_ip' => request()->ip(),
'h5_info' => [
'type' => 'Wap'
]
];
$data['callback'] = function ($result) {
return success(0, '', [
"type" => "url",
"url" => $result['h5_url']
]);
};
break;
case 'APP':
$data['trade_type'] = 'app';
$data['callback'] = function ($result) use ($self) {
return success(0, '', [
"type" => "app",
"data" => $self->appConfig($result['prepay_id'])
]);
};
break;
}
$result = $this->unify($data);
if ($result['code'] != 0) return $result;
$result = $data['callback']($result['data']);
return $result;
}
/**
* 统一下单接口
* @param array $param
*/
public function unify(array $param)
{
try {
$resp = $this->app->chain('v3/pay/transactions/' . $param['trade_type'])->post([
'json' => $param['json'],
'headers' => [
'Wechatpay-Serial' => $this->plateform_certificate_serial
]
]);
$result = json_decode($resp->getBody()->getContents(), true);
return $this->success($result);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
$result = json_decode($e->getMessage(), true);
return $this->error($result, $result['message']);
} else {
return $this->error([], $e->getMessage());
}
}
}
/**
* 生成支付配置
* @param string $prepay_id
* @return array
*/
private function jsskdConfig(string $prepay_id)
{
$param = [
'appId' => $this->config['appid'],
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => uniqid(),
'package' => "prepay_id=$prepay_id"
];
$param += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($param)),
Rsa::from('file://' . realpath($this->config['apiclient_key']))
), 'signType' => 'RSA'];
return $param;
}
/**
* 生成支付配置
* @param string $prepay_id
* @return array
*/
private function appConfig(string $prepay_id)
{
$param = [
'appid' => $this->config['appid'],
'timestamp' => (string)Formatter::timestamp(),
'noncestr' => uniqid(),
'prepayid' => $prepay_id
];
$param += [
'sign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($param)),
Rsa::from('file://' . realpath($this->config['apiclient_key']))
),
'package' => 'Sign=WXPay',
'partnerid' => $this->config['mch_id']
];
return $param;
}
/**
* 异步回调
*/
public function payNotify()
{
$inWechatpaySignature = request()->header('Wechatpay-Signature'); // 从请求头中拿到 签名
$inWechatpayTimestamp = request()->header('Wechatpay-Timestamp'); // 从请求头中拿到 时间戳
$inWechatpaySerial = request()->header('Wechatpay-Serial'); // 从请求头中拿到 时间戳
$inWechatpayNonce = request()->header('Wechatpay-Nonce'); // 从请求头中拿到 时间戳
$inBody = file_get_contents('php://input');
$platformPublicKeyInstance = Rsa::from('file://' . realpath($this->config['plateform_cert']), Rsa::KEY_TYPE_PUBLIC);
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, file_get_contents('php://input')),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 使用PHP7的数据解构语法从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $this->config['v3_pay_signkey'], $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$message = json_decode($inBodyResource, true);
Log::write('message' . $inBodyResource);
// 交易状态为成功
if (isset($message['trade_state']) && $message['trade_state'] == 'SUCCESS') {
if (isset($message['out_trade_no'])) {
$pay_common = new PayCommon();
$pay_info = $pay_common->getPayInfo($message['out_trade_no'])['data'];
if (empty($pay_info)) return;
if ($message['amount']['total'] != round($pay_info['pay_money'] * 100)) return;
// 用户是否支付成功
$pay_common->onlinePay($message['out_trade_no'], "wechatpay", $message["transaction_id"], "wechatpay");
header('', '', 200);
}
} else {
$this->payNotifyFail(501, 'FAIL', '没有trade_state字段或不是SUCCESS');
}
} else {
$this->payNotifyFail(401, 'FAIL', '验签失败');
}
}
/**
* 支付回调失败处理
* @param $response_code
* @param $code
* @param $message
*/
protected function payNotifyFail($response_code, $code, $message)
{
//记录日志
Log::write('V3支付回调失败');
Log::write(['response_code' => $response_code, 'code' => $code, 'message' => $message]);
// 设置HTTP响应头
header('Content-Type: application/json; charset=utf-8');
http_response_code($response_code); // 或400等适当的状态码
// 构造错误响应
$errorResponse = [
'code' => $code,
'message' => $message
];
// 输出JSON响应
echo json_encode($errorResponse, JSON_UNESCAPED_UNICODE);
exit;
}
/**
* 支付单据关闭
* @param array $param
* @return array
*/
public function payClose(array $param)
{
$get_res = $this->get($param['out_trade_no']);
if ($get_res['code'] >= 0 && $get_res['data']['trade_state'] != 'CLOSED') {
if ($get_res['data']['trade_state'] == 'SUCCESS') {
return $this->error(['is_paid' => 1, 'pay_type' => 'wechatpay'], '微信已支付不可关闭');
}
try {
$resp = $this->app->chain("v3/pay/transactions/out-trade-no/{$param['out_trade_no']}/close")->post([
'json' => [
'mchid' => $this->config['mch_id']
]
]);
$result = json_decode($resp->getBody()->getContents(), true);
return $this->success($result);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
return $this->error($result, $result['message']);
}
return $this->error(['file' => $e->getFile(), 'line' => $e->getLine(), 'message' => $e->getMessage()], $e->getMessage());
}
}
return $this->success();
}
/**
* 申请退款
* @param array $param
* @return array
*/
public function refund(array $param)
{
$pay_info = $param["pay_info"];
try {
$resp = $this->app->chain("v3/refund/domestic/refunds")->post([
'json' => [
'out_trade_no' => $pay_info['out_trade_no'],
'out_refund_no' => $param['refund_no'],
'notify_url' => addon_url("pay/pay/refundnotify"),
'amount' => [
'refund' => round($param['refund_fee'] * 100),
'total' => round($pay_info['pay_money'] * 100),
'currency' => $param['currency'] ?? 'CNY'
]
]
]);
$result = json_decode($resp->getBody()->getContents(), true);
if (isset($result['status']) && ($result['status'] == 'SUCCESS' || $result['status'] == 'PROCESSING'))
return $this->success($result);
else return $this->success($result, '退款异常');
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
return $this->error($result, $result['message']);
} else {
return $this->error([], $e->getMessage());
}
}
}
/**
* 转账
* @param array $param
* @return array|mixed
*/
public function transfer(array $param)
{
if($this->config['transfer_v3_type'] == WechatPayConfig::TRANSFER_V3_TYPE_SHOP){
return $this->transferByShop($param);
}else{
return $this->transferByUser($param);
}
}
/**
* 商户直接转账
* @param array $param
* @return mixed
*/
public function transferByShop(array $param)
{
$data = [
'appid' => $this->config['appid'],
'out_batch_no' => $param['out_trade_no'],
'batch_name' => '客户提现转账',
'batch_remark' => '客户提现转账提现交易号' . $param['out_trade_no'],
'total_amount' => round($param['amount'] * 100),
'total_num' => 1,
'transfer_detail_list' => [
[
'out_detail_no' => $param['out_trade_no'],
'transfer_amount' => round($param['amount'] * 100, 2),
'transfer_remark' => $param['desc'],
'openid' => $param['account_number'],
'user_name' => $this->encryptor($param['real_name'])
]
]
];
\think\facade\Log::write('转账发起数据');
\think\facade\Log::write($data);
$this->app->chain('v3/transfer/batches')
->postAsync([
'json' => $data,
'headers' => [
'Wechatpay-Serial' => $this->plateform_certificate_serial
]
])->then(static function ($response) use (&$result) {
$pay_transfer_model = new PayTransfer();
$result = json_decode($response->getBody()->getContents(), true);
\think\facade\Log::write('转账成功返回');
\think\facade\Log::write($result);
$result = $pay_transfer_model->success([
'status' => $pay_transfer_model::STATUS_IN_PROCESS,
]);
})->otherwise(static function ($exception) use (&$result) {
if ($exception instanceof \GuzzleHttp\Exception\RequestException && $exception->hasResponse()) {
$result = json_decode($exception->getResponse()->getBody()->getContents(), true);
\think\facade\Log::write('转账其他返回');
\think\facade\Log::write($result);
$pay_transfer_model = new PayTransfer();
if (isset($result['batch_status'])) {
if (in_array($result['batch_status'], ['ACCEPTED', 'PROCESSING'])) {
$result = $pay_transfer_model->success([
'status' => $pay_transfer_model::STATUS_IN_PROCESS,
]);
} else if ($result['batch_status'] == 'FINISHED') {
$result = $pay_transfer_model->success([
'status' => $pay_transfer_model::STATUS_SUCCESS,
]);
} else {
$result = $pay_transfer_model->error($result, '转账失败');
}
} else {
$result = error(-1, '转账错误:' . $result['message'] ?? '未知错误', $result);
}
} else {
$result = error(-1, '转账报错:' . $exception->getMessage());
}
})->wait();
return $result;
}
/**
* 查询转账明细
* @param string $out_batch_no
* @param string $out_detail_no
* @return array
*/
public function transferDetail(string $out_batch_no, string $out_detail_no): array
{
try {
$resp = $this->app->chain("v3/transfer/batches/out-batch-no/{$out_batch_no}/details/out-detail-no/{$out_detail_no}")
->get();
$result = json_decode($resp->getBody()->getContents(), true);
\think\facade\Log::write('转账查询正常返回');
\think\facade\Log::write($result);
return $this->success($result);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
\think\facade\Log::write('转账查询其他返回');
\think\facade\Log::write($result);
if (isset($result['detail_status'])) {
return $this->success($result);
} else {
return $this->error($result, $result['message'] ?? '未知错误');
}
} else {
return $this->error([], $e->getMessage());
}
}
}
/**
* 加密数据
* @param string $str
* @return string
*/
public function encryptor(string $str)
{
$publicKey = $this->plateform_certificate_instance;
// 加密方法
$encryptor = function ($msg) use ($publicKey) {
return Rsa::encrypt($msg, $publicKey);
};
return $encryptor($str);
}
/**
* 获取转账结果
* @param $withdraw_info
* @return array
*/
public function getTransferResult($withdraw_info)
{
$pay_transfer_model = new PayTransfer();
$result = $this->transferDetail($withdraw_info['out_trade_no'], $withdraw_info['out_trade_no']);
if ($result['code'] == 0) {
switch ($result['data']['detail_status']) {
case 'SUCCESS':
$status = $pay_transfer_model::STATUS_SUCCESS;
break;
case 'FAIL':
$status = $pay_transfer_model::STATUS_FAIL;
$reason_config = [
'ACCOUNT_FROZEN' => '账户冻结',
'REAL_NAME_CHECK_FAIL' => '用户未实名',
'NAME_NOT_CORRECT' => '用户姓名校验失败',
'OPENID_INVALID' => 'Openid校验失败',
'TRANSFER_QUOTA_EXCEED' => '超过用户单笔收款额度',
'DAY_RECEIVED_QUOTA_EXCEED' => '超过用户单日收款额度',
'MONTH_RECEIVED_QUOTA_EXCEED' => '超过用户单月收款额度',
'DAY_RECEIVED_COUNT_EXCEED' => '超过用户单日收款次数',
'PRODUCT_AUTH_CHECK_FAIL' => '产品权限校验失败',
'OVERDUE_CLOSE' => '转账关闭',
'ID_CARD_NOT_CORRECT' => '用户身份证校验失败',
'ACCOUNT_NOT_EXIST' => '用户账户不存在',
'TRANSFER_RISK' => '转账存在风险',
'REALNAME_ACCOUNT_RECEIVED_QUOTA_EXCEED' => '用户账户收款受限,请引导用户在微信支付查看详情',
'RECEIVE_ACCOUNT_NOT_PERMMIT' => '未配置该用户为转账收款人',
'PAYER_ACCOUNT_ABNORMAL' => '商户账户付款受限,可前往商户平台-违约记录获取解除功能限制指引',
'PAYEE_ACCOUNT_ABNORMAL' => '用户账户收款异常,请引导用户完善其在微信支付的身份信息以继续收款',
'MERCHANT_REJECT' => '商家拒绝',
];
$reason_code = $result['data']['fail_reason'] ?? '';
$fail_reason = $reason_config[$reason_code] ?? $reason_code;
break;
default:
$status = $pay_transfer_model::STATUS_IN_PROCESS;
}
return $this->success([
'status' => $status,
'fail_reason' => $fail_reason ?? '',
'reason_code' => $reason_code ?? '',
]);
} else {
return $result;
}
}
/**
* 查询订单信息
* @param $out_trade_no
* @return array
*/
public function get($out_trade_no)
{
try {
$resp = $this->app->chain("/v3/pay/transactions/out-trade-no/{$out_trade_no}")->get();
$result = json_decode($resp->getBody()->getContents(), true);
return $this->success($result);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$result = json_decode($e->getResponse()->getBody()->getContents(), true);
return $this->error($result, $result['message']);
} else {
return $this->error([], $e->getMessage());
}
}
}
/**
* 会员转账下单
* @param array $param
*/
public function transferByUser(array $param)
{
$data = [
'appid' => $this->config['appid'],
'out_bill_no' => $param['out_trade_no'],
'transfer_scene_id' => $this->config[$param['from_type'].'_code'],
'openid' => $param['account_number'],
'user_name' => $param['amount'] >= 2000 ? $this->encryptor($param['real_name']) : '',
'transfer_amount' => round($param['amount'] * 100),
'transfer_remark' => '客户提现转账',
'notify_url' => addon_url("wechatpay://api/transfer/notify"),
'user_recv_perception'=> $this->config[$param['from_type'].'_recv'] ?? '',
'transfer_scene_report_infos' => $this->config[$param['from_type'].'_info'],
];
Log::write('会员发起转账数据');
Log::write($data);
try {
$resp = $this->app
->chain('/v3/fund-app/mch-transfer/transfer-bills')
->postAsync([
'json' => $data,
'headers' => [
'Wechatpay-Serial' => $this->plateform_certificate_serial
]
])->then(static function ($response) use (&$result) {
$result = json_decode($response->getBody()->getContents(), true);
\think\facade\Log::write('申请转账返回');
\think\facade\Log::write($result);
})->otherwise(static function ($exception) use (&$result) {
if ($exception instanceof \GuzzleHttp\Exception\RequestException && $exception->hasResponse()) {
$result = json_decode($exception->getResponse()->getBody()->getContents(), true);
\think\facade\Log::write('申请转账返回异常');
\think\facade\Log::write($result);
$result = error(-1, '转账异常:' .$result['message'] ?? '');
} else {
$result = error(-1, '转账异常:' . $exception->getMessage());
}
})->wait();
if(isset($result['code']) && $result['code'] != 0){
return $result;
}
//状态为待转账
$result['status'] = PayTransfer::STATUS_WAIT;
return $this->success($result);
} catch (\Exception $e) {
\think\facade\Log::write('会员提现转账失败,原因'.$e->getMessage());
return $this->error([], $e->getMessage());
}
}
/**
* 撤销转账
* @param $out_bill_no
*/
public function transferCancel($out_bill_no){
Log::write('会员撤销转账');
Log::write($out_bill_no);
try {
$resp = $this->app
->chain('/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$out_bill_no.'/cancel')
->postAsync()
->then(static function ($response) use (&$result) {
$result = json_decode($response->getBody()->getContents(), true);
Log::write('会员撤销转账数据返回');
Log::write($result);
})->otherwise(static function ($exception) use (&$result) {
if ($exception instanceof \GuzzleHttp\Exception\RequestException && $exception->hasResponse()) {
$result = json_decode($exception->getResponse()->getBody()->getContents(), true);
Log::write('撤销转账返回异常');
Log::write($result);
} else {
$result = error(-1, '撤销转账报错:' . $exception->getMessage());
}
})->wait();
return $this->success($result);
} catch (\Exception $e) {
Log::write('会员撤销转账失败,原因'.$e->getMessage());
return $this->error([], $e->getMessage());
}
}
/**
* 会员提现回调
* @throws \Exception
*/
public function transferNotify(){
$inWechatpaySignature = request()->header('Wechatpay-Signature'); // 从请求头中拿到 签名
$inWechatpayTimestamp = request()->header('Wechatpay-Timestamp'); // 从请求头中拿到 时间戳
$inWechatpaySerial = request()->header('Wechatpay-Serial'); // 从请求头中拿到 时间戳
$inWechatpayNonce = request()->header('Wechatpay-Nonce'); // 从请求头中拿到 时间戳
$inBody = file_get_contents('php://input');
Log::write('transferNotifyHeader'.json_encode(request()->header()));
Log::write('transferNotifyBody' . $inBody);
$platformPublicKeyInstance = Rsa::from('file://' . realpath($this->config['plateform_cert']), Rsa::KEY_TYPE_PUBLIC);
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 构造验签名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, file_get_contents('php://input')),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 使用PHP7的数据解构语法从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $this->config['v3_pay_signkey'], $nonce, $aad);
// 把解密后的文本转换为PHP Array数组
$message = json_decode($inBodyResource, true);
Log::write('transferNotifyMessage' . $inBodyResource);
// 交易状态为成功
$pay_transfer_model = new PayTransfer();
if (isset($message['state']) && $message['state'] == 'SUCCESS') {
if (isset($message['out_bill_no'])) {
$transfer_info = $pay_transfer_model->getTransferInfo([['out_trade_no', '=', $message['out_bill_no']]],'id')['data'];
$update_result = $pay_transfer_model->updateStatus(['status' => PayTransfer::STATUS_SUCCESS, 'result' => $message], $transfer_info['id']);
Log::write('transferNotify' . json_encode(['transfer_info'=>$transfer_info,'update_result'=>$update_result]));
header('', '', 200);
}
} else {
if (isset($message['out_bill_no'])) {
$transfer_info = $pay_transfer_model->getTransferInfo([['out_trade_no', '=', $message['out_bill_no']]],'id')['data'];
$update_result = $pay_transfer_model->updateStatus(['status' => PayTransfer::STATUS_FAIL, 'result' => $message], $transfer_info['id']);
Log::write('transferNotify' . json_encode(['transfer_info'=>$transfer_info,'update_result'=>$update_result]));
}
throw new HttpException(500, '失败', null, [], 'FAIL');
}
} else {
throw new HttpException(500, '失败', null, [], 'FAIL');
}
}
}