微信小程序支付 相关工具类和回调
支付和退款公共接口
@GetMapping("/back") public ResultData back(Integer id,HttpServletRequest request){ Integer userId = userService.getUserId(request); String uuid = WXPayUtil.getUUID(); //构造退款的请求body 参数为 微信订单号或自己生成的订单号,退款单号(自己生成) ,退款金额,总金额 String backJson = WXPayUtil.buildBackJson(order.getOrderId(), uuid, Integer.valueOf(order.getAmount()), Integer.valueOf(order.getAmount())); //封装的退款工具类 调用即发起退款 JSONObject back = WXPayUtil.back(backJson); //自己封装的通用返回 if (ObjectUtils.isNotEmpty(back)) return ResultData.success(); return ResultData.fail(); } @PostMapping("/pay") public ResultData pay(@RequestBody WxPay wxPay,HttpServletRequest request) throws Exception { String uuid = WXPayUtil.getUUID(); String openid = request.getHeader("openid"); Integer userId = userService.getUserId(request); // 穿插自己的业务和获取需要支付的金额 // 构造付款的body 参数为 商品描述,自己生成的订单号,金额,付款人的openid JSONObject payJson = WXPayUtil.buildPayJson(wxPay.getDescription(), uuid, Integer.valueOf(payAmount), openid); String prepayId = WXPayUtil.pay(payJson.toJSONString()); if (StringUtils.isNotBlank(prepayId)){ Long time = WXPayUtil.getTime(); //生成签名的工具类 String sign = WXPayUtil.getSign(time,uuid,"prepay_id=" + prepayId); //封装的给前端用于唤起小程序支付的参数 AppletsPay appletsPay = new AppletsPay(); appletsPay.setTimestamp(time); appletsPay.setNonceStr(uuid); appletsPay.setPrepayId(prepayId); appletsPay.setPaySign(sign); if (ObjectUtils.isNotEmpty(appletsPay)) return ResultData.success(appletsPay); } return ResultData.fail(); }
支付回调(支付和退款写在一起)
@ResponseBody @PostMapping(value = "/callBack") public WxResponse wxPayCallback(HttpServletRequest request, HttpServletResponse response) throws Exception { try { Gson gson =new Gson(); String body = WXPayUtil.readData(request); String serialNumber = request.getHeader("Wechatpay-Serial"); String nonce = request.getHeader("Wechatpay-Nonce"); String timestamp = request.getHeader("Wechatpay-Timestamp"); String signature = request.getHeader("Wechatpay-Signature"); Map<String,String> map =new HashMap<>(); // 构建request,传入必要参数 NotificationRequest Nrequest = new NotificationRequest.Builder().withSerialNumber(serialNumber) .withNonce(nonce) .withTimestamp(timestamp) .withSignature(signature) .withBody(body) .build(); //WechatPayUtils.getVerifier()就是获取上面的验签器,这只是我定义的一个方法来获取而已,你要怎样获取都行。构造验签器方法跟上面还是一模一样. NotificationHandler handler = new NotificationHandler(WXPayUtil.getVerifier(), WXPayUtil.v3Key.getBytes(StandardCharsets.UTF_8)); //WechatPayConfig.v3Key 也就是APIV3key //JSON.parseObject,是将Json字符串转化为相应的对象;JSON.toJSONString则是将对象转化为Json字符串.用 Gson.toJson也行 try { // 验签和解析请求体 Notification notification = handler.parse(Nrequest); // 从notification中获取解密报文。 String plainText = notification.getDecryptData(); //将密文转为map ,之后处理业务逻辑 Map resultMap =gson.fromJson(plainText,HashMap.class); //支付成功的处理 if (ObjectUtils.isNotEmpty(resultMap.get("trade_state")) && "SUCCESS".equals(resultMap.get("trade_state").toString())){ //支付成功的业务逻辑 } //退款成功的处理 if (ObjectUtils.isNotEmpty(resultMap.get("refund_status")) && "SUCCESS".equals(resultMap.get("refund_status").toString())){ //退款成功的业务逻辑 } //成功应答 WxResponse wxResponse = new WxResponse(); wxResponse.setCode("SUCCESS"); wxResponse.setMessage("成功"); return wxResponse; } catch (Exception e) { e.printStackTrace(); //应答失败 WxResponse wxResponse = new WxResponse(); wxResponse.setCode("FAIL"); wxResponse.setMessage("出错了"); return wxResponse; } }catch (Exception e){ WxResponse wxResponse = new WxResponse(); wxResponse.setCode("SUCCESS"); wxResponse.setMessage("成功"); return wxResponse; } } @Data class WxResponse{ private String code; private String message; }
微信支付工具类
public class WXPayUtil { // 商户号 后台获取 private static final String mchId = ""; //appid 后台获取 public static final String appid = ""; // v3Key 后台自己设定 public static final String v3Key = ""; // 商户证书序列号 后台获取 private static final String mchSerialNo = ""; // 支付链接 private static final String pay_url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; // 退款链接 private static final String back_url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; // 你的商户私钥 去掉begin头和end结尾 还有\n换行符 private static final String privateKey = ""; // 你的微信支付平台证书 可不去掉多余信息 private static final String certificate = ""; private static CloseableHttpClient httpClient; public static void setup() { PrivateKey key = PemUtil.loadPrivateKey(privateKey); X509Certificate wechatPayCertificate = PemUtil.loadCertificate( new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8))); ArrayList<X509Certificate> listCertificates = new ArrayList<>(); listCertificates.add(wechatPayCertificate); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(mchId,mchSerialNo, key) .withWechatPay(listCertificates); httpClient = builder.build(); } /** *wxMchid商户号 *wxCertno证书编号 *wxCertPath证书地址 *wxPaternerKey v3秘钥 *pay_url jsapi下单地址 *body buildWxJsApiPayJson返回的json.toString */ public static String pay(String body) { if (httpClient == null) { setup(); } HttpPost httpPost = new HttpPost(pay_url); httpPost.addHeader("Content-Type","application/json;chartset=utf-8"); httpPost.addHeader("Accept", "application/json"); try{ if(StringUtils.isBlank(body)){ throw new IllegalArgumentException("data参数不能为空"); } StringEntity stringEntity = new StringEntity(body,"utf-8"); httpPost.setEntity(stringEntity); // 直接执行execute方法,官方会自动处理签名和验签,并进行证书自动更新 HttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); if(httpResponse.getStatusLine().getStatusCode() == 200){ String jsonResult = EntityUtils.toString(httpEntity); JSONObject jsonObject = JSONObject.parseObject(jsonResult); return jsonObject.getString("prepay_id"); }else{ System.err.println("微信支付错误信息"+EntityUtils.toString(httpEntity)); } }catch (Exception e){ e.printStackTrace(); } return null; } /** * 构造微信JsApi付款的json * @param description * @param out_trade_no * @param amount * @return */ public static JSONObject buildPayJson(String description , String out_trade_no , Integer amount, String openId){ //订单金额json JSONObject amountJson = new JSONObject(); amountJson.put("total",amount); amountJson.put("currency","CNY"); //支付者json JSONObject payerJson = new JSONObject(); payerJson.put("openid",openId); //基础信息json JSONObject json = new JSONObject(); json.put("appid",appid); json.put("mchid",mchId); json.put("description",description); json.put("out_trade_no",out_trade_no); json.put("notify_url","https://域名/callBack"); json.put("amount",amountJson); json.put("payer",payerJson); return json; } public static String buildBackJson(String orderId,String refundId ,Integer total , Integer refund){ Map<String, Object> map = new HashMap<>(); map.put("out_trade_no",orderId); map.put("out_refund_no",refundId); map.put("reason","退款理由 可参数参入"); map.put("notify_url","https://域名/callBack"); map.put("funds_account","AVAILABLE"); Map<String, Object> amounts = new HashMap<>(); amounts.put("refund",refund); amounts.put("total",total); amounts.put("currency","CNY"); map.put("amount",amounts); Gson gson = new Gson(); String json = gson.toJson(map); return json; } public static JSONObject back(String body) { if (httpClient == null) { setup(); } HttpPost httpPost = new HttpPost(back_url); httpPost.addHeader("Content-Type","application/json;chartset=utf-8"); httpPost.addHeader("Accept", "application/json"); try{ if(StringUtils.isBlank(body)){ throw new IllegalArgumentException("data参数不能为空"); } StringEntity stringEntity = new StringEntity(body,"utf-8"); httpPost.setEntity(stringEntity); // 直接执行execute方法,官方会自动处理签名和验签,并进行证书自动更新 HttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); if(httpResponse.getStatusLine().getStatusCode() == 200){ String jsonResult = EntityUtils.toString(httpEntity); JSONObject jsonObject = JSONObject.parseObject(jsonResult); return jsonObject; }else{ System.err.println("微信支付错误信息"+EntityUtils.toString(httpEntity)); } }catch (Exception e){ e.printStackTrace(); } return null; } // 获取签名 ========================================================= // 签名 private static String sign(byte[] message) throws Exception { Signature sign = Signature.getInstance("SHA256withRSA"); PrivateKey key = PemUtil.loadPrivateKey(privateKey); sign.initSign(key); sign.update(message); return Base64.getEncoder().encodeToString(sign.sign()); } public static String getUUID(){ return UUID.randomUUID().toString().replaceAll("-",""); } public static Long getTime(){ return System.currentTimeMillis() / 1000; } public static String readData(HttpServletRequest request) { BufferedReader br = null; try { StringBuilder result = new StringBuilder(); br = request.getReader(); for (String line; (line = br.readLine()) != null; ) { if (result.length() > 0) { result.append("\n"); } result.append(line); } return result.toString(); } catch (IOException e) { throw new RuntimeException(e); } finally { if (br != null) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static Verifier getVerifier() throws Exception { // 获取证书管理器实例 CertificatesManager certificatesManager = CertificatesManager.getInstance(); // 向证书管理器增加需要自动更新平台证书的商户信息 certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, PemUtil.loadPrivateKey(privateKey))) , v3Key.getBytes(StandardCharsets.UTF_8)); // 从证书管理器中获取verifier Verifier verifier = certificatesManager.getVerifier(mchId); return verifier; } /** * 作用:使用字段appId、timeStamp、nonceStr、package计算得出的签名值 * 场景:根据微信统一下单接口返回的 prepay_id 生成调启支付所需的签名值 * @param timestamp * @param nonceStr * @param pack package * @return * @throws Exception */ public static String getSign(long timestamp, String nonceStr, String pack){ String message = buildMessage(timestamp, nonceStr, pack); String paySign= null; try { paySign = sign(message.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } return paySign; } private static String buildMessage(long timestamp, String nonceStr, String pack) { return appid + "\n" + timestamp + "\n" + nonceStr + "\n" + pack + "\n"; } }
tips!!!!!!!!!!!!!!!!
certificate 获取方式按下列步骤
(1)https://github.com/EliasZzz/CertificateDownloader/releases这里下载那个jar包, 2022-7-22版本为1.2.0
(2)执行:“java -jar CertificateDownloader完整包.jar -f 商户私钥文件路径 -k v3密钥 -m 商户号 -o 证书保存路径 -s 商户证书序列号”就行了。
商户私钥文件路径”是账号中心->API安全->API证书中设置并下载的证书(就是其中的apiclient_key.pem,下载还会获得apiclient_cert.pem,我之前把这个当做支付证书了,其实不是,apiclient_cert.pem这用不着)
执行完了是个类似wechatpay_****************.pem的文件。
最后补上相关版本信息
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version> </dependency> <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.15.2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> </dependency>