微信扫码支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_3
官方demo下载:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
官方下载demo,目录结构如下:
引入微信支付
把 lib 文件夹复制到你需要添加的扩展文件夹下,重命名为wxpay,我在这里放到extend目录下
把 example 文件夹下的 WxPay.Config.php 拿出来,放到wxpay目录下,修改require_once,并配置支付参数:
<?php
/**
*
* example目录下为简单的支付样例,仅能用于搭建快速体验微信支付使用
* 样例的作用仅限于指导如何使用sdk,在安全上面仅做了简单处理, 复制使用样例代码时请慎重
* 请勿直接直接使用样例对外提供服务
*
**/
require_once "WxPay.Config.Interface.php";
/**
*
* 该类需要业务自己继承, 该类只是作为deamon使用
* 实际部署时,请务必保管自己的商户密钥,证书等
*
*/
class WxPayConfig extends WxPayConfigInterface
{
//=======【基本信息设置】=====================================
/**
* TODO: 修改这里配置为您自己申请的商户信息
* 微信公众号信息配置
*
* APPID:绑定支付的APPID(必须配置,开户邮件中可查看)wx75502ffcfac1522e
*
* MCHID:商户号(必须配置,开户邮件中可查看)1516924601
*
*/
public function GetAppId()
{
return 'xxx';
}
public function GetMerchantId()
{
return 'xxx';
}
//=======【支付相关配置:支付成功回调地址/签名方式】===================================
/**
* TODO:支付回调url
* 签名和验证签名方式, 支持md5和sha256方式
**/
public function GetNotifyUrl()
{
return "xxx";
}
public function GetSignType()
{
return "HMAC-SHA256";
}
//=======【curl代理设置】===================================
/**
* TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
* 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
* 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
* @var unknown_type
*/
public function GetProxy(&$proxyHost, &$proxyPort)
{
$proxyHost = "0.0.0.0";
$proxyPort = 0;
}
//=======【上报信息配置】===================================
/**
* TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
* 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
* 开启错误上报。
* 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
* @var int
*/
public function GetReportLevenl()
{
return 1;
}
//=======【商户密钥信息-需要业务方继承】===================================
/*
* KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置), 请妥善保管, 避免密钥泄露
* 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
*
* APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
* 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
* @var string
*/
public function GetKey()
{
return 'xxx';
}
public function GetAppSecret()
{
return '';
}
//=======【证书路径设置-需要业务方继承】=====================================
/**
* TODO:设置商户证书路径
* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
* API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
* 注意:
* 1.证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
* 2.建议将证书文件名改为复杂且不容易猜测的文件名;
* 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
* @var path
*/
public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath)
{
$sslCertPath = 'cert/apiclient_cert.pem';
$sslKeyPath = 'cert/apiclient_key.pem';
}
}
修改 WxPay.Api.php 第二个require_once:
require_once "WxPay.Config.php";
统一下单:
官方文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
接口链接:
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
URL地址:https://api2.mch.weixin.qq.com/pay/unifiedorder(备用域名)见跨城冗灾方案
否
请求参数:
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信支付分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 | 商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传,详见“单品优惠参数说明” | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(64) | 123.12.12.123 | 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 | 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则 time_expire只能第一次下单传值,不允许二次修改,二次修改系统将报错。如用户支付失败后,需再次支付,需更换原订单号重新下单。建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit--可限制用户不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换 |
电子发票入口开放标识 | receipt | 否 | String(8) | Y | Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 |
+场景信息 | scene_info | 否 | String(256) | {"store_info" : { | 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }} ,字段详细说明请点击行前的+展开 |
下单
WechatPay.php控制器:
public function pay($payMoney, $orderSn, $goodId)
{
require ROOT_PATH . 'extend' . DS . 'wxpay/WxPay.Api.php'; //引入微信支付
$input = new \WxPayUnifiedOrder();//统一下单
$config = new \WxPayConfig();//配置参数
$goods_name = '扫码支付' . $payMoney . '元'; //商品名称(自定义)
// 商品描述
$input->SetBody($goods_name);
// 附加数据
// $input->SetAttach($goods_name);
// 商户订单号
$input->SetOut_trade_no($orderSn);
// 标价金额 (订单总金额,单位为分)
$input->SetTotal_fee($payMoney * 100);//金额乘以100
// $input->SetTime_start(date("YmdHis"));// 交易起始时间
// $input->SetTime_expire(date("YmdHis", time() + 600));// 交易结束时间
// $input->SetGoods_tag("test");// 订单优惠标记
// 通知地址
$input->SetNotify_url($this->notifyUrl); //回调地址
// 交易类型
$input->SetTrade_type("NATIVE");
// 商品ID
$input->SetProduct_id($goodId);
$result = \WxPayApi::unifiedOrder($config, $input);
if ($result['result_code'] == 'SUCCESS' && $result['return_code'] == 'SUCCESS') {
// 请求成功,返回url
$url = $result["code_url"];
return ['code' => 1, 'url' => $url];
} else {
return ['code' => 0, 'msg' => $result['err_code_des']];
}
}
生成支付二维码:
这里使用QRCode.js生成二维码,首先下载QRCode.js,下载地址:https://github.com/davidshimjs/qrcodejs
html:
<div class="layui-card-body">
<div class="content" style="border:1px solid #ccc;width: 170px;height: 160px; margin: 0 auto;padding: 10px;">
<input type="hidden" id="url" value="{$data.url}">
<div id="qrcode" style="width: 150px;height: 150px;margin: 0 auto;"></div>
</div>
<p style="text-align: center">使用微信扫一扫付款</p>
</div>
js:
// 获取支付链接
var url = $("#url").val();
var qrcode = new QRCode(document.getElementById("qrcode"), { // 显示二维码的元素或该元素的 ID
text: url, // 支付链接
width: 150, //设置宽度
height: 150, // 设置高度
colorDark : "#000000", // 设置前景色
colorLight : "#ffffff", // 设置背景色
correctLevel : QRCode.CorrectLevel.L // 设置容错级别
});
二维码:
支付页面查询订单:
$(function(){
setInterval(function(){check()}, 3000); //3秒查询一次支付是否成功
});
function check(){
var url = "{:url('orderquery','',false)}";
var param = {'outTradeNo':outTradeNo}; // 订单号
$.post(url, param, function(data){
if(data['code'] == 0){
layer.msg("订单支付成功,即将跳转...");
// 支付成功之后的操作
}
});
}
PHP:
// 查询订单
public function orderquery($outTradeNo)
{
$config = array(
'mch_id' => $this->mchid,
'appid' => $this->appid,
'key' => $this->apiKey,
);
//$orderName = iconv('GBK','UTF-8',$orderName);
$unified = array(
'appid' => $config['appid'],
'mch_id' => $config['mch_id'],
'out_trade_no' => $outTradeNo,
'nonce_str' => self::createNonceStr(),
);
$unified['sign'] = self::getSign($unified, $config['key']);
$responseXml = self::curlPost('https://api.mch.weixin.qq.com/pay/orderquery', self::arrayToXml($unified));
$queryResult = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($queryResult === false) {
die('parse xml error');
}
if ($queryResult->return_code != 'SUCCESS') {
die($queryResult->return_msg);
}
$trade_state = $queryResult->trade_state;
$data['code'] = $trade_state=='SUCCESS' ? 0 : 1;
$data['data'] = $trade_state;
$data['msg'] = $this->getTradeSTate($trade_state);
$data['time'] = date('Y-m-d H:i:s');
return $data;
}
public function getTradeSTate($str)
{
switch ($str){
case 'SUCCESS';
return '支付成功';
case 'REFUND';
return '转入退款';
case 'NOTPAY';
return '未支付';
case 'CLOSED';
return '已关闭';
case 'REVOKED';
return '已撤销(刷卡支付)';
case 'USERPAYING';
return '用户支付中';
case 'PAYERROR';
return '支付失败';
}
}
public static function createNonceStr($length = 16)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 获取签名
*/
public static function getSign($params, $key)
{
ksort($params, SORT_STRING);
$unSignParaString = self::formatQueryParaMap($params, false);
$signStr = strtoupper(md5($unSignParaString . "&key=" . $key));
return $signStr;
}
protected static function formatQueryParaMap($paraMap, $urlEncode = false)
{
$buff = "";
ksort($paraMap);
foreach ($paraMap as $k => $v) {
if (null != $v && "null" != $v) {
if ($urlEncode) {
$v = urlencode($v);
}
$buff .= $k . "=" . $v . "&";
}
}
$reqPar = '';
if (strlen($buff) > 0) {
$reqPar = substr($buff, 0, strlen($buff) - 1);
}
return $reqPar;
}
public static function arrayToXml($arr)
{
$xml = "<xml>";
foreach ($arr as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
$xml .= "</xml>";
return $xml;
}
public static function curlPost($url = '', $postData = '', $options = array())
{
if (is_array($postData)) {
$postData = http_build_query($postData);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
//https请求 不验证证书和host
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$data = curl_exec($ch);
curl_close($ch);
return $data;
}