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(); } }