Files
2026-04-04 17:27:12 +08:00

849 lines
36 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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');
}
}
}