This article explains how to develop WeChat mini-program payment, including the mini-program initiating payment, back-end order placement, order query, order callback, order storage and other operations.
process
The WeChat applet obtains order parameters -> initiates a request to agree to place an order to the backend -> obtains order parameters -> the applet calls the Api to initiate payment -> payment is completed -> sends callback -> payment result is stored -> query order payment status .
backend code
getOpenid.php
The parameters that need to be configured are $appid、$secret、orderPrice
, among them $appid、$secret、orderPrice
are two parameters of your applet, orderPrice
which is the order amount, in yuan.
<?php
// page encoding
header("content-type:application/json");
// Get the CODE passed by the applet
$code = trim($_GET['code']);
// Applet appid
$appid = "Fill in yours here";
// Applet appscret
$secret = "Fill in your secret here";
// Authorized login api interface
$api = "https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code";
// Initiate a request
$result = file_get_contents($api);
// 获取openid
$arr_result = json_decode($result, true);
$openid = $arr_result["openid"];
// Return information
$result = array(
'code' => 200,
'msg' => 'Get openid successfully',
'openid' => $openid,
'orderPrice' => 0.01 // Unit: Yuan
);
// 输出JSON
echo json_encode($result,JSON_UNESCAPED_UNICODE);
?>
creatOrder.php
The parameters that need to be configured are described in the code.$appid、$mchid、$xlid、$data['notify_url']、$set_body、$price
<?php
// 页面编码
header('Content-type:text/html; Charset=utf-8');
ini_set('date.timezone','Asia/Shanghai');
// 统一下单
function wechartAddOrder($name,$ordernumber,$money,$openid,$timeStamp,$noncestr){
$url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
$urlarr = parse_url($url);
// 需要配置以下参数
// 需要配置以下参数
// 需要配置以下参数
// 需要配置以下参数
// 需要配置以下参数
$appid = '填写你的小程序appid'; // appID
$mchid = '填写你的微信支付商户号'; // 商户ID
$xlid = '填写你的秘钥序列号'; // 秘钥序列号 可在这个网址中查询 https://myssl.com/cert_decode.html
$data = array();
$time = $timeStamp;
$data['appid'] = $appid;
$data['mchid'] = $mchid;
$data['description'] = $name; // 商品描述
$data['out_trade_no'] = $ordernumber; // 订单编号
// 异步订单回调线上地址
// 就是notify.php这个文件的线上URL
// 例如你的notify.php所在服务器的位置是wwwroot/xcxpay/notify.php
// 你的域名是https://www.qq.com
// 那么应该按照这个格式填写URL:https://www.qq.com/xcxpay/notify.php
$data['notify_url'] = "填写notify.php这个文件的线上URL";
$data['amount']['total'] = intval($money * 1); // 金额(单位:分)
$data['payer']['openid'] = $openid; // 用户openID
$data = json_encode($data);
$key = getSign($data,$urlarr['path'],$noncestr,$time); // 签名
$token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$noncestr,$time,$key); // 头部信息
$header = array(
'Content-Type:'.'application/json; charset=UTF-8',
'Accept:application/json',
'User-Agent:*/*',
'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
);
$ret = curl_post_https($url,$data,$header);
$ret = ltrim($ret,'{"prepay_id":"');
$ret = rtrim($ret,'}"');
// 微信支付(小程序)签名
$str = getWechartSign($appid,$timeStamp,$noncestr,'prepay_id='.$ret);
// 需返回的一些预支付参数
$arr = array(
'appid' => $appid,
'timestamp' => $timeStamp,
'package' => 'prepay_id='.$ret,
'paySign' => $str,
'noncestr' => $noncestr,
'orderNum' => $ordernumber,
'orderPrice' => intval($money * 1) // 可用number_format转换金额
);
exit(json_encode($arr));
}
// 微信支付签名
function getSign($data=array(),$url,$randstr,$time){
$str = "POST"."\n".$url."\n".$time."\n".$randstr."\n".$data."\n";
$key = file_get_contents('apiclient_key.pem');// 在商户平台下载的秘钥
$str = getSha256WithRSA($str,$key);
return $str;
}
// 调起支付的签名
function getWechartSign($appid,$timeStamp,$noncestr,$prepay_id){
$str = $appid."\n".$timeStamp."\n".$noncestr."\n".$prepay_id."\n";
$key = file_get_contents('apiclient_key.pem');
$str = getSha256WithRSA($str,$key);
return $str;
}
function getSha256WithRSA($content, $privateKey){
$binary_signature = "";
$algo = "SHA256";
openssl_sign($content, $binary_signature, $privateKey, $algo);
$sign = base64_encode($binary_signature);
return $sign;
}
/* PHP CURL HTTPS POST */
function curl_post_https($url,$data,$header){ // 模拟提交数据函数
$curl = curl_init(); // 启动一个CURL会话
curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$tmpInfo = curl_exec($curl); // 执行操作
if (curl_errno($curl)) {
echo 'Errno'.curl_error($curl);//捕抓异常
}
curl_close($curl); // 关闭CURL会话
return $tmpInfo; // 返回数据,json格式
}
// 生成noncestr
function creatnoncestr($length){
$str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
$randStr = str_shuffle($str);
$rands= substr($randStr,0,$length);
return $rands;
}
// 商品名称
$set_body = '小程序支付';
// 支付金额(单位:分)
// 还需要去getOpenid.php里面修改这个价格才会在小程序显示准确的价格
$price = 1;
// 商户自定义订单号
$out_trade_no = date('Ymd').time();
// 时间戳
$timeStamp = time();
// 支付用户
$openid = trim($_GET['openid']);
// 随机字符串
$noncestr = creatnoncestr(8);
// 创建订单
wechartAddOrder($set_body,$out_trade_no,$price,$openid,$timeStamp,$noncestr);
?>
The v3OrderCallBack.php
file is the signature verification of the payment callback, and the parameters that need to be configured are ApiV3Key
<?php
class AesUtil
{
public $aesKey = '填写你的ApiV3Key,在商户平台设置并获取';
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
public
function __construct()
{
$aesKey = '填写你的ApiV3Key,在商户平台设置并获取';
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
public function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
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->aesKey);
}
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->aesKey);
}
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->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
Db.php
database configuration.
<?php
/**
* Database configuration file
* BY TANKING
* 2023-1-2
**/
$dbhost = "database address";
$dbuser = "database account";
$dbpwd = "database password";
$dbname = "database name" ;
?>
The database table structure is as follows:
Table creation can be performed through the following SQL statements.
SQL statement
CREATE TABLE `xcxpay_order` (
`id` int(10) NOT NULL COMMENT 'auto-increment ID',
`order_num` varchar(32) DEFAULT NULL COMMENT 'order number',
`openid` varchar(32) DEFAULT NULL COMMENT 'openid' ,
`order_total` varchar(10) DEFAULT NULL COMMENT 'order amount (cents)',
`order_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'order time'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
certificate file
You also need to go to the merchant platform to download the certificate file.
method of obtaining:
applet code
pay.wxml
<view class="orderInfo">You need to pay { {orderPrice}} yuan</view> <view class="paybtn" bindtap="Reqpay">Pay Now</view> <view class="paySuccess" wx:if="{ {paySuccess == true}}">Payment succeeded</view>
pay.js
needs to configure the domain name and backend directory name.
Page({
data: {
paySuccess:false
},
// enter page load
onLoad(e) { var that = this;
// Get user
wx.login({ success: function(res) { if (res.code) { wx.request({ url: "https://your domain name/directory/getOpenid.php?code="+res .code, header: {"content-type": "application/json"}, success: function(res) {
console.log(res.data)
// get openid and order amount
that.setData({ openid:res.data.openid, orderPrice:res.data.orderPrice }) } }); } } }) },
// Initiate payment
Reqpay: function(){ var that = this; wx.request({ url: "https://your domain name/directory/creatOrder.php?openid="+that.data.openid, header: { 'content-type': 'application/json'}, success (res) {
console.log(res.data)
// request payment parameters
var timestamp_ = res.data.timestamp;
var noncestr_ = res.data.noncestr;
var package_ = res.data.package;
var paySign_ = res.data.paySign;
// order parameter
var orderNum = res.data.orderNum; // order number
wx.requestPayment({ timeStamp: timestamp_.toString(), nonceStr: noncestr_, package: package_, signType: 'MD5', paySign: paySign_, success ( res) { console.log(res) if(res.errMsg == 'requestPayment:ok'){
// The payment is successful
console.log('Payment is successful')
that.setData({ paySuccess:true })
// Do not use the result of requestPayment:ok as the basis for judging the success of the payment // If you need to determine the real result of the
order payment, please continue to add your logic for querying the order payment result
}
} }) } }) } })
pay.wxss
.orderInfo{
width: 88%;
height: 100px;
background: #eee;
border-radius: 20px;
margin: 30px auto 0;
line-height: 100px;
text-align: center;
font-size: 25px;
}
.paybtn{
width: 88%;
height: 55px;
line-height: 55px;
background: #f7c58a;
border-radius: 10px;
margin: 10px auto 0;
text-align: center;
font-size: 18px;
}
.paySuccess{
text-align: center;
margin-top: 30px;
font-size: 16px;
color: #f7c58a;
font-weight: bold;
}
pay.json
{ "usingComponents": {}, "navigationBarTextStyle": "black", "backgroundColor": "#eee", "navigationBarBackgroundColor": "#f7c58a", "navigationBarTitleText": "WeChat applet payment demo" }