new files

This commit is contained in:
panchengyong
2026-03-07 22:29:07 +08:00
parent cd7e80b502
commit 7acbf45ff7
12516 changed files with 1808447 additions and 194 deletions

View File

@@ -0,0 +1,281 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\v3pay;
use crmeb\exceptions\PayException;
use crmeb\services\wechat\config\V3PaymentConfig;
use crmeb\services\wechat\WechatException;
use think\exception\InvalidArgumentException;
/**
* Class BaseClient
* @author 等风来
* @email 136327134@qq.com
* @date 2022/9/30
* @package crmeb\services\wechat\v3pay
*/
class BaseClient
{
use Certficates;
const BASE_URL = 'https://api.mch.weixin.qq.com/';
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* BaseClient constructor.
* @param V3PaymentConfig $config
*/
public function __construct(protected V3PaymentConfig $config)
{
}
/**
* request.
*
* @param string $endpoint
* @param string $method
* @param array $options
* @param bool $serial
* @return mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function request(string $endpoint, string $method = 'POST', array $options = [], bool $serial = true)
{
$body = $options['body'] ?? '';
if (isset($options['json'])) {
$body = json_encode($options['json']);
$options['body'] = $body;
unset($options['json']);
}
$headers = [
'Content-Type' => 'application/json',
'User-Agent' => 'curl',
'Accept' => 'application/json',
'Authorization' => $this->getAuthorization($endpoint, $method, $body),
];
$options['headers'] = array_merge($headers, ($options['headers'] ?? []));
if ($serial) {
if ($this->config->publicKey != '') {
$options['headers']['Wechatpay-Serial'] = $this->config->publicKey;
} else {
$options['headers']['Wechatpay-Serial'] = $this->getCertficatescAttr('serial_no');
}
}
return $this->_doRequestCurl($method, self::BASE_URL . $endpoint, $options);
}
/**
* @param string $method
* @param string $location
* @param array $options
* @return mixed
*/
private function _doRequestCurl(string $method, string $location, array $options = [])
{
$curl = curl_init();
// POST数据设置
if (strtolower($method) === 'post') {
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $options['data'] ?? $options['body'] ?? '');
}
// CURL头信息设置
if (!empty($options['headers'])) {
$headers = [];
foreach ($options['headers'] as $k => $v) {
$headers[] = "$k: $v";
}
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $location);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
$content = curl_exec($curl);
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
curl_close($curl);
return json_decode(substr($content, $headerSize), true);
}
/**
* To id card, mobile phone number and other fields sensitive information encryption.
*
* @param string $string
*
* @return string
*/
protected function encryptSensitiveInformation(string $string)
{
$certificates = $this->config->certPath;
if (null === $certificates) {
throw new WechatException('config certificate connot be empty.');
}
$encrypted = '';
if (openssl_public_encrypt($string, $encrypted, $certificates, OPENSSL_PKCS1_OAEP_PADDING)) {
//base64编码
$sign = base64_encode($encrypted);
} else {
throw new WechatException('Encryption of sensitive information failed');
}
return $sign;
}
/**
* @param string $url
* @param string $method
* @param string $body
* @return string
*/
protected function getAuthorization(string $url, string $method, string $body)
{
$nonceStr = uniqid();
$timestamp = time();
$message = $method . "\n" .
'/' . $url . "\n" .
$timestamp . "\n" .
$nonceStr . "\n" .
$body . "\n";
openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$schema = 'WECHATPAY2-SHA256-RSA2048 ';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$this->config->mchId, $nonceStr, $timestamp, $this->config->serialNo, $sign);
return $schema . $token;
}
/**
* 获取商户私钥
* @return bool|resource
*/
protected function getPrivateKey()
{
$key_path = $this->config->keyPath;
if (!file_exists($key_path)) {
throw new \InvalidArgumentException(
"SSL certificate not found: {$key_path}"
);
}
return openssl_pkey_get_private(file_get_contents($key_path));
}
/**
* 获取商户公钥
* @return bool|resource
*/
protected function getPublicKey()
{
if ($this->config->publicKey != '') {
$key_path = $this->config->publicPem;
} else {
$key_path = $this->config->certPath;
}
if (!file_exists($key_path)) {
throw new \InvalidArgumentException(
"SSL certificate not found: {$key_path}"
);
}
return openssl_pkey_get_public(file_get_contents($key_path));
}
/**
* 替换url
* @param string $url
* @param $search
* @param $replace
* @return array|string|string[]
*/
public function getApiUrl(string $url, $search, $replace)
{
$newSearch = [];
foreach ($search as $key) {
$newSearch[] = '{' . $key . '}';
}
return str_replace($newSearch, $replace, $url);
}
/**
* @param int $padding
*/
private static function paddingModeLimitedCheck(int $padding): void
{
if (!($padding === OPENSSL_PKCS1_OAEP_PADDING || $padding === OPENSSL_PKCS1_PADDING)) {
throw new PayException(sprintf("Doesn't supported padding mode(%d), here only support OPENSSL_PKCS1_OAEP_PADDING or OPENSSL_PKCS1_PADDING.", $padding));
}
}
/**
* 加密数据
* @param string $plaintext
* @param int $padding
* @return string
*/
public function encryptor(string $plaintext, int $padding = OPENSSL_PKCS1_OAEP_PADDING)
{
self::paddingModeLimitedCheck($padding);
if (!openssl_public_encrypt($plaintext, $encrypted, $this->getPublicKey(), $padding)) {
throw new PayException('Encrypting the input $plaintext failed, please checking your $publicKey whether or nor correct.');
}
return base64_encode($encrypted);
}
/**
* decrypt ciphertext.
*
* @param array $encryptCertificate
*
* @return string
*/
public function decrypt(array $encryptCertificate)
{
$ciphertext = base64_decode($encryptCertificate['ciphertext'], true);
$associatedData = $encryptCertificate['associated_data'];
$nonceStr = $encryptCertificate['nonce'];
$aesKey = $this->config->key;
try {
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
}
} catch (\Exception $exception) {
throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
} catch (\SodiumException $exception) {
throw new InvalidArgumentException($exception->getMessage(), $exception->getCode());
}
throw new InvalidArgumentException('AEAD_AES_256_GCM 需要 PHP 7.1 以上或者安装 libsodium-php');
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\v3pay;
use crmeb\exceptions\PayException;
use crmeb\services\wechat\config\V3PaymentConfig;
use Psr\SimpleCache\InvalidArgumentException;
use think\facade\Cache;
/**
* Class Certficates
* @package crmeb\services\easywechat\v3pay
* @property V3PaymentConfig $config
*/
trait Certficates
{
/**
* @param string|null $key
* @return array|mixed|null
* @throws InvalidArgumentException
*/
public function getCertficatescAttr(string $key = null)
{
$driver = Cache::store('file');
$cacheKey = '_wx_v3' . $this->config->serialNo;
if ($driver->has($cacheKey)) {
$res = $driver->get($cacheKey);
if ($key && $res) {
return $res[$key] ?? null;
} else {
return $res;
}
}
$certficates = $this->getCertficates();
$driver->set($cacheKey, $certficates, 3600 * 24 * 30);
if ($key && $certficates) {
return $certficates[$key] ?? null;
}
return $certficates;
}
/**
* get certficates.
*
* @return array
*/
public function getCertficates()
{
$response = $this->request('v3/certificates', 'GET', [], false);
if (isset($response['code'])) {
throw new PayException($response['message']);
}
$certificates = $response['data'][0];
$certificates['certificates'] = $this->decrypt($certificates['encrypt_certificate']);
unset($certificates['encrypt_certificate']);
return $certificates;
}
}

View File

@@ -0,0 +1,526 @@
<?php
/**
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2022 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
namespace crmeb\services\wechat\v3pay;
use crmeb\exceptions\PayException;
use crmeb\services\wechat\config\V3PaymentConfig;
use crmeb\services\wechat\Payment;
use Psr\SimpleCache\InvalidArgumentException;
/**
* v3支付
* Class PayClient
* @package crmeb\services\easywechat\v3pay
*/
class PayClient extends BaseClient
{
//app支付
const API_APP_APY_URL = 'v3/pay/transactions/app';
//二维码支付截图
const API_NATIVE_URL = 'v3/pay/transactions/native';
//h5支付接口
const API_H5_URL = 'v3/pay/transactions/h5';
//jsapi支付接口
const API_JSAPI_URL = 'v3/pay/transactions/jsapi';
//发起商家转账API
const API_BATCHES_URL = 'v3/transfer/batches';
//退款
const API_REFUND_URL = 'v3/refund/domestic/refunds';
//退款查询接口
const API_REFUND_QUERY_URL = 'v3/refund/domestic/refunds/{out_refund_no}';
//发起转账
const API_TRANSFER_BILLS_URL = 'v3/fund-app/mch-transfer/transfer-bills';
//撤销转账
const API_TRANSFER_BILLS_CANCEL_URL = '/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}/cancel';
//查询转账
const API_TRANSFER_QUERY_URL = 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/{out_bill_no}';
/**
* @var string
*/
protected string $type = Payment::WEB;
protected string $notify_url = '';
/**
* @param string $type
* @return $this
* @author 等风来
* @email 136327134@qq.com
* @date 2022/11/18
*/
public function setType(string $type)
{
$this->type = $type;
return $this;
}
/**
* 公众号jsapi支付下单
* @param string $openid
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @return mixed
* @throws InvalidArgumentException
*/
public function jsapiPay(string $openid, string $outTradeNo, string $total, string $description, string $attach)
{
$appId = $this->config->wechatAppid;
$res = $this->pay('jsapi', $appId, $outTradeNo, $total, $description, $attach, ['openid' => $openid]);
return $this->configForJSSDKPayment($appId, $res['prepay_id']);
}
/**
* 小程序支付
* @param string $openid
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @return array|false|string
* @throws InvalidArgumentException
*/
public function miniprogPay(string $openid, string $outTradeNo, string $total, string $description, string $attach)
{
$appId = $this->config->miniprogAppid;
$res = $this->pay('jsapi', $appId, $outTradeNo, $total, $description, $attach, ['openid' => $openid]);
return $this->configForJSSDKPayment($appId, $res['prepay_id']);
}
/**
* APP支付下单
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @return mixed
* @throws InvalidArgumentException
*/
public function appPay(string $outTradeNo, string $total, string $description, string $attach)
{
$res = $this->pay('app', $this->config->appAppid, $outTradeNo, $total, $description, $attach);
return $this->configForAppPayment($res['prepay_id']);
}
/**
* native支付下单
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @return mixed
* @throws InvalidArgumentException
*/
public function nativePay(string $outTradeNo, string $total, string $description, string $attach)
{
return $this->pay('native', $this->config->wechatAppid, $outTradeNo, $total, $description, $attach);
}
/**
* h5支付下单
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @return mixed
* @throws InvalidArgumentException
*/
public function h5Pay(string $outTradeNo, string $total, string $description, string $attach)
{
$res = $this->pay('h5', $this->config->wechatAppid, $outTradeNo, $total, $description, $attach);
return ['mweb_url' => $res['h5_url']];
}
/**
* 下单
* @param string $type
* @param string $appid
* @param string $outTradeNo
* @param string $total
* @param string $description
* @param string $attach
* @param array $payer
* @return mixed
* @throws InvalidArgumentException
*/
public function pay(string $type, string $appid, string $outTradeNo, string $total, string $description, string $attach, array $payer = [])
{
$totalFee = (int)bcmul($total, '100');
$data = [
'appid' => $appid,
'mchid' => $this->config->mchId,
'out_trade_no' => $outTradeNo,
'attach' => $attach,
'description' => $description,
'notify_url' => $this->config->notifyUrl,
'amount' => [
'total' => $totalFee,
'currency' => 'CNY'
],
];
if ($payer) {
$data['payer'] = $payer;
}
$url = '';
switch ($type) {
case 'h5':
$url = self::API_H5_URL;
$data['scene_info'] = [
'payer_client_ip' => request()->ip(),
'h5_info' => [
'type' => 'Wap'
]
];
break;
case 'native':
$url = self::API_NATIVE_URL;
break;
case 'app':
$url = self::API_APP_APY_URL;
break;
case 'jsapi':
$url = self::API_JSAPI_URL;
break;
}
if (!$url) {
throw new PayException('缺少请求地址');
}
$res = $this->request($url, 'POST', ['json' => $data]);
if (!$res) {
throw new PayException('微信支付:下单失败');
}
if (isset($res['code']) && isset($res['message'])) {
throw new PayException($res['message']);
}
return $res;
}
/**
* 发起转账新接口(2025年1月15日升级)
* @param string $outBatchNo
* @param string $amount
* @param string $userName
* @param string $remark
* @param array $transferDetailList
* @return void
* User: liusl
* DateTime: 2025/2/14 上午11:20
*/
/**
* @param string $outBatchNo
* @param string $amount
* @param $openid
* @param string $userName
* @param string $remark
* @param string $perception
* @param array $transferDetailList
* @return mixed
* @throws InvalidArgumentException
* User: liusl
* DateTime: 2025/2/14 下午2:14
*/
public function transferBills(string $outBatchNo, string $amount, $openid, string $userName, string $remark, string $perception, array $transferDetailList, string $transfer_scene_id = '1000')
{
$data = [
'transfer_amount' => (int)bcmul($amount, 100, 0),
'out_bill_no' => $outBatchNo,
'transfer_scene_id' => $transfer_scene_id,
'openid' => $openid,
'transfer_remark' => $remark,
'notify_url' => sys_config('site_url') . '/api/pay/mchNotify/'.$this->type,
'user_recv_perception' => '现金奖励',
'transfer_scene_report_infos' => $transferDetailList,
];
if ($amount >= 2000) {
if (empty($userName)) {
throw new PayException('明细金额大于等于2000时,收款人姓名必须填写');
}
$data['user_name'] = $this->encryptor($userName);
}
$appidMap = [
Payment::WEB => $this->config->wechatAppid,
Payment::MINI => $this->config->miniprogAppid,
Payment::APP => $this->config->appAppid,
];
if (!isset($appidMap[$this->type])) {
throw new PayException('暂时只支持微信用户、小程序用户、APP微信登录用户提现');
}
$data['appid'] = $appidMap[$this->type];
$res = $this->request(self::API_TRANSFER_BILLS_URL, 'POST', ['json' => $data]);
if (!$res || isset($res['code'], $res['message'])) {
throw new PayException($res['message'] ?? '微信支付:发起商家转账失败');
}
return $res;
}
/**
* 发起商家转账API
* @param string $outBatchNo
* @param string $amount
* @param string $batchName
* @param string $remark
* @param array $transferDetailList
* @return mixed
* @throws InvalidArgumentException
*/
public function batches(string $outBatchNo, string $amount, string $batchName, string $remark, array $transferDetailList)
{
$totalFee = '0';
$amount = bcadd($amount, '0', 2);
foreach ($transferDetailList as &$item) {
if ($item['transfer_amount'] >= 2000 && !empty($item['user_name'])) {
throw new PayException('明细金额大于等于2000时,收款人姓名必须填写');
}
$totalFee = bcadd($totalFee, $item['transfer_amount'], 2);
$item['transfer_amount'] = (int)bcmul($item['transfer_amount'], 100, 0);
if (isset($item['user_name'])) {
$item['user_name'] = $this->encryptor($item['user_name']);
}
}
if ($totalFee !== $amount) {
throw new PayException('转账明细金额总和和转账总金额不一致');
}
$amount = (int)bcmul($amount, 100, 0);
$appid = null;
if ($this->type === Payment::WEB) {
$appid = $this->config->wechatAppid;
} else if ($this->type === Payment::MINI) {
$appid = $this->config->miniprogAppid;
} else if ($this->type === Payment::APP) {
$appid = $this->config->appAppid;
}
if (!$appid) {
throw new PayException('暂时只支持微信用户、小程序用户、APP微信登录用户提现');
}
$data = [
'appid' => $appid,
'out_batch_no' => $outBatchNo,
'batch_name' => $batchName,
'batch_remark' => $remark,
'total_amount' => $amount,
'total_num' => count($transferDetailList),
'transfer_detail_list' => $transferDetailList
];
$res = $this->request(self::API_BATCHES_URL, 'POST', ['json' => $data]);
if (!$res) {
throw new PayException('微信支付:发起商家转账失败');
}
if (isset($res['code']) && isset($res['message'])) {
throw new PayException($res['message']);
}
return $res;
}
/**
* 退款
* @param string $outTradeNo
* @param array $options
* @return mixed
* @throws InvalidArgumentException
*/
public function refund(string $outTradeNo, array $options = [])
{
if (!isset($options['pay_price'])) {
throw new PayException(400730);
}
$totalFee = floatval(bcmul($options['pay_price'], 100, 0));
$refundFee = isset($options['refund_price']) ? floatval(bcmul($options['refund_price'], 100, 0)) : null;
$refundReason = $options['desc'] ?? '';
$refundNo = $options['refund_id'] ?? $outTradeNo;
/*仅针对老资金流商户使用
REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款(默认使用未结算资金退款)
REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款
*/
$refundAccount = $opt['refund_account'] ?? 'AVAILABLE';
$data = [
'transaction_id' => $outTradeNo,
'out_refund_no' => $refundNo,
'amount' => [
'refund' => (int)$refundFee,
'currency' => 'CNY',
'total' => (int)$totalFee
],
'funds_account' => $refundAccount
];
if ($refundReason) {
$data['reason'] = $refundReason;
}
$res = $this->request(self::API_REFUND_URL, 'POST', ['json' => $data]);
if (!$res) {
throw new PayException('微信支付:发起退款失败');
}
if (isset($res['code']) && isset($res['message'])) {
throw new PayException($res['message']);
}
return $res;
}
/**
* 查询退款
* @param string $outRefundNo
* @return mixed
* @throws InvalidArgumentException
*/
public function queryRefund(string $outRefundNo)
{
$res = $this->request($this->getApiUrl(self::API_REFUND_QUERY_URL, ['out_refund_no'], [$outRefundNo]), 'GET');
if (!$res) {
throw new PayException(500000);
}
if (isset($res['code']) && isset($res['message'])) {
throw new PayException($res['message']);
}
return $res;
}
/**
* jsapi支付
* @param string $appid
* @param string $prepayId
* @param bool $json
* @return array|false|string
*/
public function configForPayment(string $appid, string $prepayId, bool $json = true)
{
$params = [
'appId' => $appid,
'timeStamp' => strval(time()),
'nonceStr' => uniqid(),
'package' => "prepay_id=$prepayId",
'signType' => 'RSA',
];
$message = $params['appId'] . "\n" .
$params['timeStamp'] . "\n" .
$params['nonceStr'] . "\n" .
$params['package'] . "\n";
openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$params['paySign'] = $sign;
return $json ? json_encode($params) : $params;
}
/**
* Generate app payment parameters.
* @param string $prepayId
* @return array
*/
public function configForAppPayment(string $prepayId): array
{
$params = [
'appid' => $this->config->wechatAppid,
'partnerid' => $this->config->mchId,
'prepayid' => $prepayId,
'noncestr' => uniqid(),
'timestamp' => time(),
'package' => 'Sign=WXPay',
];
$message = $params['appid'] . "\n" .
$params['timestamp'] . "\n" .
$params['noncestr'] . "\n" .
$params['prepayid'] . "\n";
openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$params['sign'] = $sign;
return $params;
}
/**
* 小程序支付
* @param string $appid
* @param string $prepayId
* @return array|false|string
*/
public function configForJSSDKPayment(string $appid, string $prepayId)
{
$config = $this->configForPayment($appid, $prepayId, false);
$config['timestamp'] = $config['timeStamp'];
unset($config['timeStamp']);
return $config;
}
/**
* @param $callback
* @return \think\Response
*/
public function handleNotify($callback)
{
$request = request();
$event_type = $request->post('event_type');
// 判断成功事件
$successEvents = ['TRANSACTION.SUCCESS', 'MCHTRANSFER.BILL.FINISHED'];
$success = in_array($event_type, $successEvents);
$data = $this->decrypt($request->post('resource', []));
$handleResult = call_user_func_array($callback, [json_decode($data, true), $success]);
$response = [
'code' => $handleResult === true ? 'SUCCESS' : 'FAIL',
'message' => $handleResult === true ? 'OK' : $handleResult,
];
return response($response, 200, [], 'json');
}
public function queryTransferBills(string $outBillNo)
{
$res = $this->request($this->getApiUrl(self::API_TRANSFER_QUERY_URL, ['out_bill_no'], [$outBillNo]), 'GET');
if (!$res) {
throw new PayException(500000);
}
return $res;
}
}