一文搞懂微信支付 Api-v3 规则实现(附源码) https://developers.weixin.qq.com/community/develop/article/doc/000040ff8642e0555b0afe78951813
https://blog.csdn.net/qq_23564667/article/details/132169681
https://www.cnblogs.com/wjh0521/articles/16740612.html
使用条件
1、商户号已入驻 90 日且截止今日回推 30 天商户号保持连续不间断的交易。
2、登录微信支付商户平台 – 产品中心,开通付款到零钱。
付款资金
付款到零钱资金使用商户号余额资金。
根据商户号的账户开通情况,实际出款账户有做区别:
默认情况下,付款到零钱使用商户号基本户(或余额账户)余额。如商户号已开通运营账户,则付款到零钱使用运营账户内的资金。
基本户(或上述其他出款账户)的资金来源,可能是交易结算款项(仅基本户),或给账户充值的资金。当出款账户余额不足时,付款将因余额不足而付款失败。
1、开通 微信 商家转账到零钱;
。申请api v3秘钥(和api秘钥可以是一样的,需要分开在平台申请);
2、微信sdk安装:composer:composer require wechatpay/wechatpay;
3、证书生成:
4、PHP demo源码:
<?php
namespace app\common\extend;
use think\Exception;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
/**
* 实测 可用 相关配置弄好
* date: 2022/09/19 18:10
* desc:
*/
class WechatPayV3
{
private $v3_key;
private $merchantId;
private $merchantCertificateSerial;
private $merchantPrivateKeyFilePath;
private $platformCertificateFilePath;
private $platformPublicKeyInstance;
private $instance;
const AUTH_TAG_LENGTH_BYTE = 16;
public function __construct()
{
//证书生成命令(生成的证书在sdk目录 或 自己指定)
//php ./bin/CertificateDownloader.php -k jiaxin**************uzhou20 -m 1614781491 -f /www/wwwroot/项目目录/server/public/cert/apiclient_key.pem -s 629BD177D7******************8CE43244 -o ./
$pay_config = PayConfig::find(2);
$pay_config = $pay_config->config;
// 商户号
$this->merchantId = $pay_config['mch_id'];
// v3api秘钥
$this->v3_key = $pay_config['pay_sign_key'];
// 「商户API证书」的「证书序列号」 商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号
$this->merchantCertificateSerial = '629BD177D7BB33*******13620F8CE43244';
// 商户API私钥
//$this->merchantPrivateKeyFilePath = $pay_config['apiclient_key']; //"file://"
$this->merchantPrivateKeyFilePath = "file://" . root_path() . "public/upload/cert/apiclient_key.pem"; //
;
// 微信支付平台证书 首次通过命令行生成 https://github.com/wechatpay-apiv3/wechatpay-php/tree/main/bin
//$this->platformCertificateFilePath = $pay_config['apiclient_cert'];
$this->platformCertificateFilePath = "file://" . root_path() . "public/upload/cert/wechatpay_547228274D64DE93A481DEAB6F44CC22CB604784.pem";
// 构建客户端实例
$this->instance = $this->buildInstance();
//var_dump($this->merchantPrivateKeyFilePath,$this->platformCertificateFilePath);exit;
}
/**
* 商家付款到零钱
* @param string $appid 申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid),小程序、公众号、APP
* @param string $batch_sn 商户系统内部的商家批次单号
* @param string $order_sn 账明细单的唯一标识
* @param float $money 转账金额,单位:元
* @param string $openid 用户openid
* @param string $remark 转账备注
* @param string $user_name 用户姓名,转账金额大于2000元,必填
* @return array
* @throws Exception
*/
public function transfer(string $appid, string $batch_sn, string $order_sn, float $money, string $openid, string $remark, string $user_name = ''): array
{
$trans_detail = [
'out_detail_no' => $order_sn,
'transfer_amount' => $money * 100,
'transfer_remark' => $remark,
'openid' => $openid,
];
if ($money >= 2000) {
if (empty($user_name)) {
throw new Exception('提现金额大于2000元,真实姓名不能为空');
}
$trans_detail['user_name'] = $this->encrypt($user_name);
}
$response = $this->instance->chain('v3/transfer/batches')->post([
'json' => [
'appid' => $appid,
'out_batch_no' => $batch_sn,
'batch_name' => '提现转账',
'batch_remark' => '提现转账',
'total_amount' => $money * 100,
'total_num' => 1,
'transfer_detail_list' => [
$trans_detail
]
]
]);
return json_decode($response->getBody(), true);
}
/**
* 转账明细查询
* @param string $out_batch_no 商户系统内部的商家批次单号
* @param string $out_detail_no 账明细单的唯一标识
* @return array
*/
public function transferQuery(string $out_batch_no, string $out_detail_no = ''): array
{
$api = 'v3/transfer/batches/out-batch-no/' . $out_batch_no;
$query = [];
if ($out_detail_no) {
$api .= '/details/out-detail-no/' . $out_detail_no;
} else {
$query = [
'query' => [
'need_query_detail' => true,
'detail_status' => 'ALL'
]
];
}
$response = $this->instance->chain($api)->get($query);
return json_decode($response->getBody(), true);
}
// 自动更新平台证书
public function autoUpdatePlatformCertificateFile()
{
$result = $this->v3Certificates()['data'];
$data = end($result);
$effective_time = strtotime($data['effective_time']);
if ($effective_time - time() > 0 && $effective_time - time() <= 86400) {
$encrypt_certificate = $data['encrypt_certificate'];
$cert_content = $this->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext']);
file_exists($this->platformCertificateFilePath) && file_put_contents($this->platformCertificateFilePath, $cert_content);
}
}
// 获取平台证书
private function v3Certificates()
{
$response = $this->instance->chain('v3/certificates')->get();
return json_decode($response->getBody(), true);
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
* 用于解密v3Certificates接口数据获取证书
* https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
private function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// 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, $this->v3_key);
}
// 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, $this->v3_key);
}
// 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', $this->v3_key, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
// 加密
private function encrypt(string $msg): string
{
return Rsa::encrypt($msg, $this->platformPublicKeyInstance);
}
private function buildInstance(): \WeChatPay\BuilderChainable
{
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 从本地文件中加载 微信支付平台证书
$this->platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
return Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $this->platformPublicKeyInstance,
],
]);
}
}
