微信支付豐富着人們日常生活,下面我們來依據微信提供的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