first commit

This commit is contained in:
2025-11-28 10:08:12 +08:00
commit 09a14bf0a3
1088 changed files with 145132 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 消失的彩虹海
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+44
View File
@@ -0,0 +1,44 @@
# WeChatPay SDK for PHP
微信支付第三方 PHP SDK,基于官方最新版本,包含V2和V3两种接口。
### 功能特点
- 根据微信支付最新API开发,相比官方SDK,功能更完善,代码更简洁
- 支持V2和V3两种接口,支持微信支付服务商模式与电商收付通模式
- 支持Composer安装,无需加载多余组件,可应用于任何平台或框架
- 符合`PSR`标准,你可以各种方便的与你的框架集成
- 基本完善的PHPDoc,可以随心所欲添加本项目中没有的API接口
### 环境要求
`PHP` >= 7.1
### 使用方法
1. Composer 安装。
```bash
composer require cccyun/wechatpay-sdk
```
2. 创建APIv2配置文件 [`config.php`](./examples/config.php),或APIv3配置文件 [`config.php`](./examples/V3/config.php),填写微信支付商户信息。
3. 引入配置文件,构造请求参数,调用PaymentService中的方法发起请求,APIv2参考 [`examples/qrpay.php`](./examples/qrpay.php)APIv3参考 [`examples/V3/qrpay.php`](./examples/V3/qrpay.php)
4. 更多实例,请移步 [`examples`](examples/) 目录。
5. 类功能说明
| 类名 | 说明 |
| --------------------- | ------------------------------------------------- |
| PaymentService | 基础支付服务类,所有支付功能都用这个 |
| JsApiTool | JSAPI支付工具类,用于公众号、小程序登录获取Openid |
| TransferService | 微信支付商家转账功能 |
| ProfitsharingService | 微信支付分账功能 |
| ComplainService | 消费者投诉处理功能 |
| PartnerPaymentService | 服务商基础支付服务类,APIv3服务商调用支付功能使用 |
6. 要对接的API在以上实现类中没有,可根据微信支付官方的文档,使用BaseService类中的execute方法直接调用接口,参考 [`examples/V3/other.php`](./examples/V3/other.php)
+27
View File
@@ -0,0 +1,27 @@
{
"name": "cccyun/wechatpay-sdk",
"description": "微信支付第三方 PHP SDK,基于官方最新版本,包含V2和V3两种接口。",
"type": "library",
"keywords": [
"wxpay",
"wechat",
"微信支付"
],
"license": "MIT",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=7.1"
},
"authors": [
{
"name": "caihong",
"email": "admin@cccyun.cn"
}
],
"autoload": {
"psr-4": {
"WeChatPay\\": "src/"
}
}
}
+225
View File
@@ -0,0 +1,225 @@
<?php
namespace WeChatPay;
use Exception;
class BaseService
{
//SDK版本号
static $VERSION = "3.0.10";
//应用APPID
protected $appId;
//商户号
protected $mchId;
//商户API密钥
protected $apiKey;
//子商户号
protected $subMchId;
//子商户公众账号ID
protected $subAppId;
//商户证书路径
protected $sslCertPath;
//商户证书私钥路径
protected $sslKeyPath;
//公共请求参数
protected $publicParams = [];
/**
* @param array $config 微信支付配置信息
*/
public function __construct(array $config)
{
if (empty($config['appid'])) {
throw new \InvalidArgumentException('应用APPID不能为空');
}
if (empty($config['mchid'])) {
throw new \InvalidArgumentException("商户号不能为空");
}
if (empty($config['apikey'])) {
throw new \InvalidArgumentException("商户API密钥不能为空");
}
$this->appId = $config['appid'];
$this->mchId = $config['mchid'];
$this->apiKey = $config['apikey'];
$this->sslCertPath = $config['sslcert_path'];
$this->sslKeyPath = $config['sslkey_path'];
if (isset($config['sub_mchid'])) {
$this->subMchId = $config['sub_mchid'];
}
if (isset($config['sub_appid'])) {
$this->subAppId = $config['sub_appid'];
}
}
/**
* 请求接口并解析返回数据
* @param string $url url
* @param array $params 请求参数
* @param bool $cert 是否需要证书
* @return mixed
* @throws Exception
*/
public function execute(string $url, array $params, bool $cert = false)
{
$params = array_merge($this->publicParams, $params);
$params['sign'] = $this->makeSign($params);
$xml = $this->array2Xml($params);
$response = $this->curl($url, $xml, $cert);
$result = $this->xml2array($response);
if (isset($result['return_code']) && $result['return_code'] == 'SUCCESS') {
if (isset($result['result_code']) && $result['result_code'] == 'SUCCESS') {
if (isset($result['sign']) && !$this->checkSign($result)) {
throw new Exception('返回数据验签失败');
}
return $result;
}
}
throw new WeChatPayException($result);
}
/**
* 验签
* @param $data
* @return bool
*/
protected function checkSign($data): bool
{
if (!isset($data['sign'])) return false;
$sign = $this->makeSign($data);
return $sign === $data['sign'];
}
/**
* 生成签名
* @param $data
* @return string
*/
protected function makeSign($data): string
{
ksort($data);
$signStr = '';
foreach ($data as $k => $v) {
if($k != 'sign' && !is_array($v) && !$this->isEmpty($v)){
$signStr .= $k . '=' . $v . '&';
}
}
$signStr = trim($signStr, '&') . '&key=' . $this->apiKey;
if (isset($data['sign_type']) && $data['sign_type'] == 'HMAC-SHA256') {
$sign = hash_hmac("sha256", $signStr, $this->apiKey);
} else {
$sign = md5($signStr);
}
return strtoupper($sign);
}
/**
* 校验某字符串或可被转换为字符串的数据,是否为 NULL 或均为空白字符.
*
* @param string|null $value
*
* @return bool
*/
protected function isEmpty(?string $value): bool
{
return $value === null || $value === '';
}
/**
* 产生随机字符串,不长于32位
* @param int $length
* @return string 产生的随机字符串
*/
protected function getNonceStr(int $length = 32): string
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 转为XML数据
* @param array $data 源数据
* @return string
*/
protected function array2Xml(array $data): string
{
$xml = '<xml>';
foreach ($data as $key => $val) {
$xml .= (is_numeric($val) ? "<{$key}>{$val}</{$key}>" : "<{$key}><![CDATA[{$val}]]></{$key}>");
}
return $xml . '</xml>';
}
/**
* 解析XML数据
* @param string $xml 源数据
* @return mixed
*/
protected function xml2array(string $xml)
{
if (!$xml) {
return false;
}
LIBXML_VERSION < 20900 && libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
}
/**
* 以post方式提交xml到对应的接口url
* @param string $url url
* @param mixed $xml 需要post的xml数据
* @param bool $useCert 是否需要证书
* @param int $second url执行超时时间
* @return string
* @throws Exception
*/
protected function curl(string $url, $xml, bool $useCert = false, int $second = 10): string
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "WXPaySDK/" . self::$VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " ". $this->mchId;
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($useCert) {
if (!file_exists($this->sslCertPath) || !file_exists($this->sslKeyPath)) {
throw new Exception('商户证书文件不存在');
}
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $this->sslCertPath);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $this->sslKeyPath);
}
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
$data = curl_exec($ch);
if (curl_errno($ch) > 0) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg, 0);
}
curl_close($ch);
return $data;
}
}
+155
View File
@@ -0,0 +1,155 @@
<?php
namespace WeChatPay;
use Exception;
/**
* JSAPI支付工具类
* 实现了从微信公众平台获取code、通过code获取openid和access_token
*/
class JsApiTool
{
const GET_AUTH_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
const GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
const GET_MINIAPP_TOKEN_URL = "https://api.weixin.qq.com/sns/jscode2session";
private $appid;
private $appsecret;
/**
* 网页授权接口微信服务器返回的数据,返回样例如下
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
* openid是微信支付jsapi支付接口必须的参数
* @var array
*/
public $data = null;
public function __construct($appid, $appsecret)
{
$this->appid = $appid;
$this->appsecret = $appsecret;
}
/**
* 通过跳转获取用户的openid,跳转流程如下:
* 1、设置自己需要调回的url及其其他参数,跳转到微信服务器
* 2、微信服务处理完成之后会跳转回用户redirect_uri地址,此时会带上一些参数,如:code
*
* @return string 用户的openid
* @throws Exception
*/
public function GetOpenid(): string
{
if (!isset($_GET['code'])) {
$this->login();
}
$code = $_GET['code'];
return $this->GetOpenidFromMp($code);
}
/**
* 跳转到微信公众平台登录
*/
public function login()
{
if (function_exists('is_https')) {
$redirect_uri = (is_https() ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
} else {
$redirect_uri = ($_SERVER['SERVER_PORT'] == 443 ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}
$param = [
"appid" => $this->appid,
"redirect_uri" => $redirect_uri,
"response_type" => "code",
"scope" => "snsapi_base",
"state" => "STATE"
];
$url = self::GET_AUTH_CODE_URL . '?' . http_build_query($param) . "#wechat_redirect";
Header("Location: $url");
exit;
}
/**
* 从公众平台获取openid
* @param string $code 微信跳转回来带上的code
*
* @return string openid
* @throws Exception
*/
public function GetOpenidFromMp(string $code): string
{
$param = [
"appid" => $this->appid,
"secret" => $this->appsecret,
"code" => $code,
"grant_type" => "authorization_code"
];
$url = self::GET_ACCESS_TOKEN_URL . '?' . http_build_query($param);
$res = $this->curl($url);
$data = json_decode($res, true);
if (isset($data['access_token']) && isset($data['openid'])) {
$this->data = $data;
return $data['openid'];
} elseif (isset($data['errcode'])) {
throw new Exception('Openid获取失败 [' . $data['errcode'] . ']' . $data['errmsg']);
} else {
throw new Exception('Openid获取失败,原因未知');
}
}
/**
* 微信小程序获取Openid
* @param string $code 登录时获取的code
*
* @return string openid
* @throws Exception
*/
public function AppGetOpenid(string $code): string
{
$param = [
"appid" => $this->appid,
"secret" => $this->appsecret,
"js_code" => $code,
"grant_type" => "authorization_code"
];
$url = self::GET_MINIAPP_TOKEN_URL . '?' . http_build_query($param);
$res = $this->curl($url);
$data = json_decode($res, true);
if (isset($data['session_key']) && isset($data['openid'])) {
$this->data = $data;
return $data['openid'];
} elseif (isset($data['errcode'])) {
throw new Exception('获取openid失败 [' . $data['errcode'] . ']' . $data['errmsg']);
} else {
throw new Exception('获取openid失败,原因未知');
}
}
/**
* 发起GET请求
* @param string $url 请求url
* @return string
*/
private function curl(string $url): string
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 6);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Linux; U; Android 4.0.4; es-mx; HTC_One_X Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0");
$res = curl_exec($ch);
curl_close($ch);
return $res;
}
}
@@ -0,0 +1,386 @@
<?php
namespace WeChatPay;
use Exception;
/**
* 基础支付服务类
* @see https://pay.weixin.qq.com/wiki/doc/api/index.html
*/
class PaymentService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
$this->publicParams = [
'appid' => $this->appId,
'mch_id' => $this->mchId,
'nonce_str' => $this->getNonceStr(),
'sign_type' => 'MD5',
];
if (!empty($this->subMchId)) {
$this->publicParams['sub_mch_id'] = $this->subMchId;
}
if (!empty($this->subAppId)) {
$this->publicParams['sub_appid'] = $this->subAppId;
}
}
/**
* 统一下单
* @param array $params 下单参数
* @return mixed
* @throws Exception
*/
public function unifiedOrder(array $params)
{
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
if (empty($params['out_trade_no'])) {
throw new \InvalidArgumentException('缺少统一支付接口必填参数out_trade_no');
}
if (empty($params['body'])) {
throw new \InvalidArgumentException('缺少统一支付接口必填参数body');
}
if (empty($params['total_fee'])) {
throw new \InvalidArgumentException('缺少统一支付接口必填参数total_fee');
}
if (empty($params['trade_type'])) {
throw new \InvalidArgumentException('缺少统一支付接口必填参数trade_type');
}
return $this->execute($url, $params);
}
/**
* NATIVE支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function nativePay(array $params)
{
if (empty($params['product_id'])) {
throw new \InvalidArgumentException('缺少NATIVE支付必填参数product_id');
}
$params['trade_type'] = 'NATIVE';
return $this->unifiedOrder($params);
}
/**
* JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function jsapiPay(array $params)
{
if (empty($params['openid'])) {
throw new \InvalidArgumentException('缺少JSAPI支付必填参数openid');
}
$params['trade_type'] = 'JSAPI';
$result = $this->unifiedOrder($params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 获取JSAPI支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array json数据
*/
private function getJsApiParameters(string $prepay_id): array
{
$params = [
'appId' => $this->appId,
'timeStamp' => time() . '',
'nonceStr' => $this->getNonceStr(),
'package' => 'prepay_id=' . $prepay_id,
'signType' => 'MD5',
];
$params['paySign'] = $this->makeSign($params);
return $params;
}
/**
* APP支付
* @param array $params 下单参数
* @return array APP支付json数据
* @throws Exception
*/
public function appPay(array $params): array
{
$params['trade_type'] = 'APP';
$result = $this->unifiedOrder($params);
return $this->getAppParameters($result['prepay_id']);
}
/**
* 获取APP支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array
*/
private function getAppParameters(string $prepay_id): array
{
$params = [
'appid' => $this->appId,
'partnerid' => $this->mchId,
'prepayid' => $prepay_id,
'package' => 'Sign=WXPay',
'noncestr' => $this->getNonceStr(),
'timestamp' => time().'',
];
$params['sign'] = $this->makeSign($params);
return $params;
}
/**
* H5支付
* @param array $params 下单参数
* @return mixed {"mweb_url":"支付跳转链接"}
* @throws Exception
*/
public function h5Pay(array $params)
{
$params['trade_type'] = 'MWEB';
return $this->unifiedOrder($params);
}
/**
* 付款码支付
* @param array $params 下单参数
* @return mixed {"openid":"用户标识","is_subscribe":"N","total_fee":888,"cash_fee":888,"transaction_id":"微信支付订单号","out_trade_no":"商户订单号","time_end":"支付完成时间"}
* @throws Exception
*/
public function microPay(array $params)
{
$url = 'https://api.mch.weixin.qq.com/pay/micropay';
if (empty($params['out_trade_no'])) {
throw new \InvalidArgumentException('缺少付款码支付接口必填参数out_trade_no');
}
if (empty($params['body'])) {
throw new \InvalidArgumentException('缺少付款码支付接口必填参数body');
}
if (empty($params['total_fee'])) {
throw new \InvalidArgumentException('缺少付款码支付接口必填参数total_fee');
}
if (empty($params['auth_code'])) {
throw new \InvalidArgumentException('缺少付款码支付接口必填参数auth_code');
}
return $this->execute($url, $params);
}
/**
* 撤销订单
* @param string|null $transaction_id 微信订单号
* @param string|null $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function reverse(string $transaction_id = null, string $out_trade_no = null)
{
$url = 'https://api.mch.weixin.qq.com/secapi/pay/reverse';
$params = [];
if ($transaction_id) {
$params['transaction_id'] = $transaction_id;
} elseif ($out_trade_no) {
$params['out_trade_no'] = $out_trade_no;
}
return $this->execute($url, $params, true);
}
/**
* 查询订单,微信订单号、商户订单号至少填一个
* @param string|null $transaction_id 微信订单号
* @param string|null $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function orderQuery(string $transaction_id = null, string $out_trade_no = null)
{
$url = 'https://api.mch.weixin.qq.com/pay/orderquery';
$params = [];
if ($transaction_id) {
$params['transaction_id'] = $transaction_id;
} elseif ($out_trade_no) {
$params['out_trade_no'] = $out_trade_no;
}
return $this->execute($url, $params);
}
/**
* 判断订单是否已完成
* @param string $transaction_id 微信订单号
* @return bool
*/
public function orderQueryResult(string $transaction_id): bool
{
try {
$data = $this->orderQuery($transaction_id);
return $data['trade_state'] == 'SUCCESS' || $data['trade_state'] == 'REFUND';
} catch (Exception $e) {
return false;
}
}
/**
* 关闭订单
* @param string $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function closeOrder(string $out_trade_no)
{
$url = 'https://api.mch.weixin.qq.com/pay/orderquery';
$params = [
'out_trade_no' => $out_trade_no
];
return $this->execute($url, $params);
}
/**
* 申请退款
* @param array $params
* @return mixed
* @throws Exception
*/
public function refund(array $params)
{
$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
if (empty($params['transaction_id']) && empty($params['out_trade_no'])) {
throw new \InvalidArgumentException('out_trade_no、transaction_id至少填一个');
}
if (empty($params['out_refund_no'])) {
throw new \InvalidArgumentException('out_refund_no参数不能为空');
}
if (empty($params['total_fee'])) {
throw new \InvalidArgumentException('total_fee参数不能为空');
}
if (empty($params['refund_fee'])) {
throw new \InvalidArgumentException('refund_fee参数不能为空');
}
return $this->execute($url, $params, true);
}
/**
* 查询退款
* @param array $params
* @return mixed
* @throws Exception
*/
public function refundQuery(array $params)
{
$url = 'https://api.mch.weixin.qq.com/pay/refundquery';
if (empty($params['transaction_id']) && empty($params['out_trade_no']) && empty($params['out_refund_no']) && empty($params['refund_id'])) {
throw new \InvalidArgumentException('退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个');
}
return $this->execute($url, $params);
}
/**
* 下载对账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function downloadBill(array $params)
{
$url = 'https://api.mch.weixin.qq.com/pay/downloadbill';
if (empty($params['bill_date'])) {
throw new \InvalidArgumentException('bill_date参数不能为空');
}
return $this->execute($url, $params);
}
/**
* 下载资金账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function downloadFundFlow(array $params)
{
$url = 'https://api.mch.weixin.qq.com/pay/downloadfundflow';
if (empty($params['bill_date'])) {
throw new \InvalidArgumentException('bill_date参数不能为空');
}
if (empty($params['account_type'])) {
throw new \InvalidArgumentException('account_type参数不能为空');
}
return $this->execute($url, $params);
}
/**
* 支付结果通知
* @return bool|mixed
* @throws Exception
*/
public function notify()
{
$xml = file_get_contents("php://input");
if (empty($xml)) {
throw new Exception('NO_DATA');
}
$result = $this->xml2array($xml);
if (!$result) {
throw new Exception('XML_ERROR');
}
if ($result['return_code'] != 'SUCCESS') {
throw new Exception($result['return_msg']);
}
if (!$this->checkSign($result)) {
throw new Exception('签名校验失败');
}
if (!isset($result['transaction_id'])) {
throw new Exception('缺少订单号参数');
}
if (!$this->orderQueryResult($result['transaction_id'])) {
throw new Exception('订单未完成');
}
return $result;
}
/**
* 退款结果通知
* @param array &$errmsg 错误信息
* @return bool|string
*/
public function refundNotify(array &$errmsg): bool
{
$xml = file_get_contents("php://input");
if (empty($xml)) {
$errmsg = 'NO_DATA';
return false;
}
$result = $this->xml2array($xml);
if (!$result) {
$errmsg = 'XML_ERROR';
return false;
}
if ($result['return_code'] != 'SUCCESS') {
$errmsg = $result['return_msg'];
return false;
}
$req_info = base64_decode($result['req_info']);
$md5_key = md5($this->apiKey);
return openssl_decrypt($req_info, 'aes-256-ecb', $md5_key);
}
/**
* 回复通知
* @param bool $isSuccess 是否成功
* @param string|null $msg 失败原因
*/
public function replyNotify(bool $isSuccess = true, ?string $msg = '')
{
$data = [];
if ($isSuccess) {
$data['return_code'] = 'SUCCESS';
$data['return_msg'] = 'OK';
} else {
$data['return_code'] = 'FAIL';
$data['return_msg'] = $msg;
}
$xml = $this->array2Xml($data);
echo $xml;
}
}
@@ -0,0 +1,185 @@
<?php
namespace WeChatPay;
use Exception;
/**
* 分账服务类
* @see https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=26_1
*/
class ProfitsharingService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
$this->publicParams = [
'appid' => $this->appId,
'mch_id' => $this->mchId,
'nonce_str' => $this->getNonceStr(),
'sign_type' => 'HMAC-SHA256',
];
if (!empty($this->subMchId)) {
$this->publicParams['sub_mch_id'] = $this->subMchId;
}
if (!empty($this->subAppId)) {
$this->publicParams['sub_appid'] = $this->subAppId;
}
}
/**
* 添加分账接收方
* @param string $account 分账接收方账号
* @param string|null $name 用户姓名(填写后校验)
* @return mixed
* @throws Exception
*/
public function addReceiver(string $account, string $name = null)
{
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver';
$receiver = [
'type' => 'PERSONAL_OPENID',
'account' => $account,
'relation_type' => 'SERVICE_PROVIDER'
];
if(!empty($name)) $receiver['name'] = $name;
$params = [
'receiver' => json_encode($receiver, JSON_UNESCAPED_UNICODE)
];
return $this->execute($url, $params);
}
/**
* 删除分账接收方
* @param string $account 分账接收方账号
* @return mixed
* @throws Exception
*/
public function deleteReceiver(string $account)
{
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingremovereceiver';
$receiver = [
'type' => 'PERSONAL_OPENID',
'account' => $account
];
$params = [
'receiver' => json_encode($receiver, JSON_UNESCAPED_UNICODE)
];
return $this->execute($url, $params);
}
/**
* 请求单次分账
* @param string $out_order_no 商户分账单号
* @param string $transaction_id 微信订单号
* @param string $openid 分账接收方账号
* @param string $name 用户姓名(填写后校验)
* @param int $amount 分账金额(分)
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号","status":"分账单状态","receivers":""}
* @throws Exception
*/
public function submit(string $out_order_no, string $transaction_id, string $openid, string $name, int $amount)
{
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharing';
$receiver = [
'type' => 'PERSONAL_OPENID',
'account' => $openid,
'amount' => $amount,
'description' => '订单分账'
];
if(!empty($name)) $receiver['name'] = $name;
$params = [
'out_order_no' => $out_order_no,
'transaction_id' => $transaction_id,
'receivers' => json_encode([$receiver], JSON_UNESCAPED_UNICODE)
];
return $this->execute($url, $params, true);
}
/**
* 查询分账结果
* @param string $out_order_no 商户分账单号
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号","status":"分账单状态","receivers":""}
* @throws Exception
*/
public function query(string $out_order_no, string $transaction_id)
{
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingquery';
$params = [
'out_order_no' => $out_order_no,
'transaction_id' => $transaction_id
];
return $this->execute($url, $params);
}
/**
* 解冻剩余资金
* @param string $out_order_no 商户分账单号
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号"}
* @throws Exception
*/
public function unfreeze(string $out_order_no, string $transaction_id)
{
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish';
$params = [
'out_order_no' => $out_order_no,
'transaction_id' => $transaction_id,
'description' => '分账已完成'
];
return $this->execute($url, $params, true);
}
/**
* 查询订单待分账金额
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","unsplit_amount":"订单剩余待分金额"}
* @throws Exception
*/
public function orderAmountQuery(string $transaction_id)
{
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingorderamountquery';
$params = [
'transaction_id' => $transaction_id,
];
return $this->execute($url, $params);
}
/**
* 分账回退
* @param array $params 请求参数
* @return mixed
* @throws Exception
*/
public function return(array $params)
{
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharingreturn';
if (empty($params['order_id']) && empty($params['out_order_no'])) {
throw new \InvalidArgumentException('order_id、out_order_no至少填一个');
}
if (empty($params['out_return_no'])) {
throw new \InvalidArgumentException('out_return_no参数不能为空');
}
return $this->execute($url, $params, true);
}
/**
* 分账回退结果查询
* @param string $out_return_no
* @param string $out_order_no
* @return mixed
* @throws Exception
*/
public function returnQuery(string $out_return_no, string $out_order_no)
{
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingreturnquery';
$params = [
'out_order_no' => $out_order_no,
'out_return_no' => $out_return_no
];
return $this->execute($url, $params);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace WeChatPay;
class RsaTool
{
/**
* @var string - Equal to `sequence(oid(1.2.840.113549.1.1.1), null))`
* @link https://datatracker.ietf.org/doc/html/rfc3447#appendix-A.2
*/
private const ASN1_OID_RSAENCRYPTION = '300d06092a864886f70d0101010500';
private const ASN1_SEQUENCE = 48;
private const CHR_NUL = "\0";
private const CHR_ETX = "\3";
/**
* Translate the \$thing strlen from `X690` style to the `ASN.1` 128bit hexadecimal length string
*
* @param string $thing - The string
*
* @return string The `ASN.1` 128bit hexadecimal length string
*/
private static function encodeLength(string $thing): string
{
$num = strlen($thing);
if ($num <= 0x7F) {
return sprintf('%c', $num);
}
$tmp = ltrim(pack('N', $num), self::CHR_NUL);
return pack('Ca*', strlen($tmp) | 0x80, $tmp);
}
/**
* Convert the `PKCS#1` format RSA Public Key to `SPKI` format
*
* @param string $thing - The base64-encoded string, without evelope style
*
* @return string The `SPKI` style public key without evelope string
*/
public static function pkcs1ToSpki(string $thing): string
{
$raw = self::CHR_NUL . base64_decode($thing);
$new = pack('H*', self::ASN1_OID_RSAENCRYPTION) . self::CHR_ETX . self::encodeLength($raw) . $raw;
return base64_encode(pack('Ca*a*', self::ASN1_SEQUENCE, self::encodeLength($new), $new));
}
public static function pemToBase64(string $data): string
{
$line = explode("\n", $data);
$base64 = '';
foreach($line as $row){
if(empty($row) || strpos($row, '-----BEGIN')!==false || strpos($row, '-----END')!==false) continue;
$base64 .= trim($row);
}
return $base64;
}
public static function base64ToPem(string $data, $type): string
{
if(empty($data) || strpos($data, '-----BEGIN')!==false) return $data;
$pem = "-----BEGIN ".$type."-----\n" .
wordwrap($data, 64, "\n", true) .
"\n-----END ".$type."-----";
return $pem;
}
public static function pkcs1ToSpkiPem(string $thing): string
{
$raw = self::pemToBase64($thing);
$new = self::pkcs1ToSpki($raw);
return self::base64ToPem($new, 'PUBLIC KEY');
}
}
@@ -0,0 +1,160 @@
<?php
namespace WeChatPay;
use Exception;
/**
* 转账服务类
* @see https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
* @see https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=25_1
*/
class TransferService extends BaseService
{
//加密公钥
private $publicKeyPath;
public function __construct($config)
{
parent::__construct($config);
$this->publicKeyPath = $config['publickey_path'];
$this->publicParams = [
'nonce_str' => $this->getNonceStr(),
];
}
/**
* 企业付款到零钱
* @param string $partner_trade_no 商户唯一订单号
* @param string $openid 用户openid
* @param string $name 用户姓名(填写后校验)
* @param numeric $amount 金额
* @param string $desc 备注
* @return mixed {"partner_trade_no":"商户唯一订单号","payment_no":"微信付款单号","payment_time":"付款成功时间"}
* @throws Exception
*/
public function transfer(string $partner_trade_no, string $openid, string $name, $amount, string $desc)
{
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
$params = [
'mch_appid' => $this->appId,
'mchid' => $this->mchId,
'partner_trade_no' => $partner_trade_no,
'openid' => $openid,
'amount' => $amount,
'desc' => $desc
];
if (!empty($name)) {
$params['check_name'] = 'FORCE_CHECK';
$params['re_user_name'] = $name;
}
return $this->execute($url, $params, true);
}
/**
* 查询付款
* @param string $partner_trade_no 商户唯一订单号
* @return mixed {"partner_trade_no":"商户唯一订单号","detail_id":"微信付款单号","status":"转账状态","reason":"失败原因","openid":"用户openid","transfer_name":"用户姓名","payment_amount":"付款金额","transfer_time":"转账时间","payment_time":"付款成功时间","desc":"付款备注"}
* @throws Exception
*/
public function transferQuery(string $partner_trade_no)
{
$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo';
$params = [
'mch_appid' => $this->appId,
'mchid' => $this->mchId,
'partner_trade_no' => $partner_trade_no
];
return $this->execute($url, $params);
}
/**
* 企业付款到银行卡
* @param string $partner_trade_no 商户唯一订单号
* @param string $bank_no 收款方银行卡号
* @param string $name 收款方用户名
* @param string $bank_code 收款方开户行(https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=24_4)
* @param numeric $amount 金额
* @param string $desc 备注
* @return mixed {"partner_trade_no":"商户唯一订单号","payment_no":"微信付款单号","cmms_amt":"手续费金额"}
* @throws Exception
*/
public function transferToBank(string $partner_trade_no, string $bank_no, string $name, string $bank_code, $amount, string $desc)
{
$pubKey = null;
if (empty($this->publicKeyPath)) {
throw new Exception('RSA加密公钥路径不能为空');
}
if (file_exists($this->publicKeyPath)) {
$pubKey = file_get_contents($this->publicKeyPath);
}
if (!$pubKey) {
$pubKey = $this->getPublicKey();
if (!file_put_contents($this->publicKeyPath, $pubKey)) {
throw new Exception('RSA加密公钥文件写入失败');
}
}
$pubkeyid = openssl_pkey_get_public($pubKey);
if (!$pubkeyid) {
throw new Exception('RSA加密公钥不正确');
}
$url = 'https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank';
$params = [
'mch_id' => $this->mchId,
'partner_trade_no' => $partner_trade_no,
'enc_bank_no' => $this->encryptData($bank_no, $pubkeyid),
'enc_true_name' => $this->encryptData($name, $pubkeyid),
'bank_code' => $bank_code,
'amount' => $amount,
'desc' => $desc
];
return $this->execute($url, $params, true);
}
/**
* 查询付款银行卡
* @param string $partner_trade_no 商户唯一订单号
* @return mixed {"partner_trade_no":"商户唯一订单号","payment_no":"微信付款单号","bank_no_md5":"收款用户银行卡号(MD5加密)","true_name_md5":"收款人真实姓名(MD5加密)","amount":"金额","cmms_amt":"手续费金额","status":"转账状态","create_time":"商户下单时间","pay_succ_time":"成功付款时间","reason":"失败原因"}
* @throws Exception
*/
public function queryBank(string $partner_trade_no)
{
$url = 'https://api.mch.weixin.qq.com/mmpaysptrans/query_bank';
$params = [
'mch_id' => $this->mchId,
'partner_trade_no' => $partner_trade_no
];
return $this->execute($url, $params);
}
/**
* 获取RSA加密公钥
* @throws Exception
*/
public function getPublicKey()
{
$url = 'https://fraud.mch.weixin.qq.com/risk/getpublickey';
$params = [
'mch_id' => $this->mchId,
];
$result = $this->execute($url, $params, true);
$pub_key = $result['pub_key'];
$pub_key = RsaTool::pkcs1ToSpkiPem($pub_key);
return $pub_key;
}
/**
* RSA加密
* @param string $data
* @param $pubKey
* @return string
* @throws Exception
*/
private function encryptData(string $data, $pubkeyid): string
{
openssl_public_encrypt($data, $encrypted, $pubkeyid, OPENSSL_PKCS1_OAEP_PADDING);
return base64_encode($encrypted);
}
}
@@ -0,0 +1,525 @@
<?php
namespace WeChatPay\V3;
use Exception;
class BaseService
{
//SDK版本号
static $VERSION = "1.4.8";
static $GATEWAY = "https://api.mch.weixin.qq.com";
//应用APPID
protected $appId;
//商户号
protected $mchId;
//商户APIv3密钥
protected $apiKey;
//子商户号
protected $subMchId;
//子商户公众账号ID
protected $subAppId;
//是否电商收付通
protected $ecommerce;
//「商户API私钥」文件路径
protected $merchantPrivateKeyFilePath;
//「商户API证书」的「证书序列号」
protected $merchantCertificateSerial;
//「微信支付平台证书」文件路径
protected $platformCertificateFilePath;
//微信支付平台证书序列号
protected $platformCertificateSerial;
//商户API私钥
protected $merchantPrivateKeyInstance;
//微信支付平台证书
protected $platformPublicKeyInstance;
//是否国际版商户
private $isGlobal = false;
private $download_cert = false;
/**
* @param array $config 微信支付配置信息
* @throws Exception
*/
public function __construct(array $config)
{
if (empty($config['appid'])) {
throw new \InvalidArgumentException('应用APPID不能为空');
}
if (empty($config['mchid'])) {
throw new \InvalidArgumentException("商户号不能为空");
}
if (empty($config['apikey'])) {
throw new \InvalidArgumentException("商户APIv3密钥不能为空");
}
if (strlen($config['apikey']) != 32) {
throw new \InvalidArgumentException("无效的商户APIv3密钥");
}
if (empty($config['merchantPrivateKeyFilePath'])) {
throw new \InvalidArgumentException("商户API私钥路径不能为空");
}
if (empty($config['merchantCertificateSerial'])) {
throw new \InvalidArgumentException("商户API证书序列号不能为空");
}
if (!file_exists($config['merchantPrivateKeyFilePath'])) {
throw new \InvalidArgumentException("商户API私钥文件不存在");
}
$this->appId = $config['appid'];
$this->mchId = $config['mchid'];
$this->apiKey = $config['apikey'];
$this->merchantPrivateKeyFilePath = $config['merchantPrivateKeyFilePath'];
$this->merchantCertificateSerial = $config['merchantCertificateSerial'];
$this->platformCertificateFilePath = $config['platformCertificateFilePath'];
$this->platformCertificateSerial = $config['platformCertificateSerial'];
if (isset($config['sub_mchid'])) {
$this->subMchId = $config['sub_mchid'];
}
if (isset($config['sub_appid'])) {
$this->subAppId = $config['sub_appid'];
}
if (isset($config['ecommerce'])) {
$this->ecommerce = $config['ecommerce'];
}
if (isset($config['isGlobal'])) {
$this->isGlobal = $config['isGlobal'];
}
$this->initCertificate();
}
/**
* 初始化证书与私钥
* @throws Exception
*/
private function initCertificate()
{
//读取商户API私钥
$this->merchantPrivateKeyInstance = openssl_pkey_get_private(file_get_contents($this->merchantPrivateKeyFilePath));
if (!$this->merchantPrivateKeyInstance) {
throw new Exception("商户API私钥错误");
}
//读取微信支付平台证书或公钥
if (file_exists($this->platformCertificateFilePath)) {
$certificate = file_get_contents($this->platformCertificateFilePath);
$this->platformPublicKeyInstance = openssl_pkey_get_public($certificate);
if($this->platformPublicKeyInstance && empty($this->platformCertificateSerial)) {
$cert_info = openssl_x509_parse($certificate);
if ($cert_info && isset($cert_info['serialNumberHex'])) {
$this->platformCertificateSerial = $cert_info['serialNumberHex'];
}
}
}
//没有微信支付平台证书,则下载证书
if (!$this->platformPublicKeyInstance) {
$this->downloadCertificate();
}
}
/**
* 下载微信支付平台证书
* @throws Exception
*/
private function downloadCertificate()
{
$result = $this->execute('GET', $this->isGlobal ? '/v3/global/certificates' : '/v3/certificates');
$effective_time = 0;
foreach ($result['data'] as $item) {
if (strtotime($item['effective_time']) > $effective_time) {
$effective_time = strtotime($item['effective_time']);
$encert = $item['encrypt_certificate'];
}
}
$certificate = $this->decryptToString($encert['ciphertext'], $encert['nonce'], $encert['associated_data']);
if (!$certificate) {
throw new Exception('微信支付平台证书解密失败');
}
if (!file_put_contents($this->platformCertificateFilePath, $certificate)) {
throw new Exception('微信支付平台证书保存失败,可能无文件写入权限');
}
//从证书解析公钥与序列号
$this->platformPublicKeyInstance = openssl_x509_read($certificate);
if (!$this->platformPublicKeyInstance) {
throw new Exception("微信支付平台证书错误");
}
$cert_info = openssl_x509_parse($certificate);
if ($cert_info && isset($cert_info['serialNumberHex'])) {
$this->platformCertificateSerial = $cert_info['serialNumberHex'];
}
$this->download_cert = true;
}
/**
* 请求接口并解析返回数据
* @param string $method 请求方式 GET POST PUT
* @param string $path 请求路径
* @param array $params 请求参数
* @param bool $cert 是否包含平台公钥序列号
* @return mixed
* @throws Exception
*/
public function execute(string $method, string $path, array $params = [], bool $cert = false)
{
$url = self::$GATEWAY . $path;
$body = '';
if ($method == 'GET' || $method == 'DELETE') {
if (count($params) > 0) {
$url .= '?' . http_build_query($params);
}
} elseif(!empty($params)) {
$body = json_encode($params);
}
$authorization = $this->getAuthorization($method, $url, $body);
$header[] = 'Accept: application/json';
$header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $authorization;
if ($cert) {
$header[] = 'Wechatpay-Serial: ' . $this->platformCertificateSerial;
}
if ($method == 'POST' || $method == 'PUT') {
$header[] = 'Content-Type: application/json';
}
[$httpCode, $header, $response] = $this->curl($method, $url, $header, $body);
$result = json_decode($response, true);
if ($httpCode >= 200 && $httpCode <= 299) {
if ($path != '/v3/certificates' && $path != '/v3/global/certificates' && !$this->checkResponseSign($response, $header)) {
throw new Exception("微信支付返回数据验签失败");
}
return $result;
}
throw new WeChatPayException($result, $httpCode);
}
/**
* 下载账单/图片
* @param string $download_url 下载地址
* @return mixed
* @throws Exception
*/
public function download(string $download_url)
{
$method = 'GET';
$authorization = $this->getAuthorization($method, $download_url);
$header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $authorization;
[$httpCode, $header, $response] = $this->curl($method, $download_url, $header);
if ($httpCode >= 200 && $httpCode <= 299) {
return $response;
} else {
$result = json_decode($response, true);
throw new WeChatPayException($result, $httpCode);
}
}
/**
* 上传文件
* @param string $path 请求路径
* @param string $file_path 本地文件路径
* @param string $file_name 文件名
* @return mixed
* @throws Exception
*/
public function upload(string $path, string $file_path, string $file_name)
{
$url = self::$GATEWAY . $path;
if (!file_exists($file_path)) {
throw new Exception("文件不存在");
}
$meta = [
'filename' => $file_name,
'sha256' => hash_file("sha256", $file_path)
];
$meta_json = json_encode($meta);
$params = [
'file' => new \CURLFile($file_path, '', $file_name),
'meta' => $meta_json
];
$authorization = $this->getAuthorization('POST', $url, $meta_json);
$header[] = 'Accept: application/json';
$header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $authorization;
[$httpCode, $header, $response] = $this->curl('POST', $url, $header, $params);
$result = json_decode($response, true);
if ($httpCode >= 200 && $httpCode <= 299) {
if (!$this->checkResponseSign($response, $header)) {
throw new Exception("微信支付返回数据验签失败");
}
return $result;
}
throw new WeChatPayException($result, $httpCode);
}
/**
* 返回数据验签
* @param string $body 返回内容
* @param string $header 返回头部
* @return bool
* @throws Exception
*/
protected function checkResponseSign(string $body, string $header): bool
{
if (!$this->platformCertificateSerial) return true;
if (preg_match('/Wechatpay-Signature: (.*?)\r\n/', $header, $signature)) {
$signature = $signature[1];
}
if (preg_match('/Wechatpay-Nonce: (.*?)\r\n/', $header, $nonce)) {
$nonce = $nonce[1];
}
if (preg_match('/Wechatpay-Timestamp: (.*?)\r\n/', $header, $timestamp)) {
$timestamp = $timestamp[1];
}
if (preg_match('/Wechatpay-Serial: (.*?)\r\n/', $header, $serial)) {
$serial = $serial[1];
}
if (empty($signature)) return false;
if ($serial != $this->platformCertificateSerial) {
if (!$this->download_cert) {
$this->downloadCertificate();
}
if ($serial != $this->platformCertificateSerial) {
throw new Exception('平台证书序列号不匹配');
}
}
return $this->checkSign($timestamp, $nonce, $body, $signature);
}
/**
* 验证签名
* @param string $timestamp 应答时间戳
* @param string $nonce 应答随机串
* @param string $body 应答报文主体
* @param string $signature 应答签名
* @return bool
*/
protected function checkSign(string $timestamp, string $nonce, string $body, string $signature): bool
{
$message = $timestamp . "\n" . $nonce . "\n" . $body . "\n";
$result = openssl_verify($message, base64_decode($signature), $this->platformPublicKeyInstance, OPENSSL_ALGO_SHA256);
return $result === 1;
}
/**
* 生成签名
* @param array $arr - 待签名数组
* @return string
*/
protected function makeSign(array $arr): string
{
$message = implode("\n", array_merge($arr, ['']));
openssl_sign($message, $sign, $this->merchantPrivateKeyInstance, OPENSSL_ALGO_SHA256);
return base64_encode($sign);
}
/**
* 生成authorization
* @param string $method 请求方式 GET POST PUT
* @param string $url 请求URL
* @param string $body 请求内容 GET时留空
*/
protected function getAuthorization(string $method, string $url, string $body = ''): string
{
$url_values = parse_url($url);
$url = $url_values['path'] . (isset($url_values['query']) ? ('?' . $url_values['query']) : '');
$timestamp = (string)time();
$nonce = $this->getNonceStr();
$sign = $this->makeSign([$method, $url, $timestamp, $nonce, $body]);
return sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $this->mchId, $nonce, $timestamp, $this->merchantCertificateSerial, $sign);
}
/**
* 异步回调处理
* @return array 回调解密后的数据
* @throws Exception
*/
public function notify(): array
{
$inWechatpaySignature = $_SERVER['HTTP_WECHATPAY_SIGNATURE'];
$inWechatpayTimestamp = $_SERVER['HTTP_WECHATPAY_TIMESTAMP'];
$inWechatpaySerial = $_SERVER['HTTP_WECHATPAY_SERIAL'];
$inWechatpayNonce = $_SERVER['HTTP_WECHATPAY_NONCE'];
$inBody = file_get_contents('php://input');
if (empty($inBody)) {
throw new Exception('no data');
}
if ($this->platformCertificateSerial != $inWechatpaySerial) {
throw new Exception('平台证书序列号不匹配');
}
// 使用平台API证书验签
if (!$this->checkSign($inWechatpayTimestamp, $inWechatpayNonce, $inBody, $inWechatpaySignature)) {
throw new Exception('签名校验失败');
}
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 使用PHP7的数据解构语法,从Array中解构并赋值变量
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $associated_data
]] = $inBodyArray;
// 加密文本消息解密
$inBodyResource = $this->decryptToString($ciphertext, $nonce, $associated_data);
// 把解密后的文本转换为PHP Array数组
// print_r($inBodyResourceArray);
return json_decode($inBodyResource, true);
}
/**
* 回复通知
* @param bool $isSuccess 是否成功
* @param string|null $msg 失败原因
*/
public function replyNotify(bool $isSuccess = true, ?string $msg = '')
{
$data = [];
if ($isSuccess) {
$data['code'] = 'SUCCESS';
} else {
@header("HTTP/1.1 499 Error");
$data['code'] = 'FAIL';
$data['message'] = $msg;
}
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
echo $json;
}
/**
* 产生随机字符串,不长于32位
* @param int $length
* @return string 产生的随机字符串
*/
protected function getNonceStr(int $length = 32): string
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 敏感信息RSA加密
* @param $str
* @return string|bool
*/
public function rsaEncrypt($str)
{
if (openssl_public_encrypt($str, $encrypted, $this->platformPublicKeyInstance, OPENSSL_PKCS1_OAEP_PADDING)) {
return base64_encode($encrypted);
}
return false;
}
/**
* 敏感信息RSA解密
* @param $str
* @return string|bool
*/
public function rsaDecrypt($str)
{
if (openssl_private_decrypt(base64_decode($str), $decrypted, $this->merchantPrivateKeyInstance, OPENSSL_PKCS1_OAEP_PADDING)) {
return $decrypted;
}
return false;
}
/**
* 解密AEAD AES 256gcm密文
* @param string $ciphertext AES GCM cipher text
* @param string $nonceStr AES GCM nonce
* @param string $associatedData AES GCM additional authentication data
*
* @return string|bool Decrypted string on success or FALSE on failure
* @throws Exception
*/
protected function decryptToString(string $ciphertext, string $nonceStr, string $associatedData)
{
$ciphertext = base64_decode($ciphertext);
if (strlen($ciphertext) <= 16) {
return false;
}
if (function_exists('sodium_crypto_aead_aes256gcm_is_available') && sodium_crypto_aead_aes256gcm_is_available()) {
return sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->apiKey);
}
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -16);
$authTag = substr($ciphertext, -16);
return openssl_decrypt($ctext, 'aes-256-gcm', $this->apiKey, OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
}
throw new Exception('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
/**
* 发起curl请求
* @param string $method 请求方式 GET POST PUT
* @param string $url 请求URL
* @param array $header 请求头部
* @param null $body POST内容
* @param int $timeout 超时时间
* @return array [http状态码,响应头部,响应数据]
* @throws Exception
*/
protected function curl(string $method, string $url, array $header, $body = null, int $timeout = 10): array
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "wechatpay-php/" . self::$VERSION . " curl/" . $curlVersion['version'] . " (" . PHP_OS . "/" . php_uname('r') . ") PHP/" . PHP_VERSION;
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} elseif ($method == 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} elseif ($method == 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
}
$data = curl_exec($ch);
if (curl_errno($ch) > 0) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg, 0);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($data, 0, $headerSize);
$body = substr($data, $headerSize);
curl_close($ch);
return [$httpCode, $header, $body];
}
}
@@ -0,0 +1,190 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 消费者投诉服务类
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter6_2_5.shtml
*/
class ComplainService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 查询投诉单列表
* @param string $begin_date 开始日期,格式为yyyy-MM-DD
* @param string $end_date 结束日期,格式为yyyy-MM-DD
* @param int $page_no 分页号,从1开始
* @param int $page_size 分页大小1-50,默认为10
* @return mixed {"limit":10,"offset":0,"total_count":100,"data":[]}
* @throws Exception
*/
public function batchQuery(string $begin_date, string $end_date, int $page_no = 1, int $page_size = 10)
{
$path = '/v3/merchant-service/complaints-v2';
$offset = $page_size * ($page_no - 1);
$params = [
'limit' => $page_size,
'offset' => $offset,
'begin_date' => $begin_date,
'end_date' => $end_date
];
return $this->execute('GET', $path, $params);
}
/**
* 查询投诉单详情
* @param string $complaint_id 投诉单号
* @return mixed
* @throws Exception
*/
public function query(string $complaint_id)
{
$path = '/v3/merchant-service/complaints-v2/'.$complaint_id;
return $this->execute('GET', $path);
}
/**
* 查询投诉协商历史
* @param string $complaint_id 投诉单号
* @return mixed
* @throws Exception
*/
public function queryHistorys(string $complaint_id)
{
$path = '/v3/merchant-service/complaints-v2/'.$complaint_id.'/negotiation-historys';
return $this->execute('GET', $path);
}
/**
* 创建投诉通知回调地址
* @param string $url 通知地址
* @return mixed
* @throws Exception
*/
public function createNotifications(string $url)
{
$path = '/v3/merchant-service/complaint-notifications';
$params = [
'url' => $url
];
return $this->execute('POST', $path, $params);
}
/**
* 查询投诉通知回调地址
* @return mixed {"mchid":"商户号","url":"通知地址"}
* @throws Exception
*/
public function queryNotifications()
{
$path = '/v3/merchant-service/complaint-notifications';
return $this->execute('GET', $path);
}
/**
* 更新投诉通知回调地址
* @param string $url 通知地址
* @return mixed
* @throws Exception
*/
public function updateNotifications(string $url)
{
$path = '/v3/merchant-service/complaint-notifications';
$params = [
'url' => $url
];
return $this->execute('PUT', $path, $params);
}
/**
* 删除投诉通知回调地址
* @return void
* @throws Exception
*/
public function deleteNotifications()
{
$path = '/v3/merchant-service/complaint-notifications';
$this->execute('DELETE', $path);
}
/**
* 回复用户
* @param string $complaint_id 投诉单号
* @param string $complainted_mchid 被诉商户号
* @param string $response_content 回复内容
* @param array $response_images 回复图片列表
* @return void
* @throws Exception
*/
public function response(string $complaint_id, string $complainted_mchid, string $response_content, array $response_images)
{
$path = '/v3/merchant-service/complaints-v2/'.$complaint_id.'/response';
$params = [
'complainted_mchid' => $complainted_mchid,
'response_content' => $response_content,
'response_images' => $response_images,
];
$this->execute('POST', $path, $params);
}
/**
* 反馈处理完成
* @param string $complaint_id 投诉单号
* @param string $complainted_mchid 被诉商户号
* @return void
* @throws Exception
*/
public function complete(string $complaint_id, string $complainted_mchid)
{
$path = '/v3/merchant-service/complaints-v2/'.$complaint_id.'/complete';
$params = [
'complainted_mchid' => $complainted_mchid,
];
$this->execute('POST', $path, $params);
}
/**
* 更新退款审批结果
* @param string $complaint_id 投诉单号
* @param array $params 请求参数
* @return void
* @throws Exception
*/
public function updateRefundProgress(string $complaint_id, array $params)
{
$path = '/v3/merchant-service/complaints-v2/'.$complaint_id.'/update-refund-progress';
$this->execute('POST', $path, $params);
}
/**
* 上传反馈图片
* @param string $file_path 文件路径
* @param string $file_name 文件名
* @return string
* @throws Exception
*/
public function uploadImage(string $file_path, string $file_name): string
{
$path = '/v3/merchant-service/images/upload';
$result = $this->upload($path, $file_path, $file_name);
return $result['media_id'];
}
/**
* 下载图片
* @param string $media_id 媒体文件标识ID
* @return string
* @throws Exception
*/
public function getImage(string $media_id): string
{
$url = self::$GATEWAY.'/v3/merchant-service/images/'.urlencode($media_id);
return $this->download($url);
}
}
@@ -0,0 +1,373 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 全球版支付服务类
* @see https://pay.weixin.qq.com/wiki/doc/api_external/index_ch.shtml
*/
class GlobalPaymentService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 付款码支付
* @param array $params 下单参数
* @return mixed
* @throws Exception
*/
public function microPay(array $params){
$path = '/v3/global/micropay/transactions/pay';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
$params['trade_type'] = 'MICROPAY';
return $this->execute('POST', $path, $params);
}
/**
* NATIVE支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function nativePay(array $params){
$path = '/v3/global/transactions/native';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
$params['trade_type'] = 'NATIVE';
return $this->execute('POST', $path, $params);
}
/**
* JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function jsapiPay(array $params): array
{
$path = '/v3/global/transactions/jsapi';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
$params['trade_type'] = 'JSAPI';
$result = $this->execute('POST', $path, $params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 获取JSAPI支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array json数据
*/
private function getJsApiParameters(string $prepay_id): array
{
$params = [
'appId' => $this->appId,
'timeStamp' => time().'',
'nonceStr' => $this->getNonceStr(),
'package' => 'prepay_id=' . $prepay_id,
];
$params['paySign'] = $this->makeSign([$params['appId'], $params['timeStamp'], $params['nonceStr'], $params['package']]);
$params['signType'] = 'RSA';
return $params;
}
/**
* H5支付
* @param array $params 下单参数
* @return mixed {"h5_url":"支付跳转链接"}
* @throws Exception
*/
public function h5Pay(array $params){
$path = '/v3/global/transactions/mweb';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
$params['trade_type'] = 'MWEB';
return $this->execute('POST', $path, $params);
}
/**
* APP支付
* @param array $params 下单参数
* @return array APP支付json数据
* @throws Exception
*/
public function appPay(array $params): array
{
$path = '/v3/global/transactions/app';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
$params['trade_type'] = 'APP';
$result = $this->execute('POST', $path, $params);
return $this->getAppParameters($result['prepay_id']);
}
/**
* 获取APP支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array
*/
private function getAppParameters(string $prepay_id): array
{
$params = [
'appid' => $this->appId,
'partnerid' => $this->mchId,
'prepayid' => $prepay_id,
'package' => 'Sign=WXPay',
'noncestr' => $this->getNonceStr(),
'timestamp' => time().'',
];
$params['sign'] = $this->makeSign([$params['appid'], $params['timestamp'], $params['noncestr'], $params['prepayid']]);
return $params;
}
/**
* 查询订单,微信订单号、商户订单号至少填一个
* @param string|null $transaction_id 微信订单号
* @param string|null $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function orderQuery(string $transaction_id = null, string $out_trade_no = null){
if(!empty($transaction_id)){
$path = '/v3/global/transactions/id/'.$transaction_id;
}elseif(!empty($out_trade_no)){
$path = '/v3/global/transactions/out-trade-no/'.$out_trade_no;
}else{
throw new Exception('微信支付订单号和商户订单号不能同时为空');
}
if (!empty($this->subMchId)) {
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
} else {
$params = [
'mchid' => $this->mchId,
];
}
return $this->execute('GET', $path, $params);
}
/**
* 判断订单是否已完成
* @param string $transaction_id 微信订单号
* @return bool
*/
public function orderQueryResult(string $transaction_id): bool
{
try {
$data = $this->orderQuery($transaction_id);
return $data['trade_state'] == 'SUCCESS' || $data['trade_state'] == 'REFUND';
} catch (Exception $e) {
return false;
}
}
/**
* 关闭订单
* @param string $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function closeOrder(string $out_trade_no){
$path = '/v3/global/transactions/out-trade-no/'.$out_trade_no.'/close';
if (!empty($this->subMchId)) {
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
} else {
$params = [
'mchid' => $this->mchId,
];
}
return $this->execute('POST', $path, $params);
}
/**
* 撤销订单
* @param string $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function reverseOrder($out_trade_no){
$path = '/v3/global/micropay/transactions/out-trade-no/'.$out_trade_no.'/reverse';
if (!empty($this->subMchId)) {
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
} else {
$params = [
'mchid' => $this->mchId,
];
}
return $this->execute('POST', $path, $params);
}
/**
* 申请退款
* @param array $params
* @return mixed
* @throws Exception
*/
public function refund($params){
$path = '/v3/global/refunds';
if (!empty($this->subMchId)) {
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
if (!empty($this->subAppId)) {
$publicParams['sub_appid'] = $this->subAppId;
}
} else {
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 查询退款
* @param string $out_refund_no
* @return mixed
* @throws Exception
*/
public function refundQuery(string $out_refund_no){
$path = '/v3/global/refunds/out-refund-no/'.$out_refund_no;
if (!empty($this->subMchId)) {
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
} else {
$params = [
'mchid' => $this->mchId,
];
}
return $this->execute('GET', $path, $params);
}
/**
* 下载对账单
* @param string $date
* @return mixed
* @throws Exception
*/
public function tradeBill(string $date){
$path = '/v3/global/statements';
if (!empty($this->subMchId)) {
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
} else {
$params = [
'mchid' => $this->mchId,
];
}
$params['date'] = $date;
return $this->execute('GET', $path, $params);
}
/**
* 支付通知处理
* @return array 支付成功通知参数
* @throws Exception
*/
public function notify(): array
{
$data = parent::notify();
if (!$data || !isset($data['id'])) {
throw new Exception('缺少订单号参数');
}
if (!$this->orderQueryResult($data['id'])) {
throw new Exception('订单未完成');
}
return $data;
}
}
@@ -0,0 +1,397 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 服务商基础支付服务类
* @see https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml
*/
class PartnerPaymentService extends BaseService
{
public function __construct(array $config)
{
if(strpos($config['sub_mchid'], ',')){
$sub_mchids = explode(',', $config['sub_mchid']);
$config['sub_mchid'] = $sub_mchids[array_rand($sub_mchids)];
}
parent::__construct($config);
}
/**
* NATIVE支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function nativePay(array $params){
$path = '/v3/pay/partner/transactions/native';
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_appid' => $this->subAppId,
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function jsapiPay(array $params){
$path = '/v3/pay/partner/transactions/jsapi';
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_appid' => $this->subAppId,
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 获取JSAPI支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array json数据
*/
private function getJsApiParameters(string $prepay_id): array
{
$params = [
'appId' => $this->appId,
'timeStamp' => time().'',
'nonceStr' => $this->getNonceStr(),
'package' => 'prepay_id=' . $prepay_id,
];
$params['paySign'] = $this->makeSign([$params['appId'], $params['timeStamp'], $params['nonceStr'], $params['package']]);
$params['signType'] = 'RSA';
return $params;
}
/**
* H5支付
* @param array $params 下单参数
* @return mixed {"h5_url":"支付跳转链接"}
* @throws Exception
*/
public function h5Pay(array $params){
$path = '/v3/pay/partner/transactions/h5';
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_appid' => $this->subAppId,
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* APP支付
* @param array $params 下单参数
* @return array {"prepay_id":"预支付交易会话标识"}
* @throws Exception
*/
public function appPay(array $params){
$path = '/v3/pay/partner/transactions/app';
$publicParams = [
'sp_appid' => $this->appId,
'sp_mchid' => $this->mchId,
'sub_appid' => $this->subAppId,
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getAppParameters($result['prepay_id']);
}
/**
* 获取APP支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array
*/
private function getAppParameters(string $prepay_id): array
{
$params = [
'appid' => $this->appId,
'partnerid' => $this->mchId,
'prepayid' => $prepay_id,
'package' => 'Sign=WXPay',
'noncestr' => $this->getNonceStr(),
'timestamp' => time().'',
];
$params['sign'] = $this->makeSign([$params['appid'], $params['timestamp'], $params['noncestr'], $params['prepayid']]);
return $params;
}
/**
* 查询订单,微信订单号、商户订单号至少填一个
* @param string|null $transaction_id 微信订单号
* @param string|null $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function orderQuery(string $transaction_id = null, string $out_trade_no = null){
if(!empty($transaction_id)){
$path = '/v3/pay/partner/transactions/id/'.$transaction_id;
}elseif(!empty($out_trade_no)){
$path = '/v3/pay/partner/transactions/out-trade-no/'.$out_trade_no;
}else{
throw new Exception('微信支付订单号和商户订单号不能同时为空');
}
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
return $this->execute('GET', $path, $params);
}
/**
* 判断订单是否已完成
* @param string $transaction_id 微信订单号
* @return bool
*/
public function orderQueryResult(string $transaction_id): bool
{
try {
$data = $this->orderQuery($transaction_id);
return $data['trade_state'] == 'SUCCESS' || $data['trade_state'] == 'REFUND';
} catch (Exception $e) {
return false;
}
}
/**
* 关闭订单
* @param string $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function closeOrder(string $out_trade_no){
$path = '/v3/pay/partner/transactions/out-trade-no/'.$out_trade_no.'/close';
$params = [
'sp_mchid' => $this->mchId,
'sub_mchid' => $this->subMchId,
];
return $this->execute('POST', $path, $params);
}
/**
* 申请退款
* @param array $params
* @return mixed
* @throws Exception
*/
public function refund(array $params){
$path = '/v3/refund/domestic/refunds';
$publicParams = [
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 查询退款
* @param string $out_refund_no
* @return mixed
* @throws Exception
*/
public function refundQuery(string $out_refund_no){
$path = '/v3/refund/domestic/refunds/'.$out_refund_no;
$params = [
'sub_mchid' => $this->subMchId,
];
return $this->execute('GET', $path, $params);
}
/**
* 申请交易账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function tradeBill(array $params){
$path = '/v3/bill/tradebill';
$publicParams = [
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('GET', $path, $params);
}
/**
* 申请资金账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function fundflowBill(array $params){
$path = '/v3/bill/fundflowbill';
return $this->execute('GET', $path, $params);
}
/**
* 申请单个子商户资金账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function subMerchantFundflowBill(array $params){
$path = '/v3/bill/sub-merchant-fundflowbill';
$publicParams = [
'sub_mchid' => $this->subMchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('GET', $path, $params);
}
/**
* 支付通知处理
* @return array 支付成功通知参数
* @throws Exception
*/
public function notify(): array
{
$data = parent::notify();
if (!$data || !isset($data['transaction_id']) && !isset($data['combine_out_trade_no'])) {
throw new Exception('缺少订单号参数');
}
if (!isset($data['combine_out_trade_no']) && !$this->orderQueryResult($data['transaction_id'])) {
throw new Exception('订单未完成');
}
return $data;
}
/**
* 合单Native支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function combineNativePay(array $params){
$path = '/v3/combine-transactions/native';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
if(!isset($order['sub_mchid'])) $order['sub_mchid'] = $this->subMchId;
if(!isset($order['sub_appid'])) $order['sub_appid'] = $this->subAppId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function combineJsapiPay(array $params): array
{
$path = '/v3/combine-transactions/jsapi';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
if(!isset($order['sub_mchid'])) $order['sub_mchid'] = $this->subMchId;
if(!isset($order['sub_appid'])) $order['sub_appid'] = $this->subAppId;
}
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 合单H5支付
* @param array $params 下单参数
* @return mixed {"h5_url":"支付跳转链接"}
* @throws Exception
*/
public function combineH5Pay(array $params){
$path = '/v3/combine-transactions/h5';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
if(!isset($order['sub_mchid'])) $order['sub_mchid'] = $this->subMchId;
if(!isset($order['sub_appid'])) $order['sub_appid'] = $this->subAppId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单APP支付
* @param array $params 下单参数
* @return mixed {"prepay_id":"预支付交易会话标识"}
* @throws Exception
*/
public function combineAppPay(array $params){
$path = '/v3/combine-transactions/app';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
if(!isset($order['sub_mchid'])) $order['sub_mchid'] = $this->subMchId;
if(!isset($order['sub_appid'])) $order['sub_appid'] = $this->subAppId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单查询订单
* @param string $combine_out_trade_no 合单商户订单号
* @return mixed
* @throws Exception
*/
public function combineQueryOrder(string $combine_out_trade_no){
$path = '/v3/combine-transactions/out-trade-no/'.$combine_out_trade_no;
return $this->execute('GET', $path, []);
}
/**
* 合单关闭订单
* @param string $combine_out_trade_no 合单商户订单号
* @param array $out_trade_no_list 子单订单号列表
* @return mixed
* @throws Exception
*/
public function combineCloseOrder(string $combine_out_trade_no, array $out_trade_no_list){
$path = '/v3/combine-transactions/out-trade-no/'.$combine_out_trade_no.'/close';
$sub_orders = [];
foreach($out_trade_no_list as $out_trade_no){
$sub_orders[] = [
'mchid' => $this->mchId,
'out_trade_no' => $out_trade_no,
'sub_appid' => $this->subAppId,
'sub_mchid' => $this->subMchId,
];
}
$params = [
'combine_appid' => $this->appId,
'sub_orders' => $sub_orders
];
return $this->execute('POST', $path, $params);
}
}
@@ -0,0 +1,349 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 基础支付服务类
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
*/
class PaymentService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* NATIVE支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function nativePay(array $params){
$path = '/v3/pay/transactions/native';
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function jsapiPay(array $params): array
{
$path = '/v3/pay/transactions/jsapi';
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 获取JSAPI支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array json数据
*/
private function getJsApiParameters(string $prepay_id): array
{
$params = [
'appId' => $this->appId,
'timeStamp' => time().'',
'nonceStr' => $this->getNonceStr(),
'package' => 'prepay_id=' . $prepay_id,
];
$params['paySign'] = $this->makeSign([$params['appId'], $params['timeStamp'], $params['nonceStr'], $params['package']]);
$params['signType'] = 'RSA';
return $params;
}
/**
* H5支付
* @param array $params 下单参数
* @return mixed {"h5_url":"支付跳转链接"}
* @throws Exception
*/
public function h5Pay(array $params){
$path = '/v3/pay/transactions/h5';
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* APP支付
* @param array $params 下单参数
* @return array APP支付json数据
* @throws Exception
*/
public function appPay(array $params): array
{
$path = '/v3/pay/transactions/app';
$publicParams = [
'appid' => $this->appId,
'mchid' => $this->mchId,
];
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getAppParameters($result['prepay_id']);
}
/**
* 获取APP支付的参数
* @param string $prepay_id 预支付交易会话标识
* @return array
*/
private function getAppParameters(string $prepay_id): array
{
$params = [
'appid' => $this->appId,
'partnerid' => $this->mchId,
'prepayid' => $prepay_id,
'package' => 'Sign=WXPay',
'noncestr' => $this->getNonceStr(),
'timestamp' => time().'',
];
$params['sign'] = $this->makeSign([$params['appid'], $params['timestamp'], $params['noncestr'], $params['prepayid']]);
return $params;
}
/**
* 查询订单,微信订单号、商户订单号至少填一个
* @param string|null $transaction_id 微信订单号
* @param string|null $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function orderQuery(string $transaction_id = null, string $out_trade_no = null){
if(!empty($transaction_id)){
$path = '/v3/pay/transactions/id/'.$transaction_id;
}elseif(!empty($out_trade_no)){
$path = '/v3/pay/transactions/out-trade-no/'.$out_trade_no;
}else{
throw new Exception('微信支付订单号和商户订单号不能同时为空');
}
$params = [
'mchid' => $this->mchId,
];
return $this->execute('GET', $path, $params);
}
/**
* 判断订单是否已完成
* @param string $transaction_id 微信订单号
* @return bool
*/
public function orderQueryResult(string $transaction_id): bool
{
try {
$data = $this->orderQuery($transaction_id);
return $data['trade_state'] == 'SUCCESS' || $data['trade_state'] == 'REFUND';
} catch (Exception $e) {
return false;
}
}
/**
* 关闭订单
* @param string $out_trade_no 商户订单号
* @return mixed
* @throws Exception
*/
public function closeOrder(string $out_trade_no){
$path = '/v3/pay/transactions/out-trade-no/'.$out_trade_no.'/close';
$params = [
'mchid' => $this->mchId,
];
return $this->execute('POST', $path, $params);
}
/**
* 申请退款
* @param array $params
* @return mixed
* @throws Exception
*/
public function refund(array $params){
$path = '/v3/refund/domestic/refunds';
return $this->execute('POST', $path, $params);
}
/**
* 查询退款
* @param string $out_refund_no
* @return mixed
* @throws Exception
*/
public function refundQuery(string $out_refund_no){
$path = '/v3/refund/domestic/refunds/'.$out_refund_no;
return $this->execute('GET', $path, []);
}
/**
* 申请交易账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function tradeBill(array $params){
$path = '/v3/bill/tradebill';
return $this->execute('GET', $path, $params);
}
/**
* 申请资金账单
* @param array $params
* @return mixed
* @throws Exception
*/
public function fundflowBill(array $params){
$path = '/v3/bill/fundflowbill';
return $this->execute('GET', $path, $params);
}
/**
* 支付通知处理
* @return array 支付成功通知参数
* @throws Exception
*/
public function notify(): array
{
$data = parent::notify();
if (!$data || !isset($data['transaction_id']) && !isset($data['combine_out_trade_no'])) {
throw new Exception('缺少订单号参数');
}
if (!isset($data['combine_out_trade_no']) && !$this->orderQueryResult($data['transaction_id'])) {
throw new Exception('订单未完成');
}
return $data;
}
/**
* 合单Native支付
* @param array $params 下单参数
* @return mixed {"code_url":"二维码链接"}
* @throws Exception
*/
public function combineNativePay(array $params){
$path = '/v3/combine-transactions/native';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单JSAPI支付
* @param array $params 下单参数
* @return array Jsapi支付json数据
* @throws Exception
*/
public function combineJsapiPay(array $params): array
{
$path = '/v3/combine-transactions/jsapi';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
}
$params = array_merge($publicParams, $params);
$result = $this->execute('POST', $path, $params);
return $this->getJsApiParameters($result['prepay_id']);
}
/**
* 合单H5支付
* @param array $params 下单参数
* @return mixed {"h5_url":"支付跳转链接"}
* @throws Exception
*/
public function combineH5Pay(array $params){
$path = '/v3/combine-transactions/h5';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单APP支付
* @param array $params 下单参数
* @return mixed {"prepay_id":"预支付交易会话标识"}
* @throws Exception
*/
public function combineAppPay(array $params){
$path = '/v3/combine-transactions/app';
$publicParams = [
'combine_appid' => $this->appId,
'combine_mchid' => $this->mchId,
];
foreach($params['sub_orders'] as &$order){
$order['mchid'] = $this->mchId;
}
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params);
}
/**
* 合单查询订单
* @param string $combine_out_trade_no 合单商户订单号
* @return mixed
* @throws Exception
*/
public function combineQueryOrder(string $combine_out_trade_no){
$path = '/v3/combine-transactions/out-trade-no/'.$combine_out_trade_no;
return $this->execute('GET', $path, []);
}
/**
* 合单关闭订单
* @param string $combine_out_trade_no 合单商户订单号
* @param array $out_trade_no_list 子单订单号列表
* @return mixed
* @throws Exception
*/
public function combineCloseOrder(string $combine_out_trade_no, array $out_trade_no_list){
$path = '/v3/combine-transactions/out-trade-no/'.$combine_out_trade_no.'/close';
$sub_orders = [];
foreach($out_trade_no_list as $out_trade_no){
$sub_orders[] = [
'mchid' => $this->mchId,
'out_trade_no' => $out_trade_no,
];
}
$params = [
'combine_appid' => $this->appId,
'sub_orders' => $sub_orders
];
return $this->execute('POST', $path, $params);
}
}
@@ -0,0 +1,164 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 分账服务类
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter4_1_4.shtml
*/
class ProfitsharingService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 添加分账接收方
* @param string $type 分账接收方类型
* @param string $account 分账接收方账号
* @param string|null $name 用户姓名(填写后校验)
* @return mixed
* @throws Exception
*/
public function addReceiver(string $type, string $account, string $name = null)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/receivers/add' : '/v3/profitsharing/receivers/add';
$params = [
'appid' => $this->appId,
'type' => $type,
'account' => $account,
'relation_type' => 'SUPPLIER',
];
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
if (!empty($name)) {
if ($this->ecommerce) {
$params['encrypted_name'] = $this->rsaEncrypt($name);
} else {
$params['name'] = $this->rsaEncrypt($name);
}
}
return $this->execute('POST', $path, $params, true);
}
/**
* 删除分账接收方
* @param string $type 分账接收方类型
* @param string $account 分账接收方账号
* @return mixed
* @throws Exception
*/
public function deleteReceiver($type, string $account)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/receivers/delete' : '/v3/profitsharing/receivers/delete';
$params = [
'appid' => $this->appId,
'type' => $type,
'account' => $account,
];
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
return $this->execute('POST', $path, $params);
}
/**
* 请求分账
* @param array $params 请求参数
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号","status":"分账单状态","receivers":""}
* @throws Exception
*/
public function submit(array $params)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/orders' : '/v3/profitsharing/orders';
$publicParams = [
'appid' => $this->appId,
];
if (!empty($this->subMchId)) $publicParams['sub_mchid'] = $this->subMchId;
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params, true);
}
/**
* 查询分账结果
* @param string $out_order_no 商户分账单号
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号","status":"分账单状态","receivers":""}
* @throws Exception
*/
public function query(string $out_order_no, string $transaction_id)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/orders' : '/v3/profitsharing/orders/' . $out_order_no;
$params = [
'transaction_id' => $transaction_id,
];
if ($this->ecommerce) {
$params['out_order_no'] = $out_order_no;
}
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
return $this->execute('GET', $path, $params);
}
/**
* 解冻剩余资金
* @param string $out_order_no 商户分账单号
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","out_order_no":"商户分账单号","order_id":"微信分账单号"}
* @throws Exception
*/
public function unfreeze(string $out_order_no, string $transaction_id)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/finish-order' : '/v3/profitsharing/orders/unfreeze';
$params = [
'transaction_id' => $transaction_id,
'out_order_no' => $out_order_no,
'description' => '取消分账'
];
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
return $this->execute('POST', $path, $params);
}
/**
* 查询订单待分账金额
* @param string $transaction_id 微信订单号
* @return mixed {"transaction_id":"微信订单号","unsplit_amount":"订单剩余待分金额"}
* @throws Exception
*/
public function orderAmountQuery(string $transaction_id)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/orders/' . $transaction_id . '/amounts' : '/v3/profitsharing/transactions/' . $transaction_id . '/amounts';
return $this->execute('GET', $path);
}
/**
* 请求分账回退
* @param array $params 请求参数
* @return mixed
* @throws Exception
*/
public function return(array $params)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/returnorders' : '/v3/profitsharing/return-orders';
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
return $this->execute('POST', $path, $params);
}
/**
* 查询分账回退结果
* @param string $out_return_no 商户回退单号
* @param string $out_order_no 商户分账单号
* @return mixed
* @throws Exception
*/
public function returnQuery(string $out_return_no, string $out_order_no)
{
$path = $this->ecommerce ? '/v3/ecommerce/profitsharing/returnorders' : '/v3/profitsharing/return-orders/' . $out_return_no;
$params = [
'out_order_no' => $out_order_no
];
if ($this->ecommerce) $params['out_return_no'] = $out_return_no;
if (!empty($this->subMchId)) $params['sub_mchid'] = $this->subMchId;
return $this->execute('GET', $path, $params);
}
}
@@ -0,0 +1,223 @@
<?php
namespace WeChatPay\V3;
use Exception;
/**
* 商家转账服务类
* @see https://pay.weixin.qq.com/docs/merchant/products/batch-transfer-to-balance/apilist.html
*/
class TransferService extends BaseService
{
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 发起批量转账
* @param array $params 请求参数
* @return mixed {"out_batch_no":"商家批次单号","batch_id":"微信批次单号","create_time":"批次创建时间"}
* @throws Exception
*/
public function transfer(array $params)
{
$path = '/v3/transfer/batches';
$publicParams = [
'appid' => $this->appId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params, true);
}
/**
* 微信批次单号查询转账批次单
* @param string $batch_id 微信批次单号
* @param array $params 查询参数
* @return mixed {"transfer_batch":{},"transfer_detail_list":[]}
* @throws Exception
*/
public function transferbatch(string $batch_id, array $params){
$path = '/v3/transfer/batches/batch-id/'.$batch_id;
return $this->execute('GET', $path, $params);
}
/**
* 微信明细单号查询转账明细单
* @param string $batch_id 微信批次单号
* @param string $detail_id 微信明细单号
* @return mixed
* @throws Exception
*/
public function transferdetail(string $batch_id, string $detail_id){
$path = '/v3/transfer/batches/batch-id/'.$batch_id.'/details/detail-id/'.$detail_id;
return $this->execute('GET', $path);
}
/**
* 商家批次单号查询转账批次单
* @param string $out_batch_no 商家批次单号
* @param array $params 查询参数
* @return mixed {"transfer_batch":{},"transfer_detail_list":[]}
* @throws Exception
*/
public function transferoutbatch(string $out_batch_no, array $params){
$path = '/v3/transfer/batches/out-batch-no/'.$out_batch_no;
return $this->execute('GET', $path, $params);
}
/**
* 商家明细单号查询转账明细单
* @param string $out_batch_no 商家批次单号
* @param string $out_detail_no 商家明细单号
* @return mixed
* @throws Exception
*/
public function transferoutdetail(string $out_batch_no, string $out_detail_no){
$path = '/v3/transfer/batches/out-batch-no/'.$out_batch_no.'/details/out-detail-no/'.$out_detail_no;
return $this->execute('GET', $path);
}
/**
* 转账账单电子回单申请
* @param string $out_batch_no 商家批次单号
* @return mixed
* @throws Exception
*/
public function transferBatchReceiptApply(string $out_batch_no)
{
$path = '/v3/transfer/bill-receipt';
$params = [
'out_batch_no' => $out_batch_no
];
return $this->execute('POST', $path, $params);
}
/**
* 查询转账账单电子回单
* @param string $out_batch_no 商家批次单号
* @return mixed
* @throws Exception
*/
public function transferBatchReceiptQuery(string $out_batch_no)
{
$path = '/v3/transfer/bill-receipt/'.$out_batch_no;
return $this->execute('GET', $path);
}
/**
* 转账明细电子回单申请
* @param string $out_batch_no 商家批次单号
* @param string $out_detail_no 商家明细单号
* @return mixed
* @throws Exception
*/
public function transferDetailReceiptApply(string $out_batch_no, string $out_detail_no)
{
$path = '/v3/transfer-detail/electronic-receipts';
$params = [
'accept_type' => 'BATCH_TRANSFER',
'out_batch_no' => $out_batch_no,
'out_detail_no' => $out_detail_no
];
return $this->execute('POST', $path, $params);
}
/**
* 查询转账明细电子回单
* @param string $out_batch_no 商家批次单号
* @param string $out_detail_no 商家明细单号
* @return mixed
* @throws Exception
*/
public function transferDetailReceiptQuery(string $out_batch_no, string $out_detail_no)
{
$path = '/v3/transfer-detail/electronic-receipts';
$params = [
'accept_type' => 'BATCH_TRANSFER',
'out_batch_no' => $out_batch_no,
'out_detail_no' => $out_detail_no
];
return $this->execute('GET', $path, $params);
}
/**
* 发起转账
* @param array $params 请求参数
* @return mixed {"out_bill_no":"商户单号","transfer_bill_no":"微信转账单号","create_time":"批次创建时间","state":"单据状态","fail_reason":"失败原因","package_info":"跳转领取页面的package信息"}
* @throws Exception
*/
public function mchTransfer(array $params)
{
$path = '/v3/fund-app/mch-transfer/transfer-bills';
$publicParams = [
'appid' => $this->appId,
];
$params = array_merge($publicParams, $params);
return $this->execute('POST', $path, $params, true);
}
/**
* 撤销转账
* @param string $out_bill_no 商户单号
* @return mixed
* @throws Exception
*/
public function cancelTransfer(string $out_bill_no)
{
$path = '/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$out_bill_no.'/cancel';
return $this->execute('POST', $path);
}
/**
* 商户单号查询转账单
* @param string $out_bill_no 商户单号
* @return mixed
* @throws Exception
*/
public function queryTransferByOutNo(string $out_bill_no){
$path = '/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$out_bill_no;
return $this->execute('GET', $path);
}
/**
* 微信单号查询转账单
* @param string $transfer_bill_no 微信单号
* @return mixed
* @throws Exception
*/
public function queryTransfer(string $transfer_bill_no){
$path = '/v3/fund-app/mch-transfer/transfer-bills/transfer-bill-no/'.$transfer_bill_no;
return $this->execute('GET', $path);
}
/**
* 申请电子回单
* @param string $out_bill_no 商户单号
* @return mixed
* @throws Exception
*/
public function transferReceiptApply(string $out_bill_no)
{
$path = '/v3/fund-app/mch-transfer/elecsign/out-bill-no';
$params = [
'out_bill_no' => $out_bill_no,
];
return $this->execute('POST', $path, $params);
}
/**
* 查询电子回单
* @param string $out_bill_no 商户单号
* @return mixed
* @throws Exception
*/
public function transferReceiptQuery(string $out_bill_no)
{
$path = '/v3/fund-app/mch-transfer/elecsign/out-bill-no/'.$out_bill_no;
return $this->execute('GET', $path);
}
}
@@ -0,0 +1,45 @@
<?php
namespace WeChatPay\V3;
/**
* 微信支付响应内容异常
*/
class WeChatPayException extends \Exception
{
private $res = [];
private $errCode;
private $httpCode;
/**
* @param array $res
* @param string $httpCode
*/
public function __construct($res, $httpCode)
{
$this->res = $res;
$this->httpCode = $httpCode;
if(is_array($res)){
$this->errCode = $res['code'];
$message = '['.$res['code'].']'.$res['message'].(isset($res['detail']['issue'])?'('.$res['detail']['issue'].')':'');
}else{
$message = '返回数据解析失败(http_code='.$httpCode.')';
}
parent::__construct($message);
}
public function getResponse(): array
{
return $this->res;
}
public function getErrCode()
{
return $this->errCode;
}
public function getHttpCode(): string
{
return $this->httpCode;
}
}
@@ -0,0 +1,39 @@
<?php
namespace WeChatPay;
/**
* 微信支付响应内容异常
*/
class WeChatPayException extends \Exception
{
private $res = [];
private $errCode;
/**
* @param array $res
*/
public function __construct($res)
{
$this->res = $res;
if (isset($res['err_code'])) {
$this->errCode = $res['err_code'];
$message = '['.$res['err_code'].']'.$res['err_code_des'];
} elseif (isset($res['return_code'])) {
$message = '['.$res['return_code'].']'.$res['return_msg'];
} else {
$message = '返回数据解析失败';
}
parent::__construct($message);
}
public function getResponse(): array
{
return $this->res;
}
public function getErrCode()
{
return $this->errCode;
}
}