一:集成步骤
1.引入依赖:
1<dependency> 2 <groupId>com.github.wxpay</groupId> 3 <artifactId>wxpay-sdk</artifactId> 4 <version>0.0.3</version> 5</dependency>
2.微信app支付参数配置:
1#服务器域名地址 2server.service-domain = http://127.0.0.1:8080 3 4#微信app支付 5pay.wxpay.app.appID = "你的appid" 6pay.wxpay.app.mchID = "你的商户id" 7pay.wxpay.app.key = "你的api秘钥,不是appSecret" 8#从微信商户平台下载的安全证书存放的路径、我放在resources下面,切记一定要看看target目录下的class文件下有没有打包apiclient_cert.p12文件 9pay.wxpay.app.certPath = static/cert/wxpay/apiclient_cert.p12 10#微信支付成功的异步通知接口 11pay.wxpay.app.payNotifyUrl=${server.service-domain}/api/wxPay/notify
3.定义配置类:
1package com.annaru.upms.payment.config; 2 3import com.github.wxpay.sdk.WXPayConfig; 4import org.springframework.boot.context.properties.ConfigurationProperties; 5import org.springframework.stereotype.Component; 6 7import java.io.InputStream; 8 9/** 10 * 配置我们自己的信息 11 */ 12@Component 13@ConfigurationProperties(prefix = "pay.wxpay.app") 14public class WxPayAppConfig implements WXPayConfig { 15 /** 16 * appID 17 */ 18 private String appID; 19 20 /** 21 * 商户号 22 */ 23 private String mchID; 24 25 /** 26 * API 密钥 27 */ 28 private String key; 29 30 /** 31 * API证书绝对路径 (本项目放在了 resources/cert/wxpay/apiclient_cert.p12") 32 */ 33 private String certPath; 34 35 /** 36 * HTTP(S) 连接超时时间,单位毫秒 37 */ 38 private int httpConnectTimeoutMs = 8000; 39 40 /** 41 * HTTP(S) 读数据超时时间,单位毫秒 42 */ 43 private int httpReadTimeoutMs = 10000; 44 45 /** 46 * 微信支付异步通知地址 47 */ 48 private String payNotifyUrl; 49 50 /** 51 * 微信退款异步通知地址 52 */ 53 private String refundNotifyUrl; 54 55 /** 56 * 获取商户证书内容(这里证书需要到微信商户平台进行下载) 57 * 58 * @return 商户证书内容 59 */ 60 @Override 61 public InputStream getCertStream() { 62 InputStream certStream =getClass().getClassLoader().getResourceAsStream(certPath); 63 return certStream; 64 } 65 66 public String getAppID() { 67 return appID; 68 } 69 70 public void setAppID(String appID) { 71 this.appID = appID; 72 } 73 74 public String getMchID() { 75 return mchID; 76 } 77 78 public void setMchID(String mchID) { 79 this.mchID = mchID; 80 } 81 82 public String getKey() { 83 return key; 84 } 85 86 public void setKey(String key) { 87 this.key = key; 88 } 89 90 public String getCertPath() { 91 return certPath; 92 } 93 94 public void setCertPath(String certPath) { 95 this.certPath = certPath; 96 } 97 98 public int getHttpConnectTimeoutMs() { 99 return httpConnectTimeoutMs; 100 } 101 102 public void setHttpConnectTimeoutMs(int httpConnectTimeoutMs) { 103 this.httpConnectTimeoutMs = httpConnectTimeoutMs; 104 } 105 106 public int getHttpReadTimeoutMs() { 107 return httpReadTimeoutMs; 108 } 109 110 public void setHttpReadTimeoutMs(int httpReadTimeoutMs) { 111 this.httpReadTimeoutMs = httpReadTimeoutMs; 112 } 113 114 public String getPayNotifyUrl() { 115 return payNotifyUrl; 116 } 117 118 public void setPayNotifyUrl(String payNotifyUrl) { 119 this.payNotifyUrl = payNotifyUrl; 120 } 121 122 public String getRefundNotifyUrl() { 123 return refundNotifyUrl; 124 } 125 126 public void setRefundNotifyUrl(String refundNotifyUrl) { 127 this.refundNotifyUrl = refundNotifyUrl; 128 } 129}
4. 定义controller:
在调用微信服务接口进行统一下单之前,
1、为保证安全性,建议验证数据库是否存在订单号对应的订单。
1package com.annaru.upms.payment.controller; 2 3import com.annaru.common.result.ResultMap; 4import com.annaru.upms.payment.service.WxPayService; 5import io.swagger.annotations.Api; 6import io.swagger.annotations.ApiOperation; 7import io.swagger.annotations.ApiParam; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.web.bind.annotation.GetMapping; 10import org.springframework.web.bind.annotation.RequestMapping; 11import org.springframework.web.bind.annotation.RequestParam; 12import org.springframework.web.bind.annotation.RestController; 13 14import javax.servlet.http.HttpServletRequest; 15 16@Api(tags = "微信支付接口管理") 17@RestController 18@RequestMapping("/wxPay") 19public class WxPayController{ 20 21 @Autowired 22 private WxPayService wxPayService; 23 24 /** 25 * 统一下单接口 26 */ 27 @ApiOperation(value = "统一下单", notes = "统一下单") 28 @GetMapping("/unifiedOrder") 29 public ResultMap unifiedOrder( 30 @ApiParam(value = "订单号") @RequestParam String orderNo, 31 @ApiParam(value = "订单金额") @RequestParam double amount, 32 @ApiParam(value = "商品名称") @RequestParam String body, 33 HttpServletRequest request) { 34 try { 35 // 1、验证订单是否存在 36 37 // 2、开始微信支付统一下单 38 ResultMap resultMap = wxPayService.unifiedOrder(orderNo, orderNo, body); 39 return resultMap;//系统通用的返回结果集,见文章末尾 40 } catch (Exception e) { 41 logger.error(e.getMessage()); 42 return ResultMap.error("运行异常,请联系管理员"); 43 } 44 } 45 46 /** 47 * 微信支付异步通知 48 */ 49 @RequestMapping(value = "/notify") 50 public String payNotify(HttpServletRequest request) { 51 InputStream is = null; 52 String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> "; 53 try { 54 is = request.getInputStream(); 55 // 将InputStream转换成String 56 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 57 StringBuilder sb = new StringBuilder(); 58 String line = null; 59 while ((line = reader.readLine()) != null) { 60 sb.append(line + "\n"); 61 } 62 xmlBack = wxPayService.notify(sb.toString()); 63 } catch (Exception e) { 64 logger.error("微信手机支付回调通知失败:", e); 65 } finally { 66 if (is != null) { 67 try { 68 is.close(); 69 } catch (IOException e) { 70 e.printStackTrace(); 71 } 72 } 73 } 74 return xmlBack; 75 } 76 77 @ApiOperation(value = "退款", notes = "退款") 78 @PostMapping("/refund") 79 public ResultMap refund(@ApiParam(value = "订单号") @RequestParam String orderNo, 80 @ApiParam(value = "退款金额") @RequestParam double amount, 81 @ApiParam(value = "退款原因") @RequestParam(required = false) String refundReason){ 82 83 return wxPayService.refund(orderNo, amount, refundReason); 84 } 85 86}
5、定义service接口:
1package com.annaru.upms.payment.service; 2 3import com.annaru.common.result.ResultMap; 4 5/** 6 * 微信支付服务接口 7 */ 8public interface WxPayService { 9 10 /** 11 * @Description: 微信支付统一下单 12 * @param orderNo: 订单编号 13 * @param amount: 实际支付金额 14 * @param body: 订单描述 15 * @Author: 16 * @Date: 2019/8/1 17 * @return 18 */ 19 ResultMap unifiedOrder(String orderNo, double amount, String body) ; 20 21 /** 22 * @Description: 订单支付异步通知 23 * @param notifyStr: 微信异步通知消息字符串 24 * @Author: 25 * @Date: 2019/8/1 26 * @return 27 */ 28 String notify(String notifyStr) throws Exception; 29 30 /** 31 * @Description: 退款 32 * @param orderNo: 订单编号 33 * @param amount: 实际支付金额 34 * @param refundReason: 退款原因 35 * @Author: XCK 36 * @Date: 2019/8/6 37 * @return 38 */ 39 ResultMap refund(String orderNo, double amount, String refundReason); 40 41} 42
6、service实现类
1package com.annaru.upms.payment.service.impl; 2 3import com.alibaba.dubbo.config.annotation.Reference; 4import com.annaru.common.result.ResultMap; 5import com.annaru.common.util.HttpContextUtils; 6import com.annaru.upms.payment.config.WxPayAppConfig; 7import com.annaru.upms.payment.service.WxPayService; 8import com.annaru.upms.service.IOrderPaymentService; 9import com.github.wxpay.sdk.WXPay; 10import com.github.wxpay.sdk.WXPayUtil; 11import org.apache.commons.lang3.StringUtils; 12import org.slf4j.Logger; 13import org.slf4j.LoggerFactory; 14import org.springframework.beans.factory.annotation.Autowired; 15import org.springframework.stereotype.Service; 16 17import java.util.HashMap; 18import java.util.Map; 19 20@Service 21public class WxPayServiceImpl implements WxPayService { 22 private final Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class); 23 24 @Reference 25 private IOrderPaymentService orderPaymentService; 26 @Autowired 27 private WxPayAppConfig wxPayAppConfig; 28 29 @Override 30 public ResultMap unifiedOrder(String orderNo, double amount, String body) { 31 Map<String, String> returnMap = new HashMap<>(); 32 Map<String, String> responseMap = new HashMap<>(); 33 Map<String, String> requestMap = new HashMap<>(); 34 try { 35 WXPay wxpay = new WXPay(wxPayAppConfig); 36 requestMap.put("body", body); // 商品描述 37 requestMap.put("out_trade_no", orderNo); // 商户订单号 38 requestMap.put("total_fee", String.valueOf((int)(amount*100))); // 总金额 39 requestMap.put("spbill_create_ip", HttpContextUtils.getIpAddr()); // 终端IP 40 requestMap.put("trade_type", "APP"); // App支付类型 41 requestMap.put("notify_url", wxPayAppConfig.getPayNotifyUrl()); // 接收微信支付异步通知回调地址 42 Map<String, String> resultMap = wxpay.unifiedOrder(requestMap); 43 //获取返回码 44 String returnCode = resultMap.get("return_code"); 45 String returnMsg = resultMap.get("return_msg"); 46 //若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断 47 if ("SUCCESS".equals(returnCode)) { 48 String resultCode = resultMap.get("result_code"); 49 String errCodeDes = resultMap.get("err_code_des"); 50 if ("SUCCESS".equals(resultCode)) { 51 responseMap = resultMap; 52 } 53 } 54 if (responseMap == null || responseMap.isEmpty()) { 55 return ResultMap.error("获取预支付交易会话标识失败"); 56 } 57 // 3、签名生成算法 58 Long time = System.currentTimeMillis() / 1000; 59 String timestamp = time.toString(); 60 returnMap.put("appid", wxPayAppConfig.getAppID()); 61 returnMap.put("partnerid", wxPayAppConfig.getMchID()); 62 returnMap.put("prepayid", responseMap.get("prepay_id")); 63 returnMap.put("noncestr", responseMap.get("nonce_str")); 64 returnMap.put("timestamp", timestamp); 65 returnMap.put("package", "Sign=WXPay"); 66 returnMap.put("sign", WXPayUtil.generateSignature(returnMap, wxPayAppConfig.getKey()));//微信支付签名 67 return ResultMap.ok().put("data", returnMap); 68 } catch (Exception e) { 69 logger.error("订单号:{},错误信息:{}", orderNo, e.getMessage()); 70 return ResultMap.error("微信支付统一下单失败"); 71 } 72 } 73 74 @Override 75 public String notify(String notifyStr) { 76 String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> "; 77 try { 78 // 转换成map 79 Map<String, String> resultMap = WXPayUtil.xmlToMap(notifyStr); 80 WXPay wxpayApp = new WXPay(wxPayAppConfig); 81 if (wxpayApp.isPayResultNotifySignatureValid(resultMap)) { 82 String returnCode = resultMap.get("return_code"); //状态 83 String outTradeNo = resultMap.get("out_trade_no");//商户订单号 84 String transactionId = resultMap.get("transaction_id"); 85 if (returnCode.equals("SUCCESS")) { 86 if (StringUtils.isNotBlank(outTradeNo)) { 87 /** 88 * 注意!!! 89 * 请根据业务流程,修改数据库订单支付状态,和其他数据的相应状态 90 * 91 */ 92 logger.info("微信手机支付回调成功,订单号:{}", outTradeNo); 93 xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; 94 } 95 } 96 } 97 } catch (Exception e) { 98 e.printStackTrace(); 99 } 100 return xmlBack; 101 } 102 103 @Override 104 public ResultMap refund(String orderNo, double amount, String refundReason){ 105 106 if(StringUtils.isBlank(orderNo)){ 107 return ResultMap.error("订单编号不能为空"); 108 } 109 if(amount <= 0){ 110 return ResultMap.error("退款金额必须大于0"); 111 } 112 113 Map<String, String> responseMap = new HashMap<>(); 114 Map<String, String> requestMap = new HashMap<>(); 115 WXPay wxpay = new WXPay(wxPayAppConfig); 116 requestMap.put("out_trade_no", orderNo); 117 requestMap.put("out_refund_no", UUIDGenerator.getOrderNo()); 118 requestMap.put("total_fee", "订单支付时的总金额,需要从数据库查"); 119 requestMap.put("refund_fee", String.valueOf((int)(amount*100)));//所需退款金额 120 requestMap.put("refund_desc", refundReason); 121 try { 122 responseMap = wxpay.refund(requestMap); 123 } catch (Exception e) { 124 e.printStackTrace(); 125 } 126 String return_code = responseMap.get("return_code"); //返回状态码 127 String return_msg = responseMap.get("return_msg"); //返回信息 128 if ("SUCCESS".equals(return_code)) { 129 String result_code = responseMap.get("result_code"); //业务结果 130 String err_code_des = responseMap.get("err_code_des"); //错误代码描述 131 if ("SUCCESS".equals(result_code)) { 132 //表示退款申请接受成功,结果通过退款查询接口查询 133 //修改用户订单状态为退款申请中或已退款。退款异步通知根据需求,可选 134 // 135 return ResultMap.ok("退款申请成功"); 136 } else { 137 logger.info("订单号:{}错误信息:{}", orderNo, err_code_des); 138 return ResultMap.error(err_code_des); 139 } 140 } else { 141 logger.info("订单号:{}错误信息:{}", orderNo, return_msg); 142 return ResultMap.error(return_msg); 143 } 144 } 145 146} 147
7、定义通用返回结果集 ResultMap
1package com.annaru.common.result; 2 3import org.apache.http.HttpStatus; 4 5import java.util.HashMap; 6import java.util.Map; 7 8/** 9 * @Description 通用返回结果集 10 * @Author 11 * @Date 2018/6/12 15:13 12 */ 13public class ResultMap extends HashMap<String, Object> { 14 public ResultMap() { 15 put("state", true); 16 put("code", 0); 17 put("msg", "success"); 18 } 19 20 public static ResultMap error(int code, String msg) { 21 ResultMap r = new ResultMap(); 22 r.put("state", false); 23 r.put("code", code); 24 r.put("msg", msg); 25 return r; 26 } 27 28 public static ResultMap error(String msg) { 29 return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg); 30 } 31 32 public static ResultMap error() { 33 return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员"); 34 } 35 36 public static ResultMap ok(String msg) { 37 ResultMap r = new ResultMap(); 38 r.put("msg", msg); 39 return r; 40 } 41 42 public static ResultMap ok(Map<String, Object> par) { 43 ResultMap r = new ResultMap(); 44 r.putAll(par); 45 return r; 46 } 47 48 public static ResultMap ok() { 49 return new ResultMap(); 50 } 51 52 public ResultMap put(String key, Object value) { 53 super.put(key, value); 54 return this; 55 } 56 57}