微信支付丰富着人们日常生活,下面我们来依据微信提供的API文档尝试着学习编写微信支付工具类,避免重复造轮子。
1.JSAPI:
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
- ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
- ◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
- ◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
2.Native支付
Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
3.H5支付
H5支付主要是在手机、ipad等移动设备中通过浏览器来唤起微信支付的支付产品。
4.小程序支付
小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。
5.App支付
APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。
注:微信支付功能开发 :1.必须拥有一个微信公众号;2.具备开通微信支付功能;3登录微信商户平台(https://pay.weixin.qq.com/index.php/core/home/login),设置商户号,支付秘钥和下载证书(退款接口需要携带证书请求)。
===========================================================================
Utils工具类代码
================================================================
package com.sf.detectprocess.util.pay;
import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.sf.detectprocess.controller.param.*; import com.sf.detectprocess.core.constant.SystemConstants; import com.sf.detectprocess.core.constant.WXPayConstants; import com.sf.detectprocess.core.exception.WeChatPayException; import com.sf.detectprocess.entity.Order; import com.sf.detectprocess.entity.OrderPay; import com.sf.detectprocess.mapper.OrderMapper; import com.sf.detectprocess.mapper.OrderPayMapper; import com.sf.detectprocess.service.LimsService; import com.sf.detectprocess.util.FuntionUtils; import com.sf.detectprocess.util.R; import com.sun.org.apache.regexp.internal.RE; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; /** * @description: 微信支付接口工具类 * @author: zhucj * @date: 2019-09-25 10:37 */ @Slf4j @Component public class WeChatPayUtils { /** * 公众号或小程序或app端 appid */ @Value("${wx.pay.appId}") private String appId; /** * 公众号或小程序 key */ @Value("${wx.pay.key}") private String key; /** * 商户号 (微信支付商户号) */ @Value("${wx.pay.mchNo}") private String mchNo; /** * 通知地址 */ @Value("${wx.pay.notifyUrl}") private String notifyUrl; /** * 安全证书密码(默认为商户号) */ @Value("${wx.pay.lcePassWord}") private String lcePassWord; @ApiOperation(value = "请求微信统一支付接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "标价金额", dataType = "String"), @ApiImplicitParam(name = "body", required = true, value = "商品描述", dataType = "String"), @ApiImplicitParam(name = "tradeType", required = true, value = "交易类型 JSAPI:JSAPI支付(或小程序支付)," + "NATIVE:Native支付, APP:app支付, MWEB:H5支付", dataType = "String"), @ApiImplicitParam(name = "openId", required = false, value = "用户openId(JSAPI/小程序/必传)",dataType = "String"), @ApiImplicitParam(name = "spbillCreateIp", required = false, value = "APP和网页支付提交用户端ip,Native支付不需要传)", dataType = "String"), @ApiImplicitParam(name = "productId", required = false, value = "二维码中包含商品Id(Native支付必传,其他支付不传)", dataType = "String") }) public R unifiedOrder(UnfiedOrderParam map) throws WeChatPayException { /* 签名参数的map对象 */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); // 请求预下单对象 UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest(); //=>1.appId 商户Id unifiedOrderRequest.setAppid(appId); //TODO:判断当前微信支付类型 APP端/JSAPI/H5/Native/MWEB if (Objects.equals(WXPayConstants.APP,map.getTradeType())){ //TODO:App端支付 必传用户终端IP 和单独的appId(应用Id) if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE, WXPayConstants.SPBILL_CREATE_APP_ERROR); } // => 2.终端Ip app支付由前端传 unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); } else if (Objects.equals(WXPayConstants.JSAPI,map.getTradeType())) { //TODO:JSAPI支付 必传openId和用户终端IP if (isNull(map.getOpenId())) { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.OPENID_JSAPI_ERROR); } if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_JSAPI_ERROR); } unifiedOrderRequest.setOpenid(map.getOpenId()); // => 2.终端Ip 小程序或公众号支付由前端传 unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); //仅 JSAPI支付才传openId packageParams.put("openid", unifiedOrderRequest.getOpenid()); }else if (Objects.equals(WXPayConstants.NATIVE,map.getTradeType())){ //TODO:Native扫码支付 必传prductId(商品Id) if (isNull(map.getProductId())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.PRODUCT_NATIVE_ERROR); } unifiedOrderRequest.setProduct_id(map.getProductId()); packageParams.put("product_id", unifiedOrderRequest.getProduct_id()); //TODO:Native 本地获取终端Ip String localIp = WXPayUtil.getLocalIp(); //=> 2.终端Ip unifiedOrderRequest.setSpbill_create_ip(localIp); }else if (Objects.equals(WXPayConstants.MWEB,map.getTradeType())){ //TODO:H5 前端必传Ip if (isNull(map.getSpbillCreateIp())){ throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_HWEB_ERROR); } // => 2.终端Ip H5支付由前端传 unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp()); }else { throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"交易类型tradeType="+map.getTradeType()+"暂时不支持,请核对参数是否正确!"); } // => 3.商品描述 unifiedOrderRequest.setBody(map.getBody()); // => 4.商户号 unifiedOrderRequest.setMchId(mchNo); // 生成随机字符串 => 5.随机字符串 unifiedOrderRequest.setNonceStr(WXPayUtil.generateNonceStr()); // => 6.异步通知地址 unifiedOrderRequest.setNotify_url(notifyUrl); // => 7.商户订单号 unifiedOrderRequest.setOut_trade_no(map.getOutTradeNo()); // => 8.支付金额 需要扩大100倍(1代表支付时是0.01) unifiedOrderRequest.setTotal_fee(WXPayUtil.changeY2F(map.getTotalFee())); // => 9.交易类型 unifiedOrderRequest.setTrade_type(map.getTradeType()); //TODO: 封装签名map packageParams.put("appid", unifiedOrderRequest.getAppid()); packageParams.put("body", unifiedOrderRequest.getBody()); packageParams.put("mch_id", unifiedOrderRequest.getMchId()); packageParams.put("nonce_str", unifiedOrderRequest.getNonceStr()); packageParams.put("notify_url", unifiedOrderRequest.getNotify_url()); packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no()); packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip()); packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee()); packageParams.put("trade_type", unifiedOrderRequest.getTrade_type()); try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); // =>10.签名 unifiedOrderRequest.setSign(signature); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调统一下单接口 String s = WXPayUtil.doPost(1, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s, UnifiedOrderResponse.class); //预下单后 返回前端封装map TreeMap respMap = new TreeMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ //TODO: 依据交易类型返回前端参数 switch (resposeStr.getTrade_type()) { case WXPayConstants.NATIVE: { //Native扫码直接返回二维码链接 respMap.put("codeUrl", resposeStr.getCode_url()); break; } case WXPayConstants.APP : { //App端 需要商户号进行签名 返回 respMap.put("appid", resposeStr.getAppid()); respMap.put("partnerid", resposeStr.getMch_id()); respMap.put("prepayid", resposeStr.getPrepay_id()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonce_str", WXPayUtil.generateNonceStr()); respMap.put("package","Sign=WXPay"); respMap.put("sign", WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); break; } case WXPayConstants.JSAPI: { respMap.put("appId", resposeStr.getAppid()); respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp())); respMap.put("nonceStr",resposeStr.getNonce_str()); respMap.put("package","prepay_id="+resposeStr.getPrepay_id()); respMap.put("signType",WXPayConstants.MD5); respMap.put("paySign",WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5)); respMap.put("prepayid",resposeStr.getPrepay_id()); break; } case WXPayConstants.MWEB : { // h5支付链接地址 respMap.put("payUrl", resposeStr.getMweb_url()); break; } default: break; } return R.ok(respMap); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("预订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"预订单异常"); } } @ApiOperation(value = "请求微信订单查询接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String") public R orderQuery(String outTradeNo) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调微信查询订单服务接口 String s = WXPayUtil.doPost(2, WXPayUtil.mapToXml(packageParams), false, null); OrderQueryResponse resposeStr = JSON.parseObject(s, OrderQueryResponse.class); //查询返回结果封装map Map respMap = new HashMap(); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ respMap.put("statusDesc",resposeStr.getTrade_state_desc()); respMap.put("data",resposeStr); /** * 自己设置的status => 1:已支付 ,2:转入退款 3:未支付 4:已关闭 5:已撤销 6:支付失败 * */ if (Objects.equals(WXPayConstants.TradeState.SUCCESS.getName(),resposeStr.getTrade_state())){ respMap.put("status",1); }else if (Objects.equals(WXPayConstants.TradeState.REFUND.getName(),resposeStr.getTrade_state())){ respMap.put("status",2); }else if (Objects.equals(WXPayConstants.TradeState.NOTPAY.getName(),resposeStr.getTrade_state())){ respMap.put("status",3); }else if (Objects.equals(WXPayConstants.TradeState.CLOSED.getName(),resposeStr.getTrade_state())){ respMap.put("status",4); }else if (Objects.equals(WXPayConstants.TradeState.REVOKED.getName(),resposeStr.getTrade_state())){ respMap.put("status",5); }else if (Objects.equals(WXPayConstants.TradeState.PAYERROR.getName(),resposeStr.getTrade_state())){ respMap.put("status",6); } return R.ok(respMap,"查询成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("查询订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询订单异常"); } } @ApiOperation(value = "请求微信关闭订单接口" ) @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType = "String") public R closeOrder(String outTradeNo) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("out_trade_no",outTradeNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //请求微信关闭订单服务接口 String s = WXPayUtil.doPost(4, WXPayUtil.mapToXml(packageParams), false, null); UnifiedOrderResponse resposeStr = JSON.parseObject(s,UnifiedOrderResponse.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ return R.ok(null,"订单关闭成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return R.error(resposeStr.getReturn_msg()); } } catch (Exception e) { log.error("关闭订单异常:{}"+e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"关闭订单异常"); } } @ApiOperation(value = "请求微信退款订单接口" ) @ApiImplicitParams({ @ApiImplicitParam(name = "transactionId", required = true, value = "微信订单号", dataType = "String"), @ApiImplicitParam(name = "outRefundNo", required = true, value = "商户退款单号(32个字符内 )", dataType = "String"), @ApiImplicitParam(name = "totalFee", required = true, value = "订单金额", dataType = "String"), @ApiImplicitParam(name = "refundFee", required = true, value = "退款金额", dataType = "String"), @ApiImplicitParam(name = "refundDesc", required = false, value = "退款原因", dataType = "String") }) public R refund(RefundParam map) throws WeChatPayException { /* 签名map */ SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appid",appId); packageParams.put("mch_id",mchNo); packageParams.put("nonce_str",WXPayUtil.generateNonceStr()); packageParams.put("out_refund_no",map.getOutRefundNo()); packageParams.put("total_fee",WXPayUtil.changeY2F(map.getTotalFee())); packageParams.put("refund_fee",WXPayUtil.changeY2F(map.getRefundFee())); packageParams.put("transaction_id",map.getTransactionId()); //获得签名 try { String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5); packageParams.put("sign",signature); } catch (Exception e) { log.error("签名异常:{}",e.getMessage()); throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR); } try { //调统一请求退款服务接口 String s = WXPayUtil.doPost(3,WXPayUtil.mapToXml(packageParams), true,lcePassWord); RefundResp resposeStr = JSON.parseObject(s, RefundResp.class); if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){ if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){ /** * ##订单已退款 此处更新数据库订单状态和退款信息 ### */ return R.ok(null,"订单退款成功"); }else { return R.error(resposeStr.getErr_code_des()); } }else { return