一、前期准备工作
- 注册微信小程序账号:
- 访问微信公众平台(https://mp.weixin.qq.com/),点击 “立即注册”,选择账号类型为 “小程序”。
- 按照系统提示填写邮箱、密码等注册信息,完成注册流程。注册成功后,会获得小程序的 AppID,这是小程序的唯一标识,在后续对接微信支付等操作中会频繁用到。
- 完成小程序认证:
- 只有经过认证的小程序才可以申请微信支付。认证方式主要有微信认证和快速认证两种。
- 微信认证:适用于企业、政府、媒体、其他组织类型的小程序。认证时需要提交营业执照、法人身份证等相关资料,同时需支付 300 元 / 次的认证费用。认证审核通常在 1 – 5 个工作日内完成。
- 快速认证:若小程序主体已在微信开放平台完成认证,可通过快速认证的方式免去再次提交资料和支付费用的环节,直接复用开放平台的认证资质。
- 申请微信支付商户号:
- 登录小程序后台,在左侧菜单栏中找到 “微信支付” 选项,点击进入。
- 点击 “申请接入” 按钮,可选择申请新的商户号或绑定已有商户号。
- 申请新商户号:
- 填写企业基本信息,包括企业名称、营业执照注册号、法人姓名及身份证号码等。确保信息准确无误,否则会影响审核进度。
- 提供企业对公银行账户信息,用于接收微信支付的结算款项。微信支付会向该对公账户打一笔随机金额的验证款,收到款项后,需在商户平台输入该金额完成验证。
- 提交申请后,微信支付会对提交的资料进行审核,审核时间一般为 1 – 3 个工作日。审核通过后,会收到包含商户号、商户平台登录账号和密码等信息的邮件通知。
- 绑定已有商户号:若企业已有微信支付商户号,且该商户号支持小程序支付,可在小程序后台直接绑定。绑定过程中需输入商户号及相关验证信息,完成绑定后即可使用该商户号进行小程序支付。
二、配置相关信息
- 配置小程序密钥(AppSecret):
- 在小程序后台的 “设置” – “开发设置” 页面中,点击 “生成” 按钮。
- 管理员使用微信扫描出现的二维码进行身份验证。验证通过后,系统会随机生成一个 AppSecret。AppSecret 是小程序的重要安全信息,用于与微信服务器进行交互验证,如获取用户的 openid 等操作。务必妥善保管,不要将其明文存储在服务器或泄露给他人。
- 设置商户平台密钥和下载证书:
- 使用申请微信支付时获得的商户平台登录账号和密码,登录微信支付商户平台(weixin.qq.com)。
- 在商户平台的 “账户中心” – “API 安全” 页面中,进行以下操作:
- 设置 API 密钥:点击 “设置密钥” 按钮,按照要求设置一个 32 位的密钥。该密钥用于对微信支付相关接口请求和响应数据进行签名加密,保障数据传输的安全性和完整性。设置完成后,务必牢记该密钥,因为后续无法直接查看,若遗忘需重新设置。
- 下载证书:为了保证通信安全,微信支付要求商户在调用某些接口时使用证书。在 “API 安全” 页面中,点击 “证书下载” 按钮,按照提示进行操作,下载商户证书。证书文件一般为.p12 格式,下载后需妥善保存,在后续开发中配置到服务器相应位置。
- 配置 Https 服务器:
- 小程序发起的所有网络请求都必须使用 Https 协议,因此需要为小程序的后端服务器配置 Https。
- 获取 SSL 证书:可以从正规的证书颁发机构(CA)购买 SSL 证书,如阿里云、腾讯云等云服务提供商也提供 SSL 证书的申请和购买服务,部分情况下还提供免费的 SSL 证书供用户选择。
- 安装 SSL 证书到服务器:根据服务器的类型和操作系统,按照相应的教程将购买或申请到的 SSL 证书安装到服务器上。安装完成后,通过访问服务器地址,确认网址前缀变为 “https://”,且浏览器地址栏显示安全锁标志,表明 Https 配置成功。
三、编写代码实现对接
- 获取用户 openid:
- 在小程序端,使用login接口获取临时登录凭证 code。代码示例如下:
wx.login({
success(res) { if (res.code) { // 将code发送到服务器 wx.request({ url: ‘https://your – server – url.com/getOpenid’, method: ‘POST’, data: { code: res.code }, success: function (serverRes) { console.log(serverRes.data.openid); } }); } else { console.log(‘登录失败!’ + res.errMsg); } } }); |
- 在服务器端,接收到小程序发送的 code 后,通过向微信官方提供的接口(https://api.weixin.qq.com/sns/jscode2session)发送请求,换取用户的 openid。请求参数包括小程序的 AppID、AppSecret 以及接收到的 code。示例代码(以 js 和 Express 框架为例):
const express = require(‘express’);
const app = express(); const request = require(‘request’); app.post(‘/getOpenid’, (req, res) => { const code = req.body.code; const appId = ‘your – appid’; const appSecret = ‘your – appsecret’; const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code`; request(url, (error, response, body) => { if (!error && response.statusCode === 200) { const data = JSON.parse(body); res.send({ openid: data.openid }); } else { res.send({ error: ‘获取openid失败’ }); } }); }); const port = 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); }); |
- 统一下单:
- 商户服务器需要调用微信支付的统一下单接口(https://api.mch.weixin.qq.com/pay/unifiedorder)来生成预支付交易会话标识(prepay_id)。
- 统一下单接口的请求参数众多,常见的参数包括:
- appid:小程序的 AppID。
- mch_id:微信支付商户号。
- nonce_str:随机字符串,长度要求 32 位以内,用于防止重复请求。可通过程序生成随机字符串,例如在 JavaScript 中可以使用random().toString(36).substr(2, 32)生成。
- sign_type:签名类型,支持 MD5 和 HMAC – SHA256 两种,一般推荐使用 HMAC – SHA256。
- body:商品或服务的简要描述,例如 “商品名称 – 规格型号”。
- out_trade_no:商户系统内部的订单号,要求在商户系统中唯一,建议使用时间戳加随机数等方式生成,如now() + Math.floor(Math.random() * 1000000)。
- total_fee:订单总金额,单位为分。例如,订单金额为 10 元,此处应填写 1000。
- spbill_create_ip:用户端实际 IP 地址,可通过服务器获取请求的客户端 IP。
- notify_url:支付结果通知回调地址,微信支付在用户支付完成后会向该地址发送支付结果通知。需确保该地址可外网访问,且地址格式正确,例如 “https://your – server – com/pay/notify”。
- trade_type:交易类型,小程序支付固定为 “JSAPI”。
- openid:用户的 openid,通过上述获取用户 openid 的步骤得到。
- 示例代码(以 PHP 为例):
<?php
$appid = ‘your – appid’; $mch_id = ‘your – mch – id’; $nonce_str = md5(uniqid(rand(), true)); $body = ‘商品描述’; $out_trade_no = date(‘YmdHis’) . rand(1000, 9999); $total_fee = 1000; $spbill_create_ip = $_SERVER[‘REMOTE_ADDR’]; $notify_url = ‘https://your – server – url.com/pay/notify’; $trade_type = ‘JSAPI’; $openid = ‘user – openid’; // 构造请求参数数组 $params = [ ‘appid’ => $appid, ‘mch_id’ => $mch_id, ‘nonce_str’ => $nonce_str, ‘body’ => $body, ‘out_trade_no’ => $out_trade_no, ‘total_fee’ => $total_fee, ‘spbill_create_ip’ => $spbill_create_ip, ‘notify_url’ => $notify_url, ‘trade_type’ => $trade_type, ‘openid’ => $openid ]; // 计算签名 $key = ‘your – api – key’; $stringA = ”; foreach ($params as $k => $v) { if ($v!= ” && $k!=’sign’) { $stringA.= $k.’=’.$v.’&’; } } $stringA = rtrim($stringA, ‘&’); $stringSignTemp = $stringA.’&key=’.$key; $sign = strtoupper(md5($stringSignTemp)); $params[‘sign’] = $sign; // 将参数转换为XML格式 $xml = ‘<xml>’; foreach ($params as $k => $v) { $xml.= ‘<‘.$k.’>’.$v.'</’.$k.’>’; } $xml.= ‘</xml>’; // 发起统一下单请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, ‘https://api.mch.weixin.qq.com/pay/unifiedorder’); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); $response = curl_exec($ch); curl_close($ch); // 解析返回结果 libxml_disable_entity_loader(true); $responseObj = simplexml_load_string($response, ‘SimpleXMLElement’, LIBXML_NOCDATA); $prepay_id = $responseObj->prepay_id; ?> |
- 再次签名:
- 服务器接收到微信支付统一下单接口返回的结果后,需要从中获取 prepay_id,并根据 prepay_id 等参数按照微信支付的签名规则进行再次签名,生成 paySign。
- 参与签名的参数包括:
- appId:小程序的 AppID。
- timeStamp:当前时间戳,以秒为单位,可通过floor(Date.now() / 1000)在 JavaScript 中获取,或使用time()函数在 PHP 中获取。
- nonceStr:随机字符串,与统一下单时的随机字符串保持一致或重新生成一个新的。
- package:固定值 “prepay_id=prepay_id 的值”,其中 prepay_id 的值为统一下单接口返回的 prepay_id。
- signType:签名类型,与统一下单时的签名类型保持一致。
- 示例代码(以 JavaScript 为例):
const appId = ‘your – appid’;
const timeStamp = Math.floor(Date.now() / 1000).toString(); const nonceStr = Math.random().toString(36).substr(2, 32); const packageStr = `prepay_id=${prepay_id}`; const signType = ‘HMAC – SHA256’; const key = ‘your – api – key’; const stringA = `appId=${appId}&nonceStr=${nonceStr}&package=${packageStr}&signType=${signType}&timeStamp=${timeStamp}`; const stringSignTemp = stringA + ‘&key=’ + key; const crypto = require(‘crypto’); const hmac = crypto.createHmac(‘sha256’, key); hmac.update(stringSignTemp); const paySign = hmac.digest(‘hex’).toUpperCase(); |
- 小程序调起支付:
- 在小程序端,调用requestPayment接口来调起微信支付界面,让用户进行支付操作。
- requestPayment接口需要传入以下参数:
- appId:小程序的 AppID。
- timeStamp:服务器生成的时间戳字符串。
- nonceStr:随机字符串。
- package:服务器生成的 package 字符串,格式为 “prepay_id=prepay_id 的值”。
- signType:签名类型。
- paySign:服务器生成的签名。
- 代码示例:
wx.requestPayment({
timeStamp: timeStamp, nonceStr: nonceStr, package: packageStr, signType:’sha256′, paySign: paySign, success(res) { console.log(‘支付成功’, res); }, fail(err) { console.log(‘支付失败’, err); } }); |
四、支付结果处理
- 接收支付通知:
- 微信支付系统在用户支付完成后,会向商户服务器在统一下单时设置的notify_url地址发送支付结果通知。通知内容为 XML 格式的数据,包含了订单号、支付状态、支付金额等关键信息。
- 商户服务器需要在notify_url对应的接口中接收并解析该 XML 数据,验证签名的正确性,以确保通知数据的真实性和完整性。示例代码(以 js 和 Express 框架为例):
const express = require(‘express’);
const app = express(); const xml2js = require(‘xml2js’); app.post(‘/pay/notify’, (req, res) => { let xmlData = ”; req.on(‘data’, (chunk) => { xmlData += chunk; }); req.on(‘end’, async () => { const parser = new xml2js.Parser(); const result = await parser.parseStringPromise(xmlData); const sign = result.xml.sign[0]; // 重新计算签名进行验证 const params = result.xml; delete params.sign; const stringA = []; for (const key in params) { if (params[key] && params[key].length > 0) { stringA.push(`${key}=${params[key][0]}`); } } stringA.sort(); const stringSignTemp = stringA.join(‘&’) + ‘&key=your – api – key’; const crypto = require(‘crypto’); const hmac = crypto.createHmac(‘sha256’, ‘your – api – key’); hmac.update(stringSignTemp); const newSign = hmac.digest(‘hex’).toUpperCase(); if (newSign === sign) { const resultCode = result.xml.result_code[0]; const outTradeNo = result.xml.out_trade_no[0]; if (resultCode === ‘SUCCESS’) { // 更新订单状态为已支付等业务逻辑处理 console.log(‘支付成功,订单号:’, outTradeNo); } else { console.log(‘支付失败,订单号:’, outTradeNo); } } else { console.log(‘签名验证失败’); } res.send(‘<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>’); }); }); const port = 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); }); |
- 查询支付结果:
- 由于网络等原因,商户服务器可能无法及时收到微信支付的结果通知。为了确保订单状态的准确性,商户服务器可以调用微信支付的查询订单 API(https://api.mch.weixin.qq.com/pay/orderquery)主动查询订单的支付结果。
- 查询订单接口的请求参数主要包括:
- appid:小程序的 AppID。
- mch_id:微信支付商户号。
- nonce_str:随机字符串。
- out_trade_no:商户系统内部的订单号。
- sign:签名,根据请求参数和商户密钥按照微信支付签名规则生成。
- 示例代码(以 Python 为例):
import requests
import xml.etree.ElementTree as ET import hashlib import hmac import random import string appid = ‘your – appid’ |