微信的 h5 支付和 jsapi 支付
申请商户号
- 申请地址: https://pay.weixin.qq.com/
- 如果你还没有微信商户号,请点击上面的链接进行申请,如果已经有了,可以跳过这一步
申请商户证书
- 首先点击
账户中心
API安全
申请API证书
- 申请详细步骤: https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
设置APIv3密钥
- 首先点击
账户中心
API安全
设置APIv3密钥
设置
- 会看到有两个密钥,分别是
APIv2密钥
和APIv3密钥
,由于APIv2密钥
已经逐渐废弃了,所以只需要申请APIv3密钥
即可 - 密钥可由数字大小写字母组合,输入任意的
32
位字符,该密钥需要保存好,供后面使用
// 生成32位的APIv3随机密钥
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
echo substr(str_shuffle($chars), 0, $length);
下载 SDK 开发包
- 微信官方提供了
JAVA
、PHP
、GO
三种语言版本的开发库,请根据自己开发语言选择 - JAVA语言: wechatpay-java ‹推荐›、wechatpay-apache-httpclient
- PHP语言: wechatpay-php ‹推荐›、wechatpay-guzzle-middleware
- GO语言: wechatpay-go ‹推荐›
- 由于
php
实现支付相对简单,所以我将以php
作为支付的讲解 - 首先使用
composer
安装sdk
# 初始化文件夹
composer init
# 推荐使用 PHP 包管理工具 Composer 安装 SDK
composer require wechatpay/wechatpay
下载平台证书
- 平台证书跟上面申请的商户证书不是同一个东西,在后期请求中,平台证书和商户证书都要带上
- 上面命令执行完之后,会有一个
vendor/bin/CertificateDownloader.php
文件 - 如果你是第一次申请平台证书,需要执行命令:
php CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
- -k:
apiv3
秘钥,上面自己设置的32位数的密钥 - -m: 商户号,微信商户平台可以查询
- -f: 微信商户API私钥文件目录,也就是第二步申请商户证书里面生成的
apiclient_key.pem
路径 - -s: 证书序列号,在
账户中心
API安全
管理证书
中可以看见,如果有多个证书,找到自己正在使用的证书序列号 - -o: 生成后的证书保存地址
cd vendor/bin/
php CertificateDownloader.php -k 241xxxxxxxxxxxxxxxxx44 -m 1xxxxxxx1 -f ../../cert/merchant/apiclient_key.pem -s Wxxxxxxxxxxxxxxxx4 -o ../../cert/wechatpay/
关联 AppID 账号
- 因为使用的是微信支付,所以用户支付后,需要通过微信号通知用户支付的一些信息,所以需要在商户号下至少关联一个公众号
开通 H5 支付
- 点击
产品中心
我的产品
H5支付
点击开通
- 开通后,选择
开发配置
H5支付域名
申请添加H5支付域名
- 申请支付域名需要先做好产品的页面,申请的时候需要有页面的截图,截图中还要
截取到域名
,支付的审核算是很严格的,如果申请不过,驳回后再申请,审核通过的时间会越来越长,所以最好一次性就把材料收集好,另外还要域名的备案的IPC
截图 IPC
备案查询地址: https://beian.miit.gov.cn/- 关于域名的填写,如果只填写域名不填写具体域名路径,微信在支付的时候就只会校验域名,这也是最方便的,因为域名下有多个项目有支付功能的话,就不需要重复添加了
H5支付流程
H5
支付是在微信以外的浏览器使用的,如果是微信内的话,使用的是jsapi
支付- 所以一般用户进入页面的第一件事,就是检测用户使用的环境是微信浏览器还是其他浏览器
- 前端传一些用户挑选商品后的参数,并请求后端处理接口,后端应该将一些参数进行入库,顺便请求
H5
支付接口 - 接口应该返回跳转链接
h5_url
,如果你想用户付款之后到结果页面,需要添加redirect_url
参数,这个参数一定要用encodeURIComponent
进行处理 - 由于官方在
jssapi
支付中说明,不要相信前端的success
结果,所以需要在结果页中,让用户自动触发查询结果,因此需要返回后端生成的订单号,用作在结果页的用户手动点击查询
// 判断是否微信浏览器
function isWeChat() {
var ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
return true;
} else {
return false;
}
}
if(isWeChat()) {
// 是微信中打开的产品页面
alert('微信内不支持h5支付,请在外部浏览器打开页面');
} else {
// 非微信内打开的产品页面,请求接口,获取支付的跳转链接
// 前端用户选的产品,以及产品的金额,传一些参数过去
let params = {
total: 2, // 单位:元
description: 'Image形象店-深圳腾大-QQ公仔' // 产品的介绍
// ....更多入库参数
};
$.getJSON('后端接口地址/h5?' + $.param(params) + '&callback=?', function(res) {
// 拉起微信支付界面,成功后会跳转到redirect_url链接
$(location).attr("href", res.data.h5_url + "&redirect_url=" + encodeURIComponent(`https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`))
});
}
<?php
// 仅仅用作展示,不可直接复制使用
require_once('../vendor/autoload.php');
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 接受参数,相当于原生的$_GET
$input = $request->only(['name', 'total', 'description', 'phone']);
// 生成商户订单号
$out_trade_no = getOutTradeNo();
// 处理金额
// 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下
$total = $input['total'] * 100;
// 商户号
$merchantId = '1xxxxxx1';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
$resp = $instance
->chain('v3/pay/transactions/h5')
->post(['json' => [
'mchid' => $merchantId, // 商户号
'out_trade_no' => $out_trade_no, // 商户订单号
'appid' => '********换成跟商户号绑定的公众号APPID**********',
'description' => $input['description'], //商品描述
'notify_url' => 'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态
'amount' => [
'total' => $total, // 微信处理的单位是分
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => getClientIP(), // 有些框架有自带获取获取客户端IP
'h5_info' => [
'type' => 'Wap'
]
]
]]);
// 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库
$response = Db::table('order')->insert([
'name' => $input['name'],
'description' => $input['description'],
'total' => $input['total'],
'phone' => $input['phone'],
'trade_state' => 'START',
]);
// 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可
if($response) {
return jsonp([
'code' => 200,
'msg' => '操作成功',
'data' => [
'out_trade_no' => $out_trade_no,
'h5_url' => json_decode($resp->getBody(), true)['h5_url']
]
]);
} else {
return jsonp([
'code' => 100,
'msg' => '操作失败'
]);
}
} catch (\Exception $e) {
// 进行错误处理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
}
// 生成唯一商户订单号,订单号不能超过32位,并且在同一个商户下订单号不能重复
// 如果并发不高,基本这样生成就可以,不会有重复的情况出现的
function getOutTradeNo()
{
$out_trade_no = date('ymdHis') . mt_rand(1000, 9999) . uniqid();
return mb_substr($out_trade_no, 0, 32);
}
// 获取客户端的IP
function getClientIP()
{
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
} elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip = $ips[0];
} elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip = $_SERVER["HTTP_CDN_SRC_IP"];
} elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
} elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return $ip;
}
<?php
// 回调处理,当用户支付订单后,微信会请求该接口,也就是上面在notify_url中填写的接口
// 在这里我们可以修改订单的状态啥的
public function notify()
{
// 获取参数
$inBody = file_get_contents('php://input');
// APIv3密钥
$apiv3Key = 'xxxxxxxxxxxx';
// 转换通知的JSON文本消息为PHP Array数组
$inBodyArray = (array)json_decode($inBody, true);
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt(
$inBodyArray['resource']['ciphertext'],
$apiv3Key,
$inBodyArray['resource']['nonce'],
$inBodyArray['resource']['associated_data']
);
// 把解密后的文本转换为PHP Array数组
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
try {
// 获取订单信息
$order = Db::table('order')->where('out_trade_no', $inBodyResourceArray['out_trade_no'])->first();
Db::startTrans();
if ($order) {
// 修改order订单的状态
Db::table('order')->where('id', $order['id'])->update([
'openid' => $inBodyResourceArray['payer']['openid'],
'trade_state' => $inBodyResourceArray['trade_state']
]);
Db::table('payment')->insert([
'out_trade_no' => $inBodyResourceArray['out_trade_no'],
'transaction_id' => $inBodyResourceArray['transaction_id'],
'trade_type' => $inBodyResourceArray['trade_type'],
'trade_state' => $inBodyResourceArray['trade_state'],
'trade_state_desc' => $inBodyResourceArray['trade_state_desc'],
'total_amount' => $inBodyResourceArray['amount']['total'],
'bank_type' => $inBodyResourceArray['bank_type'],
'success_time' => strtotime($inBodyResourceArray['success_time'])
]);
Db::commit();
} else {
Db::rollback();
}
} catch (\Exception $e) {
Db::rollback();
}
}
开通 JSAPI 支付
- 点击
产品中心
我的产品
JSAPI支付
点击开通
- 开通后,选择
开发配置
JSAPI支付域名
申请添加JSAPI支付域名
- 关于申请支付域名的流程基本都差不多要求也差不多,看上面的
H5支付域名
申请就行,这里就不过多赘述了
JSAPI 支付流程
JSAPI
支付是在微信内的浏览器使用的,如果用户是在微信外打开的话,需要提醒去微信内打开页面JSAPI
支付需要使用微信内置的WeixinJSBridge.invoke
方法- 由于
JSAPI
调用支付需要用到用户的openid
,所以需要想方设法在用户调用JSAPI
之前获取到openid
,点击查看获取 openid 的官方文档 - 获取用户
openid
,需要先获取code
,这个经常做微信业务的人都知道,那么如何在用户无感知的情况下就获取到openid
呢 - 思路就是,一般支付最少会有3个页面,这里标注为
a
、b
、c
三个页面,通常是在a
页面挑选商品,在b
页面确认商品,也就是付款页面,c
页面查询支付状态 - 由于
code
的存在时间只有5分钟,所以注定code
获得后不能长时间不使用,也就是说用户一旦在某个页面超过5分钟,这个code
就失效了,因此最好的方法就是获取code
后,立马获取openid
- 那么就应该设计成从
a
页面先跳转到获取code
页面再跳转到b
页面,而在b
页面的一开始就去请求接口,获取用户的openid
即可 - 跳转到
b
页面后,链接后自动带上code
参数,链接应该是https://xxxx/b.html?code=xxxxxxxx
// a页面,仅做逻辑演示,更加具体的逻辑需要自己完善
// 判断是否微信浏览器
function isWeChat() {
var ua = navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
return true;
} else {
return false;
}
}
if(!isWeChat()) {
// 非微信内打开的产品页面
alert('微信外不支持JSAPI支付,请在微信中打开页面');
return false;
}
// 用户挑选完商品后跳转,这里appid需要上面跟商户绑定的公众号appid
// 微信授权分为静默授权和非静默授权,其中非静默授权,需要用户点击确认授权后,才可以获取code,
// 因为这里主打一个用户无感知,而且我们只需要openid即可,所以我们只需要使用静默授权即可
// 静默授权可以获取用户更多的信息,比如头像、昵称等,而静默授权只能获取openid,这点需要注意,具体情况选择不同
// 非静默授权
// $(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_userinfo#wechat_redirect`)
// 静默授权
$(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_base#wechat_redirect`)
// b页面,仅做逻辑演示,更加具体的逻辑需要自己完善
let openid = '';
// 获取code, 请求接口获取openid
function getParamUrl(name, url) {
if (!url) url = location.href;
if (url.indexOf('?') == -1) return '';
try {
var re = new RegExp("" + name + "=([^&?]*)", "ig");
return ((url.match(re)) ? (decodeURIComponent(url.match(re)[0].substr(name.length + 1))) : '');
} catch (_e) {
return '';
}
}
let code = getParamUrl('code');
$.getJSON('后端接口地址/openid?callback=?', function(res) {
if(res.code == 200) {
openid = res.data;
} else {
console.error(res.msg);
}
})
// 用户确定订单后,拉起支付
let params = {
total: 2, // 单位:元
description: 'Image形象店-深圳腾大-QQ公仔', // 产品的介绍
openid: openid //用户的openid
// ....更多入库参数
};
$.getJSON('后端接口地址/jssapi?' + $.param(params) + '&callback=?', function(res) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign
}, function (response) {
if (response.err_msg == "get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
} else {
// 有些用户调起了支付,但是未付款取消的处理方式,你可以给他简单简单提示
toast('支付异常取消')
// 当然有些用户是误操作,你可以提醒二次支付
if(confirm('检测到你操作有误,是否重新支付?')) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign
}, function (response) {
if (response.err_msg == "get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
}
})
}
}
});
});
<?php
// 获取用户的openid
$input = $request->only(['code']);
$response = getCurl("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->secret}&code={$input['code']}&grant_type=authorization_code");
$openid = json_decode($response, true)['openid'];
// 返回openid
return jsonp([
'code' => 200,
'msg' => '获取成功',
'data' => $openid
]);
// 封装的GET请求
function getCurl($url, $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
<?php
// 仅仅用作展示,不可直接复制使用
require_once('../vendor/autoload.php');
use WeChatPay\Builder;
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
// 接受参数,相当于原生的$_GET,这里会比h5支付多一个openid
$input = $request->only(['openid', 'name', 'total', 'description', 'phone']);
// 生成商户订单号
$out_trade_no = getOutTradeNo();
// 处理金额
// 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下
$total = $input['total'] * 100;
// 商户号
$merchantId = '1xxxxxx1';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
$platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
// 调用 transactions/jsapi 接口后会生成prepay_id
$resp = $this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json' => [
'mchid' => $merchantId, // 商户号
'out_trade_no' => $out_trade_no, // 商户订单号
'appid' => '********换成跟商户号绑定的公众号APPID**********',
'description' => $input['description'], //商品描述
'notify_url' => 'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'payer' => [
'openid' => $input['openid']
]
]]);
// 需要根据prepay_id去生成加密的信息
$prepay_id = json_decode($resp->getBody(), true)['prepay_id'];
$sign = getSign($prepay_id);
// 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库
$response = Db::table('order')->insert([
'openid' => $input['openid'],
'name' => $input['name'],
'description' => $input['description'],
'total' => $input['total'],
'phone' => $input['phone'],
'trade_state' => 'START',
]);
// 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可
if($response) {
return jsonp([
'code' => 200,
'msg' => '操作成功',
'data' => [
'out_trade_no' => $out_trade_no,
'sign' => $sign
]
]);
} else {
return jsonp([
'code' => 100,
'msg' => '操作失败'
]);
}
} catch (\Exception $e) {
// 进行错误处理
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
}
// 获取加密参数
function getSign($prepay_id)
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
$params = [
'appId' => $this->appid,
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $params;
}
通用微信支付库封装
- 由于直接使用微信的支付库,代码非常的匀余,所以封装了一个微信支付库
- 由于只针对一些业务的
api
封装,所以肯定不全,需要的可以自己添加需要的api
- 微信支付API接口列表: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml
<?php
/**
* User: tinygeeker
* Desc: 微信支付库封装
* Date: 2023/08/10
*/
namespace App;
use App\Helper;
use WeChatPay\Builder;
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
class WxPay
{
// appid
private $appid;
// 商户号
private $merchantId;
// 商户API私钥
private $merchantPrivateKeyFilePath;
// 证书序列号
private $merchantCertificateSerial;
// 微信支付平台证书
private $platformCertificateFilePath;
/**
* @param $appid
* @param $merchantId
* @param $merchantCertificateSerial
*/
public function __construct($appid = '', $merchantId = '', $merchantCertificateSerial = '')
{
$this->appid = $appid ?: '换成自己的APPID';
$this->merchantId = $merchantId ?: '换成自己的商户号';
$this->merchantCertificateSerial = $merchantCertificateSerial ?: '换成自己的证书序列号';
$this->merchantPrivateKeyFilePath = 'file:///common/cert/merchant/apiclient_key.pem'; // 换成自己的
$this->platformCertificateFilePath = 'file:///common/cert/wechatpay/wechatpay_xxx.pem'; // 换成自己的
}
/**
* @return \WeChatPay\BuilderChainable
*/
protected function instance()
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
$instance = Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
return $instance;
}
public function getSign($prepay_id)
{
$merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
$params = [
'appId' => $this->appid,
'timeStamp' => (string)Formatter::timestamp(),
'nonceStr' => Formatter::nonce(),
'package' => 'prepay_id=' . $prepay_id,
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $params;
}
public function checkOutTradeNo($out_trade_no)
{
try {
$resp = $this->instance()
->v3->pay->transactions->outTradeNo->_out_trade_no_
->get([
// Query 参数
'query' => ['mchid' => $this->merchantId],
// 变量名 => 变量值
'out_trade_no' => $out_trade_no,
]);
return $resp->getBody();
} catch (\Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
}
// h5下单
public function h5($total, $out_trade_no, $description, $notify_url)
{
try {
$resp = $this->instance()
->chain('v3/pay/transactions/h5')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $description,
'notify_url' => $notify_url,
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => Helper::getClientIp(),
'h5_info' => [
'type' => 'Wap'
]
]
]]);
return $resp->getBody();
} catch (\Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
}
// jsapi下单
public function jsapi($openid, $total, $out_trade_no, $description, $notify_url)
{
try {
$resp = $this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json' => [
'mchid' => $this->merchantId,
'out_trade_no' => $out_trade_no,
'appid' => $this->appid,
'description' => $description,
'notify_url' => $notify_url,
'amount' => [
'total' => $total,
'currency' => 'CNY'
],
'payer' => [
'openid' => $openid
]
]]);
return $resp->getBody();
} catch (\Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
}
// todo... 更多接口可根据官方文档列表自行添加
}
<?php
/**
* User: tinygeeker
* Desc: 工具库
* Date: 2023/08/10
*/
namespace App;
class Helper
{
/**
* @return array|mixed|string|string[]
*/
static public function getClientIP()
{
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
} elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip = $ips[0];
} elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip = $_SERVER["HTTP_CDN_SRC_IP"];
} elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
} elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return $ip;
}
/**
* @param $length
* @param $type
* @return false|string
*/
static public function createRandomStr($length = 32, $type = 0)
{
switch ($type) {
case 1:
$chars = '0123456789';
break;
case 2:
$chars = 'abcdefghijklmnopqrstuvwxyz';
break;
case 3:
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 4:
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
break;
case 5:
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
default:
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
break;
}
return substr(str_shuffle($chars), 0, $length);
}
/**
* @param $url
* @param $timeout
* @return bool|string
*/
static public function getCurl($url, $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* @param $url
* @param $data
* @param $header
* @param $timeout
* @return bool|string
*/
static public function postCurl($url, $data, $header = [], $timeout = 5)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if ($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
微信的 h5 支付和 jsapi 支付的更多相关文章
- 转载【微信支付】jsapi支付之传参问题(使用微信官方SDK之PHP版本) V3之WxpayPubHelper 亲测有效,V3WxpayAPI_php_v3.zip版未测试,理论上也是一样的。
本文转载至:http://blog.csdn.net/geeklx/article/details/51146151 (微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请 ...
- 微信小程序支付(JSAPI支付)
开发环境:.NET MVC+ ORM框架(EF) 一.参考文档: 1.微信JSAPI支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api. ...
- 微信支付:JSAPI支付一直提示URL未注册
今天意外碰上了这个问题,想想微信的坑真多…… 解决办法: 首先要看微信公众号里的 支付授权目录 是否已正确填写,还要验证 url大小写 必须相同 其次查看一下自己请求的地址是否与上面填写的是否一样!u ...
- 微信支付之JsApi支付
常见问题:金额错误,微信金额是int类型,最小单位为分,即是1 客户端调用微信支付的时候一闪而过:这个原因是因为微信商户后台支付目录地址没设置对,导致js调用的时候验证没通过 .aspx页面设置: x ...
- 微信支付JSAPI支付
1.介绍 JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付.应用场景有: ◆ 用户在微信公众账号内进入商家公众号,打开某 ...
- 微信支付系列(2)——jsapi支付源码解析
版权声明:转载请注明出处:http://blog.csdn.net/m0sh1 http://www.share345.com 在微信支付 开发者文档页面 下载最新的 PHP SDK http://m ...
- 网站如何接入微信公众号JSAPI支付PHP版
1.首先,我们要有一个微信公众号(分类类型有订阅号,服务号,企业号)我们的微信公众号一定是个服务号只有它才有微信支付接口.. 并且这个微信公众号一定要进行微信认证才能申请微信支付接口. 2.申请JSA ...
- JAVA微信支付接口开发——支付
微信支付接口开发--支付 这几天在做支付服务,系统接入了支付宝.微信.银联三方支付接口.个人感觉支付宝的接口开发较为简单,并且易于测试. 关于数据传输,微信是用xml,所以需要对xml进行解析. 1. ...
- 微信App支付接入步骤&支付中前后端交互流程
最近对微信App支付(App端集成微信支付SDK)申请步骤,以及终端在进行微信支付时商户App.商户Server.微信App.微信支付Server的交互流程进行了简单了解.这篇文章应该算是学习笔记,分 ...
- 微信JSAPI 公众号支付 H5支付以及APP支付 WEBAPI接口开发测试
统一下单入口 调用该方法入口: public void WxPayAPI() { //string PayPrice ="99.9"; ////订单号 //string Payor ...
随机推荐
- vue项目使用rem布局刷新页面瞬间元素尺寸由小变大,页面闪现错乱样式
vue项目使用px2remLoader插件,在index.html自定义设置font-size的大小,尤其是在首屏加载的时候,会出现页面各个元素尺寸由小变大的一个过程,很难看 刚开始一直在想是不是因为 ...
- 2023-03-18:给定一个长度n的数组,每次可以选择一个数x, 让这个数组中所有的x都变成x+1,问你最少的操作次数, 使得这个数组变成一个非降数组。 n <= 3 * 10^5, 0 <= 数值
2023-03-18:给定一个长度n的数组,每次可以选择一个数x, 让这个数组中所有的x都变成x+1,问你最少的操作次数, 使得这个数组变成一个非降数组. n <= 3 * 10^5, 0 &l ...
- Python潮流周刊#1:如何系统地自学Python?
这里记录每周值得分享的 Python 及通用技术内容,部分内容为英文,已在小标题注明.(本期标题取自其中一则分享,不代表全部内容都是该主题,特此声明.) 文章&教程 1.编程语言的错误处理模式 ...
- 使用4G通信模块和MQTT协议,完成物联网设备开发。
使用4G通信模块和MQTT协议,完成物联网设备开发. (1)安装并使用4G模块通信模块,建立microPython开发环境: (2)使用提供的Demo开发例程,使用MQTT传输协议连接阿里或腾讯网站, ...
- 什么是 Spring?为什么学它?
前言 欢迎来到本篇文章!在这里,我将带领大家快速学习 Spring 的基本概念,并解答两个关键问题:什么是 Spring,以及为什么学习 Spring. 废话少说,下面,我们开始吧! Spring 官 ...
- 将远程oracle数据库导入到本地
一.切换用户 先从普通用户 切换到root (有些时候会因为无权限直接执行 su - oracle 会被拒绝) fssa@jzsql.sn.com:/home/fssa>su - 从当前用户切换 ...
- How to use the shell command to get the version of Linux Distributions All In One
How to use the shell command to get the version of Linux Distributions All In One 如何使用 shell 命令获取 Li ...
- 如何卸载 python setup.py install 安装的包?
当我们半自动安装某些 python 包时,总是存在很多依赖关系的问题,而这些问题还是很难避免的,所以,当我们安装一个不确定的包的时候,最好提前收集一些相关资料,或者请教他人,同时最好把安装过程都记录下 ...
- R 语言常用操作与函数汇总
总结了一下 R 语言中常用的一些操作与函数使用,抛砖引玉,分享一下给大家,如有错误的地方欢迎留言指正. 怎样显示 R 软件中某个包中包含的全部数据集? > library(MASS)> d ...
- 「有问必答」Go如何优雅的对时间进行格式化?
昨天 交流群 关于「Go如何优雅的对时间进行格式化?」展开了讨论: 咋搞捏? 如何在不循环的情况下,把列表数据结构体的时间修改为咱们习惯的格式,而不是UTC模式 我们要实现的效果如下: created ...