Thinkphp6配置支付宝MD5网页支付和微信支付V2NATIVE扫码支付(未引用第三方依赖,无需使用Composer)
支付宝MD5支付方式简单好用,虽然官方给出了下线通知,但因为用户量巨大,估计也是会长期可用的,但是文档缺失,目前仅有如下文档:
https://opendocs.alipay.com/open/66/103600?pathHash=b76a0c0d
微信支付目前推荐的V3方式依赖证书且过于复杂,V2方式使用更广,虽然是XML提交但是稳定好用,所以本文以V2方式开发。
本文所编写的支付宝和微信支付集成到Thinkphp6.1.4并未引用第三方依赖,无需使用Composer
开发环境 thinkphp6.1.4
php版本8.1.28
支付宝配置
1、配置config/alipay.php相关参数
<?php
return [
//合作身份者ID,签约账号,以2088开头由16位纯数字组成的字符串
//查看地址:https://b.alipay.com/order/pidAndKey.htm
'partner' => '',
// MD5密钥,安全检验码,由数字和字母组成的32位字符串
//查看地址:https://b.alipay.com/order/pidAndKey.htm
'key' => '',
//收款支付宝账号,以2088开头由16位纯数字组成的字符串
//一般情况下收款账号就是签约账号
'seller_id' => '',
'sign_type' => 'MD5',
'input_charset' => 'utf-8',
'return_url' => 'https://'.$_SERVER['HTTP_HOST'].'/payment/alipay_return',
'notify_url' => 'http://'.$_SERVER['HTTP_HOST'].'/payment/alipay_notify',//使用https有不通知的问题,这里建议选用http
];
2、配置app/controller/Payment.php
<?php
namespace app\controller;
//引用方法按需加载
use think\facade\Db;
use think\Response;
use think\facade\Config;
use think\facade\Request;
class Payment
{
public function alipay(){
$alipay_config = Config::get('alipay');// 获取支付宝配置
$data = $this->request->param();//接收表单提交信息
$out_trade_no = $data['out_trade_no'];//商户订单号,商户网站订单系统中唯一订单号,必填
$subject = $data['subject'];//订单名称,必填
$total_fee = $data['total_fee'];//付款金额,必填
$body = $data['body'];//商品描述,可空
//插入数据库 自行修改
$res = Db::name('order')->insert([
'out_trade_no' => $out_trade_no,
'total_fee' => $total_fee,
'pay_type' => 'alipay',
'create_time' => date('Y-m-d H:i:s',time()),
'status' => 0,
]);
if($res){
// 配置交易参数
$params = [
'service' => 'create_direct_pay_by_user',
'partner' => $alipay_config['partner'],
'_input_charset' => $alipay_config['input_charset'],
'return_url' => $alipay_config['return_url'],
'notify_url' => $alipay_config['notify_url'],
'out_trade_no' => $out_trade_no,
'subject' => $subject,
'body' => $body ,
'payment_type' => '1',
'total_fee' => $total_fee,
'seller_id' => $alipay_config['seller_id'],
];
// 输出表单并提交,自动跳转到支付宝支付页面
echo buildRequest($alipay_config, $params);
}else{
return json(['code' => 0, 'msg' => '订单创建失败']);
}
}
3、配置app/common.php公共方法
/*支付宝相关*/
function md5Sign(string $prestr, string $key): string {
return md5($prestr . $key);
}
function buildRequest(array $alipay_config, array $params): string {
// 筛选和排序参数
$params = paraFilter($params);
$params = argSort($params);
return $sHtml;
}
function createLinkstring(array $para): string {
$arg = "";
foreach ($para as $key => $val) {
$arg .= $key . "=" . $val . "&";
}
// 去掉最后一个&字符
$arg = substr($arg, 0, -1);
return $arg;
}
function paraFilter(array $para): array {
$para_filter = [];
foreach ($para as $key => $val) {
if ($key == "sign" || $key == "sign_type" || $val == "") continue;
$para_filter[$key] = $para[$key];
}
return $para_filter;
}
function argSort(array $para): array {
ksort($para);
reset($para);
return $para;
}
6、配置支付宝回调方法
回调分 return_url(前台跳转返回) notify_url(后台异步通知)
因为要预防notify不通知,所以这两个地址都需要进行支付成功的判断
//位置 app/controller/Payment.php
public function alipay_return(){
//检测登录状态 前台回调跳转要判断用户是否登录有效
$user = Session::get('user');
if(!$user){
//跳转到登录
return redirect('/login');
}
$alipay_config = Config::get('alipay');
//获取支付宝返回参数
$data = $_GET;
// 验签
if (!$this->verifyAlipayMD5($data, $alipay_config)) {
return json(['code' => 0, 'msg' => '签名验证失败']);
}
// 查找订单
$order = Db::name('order')->where('out_trade_no', $data['out_trade_no'])->find();
if (!$order) {
return json(['code' => 0, 'msg' => '订单不存在']);
}
// 检查支付状态
if ($data['trade_status'] == 'TRADE_SUCCESS' || $data['trade_status'] == 'TRADE_FINISHED') {
// 处理支付成功后的逻辑
//自行实现更新订单状态、用户余额等操作
$balance = $this->update_recharge($order,$data);
// 查找订单
$order = Db::name('order')->where('out_trade_no', $data['out_trade_no'])->find();
}
View::assign('order',$order);
//这里我们是前台给用户一个结账单
View::assign('title','结账单');
return View::fetch('invoice');
}
public function alipay_notify()
{
//判断是否为post提交
if($this->request->isPost()){
// 获取支付宝配置
$alipay_config = Config::get('alipay');
// 获取支付宝返回的数据
$data = $this->request->post();
// 验签
if (!$this->verifyAlipayMD5($data, $alipay_config)) {
return json(['code' => 0, 'msg' => '签名验证失败']);
}
// 检查支付状态
if ($data['trade_status'] == 'TRADE_SUCCESS' || $data['trade_status'] == 'TRADE_FINISHED') {
// 处理支付成功后的逻辑
// 查找订单
$order = Db::name('order')->where('out_trade_no', $data['out_trade_no'])->find();
if (!$order) {
return json(['code' => 0, 'msg' => '订单不存在']);
}
//自行实现更新用户余额 更新用户缓存等操作
$this->update_recharge($order,$data);
}
// 返回成功结果给支付宝
echo 'success';
}else{
echo '401';
}
}
private function verifyAlipayMD5($data, $config)
{
}
5、测试支付宝支付
访问 /payment/alipay?out_trade_no=1024&subject=测试支付&body=支付测试
6、支付宝MD5支付总结
支付宝MD5支付无需加载其他依赖,不需要证书等文件,实现md5加密验签方法即可,简单方便,客户接受度高,用户使用习惯。
微信支付V2配置
1、配置config/wxpay.php相关参数
<?php
return [
'mch_id' => '',//商户id
'app_id' => '',//关联公众号的app_id
'key' => '', // 商户支付密钥
'notify_url' => 'http://yourdomain.com/notify',
];
2、配置app/controller/Payment.php
//位置 app/controller/Payment.php
class Payment
{
public function wechatpay(){
$wechatConfig = Config::get('wxpay');
// 构建支付请求参数
$params = [
'appid' => $wechatConfig['app_id'],
'mch_id' => $wechatConfig['mch_id'],
'nonce_str' => uniqid(),
'body' => '商品描述',
'out_trade_no' => '123',
'total_fee' => 1, // 单位为分
'spbill_create_ip' => request()->ip(),
'notify_url' => $wechatConfig['notify_url'],
'trade_type' => 'NATIVE',
];
// 生成签名
$params['sign'] = $this->makeSign($params,$wechatConfig['key']);
// 发送请求到微信支付
$response = $this->sendRequest($params);
// 返回二维码链接
return json( $response);
}
protected function makeSign($params,$key)
{
ksort($params);
$string = urldecode(http_build_query($params)) . '&key=' . $key;
return strtoupper(md5($string));
}
private function sendRequest($params, $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder')
{
}
protected function arrayToXml($array)
{
$xml = '<xml>';
foreach ($array as $key => $val) {
if (is_numeric($val)) {
$xml .= "<$key>$val</$key>";
} else {
$xml .= "<$key><![CDATA[$val]]></$key>";
}
}
$xml .= '</xml>';
return $xml;
}
3、测试微信支付NATIVE
访问 /payment/wechatpay 若返回以下内容则成功
{
"return_code": "SUCCESS",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "XXXXXX",
"appid": "XXXX",
"nonce_str": "HBW0OOPfB9R9fZfj",
"sign": "EA76XXX2ADFXXXXXXX8CF7AE",
"prepay_id": "wx240XXX04XXXXXXb9ad590000",
"trade_type": "NATIVE",
"code_url": "weixin://wxpay/bizpayurl?pr=fLxCHbZz3"
}
其中code_url就是微信支付的二维码地址,用前端或者后端生成二维码即可
4、微信支付回调方法
刚才我们看到支付宝的回调分为前端和后端两部分,前端作为给用户展示,后端用于更新订单状态。 而 微信支付前端是扫描二维码支付,所以就需要用微信支付的查询订单接口,在展示二维码后,前端js每秒查询订单状态,成功即给用户提示,然后刷新缓存,代码如下:
//前端查询订单函数,在展示支付二维码后,每秒执行
function wxpay_query(out_trade_no,wxpay_img){
$.ajax({
type:'POST',
url:'/payment/wxpay_query',
data:{
'out_trade_no':out_trade_no
},
dataType: "json",
success: function(data){
if(data.trade_state=='SUCCESS'){
$('#wxpay_state').html('支付成功');
layer.closeAll();
layer.msg('充值成功', {icon: 6});
window.location.reload();
}else{
if(data.attach){
$('#wxpay_state').html('支付结果确认中...');
}
setTimeout(wxpay_query(out_trade_no),10000);
}
}
});
}
查询控制器方法,位置app/controller/Payment.php
public function wxpay_query($outTradeNo)
{
// 获取微信支付配置
$wechatConfig = Config::get('wxpay');
// 构建查单请求参数
$params = [
'appid' => $wechatConfig['app_id'],
'mch_id' => $wechatConfig['mch_id'],
'out_trade_no' => $outTradeNo,
'nonce_str' => uniqid(),
];
// 生成签名
$params['sign'] = $this->makeSign($params, $wechatConfig['key']);
// 发送请求到微信支付查单接口
$response = $this->sendRequest($params, 'https://api.mch.weixin.qq.com/pay/orderquery');
// 将XML响应转换为数组
$responseArray = $this->xmlToArray($response);
// 返回查询结果
return $responseArray;
}
还需要一个后端异步回调方法
public function wechatpayCallback()
{
}
private function verifySign($data, $key)
{
$sign = $data['sign'];
unset($data['sign']);
return $signCheck === $sign;
}
private function returnXml($data)
{
$xml = "<xml>";
foreach ($data as $key => $value) {
$xml .= "<$key><![CDATA[$value]]></$key>";
}
$xml .= "</xml>";
return $xml;
}
5、微信支付总结
微信支付V2中NATIVE方法实现了下单输出二维码内容,方便使用微信扫描支付,这和支付宝的是截然不同,这种不同也造成了回调方式的不同,支付宝支付成功后会页面跳转,但是微信支付的NATIVE是在自有页面的二维码支付的,所以只能进行查单监控(前端js实现)。异步回调也是必须的,前端回调和后端回调两个同时进行才能确保大大降低掉单的概率。
综上,没有第三方依赖的支付宝MD5,没有用支付证书的微信支付V2的NATIVE扫码支付,适用于thinkphp,也可以拓展到其他php应用。