1、准備支付所需配置
import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * 微信配置類 */ @Configuration @Data public class WxpayConfig { /** * 移動應用appid */ @Value("${wxpay.appid}") private String appId; /** * 移動應用秘鑰 */ @Value("${wxpay.appsecret}") private String appsecret; /** * 公眾號appid */ @Value("${wxpay.officialcAcounts.appid}") private String officialcAcountsAppId; /** * 公眾號秘鑰 */ @Value("${wxpay.officialcAcounts.appsecret}") private String officialcAcountsAppsecret; /** * 商戶號id */ @Value("${wxpay.mer_id}") private String mchId; /** * 支付key */ @Value("${wxpay.key}") private String key; /** * 微信支付回調url */ @Value("${wxpay.callback}") private String payCallbackUrl; /** * 統一下單url */ private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 退款地址 */ private static final String REFUND_URL ="https://api.mch.weixin.qq.com/secapi/pay/refund"; public static String getUnifiedOrderUrl() { return UNIFIED_ORDER_URL; } public static String getRefundUrl() { return REFUND_URL; } }
2、APP支付
/** * 手機端調取APP支付 返回調起微信app所需的參數 */ @GetMapping("/mobilePayment") public APIResult<WxPayReqVo> mobilePayment(@RequestParam(value = "orderId") String orderId, HttpServletRequest request, HttpServletResponse response) throws Exception { APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId); if (result.getSuccess()) { CourseOrderVo courseOrderVo = result.getData(); //查看是否作廢 Date orderInvalidDate = courseOrderVo.getOrderInvalidDate(); if (new Date().getTime() > orderInvalidDate.getTime()) { //已過期 CourseOrderDTO courseOrderDTO = new CourseOrderDTO(); courseOrderDTO.setId(courseOrderVo.getId()); courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD); productService.updateCourseOrder(courseOrderDTO); return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "訂單已過期,請重新下單")); } //查看是否已經在微信平台下單 APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId()); if (wxPayReqVoAPIResult.getSuccess()) { return APIResult.ok(wxPayReqVoAPIResult.getData()); } //1、調取統一下單方法 獲取參數 String orderStr = unifiedOrder(courseOrderVo, "APP", "", request, response); //商戶服務器生成支付訂單,先調用統一下單API(詳見第7節)生成預付單,獲取到prepay_id后將參數再次簽名傳輸給APP發起支付。以下是調起微信支付的關鍵代碼: if (orderStr.indexOf("SUCCESS") != -1) { //2、xml轉map (WXPayUtil工具類) Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr); System.out.println(unifiedOrderMap.toString()); String prepayId = unifiedOrderMap.get("prepay_id"); if (!StringUtils.isEmpty(prepayId)) { String uuid = UUIDUtil.getUUID(); //本來生成的時間戳是13位,但是ios必須是10位,所以截取了一下 String timeStamp = String.valueOf(System.currentTimeMillis()).substring(0, 10); SortedMap<String, String> parameterMap2 = new TreeMap<String, String>(); parameterMap2.put("appid", wxpayConfig.getAppId()); parameterMap2.put("partnerid", wxpayConfig.getMchId()); parameterMap2.put("prepayid", prepayId); parameterMap2.put("package", "Sign=WXPay"); parameterMap2.put("noncestr", uuid); parameterMap2.put("timestamp", timeStamp); String sign = WXPayUtil.createSign(parameterMap2, wxpayConfig.getKey()); parameterMap2.put("sign", sign); WxPayReqDTO wxPayReqDTO = new WxPayReqDTO(); wxPayReqDTO.setCourseOrderId(courseOrderVo.getId()); wxPayReqDTO.setAppId(wxpayConfig.getAppId()); wxPayReqDTO.setPartnerId(wxpayConfig.getMchId()); wxPayReqDTO.setPrepayId(prepayId); wxPayReqDTO.setNonceStr(uuid); wxPayReqDTO.setTimeStamp(timeStamp); wxPayReqDTO.setPackageValue("Sign=WXPay"); wxPayReqDTO.setPayType("APP"); wxPayReqDTO.setSign(sign); String param = JSON.toJSONString(parameterMap2); System.out.println("調起微信APP所需的參數:" + param); return productService.createWxPayReq(wxPayReqDTO); } } return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失敗")); } return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "訂單信息不存在")); }
3、H5支付(待驗證)
/** * h5支付 返回調起微信app所需的參數 */ @GetMapping("/H5Payment") public APIResult<String> H5Payment(@RequestParam(value = "orderId") String orderId, HttpServletRequest request, HttpServletResponse response) throws Exception { APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId); if (result.getSuccess()) { CourseOrderVo courseOrderVo = result.getData(); //查看是否作廢 Date orderInvalidDate = courseOrderVo.getOrderInvalidDate(); if (new Date().getTime() > orderInvalidDate.getTime()) { //已過期 CourseOrderDTO courseOrderDTO = new CourseOrderDTO(); courseOrderDTO.setId(courseOrderVo.getId()); courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD); productService.updateCourseOrder(courseOrderDTO); return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "訂單已過期,請重新下單")); } //查看是否已經在微信平台下單 APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId()); if (wxPayReqVoAPIResult.getSuccess()) { return APIResult.ok(wxPayReqVoAPIResult.getData()); } //1、調取統一下單方法 獲取參數 String orderStr = unifiedOrder(courseOrderVo, "MWEB", "", request, response); //以下內容是返回前端頁面的json數據 String mweb_url = "";//跳轉鏈接 if (orderStr.indexOf("SUCCESS") != -1) { //2、xml轉map (WXPayUtil工具類) Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr); System.out.println(unifiedOrderMap.toString()); mweb_url = (String) unifiedOrderMap.get("mweb_url"); //支付完返回瀏覽器跳轉的地址,如跳到查看訂單頁面 String redirect_url = "http://edappstest.vtronedu.com/#/complete"; String redirect_urlEncode = URLEncoder.encode(redirect_url, "utf-8");//對上面地址urlencode mweb_url = mweb_url + "&redirect_url=" + redirect_urlEncode;//拼接返回地址 return APIResult.ok(mweb_url); } else { return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失敗")); } } return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "訂單信息不存在")); }
4、JSAPI支付
JSAPI支付需要獲取微信的openid,前端獲取code(參考https://www.cnblogs.com/ganws/p/11139149.html),回調后端的接口再獲取openid
@GetMapping("/getOpenId") public APIResult<String> getOpenId(@RequestParam(value = "code") String code) { log.info("code====" + code); //獲取openId String appid = wxpayConfig.getOfficialcAcountsAppId(); // appid String secret = wxpayConfig.getOfficialcAcountsAppsecret();//秘鑰 String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code"; String s = HttpUtil.sendPost(url, ""); log.info("getOpenId請求結果===" + s); JSONObject jsonObject = JSONObject.parseObject(s); log.info("轉為JSONObject===" + jsonObject.toString()); String openid = jsonObject.getString("openid"); log.info("openid====" + openid); return APIResult.ok(openid); }
/** * 微信中的網頁 調起微信 */ @GetMapping("/JSAPIPayment") public APIResult<WxPayReqVo> JSAPIPayment(@RequestParam(value = "orderId") String orderId, @RequestParam(value = "openId") String openId, HttpServletRequest request, HttpServletResponse response) throws Exception { APIResult<CourseOrderVo> result = productService.findCourseOrderById(orderId); if (result.getSuccess()) { CourseOrderVo courseOrderVo = result.getData(); //查看是否作廢 Date orderInvalidDate = courseOrderVo.getOrderInvalidDate(); if (new Date().getTime() > orderInvalidDate.getTime()) { //已過期 CourseOrderDTO courseOrderDTO = new CourseOrderDTO(); courseOrderDTO.setId(courseOrderVo.getId()); courseOrderDTO.setOrderStatus(OrderStatusEnum.DISCARD); productService.updateCourseOrder(courseOrderDTO); return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "訂單已過期,請重新下單")); } //查看是否已經在微信平台下單 APIResult<WxPayReqVo> wxPayReqVoAPIResult = productService.findWxPayReqByOrderId(courseOrderVo.getId()); if (wxPayReqVoAPIResult.getSuccess()) { return APIResult.ok(wxPayReqVoAPIResult.getData()); } //1、調取統一下單方法 獲取參數 String orderStr = unifiedOrder(courseOrderVo, "JSAPI", openId, request, response); //商戶服務器生成支付訂單,先調用統一下單API(詳見第7節)生成預付單,獲取到prepay_id后將參數再次簽名傳輸給APP發起支付。以下是調起微信支付的關鍵代碼: if (orderStr.indexOf("SUCCESS") != -1) { //2、xml轉map (WXPayUtil工具類) Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr); System.out.println(unifiedOrderMap.toString()); String prepayId = unifiedOrderMap.get("prepay_id"); if (!StringUtils.isEmpty(prepayId)) { String uuid = UUIDUtil.getUUID(); //本來生成的時間戳是13位,但是ios必須是10位,所以截取了一下 String timeStamp = String.valueOf(System.currentTimeMillis()).substring(0, 10); SortedMap<String, String> parameterMap2 = new TreeMap<String, String>(); parameterMap2.put("appId", wxpayConfig.getOfficialcAcountsAppId()); parameterMap2.put("timeStamp", timeStamp); parameterMap2.put("nonceStr", uuid); parameterMap2.put("signType", "MD5"); parameterMap2.put("package", "prepay_id=" + prepayId); String sign = WXPayUtil.createSign(parameterMap2, wxpayConfig.getKey()); parameterMap2.put("sign", sign); WxPayReqDTO wxPayReqDTO = new WxPayReqDTO(); wxPayReqDTO.setCourseOrderId(courseOrderVo.getId()); wxPayReqDTO.setAppId(wxpayConfig.getOfficialcAcountsAppId()); wxPayReqDTO.setPartnerId(wxpayConfig.getMchId()); wxPayReqDTO.setPrepayId(prepayId); wxPayReqDTO.setPackageValue("prepay_id=" + prepayId); wxPayReqDTO.setNonceStr(uuid); wxPayReqDTO.setTimeStamp(timeStamp); wxPayReqDTO.setPayType("JSAPI"); wxPayReqDTO.setSign(sign); String param = JSON.toJSONString(parameterMap2); System.out.println("調起微信APP所需的參數:" + param); return productService.createWxPayReq(wxPayReqDTO); } } return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, "支付失敗")); } return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "訂單信息不存在")); }
5、統一下單接口
/** * 統一下單方法 * * @return */ public String unifiedOrder(CourseOrderVo courseOrderVo, String tradeType, String openId, HttpServletRequest request, HttpServletResponse response) throws Exception { //4.1、生成簽名 按照開發文檔需要按字典排序,所以用SortedMap SortedMap<String, String> params = new TreeMap<>(); params.put("appid", wxpayConfig.getAppId()); //公眾賬號ID params.put("mch_id", wxpayConfig.getMchId()); //商戶號 params.put("nonce_str", UUIDUtil.getUUID()); //隨機字符串 params.put("body", "課程訂單"); // 商品描述 params.put("out_trade_no", courseOrderVo.getCourseOrderNo());//商戶訂單號,商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|* 且在同一個商戶號下唯一 BigDecimal bigDecimal = courseOrderVo.getFinalPrice(); String finalPrice = String.valueOf(bigDecimal); double d = Double.valueOf(finalPrice) * 100; String totalFee = String.valueOf(d); params.put("total_fee", "1");//標價金額 分 params.put("spbill_create_ip", HttpUtil.getClientIp(request)); params.put("notify_url", wxpayConfig.getPayCallbackUrl()); //通知地址 params.put("trade_type", tradeType); //交易類型 JSAPI 公眾號支付 NATIVE 掃碼支付 APP APP支付 //4.2、sign簽名 具體規則:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3 String sign = WXPayUtil.createSign(params, wxpayConfig.getKey()); if (tradeType.equals("MWEB") || tradeType.equals("JSAPI")) { params.put("appid", wxpayConfig.getOfficialcAcountsAppId()); params.put("openid", openId); sign = WXPayUtil.createSign(params, wxpayConfig.getKey()); } params.put("sign", sign); //4.3、map轉xml ( WXPayUtil工具類) String payXml = WXPayUtil.mapToXml(params); //4.4、回調微信的統一下單接口(HttpUtil工具類) String orderStr = HttpUtil.sendPost(wxpayConfig.getUnifiedOrderUrl(), payXml); System.out.println(orderStr); return orderStr; }
6、支付完成微信回調通知接口 接口需對外開放
/** * 微信支付回調 * 該鏈接是通過【統一下單API】中提交的參數notify_url設置,如果鏈接無法訪問,商戶將無法接收到微信通知。 * notify_url不能有參數,外網可以直接訪問,不能有訪問控制(比如必須要登錄才能操作)。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” * 支付完成后,微信會把相關支付結果和用戶信息發送給商戶,商戶需要接收處理,並返回應答。 * 對后台通知交互時,如果微信收到商戶的應答不是成功或超時,微信認為通知失敗,微信會通過一定的策略定期重新發起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。 * (通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒) * 注意:同樣的通知可能會多次發送給商戶系統。商戶系統必須能夠正確處理重復的通知。 * 推薦的做法是,當收到通知進行處理時,首先檢查對應業務數據的狀態,判斷該通知是否已經處理過,如果沒有處理過再進行處理,如果處理過直接返回結果成功。在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行並發控制,以避免函數重入造成的數據混亂。 * 特別提醒:商戶系統對於支付結果通知的內容一定要做簽名驗證,防止數據泄漏導致出現“假通知”,造成資金損失。 */ @RequestMapping(value = "/callback") public String orderCallback(HttpServletRequest request, HttpServletResponse response) throws Exception { InputStream inputStream = request.getInputStream(); //BufferedReader是包裝設計模式,性能更搞 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); StringBuffer sb = new StringBuffer(); //1、將微信回調信息轉為字符串 String line; while ((line = in.readLine()) != null) { sb.append(line); } in.close(); inputStream.close(); log.info("回調返回" + sb.toString()); //2、將xml格式字符串格式轉為map集合 Map<String, String> callbackMap = WXPayUtil.xmlToMap(sb.toString()); log.info("xml轉map" + callbackMap.toString()); //3、轉為有序的map SortedMap<String, String> sortedMap = WXPayUtil.getSortedMap(callbackMap); Map<String, String> return_data = new HashMap<String, String>(); //4、判斷簽名是否正確 if (WXPayUtil.isCorrectSign(sortedMap, wxpayConfig.getKey())) { //5、判斷回調信息是否成功 if ("SUCCESS".equals(sortedMap.get("result_code"))) { //獲取商戶訂單號 //商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|* 且在同一個商戶號下唯一 String outTradeNo = sortedMap.get("out_trade_no"); log.info("內部訂單號" + outTradeNo); //6、數據庫查找訂單,如果存在則根據訂單號更新該訂單 APIResult<CourseOrderVo> result = courseOrderService.findCourseOrderBySerialNo(outTradeNo); if (result.getSuccess()) { CourseOrderVo courseOrderVo = result.getData(); //修改訂單狀態為支付 courseOrderService.payment(courseOrderVo.getId()); //下發購買成功短信 return_data.put("return_code", "SUCCESS"); return_data.put("return_msg", "OK"); return WXPayUtil.mapToXml(return_data); } } } //7、通知微信訂單處理失敗 return_data.put("return_code", "FAIL"); return_data.put("return_msg", "return_code不正確"); return WXPayUtil.mapToXml(return_data); }
7、退款
/** * 申請退款 * * @return * @throws Exception */ @GetMapping("/refund") public APIResult<String> wxPayRefund(@RequestParam(value = "orderId") String orderId, @RequestParam(value = "refundReason", required = false) String refundReason) throws Exception { APIResult<CourseOrderVo> result = courseOrderService.findCourseOrderById(orderId); if (result.getSuccess()) { CourseOrderVo courseOrderVo = result.getData(); String nonceStr = UUIDUtil.getUUID();//生成32位隨機字符串 SortedMap<String, String> parameters = new TreeMap<String, String>(); parameters.put("appid", wxpayConfig.getAppId()); //公眾賬號ID parameters.put("mch_id", wxpayConfig.getMchId()); //商戶號 parameters.put("nonce_str", nonceStr); parameters.put("out_trade_no", courseOrderVo.getCourseOrderNo()); //parameters.put("transaction_id", transaction_id); parameters.put("out_refund_no", nonceStr); parameters.put("fee_type", "CNY"); BigDecimal bigDecimal = courseOrderVo.getFinalPrice(); String finalPrice = String.valueOf(bigDecimal); double d = Double.valueOf(finalPrice) * 100; String totalFee = String.valueOf(d); parameters.put("total_fee", "1"); parameters.put("refund_fee", "1");//部退款金額 parameters.put("sign", WXPayUtil.createSign(parameters, wxpayConfig.getKey())); String data = WXPayUtil.mapToXml(parameters); String doRefund = doRefund(wxpayConfig.getMchId(), wxpayConfig.getRefundUrl(), data); Map<String, String> returnMap = WXPayUtil.xmlToMap(doRefund); if (doRefund.indexOf("SUCCESS") != -1) { //修改訂單狀態 courseOrderService.refund(courseOrderVo.getId(), refundReason); //發送通知 return APIResult.ok(returnMap.get("return_msg")); } else { return APIResult.error(new BaseCode(APICode._C_OPERATE_ERROR, returnMap.get("return_msg"))); } } return APIResult.error(new BaseCode(APICode._C_NOT_EXISTS, "訂單信息不存在")); } public String doRefund(String mchId, String url, String data) throws Exception { /** * 注意PKCS12證書 是從微信商戶平台-》賬戶設置-》 API安全 中下載的 */ KeyStore keyStore = KeyStore.getInstance("PKCS12"); //這里自行實現我是使用數據庫配置將證書上傳到了服務器可以使用 FileInputStream讀取本地文件 Resource resource = resourceLoader.getResource("classpath:apiclient_cert.p12"); InputStream inputStream = resource.getInputStream(); try { //這里寫密碼..默認是你的MCHID keyStore.load(inputStream, mchId.toCharArray()); } finally { inputStream.close(); } SSLContext sslcontext = SSLContexts.custom() //這里也是寫密碼的 .loadKeyMaterial(keyStore, mchId.toCharArray()) .build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); try { HttpPost httpost = new HttpPost(url); httpost.setEntity(new StringEntity(data, "UTF-8")); CloseableHttpResponse response = httpclient.execute(httpost); try { HttpEntity entity = response.getEntity(); //接受到返回信息 String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8"); EntityUtils.consume(entity); return jsonStr; } finally { response.close(); } } finally { httpclient.close(); } }