前言
微信小程序在开发中大部分都是电商类的业务,电商必不可少的是支付环节,实现微信小程序支付看微信官方文档太晦涩(多数人都觉得官方人员写的烂),博主也是在看官方文档的路上迷失去了自己。这篇文章由博主实战采坑后总结的经验构成,尽量带着大家读懂官方文档,绕开前方大坑,哈哈哈哈哈微信小程序支付必须有商户号,绑定商户平台才有微信支付功能,个人小程序是没有支付功能的。商户平台可使用商户营业执照申请
一、支付流程导读 1.支付系统交互流程
在开发前若时间充足,建议大家通篇阅读一下微信支付官方文档,对业务名称加以熟悉,看清楚接口参数名,是否必传,方便后面的开发。[微信支付文档,点我跳转] (https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_10&index=1)
微信支付业务流程图:
文章图片
- 调用微信登录API接口获取用户openid。
- 将支付金额,回调地址,小程序appid等传入调用统一下单接口API。
- 微信官方生成预支付订单数据,包括订单id,支付金额等。
- 将返回数据进行签名,防止篡改。
- 将5个参数数据放回给前端调用Payment(输密码的面板)。
- 用户确认金额,输入密码。
- 微信进行密码鉴权。
- 微信回调你的业务系统,将支付是否成功通知给你,进行订单支付状态更新。此时你的接口必须放开权限,且公网可访问。
- appid :小程序的id
- apikey : 可在小程序开发配置找到
- mchid : 商户号,商户平台可找到
SDK下载地址 点我下载SDK
三、获取用户openid 1. 配置文件
weixin:
# 小程序ID
appid: wx08huxxxxxxxxxxx
# 小程序秘钥
appSecret: 95e2932xxxxxxxxxxxx
# 商户秘钥
apiKey: ymruurG3xxxxxxxxxx
# 商户ID
mchId: 1244xxxxxxxx
#支付回调通知地址 必须为公网可访问的域名地址 且权限放开
routine:
notify: https:xxxxxx/wx/pay/notify
2. 获取用户openid
若不懂openid的请自行百度
首先前端同学需要请求微信服务获取一个登陆code 。然后带着登陆code请求后端接口
@GetMapping(value = "https://www.it610.com/login/{code}")
@OperationLog(name = "获取用户openid", type = OperationLogType.ADD)
@ApiOperation(value = "https://www.it610.com/article/获取用户openid" , response = ApiResult.class)
@ResponseBody
public ApiResult login(@PathVariable String code) throws Exception {
Map, String> result= wxAuthApiService.wxLogin(code);
if (result.isEmpty()){
return ApiResult.fail("获取用户小程序openid失败");
}
return ApiResult.ok(result);
}
Service
public interface WxAuthApiService {
// 获取获取用户小程序openid
Map,String> wxLogin(String code) throws Exception;
}
实现类
/**
* @Author 公众号:真香号
* @Version 1.0
* @Description
*/
@Slf4j
@Service
public class WxAuthApiServiceImpl implements WxAuthApiService {
// 小程序ID
@Value("${weixin.appid}")
private String APPID;
// 小程序秘钥
@Value("${weixin.appSecret}")
private String SECRET;
@Transactional(rollbackFor = Exception.class)
@Override
public Map, String> wxLogin(String code) throws Exception {
Map, String> param = new ConcurrentHashMap<>();
if (StringUtils.isEmpty(code)) {
throw new BusinessException("获取用户openid失败,code 不能为空");
}
String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
APPID, SECRET, code);
String result = HttpUtils.sendGet(url, null);
log.info(result);
JSONObject object = JSON.parseObject(result);
if (object.getInteger("errcode") != null) {
log.info("获取用户openid失败code:{},msg:{}", object.getInteger("errcode"), object.getString("errmsg"));
throw new BusinessException("获取用户openid失败,errcode:" + object.getInteger("errcode") + "errmsg:" + object.getString("errmsg"));
}
String openId = object.getString("openid");
// 会话密钥
String sessionKey = object.getString("session_key");
// 根据业务将openid与sessionKey 持久化存入数据库
param.put("openid", openId);
param.put("sessionKey", sessionKey);
return param;
}
顺利拿到openid 以后就可以请求支付,因为微信支付必须确定订单的支付人openid。
四、支付流程 1. 支付接口
/**
* 微信小程序 统一下单支付
* @param request
* @param response
* @return
*/
@PostMapping("/unifiedOrder")
@ResponseBody
@ApiOperation(value = "https://www.it610.com/article/微信统一下单", response = ApiResult.class)
public ApiResult unifiedOrder(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("---------统一下单--------");
String openId = request.getParameter("openId");
if (StringUtils.isEmpty(openId)) {
thrownew BusinessException("openid 不能为空");
}
log.info("---------openId--------" + openId);
Map, String> map = wxPayService.unifiedOrder(openId, IpUtil.getIpAddress(request), request);
return ApiResult.ok(map);
}
service
/**
*统一下单接口
* @param openid 用户标识
* @param ip请求ip
* @param request 订单数据
* @return
*/
Map,String> unifiedOrder(String openid, String ip, HttpServletRequest request) throws Exception;
【微信开发|一文实现微信小程序支付 史上最全版】实现类
/**
* @Author 公众号:真香号
* @Version 1.0
* @Description
*/
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {@Value("${weixin.appid}")
private String APPID;
@Value("${weixin.appSecret}")
private String SECRET;
@Value("${weixin.apiKey}")
private String APIKEY;
@Value("${weixin.mchId}")
private String MCHID;
//交易类型
public static final String TRADE_TYPE_JS = "JSAPI";
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
// 微信统一下单url
public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
// 支付回调地址
@Value("${routine.notify}")
public String NOTIFY_URL;
/** 统一下单
* @param openid 用户唯一标识
* @param ip 请求方IP地址
* @param request 请求数据
* @return 已生成订单信息
*/
@Override
public Map, String> unifiedOrder(String openid, String ip, HttpServletRequest request) throws Exception {
// 1. 拼接统?下单地址参数
log.info("进入下单流程");
Map, String> paramMap = new ConcurrentHashMap, String>(10);
paramMap.put("appid", APPID);
paramMap.put("mch_id", MCHID);
// 商品信息
paramMap.put("body", "test");
paramMap.put("openid", openid);
// 订单号
paramMap.put("out_trade_no", request.getParameter("orderId"));
// 随机字符串,长度要求在32位以内。
paramMap.put("nonce_str", StringHelpUtils.generateNonceStr());
// 以分为单位,1代表0.01元
paramMap.put("total_fee", "1");
paramMap.put("spbill_create_ip", ip);
paramMap.put("notify_url", NOTIFY_URL);
// 支付类型
paramMap.put("trade_type", TRADE_TYPE_JS);
// 微信官方SDK中生成签名方法
String paramXml = WxPayUtil.generateSignedXml(paramMap, APIKEY, WxPayUtil.SignType.MD5);
log.info("签名参数:{}", paramXml);
Map, String> resultMap = new ConcurrentHashMap<>(10);
// 请求微信服务发起支付 生成预支付订单ID
String returnXml = HttpClientUtil.httpsRequest(UNIFIED_ORDER_URL, "POST", paramXml);
log.info("返回参数:{}", returnXml);
resultMap = WxPayUtil.xmlToMap(returnXml);
Map, String> returnMap = new ConcurrentHashMap<>();
// 微信SDK方法验证签名 再次签名
if (WxPayUtil.isSignatureValid(resultMap, APIKEY, WxPayUtil.SignType.MD5)) {
// 签名成功后返给前端
returnMap.put("appId", APPID);
// 时间戳
returnMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
// 随机字符串
returnMap.put("nonceStr", WXPayUtil.generateNonceStr());
// 签名类型
returnMap.put("signType", WXPayConstants.MD5);
// 微信预订单ID ,package参数名在微信文档中已指定必填
returnMap.put("package", "prepay_id=" + resultMap.get("prepay_id"));
// 5. 通过appId, timeStamp, nonceStr, signType, package及商户密钥进?key=value形式拼接并加密
String paySign = WXPayUtil.generateSignature(returnMap, APIKEY, WXPayConstants.SignType.MD5);
returnMap.put("paySign", paySign);
returnMap.put("mchId", MCHID);
returnMap.put("resultCode", SUCCESS);
log.info("returnMap:{}", returnMap);
return returnMap;
} else {
returnMap.put("resultCode", FAIL);
log.info("签名验证错误");
}
return returnMap;
}
}
https 请求方法
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
System.out.println("连接超时:{}" + ce);
} catch (Exception e) {
System.out.println("https请求异常:{}" + e);
}
return null;
}
前端同学拿到以上参数就能唤起Payment 支付密码输入面板啦。
2. 注意事项
- 微信支付金额以“分”为单位,需将"元"转换为"分"。
- 微信支付金额不能为"0"元。0元将导致签名失败,无法唤起Payment。
以上给我记牢了,我曾经一次又一次在这翻车,一步步看日志的那种,唉不说了,一把辛酸泪。
何所谓支付回调:订单支付钱到了微信,究竟是否支付成功,业务系统无从知道。微信官方提供一个消息推送接口,下单后,将会请求你订单支付时设置的回调地址接口地址。微信会传送订单号、支付是否成功、支付金额、时间等信息。根据信息我们对相应的订单进行业务处理。上代码。此处请自行脑补抖音“三支花”大妈 “上才艺”视频场景。哈哈哈哈哈
接口:
/**
* 通知回调 notify
* @param request 订单信息
* @param response
* @returnSUCCESS/FAIL
* @throws Exception
*/
@RequestMapping(value = "https://www.it610.com/notify")
@ApiOperation(value = "https://www.it610.com/article/微信支付回调", response = ApiResult.class)
public void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("微信开始回调");
StringresXml = wxPayService.notify(request,response);
log.info("微信回调成功,返回值:{}",resXml);
}
实现类:
/** 支付回调
* @param request
* @param response
* @return
*/
@Override
public String notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
log.info("---------------微信支付回调----------------------");
// 调用返回 xml
String resXml = "";
try {
// 解析request中xml 转换成map
Map, String> map = WxPayUtil.parseXml(request);
log.info("---------------微信支付回调订单号为:{}--------", map.get("out_trade_no"));
// 检验签名是否成功
if (WxPayUtil.isSignatureValid(map, APIKEY, WxPayUtil.SignType.MD5)) {
log.info("微信支付return_code:{}", map.get("return_code"));
//log.info("判断结果:{}", "SUCCESS".equals(map.get("return_code")));
if ("SUCCESS".equals(map.get("return_code"))) {
Boolean result = orderService.orderCallback(map).getData();
log.info("支付回调得到order更新数据库结果:{}", result);
if (result) {
// 返回success给微信服务器 剔除推送队列中的本次交易
resXml = " " +
" ";
} else {
log.info("----------------微信回调成功 更新数据库状态失败---------------");
}
} else {
log.info("微信回调失败:{}", map.get("return_code"));
}
} else {
resXml = "" + " " + " " + " ";
}
} catch (Exception e) {
log.error("微信回调失败,抛出异常:{}", e);
}
PrintWriter printWriter = new PrintWriter(response.getOutputStream());
log.info("回调返回给微信服务器:{}", resXml);
printWriter.write(resXml);
printWriter.flush();
printWriter.close();
return resXml;
}
一定要放开支付回调的权限啊,不然微信想回调你,也进不了你家大门啊
以上就是微信小程序支付实现的大致过程,下期讲 微信退款
文章图片
关注个人微信公众号,与作者交流,第一时间接收更新通知:
文章图片
推荐阅读
- 小程序|微信小程序转uniapp
- 微信小程序|微信小程序项目实例——图片处理小工具(自制低配版美图秀秀)
- 5 款顶级 Docker GUI 工具,好用到爆!!
- 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码
- (免费分享)java基于SSM的进销存管理系统设计与实现
- SSM|MyBatis--案例入门(一)
- #|记SQL Server实战修复死锁总结
- final关键字的使用详解
- 我的|RPMBUILD 打包