673 lines
28 KiB
PHP
673 lines
28 KiB
PHP
<?php
|
||
/**
|
||
* SaaSMall商城系统 - 团队十年电商经验汇集巨献!
|
||
* =========================================================
|
||
* Copy right 2019-2029 成都SAAS云科技有限公司, 保留所有权利。
|
||
* ----------------------------------------------
|
||
* 官方网址: https://www.gobuysaas.com
|
||
* =========================================================
|
||
*/
|
||
|
||
namespace addon\wechatpay\model;
|
||
|
||
use app\model\BaseModel;
|
||
use app\model\system\Cron;
|
||
use app\model\system\Pay as PayCommon;
|
||
use app\model\upload\Upload;
|
||
use think\Exception;
|
||
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\MediaUtil;
|
||
use WeChatPay\Util\PemUtil;
|
||
use GuzzleHttp\Middleware;
|
||
use Psr\Http\Message\ResponseInterface;
|
||
|
||
/**
|
||
* 微信支付v3支付
|
||
* 版本 1.0.4
|
||
*/
|
||
class V3 extends BaseModel
|
||
{
|
||
/**
|
||
* 应用实例
|
||
* @var \WeChatPay\BuilderChainable
|
||
*/
|
||
private $app;
|
||
|
||
/**
|
||
* @var 平台证书实例
|
||
*/
|
||
private $plateform_certificate_instance;
|
||
|
||
/**
|
||
* @var 平台证书序列号
|
||
*/
|
||
private $plateform_certificate_serial;
|
||
|
||
/**
|
||
* 微信支付配置
|
||
*/
|
||
private $config;
|
||
|
||
public function __construct($config)
|
||
{
|
||
$this->config = $config;
|
||
// 判断:是否为服务商模式 进行对应的处理
|
||
if (array_key_exists('sub_mch_id', $config) && !empty($config['sub_mch_id'])) {
|
||
// 服务商模式
|
||
$merchant_certificate_instance = openssl_x509_read($config['apiclient_cert_text']);
|
||
} else {
|
||
// 普通商户模式
|
||
$merchant_certificate_instance = PemUtil::loadCertificate(realpath($config['apiclient_cert']) ?: getCertPath($config['apiclient_cert_text'], $config['apiclient_cert']));
|
||
}
|
||
// 证书序列号
|
||
$merchant_certificate_serial = PemUtil::parseCertificateSerialNo($merchant_certificate_instance);
|
||
// 检测平台证书是否存在
|
||
if (empty($config['plateform_cert']) || !realpath($this->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'];
|
||
$this->config['plateform_cert_text'] = file_get_contents($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']) ?: getCertPath($this->config['plateform_cert_text'], $this->config['plateform_cert']));
|
||
// 平台证书序列号
|
||
$this->plateform_certificate_serial = PemUtil::parseCertificateSerialNo($this->plateform_certificate_instance);
|
||
// 接口基本信息配置
|
||
$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,
|
||
// 'debug' => true
|
||
]);
|
||
$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['app_id'],
|
||
'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)
|
||
]
|
||
]
|
||
];
|
||
if ($this->config['is_isp']) {
|
||
$data['json']['sp_appid'] = $this->config['sp_appid'];
|
||
$data['json']['sp_mchid'] = $this->config['sp_mchid'];
|
||
$data['json']['sub_appid'] = $this->config['sub_appid'];
|
||
$data['json']['sub_mchid'] = $this->config['sub_mchid'];
|
||
$data['json']['payer'] = ['sub_openid' => $param['sub_openid']];
|
||
unset($data['json']['appid'], $data['json']['mchid']);
|
||
} else {
|
||
$data['json']['payer'] = ['openid' => $param['openid']];
|
||
}
|
||
$res = event('checkAccountsAuth', ['site_id' => $this->config['site_id'], 'pay_type' => 'wechatpay','out_trade_no'=>$param["out_trade_no"]]);//检查分账状态
|
||
if ($res && array_sum(array_column($res,'isDivide'))) {
|
||
$divideState=array_sum(array_column($res,'divideState'));
|
||
$data['json']['settle_info']['profit_sharing'] = true; //判断是否参与分账
|
||
if (!$divideState) return $this->error('', implode(',', array_column($res,'err_msg')));
|
||
}
|
||
switch ($param["trade_type"]) {
|
||
case 'JSAPI':
|
||
$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['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 {
|
||
if ($this->config['is_isp']) {
|
||
$url = 'v3/pay/partner/transactions/' . $param['trade_type'];
|
||
} else {
|
||
$url = 'v3/pay/transactions/' . $param['trade_type'];
|
||
}
|
||
$resp = $this->app->chain($url)->post([
|
||
'json' => $param['json']
|
||
]);
|
||
$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 string $prepay_id
|
||
*/
|
||
private function jsskdConfig(string $prepay_id)
|
||
{
|
||
$param = [
|
||
'appId' => $this->config['app_id'],
|
||
'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['app_id'],
|
||
'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 {
|
||
throw new HttpException(500, '失败', null, [], 'FAIL');
|
||
}
|
||
} else {
|
||
throw new HttpException(500, '失败', null, [], 'FAIL');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 支付单据关闭
|
||
* @param array $param
|
||
*/
|
||
public function payClose(array $param)
|
||
{
|
||
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);
|
||
if (isset($result['code']) && ($result['code'] == 'ORDERPAID' || $result['code'] == 'ORDER_PAID'))
|
||
return $this->error(['is_paid' => 1, 'pay_type' => 'wechatpay'], $result['code']);
|
||
return $this->error($result, $result['message']);
|
||
} else {
|
||
return $this->error([], $e->getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 申请退款
|
||
* @param array $param
|
||
*/
|
||
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
|
||
*/
|
||
public function transfer(array $param)
|
||
{
|
||
$data = [
|
||
'appid' => $this->config['app_id'],
|
||
'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' => $param['amount'] * 100,
|
||
'transfer_remark' => $param['desc'],
|
||
'openid' => $param['account_number'],
|
||
'user_name' => $this->encryptor($param['real_name'])
|
||
]
|
||
]
|
||
];
|
||
|
||
$this->app->chain('v3/transfer/batches')
|
||
->postAsync([
|
||
'json' => $data,
|
||
'headers' => [
|
||
'Wechatpay-Serial' => $this->plateform_certificate_serial
|
||
]
|
||
])->then(static function ($response) use (&$result) {
|
||
$result = json_decode($response->getBody()->getContents(), true);
|
||
$result = success(0, '', $result);
|
||
})->otherwise(static function ($exception) use (&$result) {
|
||
if ($exception instanceof \GuzzleHttp\Exception\RequestException && $exception->hasResponse()) {
|
||
$result = json_decode($exception->getResponse()->getBody()->getContents(), true);
|
||
$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);
|
||
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 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 $id
|
||
* @return array
|
||
*/
|
||
public function getTransferResult($withdraw_info)
|
||
{
|
||
$result = $this->transferDetail($withdraw_info['withdraw_no'], $withdraw_info['withdraw_no']);
|
||
if ($result['code'] != 0 || (isset($result['data']['detail_status']) && $result['data']['detail_status'] == 'PROCESSING')) {
|
||
$error_num = Cache::get('get_transfer_result' . $withdraw_info['withdraw_no']) ?: 0;
|
||
if (!$error_num || $error_num < 5) {
|
||
(new Cron())->addCron(1, 0, "查询转账结果", "TransferResult", (time() + 60), $withdraw_info['id']);
|
||
Cache::set('get_transfer_result' . $withdraw_info['withdraw_no'], ($error_num + 1), 600);
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
if ($result['data']['detail_status'] == 'FAIL') {
|
||
$reason = [
|
||
'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' => '用户账户收款异常,请引导用户完善其在微信支付的身份信息以继续收款',
|
||
];
|
||
$fail_reason = '';
|
||
if (isset($result['data']['fail_reason'])) $fail_reason = $reason[$result['data']['fail_reason']] ?? '';
|
||
model('member_withdraw')->update(['status' => -2, 'status_name' => '转账失败', 'fail_reason' => $fail_reason], [['id', '=', $withdraw_info['id']]]);
|
||
} else if ($result['data']['detail_status'] != 'SUCCESS') {
|
||
model('member_withdraw')->update(['status' => -2, 'status_name' => '转账失败', 'fail_reason' => '未获取到转账结果'], [['id', '=', $withdraw_info['id']]]);
|
||
}
|
||
Cache::delete('get_transfer_result' . $withdraw_info['withdraw_no']);
|
||
return $this->success();
|
||
}
|
||
|
||
/**
|
||
* Common: 发起请求
|
||
* Author: wu-hui
|
||
* Time: 2023/06/28 14:22
|
||
* @param String $api
|
||
* @param array $params
|
||
* @param String $type
|
||
* @return array
|
||
*/
|
||
public function requestApi(string $api, array $params=[], string $type = 'post')
|
||
{
|
||
try {
|
||
// 请求使用当前方式 +api_link 不能改变,否则部分接口请求参数会在发送请求时转义 导致请求接口失败
|
||
// 根据请求类型执行不同的操作
|
||
if ($type == 'post') {
|
||
$res = $this->app->chain('{+api_link}')
|
||
->post([
|
||
'json' => $params,
|
||
'api_link' => $api,
|
||
'headers' => [
|
||
'Wechatpay-Serial' => $this->plateform_certificate_serial
|
||
]
|
||
]);
|
||
} else if ($type == 'patch') {
|
||
$res = $this->app->chain('{+api_link}')
|
||
->patch([
|
||
'json' => $params,
|
||
'api_link' => $api,
|
||
'headers' => [
|
||
'Wechatpay-Serial' => $this->plateform_certificate_serial
|
||
]
|
||
]);
|
||
} else {
|
||
$urlParams = http_build_query($params);
|
||
$api .= '?' . $urlParams;
|
||
$res = $this->app->chain('{+api_link}')
|
||
->get(['api_link' => $api]);
|
||
}
|
||
$result = json_decode($res->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());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Common: 微信图片上传
|
||
* Author: wu-hui
|
||
* Time: 2023/07/21 11:20
|
||
* @param $picPath
|
||
* @param $api
|
||
* @return mixed
|
||
* @throws Exception
|
||
*/
|
||
public function uploadImage($picPath, $api)
|
||
{
|
||
try {
|
||
$media = new MediaUtil($picPath);
|
||
$result = $this->app->chain('{+api_link}')
|
||
->post([
|
||
'api_link' => $api,
|
||
'body' => $media->getStream(),
|
||
'headers' => [
|
||
'Accept' => 'application/json',
|
||
'content-type' => $media->getContentType(),
|
||
]
|
||
]);
|
||
return json_decode($result->getBody()->getContents(), TRUE);
|
||
} catch (\Exception $e) {
|
||
$msg = $e->getMessage();
|
||
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
|
||
$result = json_decode($e->getResponse()->getBody()->getContents(), TRUE);
|
||
$msg = $result['message'];
|
||
}
|
||
throw new Exception($msg);
|
||
}
|
||
}
|
||
}
|