支付版本更新 根据资金流向使用V2或V3服务商版本支付
5个文件已删除
30个文件已添加
53个文件已修改
| | |
| | | <name>账户</name> |
| | | <description>账户</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-java-core</artifactId> |
| | | <version>0.2.12</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>joda-time</groupId> |
| | | <artifactId>joda-time</artifactId> |
| | | <version>2.10.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <!--日志处理--> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>com.github.binarywang</groupId>--> |
| | |
| | | package com.dsh.account.controller; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.dsh.account.dto.BindDto; |
| | |
| | | import com.dsh.account.util.ResultUtil; |
| | | import com.dsh.account.util.TokenUtil; |
| | | import com.dsh.account.util.ToolUtil; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | |
| | | |
| | | |
| | | /** |
| | | * 购买年度会员支付微信回调V3版本回调 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/appUser/addVipPaymentWeChatCallback1") |
| | | public void addVipPaymentWeChatCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = vipPaymentService.addVipPaymentCallback(out_trade_no, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 购买年度会员支付微信回调 |
| | | * |
| | | * @param request |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 购买年度会员支付支付宝回调 |
| | |
| | | package com.dsh.account.controller; |
| | | |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.dsh.account.service.RechargeRecordsService; |
| | | import com.dsh.account.service.TAppUserService; |
| | | import com.dsh.account.service.TStudentService; |
| | | import com.dsh.account.util.PayMoneyUtil; |
| | | import com.dsh.account.util.ResultUtil; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.Map; |
| | |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | @PostMapping("/base/coursePackage/wechatPaymentCallback1") |
| | | public void weChatCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = tstuService.insertVipPaymentCallback(out_trade_no, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | @PostMapping("/base/coursePackage/wechatPaymentCallback") |
| | | public void weChatCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 充值玩湃币支付宝支付回调接口 |
| | | */ |
| | |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | @PostMapping("/base/recharge/wechatRechargeCallback1") |
| | | public void wechatRechargeCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = recordsService.addRechargeCallbackPay(out_trade_no, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | @PostMapping("/base/recharge/wechatRechargeCallback") |
| | | public void wechatRechargeCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 兑换商品支付的微信回调 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/pointMer/exchangeGoodPaymentWeChatCallback") |
| | | public void exchangeGoodPaymentWeChatCallback(HttpServletRequest request, HttpServletResponse response) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 兑换商品支付的微信回调V3版本 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/pointMer/exchangeGoodPaymentWeChatCallback1") |
| | | public void exchangeGoodPaymentWeChatCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = tappService.exchangeAddPaymentCallback(out_trade_no, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 兑换商品支付的支付宝回调 |
| | |
| | | package com.dsh.account.controller; |
| | | |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.dsh.account.entity.TAppUser; |
| | | import com.dsh.account.feignclient.activity.model.IntegralCommodity; |
| | | import com.dsh.account.feignclient.other.SysLogClient; |
| | |
| | | import com.dsh.account.util.ResultUtil; |
| | | import com.dsh.account.util.TokenUtil; |
| | | import com.dsh.account.util.ToolUtil; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * V3版本回调 |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/coupon/weChatPaymentCouponCallback1") |
| | | public void weChatPaymentCouponCallback1(HttpServletRequest request, HttpServletResponse response){ |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String code = jsonInfo.getString("out_trade_no"); |
| | | String trade_no = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = tauService.paymentCouponCallback(code, trade_no); |
| | | if(resultUtil.getCode() == 200){ |
| | | PrintWriter out = response.getWriter(); |
| | | out.print("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/base/coupon/weChatPaymentCouponCallback") |
| | | public void weChatPaymentCouponCallback(HttpServletRequest request, HttpServletResponse response){ |
| | | try { |
| | | System.err.println("进入回调"); |
| | | Map<String, String> map = payMoneyUtil.weixinpayCallback(request); |
| | | if(null != map){ |
| | | String code = map.get("out_trade_no"); |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/base/coupon/aliPaymentCouponCallback") |
| | |
| | | // 根据运营商id获取微信商户号 |
| | | @PostMapping("/base/getmerchantNumberByOperatorId/{id}") |
| | | String getmerchantNumberByOperatorId(@PathVariable("id")Integer id); |
| | | // 根据运营商id获取支付宝商户号 |
| | | @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}") |
| | | String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id); |
| | | |
| | | @PostMapping("/store/queryByStoreId") |
| | | OperatorUser queryByStoreId(Integer storeId); |
| | |
| | | import com.dsh.account.model.vo.userBenefitDetail.RechargesDetail; |
| | | import com.dsh.account.service.RechargeRecordsService; |
| | | import com.dsh.account.util.*; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.data.domain.Page; |
| | | import org.springframework.data.domain.PageImpl; |
| | |
| | | "/base/recharge/alipayRechargeCallback"); |
| | | return alipay; |
| | | } |
| | | |
| | | private ResultUtil WeChatPayment(String code, BigDecimal amount) throws Exception { |
| | | // |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("玩湃币充值", "", code, amount.toString(), |
| | | "/base/recharge/wechatRechargeCallback", "APP", ""); |
| | | // ResultUtil weixinpay = payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"玩湃币充值", code, |
| | | // "/base/recharge/wechatRechargeCallback1", |
| | | // Long.valueOf(amount.toString())); |
| | | return weixinpay; |
| | | |
| | | } |
| | |
| | | import com.dsh.account.service.UserIntegralChangesService; |
| | | import com.dsh.account.util.*; |
| | | import com.dsh.account.util.akeylogin.Md5Util; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import com.dsh.account.vo.GroupCityInfoVO; |
| | | import com.dsh.account.vo.entity.DayData; |
| | | import com.dsh.account.vo.entity.MonthData; |
| | |
| | | userCouponPaymentClient.addUserCouponPayment(userCouponPayment); |
| | | try { |
| | | if(exchangeType.getPayType() == 1){ |
| | | String temp = "0"; |
| | | if (coupon.getPublisherType()!=null && coupon.getPublisherType() != 2){ |
| | | // 做分账 |
| | | temp = "1"; |
| | | } |
| | | return weChatPaymentCoupon("购买优惠券-"+temp, userCouponPayment); |
| | | return weChatPaymentCoupon("购买优惠券", userCouponPayment); |
| | | } |
| | | if(exchangeType.getPayType() == 2){ |
| | | return aliPaymentCoupon("购买优惠券", userCouponPayment); |
| | |
| | | } |
| | | case 2: |
| | | if (merchandise.getType() == 2) { |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, returnId); |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, merchandise.getId()); |
| | | } else { |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, returnId); |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, merchandise.getId()); |
| | | } |
| | | default: |
| | | break; |
| | |
| | | } |
| | | case 2: |
| | | if (merchandise.getType() == 2) { |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, returnId); |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, merchandise.getId()); |
| | | } else { |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, returnId); |
| | | return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, merchandise.getId()); |
| | | } |
| | | default: |
| | | break; |
| | |
| | | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 优惠券微信支付 |
| | | * @param body |
| | |
| | | Double cash = userCouponPayment.getCash(); |
| | | Integer couponId = userCouponPayment.getCouponId(); |
| | | Coupon coupon = userConponClient.queryCouponById(couponId); |
| | | String temp ="0"; |
| | | if (coupon.getPublisherType()!=null&&coupon.getPublisherType()!=2){ |
| | | temp = "1"; |
| | | } |
| | | List<Integer> couponStoreIds = userConponClient.getCouponStoreIds(couponId); |
| | | System.err.println("购买优惠券"); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(body+"-"+temp, "", code, cash.toString(), |
| | | "/base/coupon/weChatPaymentCouponCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code); |
| | | if (userCouponPayment1.getStatus() == 2) { |
| | | if (coupon!=null){ |
| | | if (!couponStoreIds.isEmpty()&&!couponStoreIds.contains(-1)){ |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(coupon.getCityManagerId()); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200 && userCouponPayment1.getStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | userCouponPayment1.setStatus(-1); |
| | | userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | userCouponPayment1.setStatus(2); |
| | | userCouponPayment1.setPaymentTime(new Date()); |
| | | userCouponPayment1.setOrderNumber(transaction_id); |
| | | userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | |
| | | UserCoupon userCoupon = new UserCoupon(); |
| | | userCoupon.setCouponId(userCouponPayment1.getCouponId()); |
| | | userCoupon.setUserId(userCouponPayment1.getUserId()); |
| | | userCoupon.setStatus(1); |
| | | userCoupon.setInsertTime(new Date()); |
| | | userConponClient.insertToAppuserCoupon(userCoupon); |
| | | |
| | | if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){ |
| | | //扣积分 |
| | | TAppUser user = appUserService.getById(userCouponPayment1.getUserId()); |
| | | user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue()); |
| | | appUserService.updateById(user); |
| | | } |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,"购买优惠券", |
| | | code,"/base/coupon/weChatPaymentCouponCallback1", |
| | | cash.toString()); |
| | | return weixinpay; |
| | | |
| | | }else{ |
| | | // 平台发布 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(body, "", code, cash.toString(), |
| | | "/base/coupon/weChatPaymentCouponCallback", "APP", ""); |
| | | return weixinpay; |
| | | } |
| | | } |
| | | return weixinpay; |
| | | |
| | | return ResultUtil.error("支付失败!"); |
| | | } |
| | | |
| | | |
| | |
| | | public ResultUtil aliPaymentCoupon(String body, UserCouponPayment userCouponPayment) throws Exception { |
| | | String code = userCouponPayment.getCode(); |
| | | Double cash = userCouponPayment.getCash(); |
| | | Integer couponId = userCouponPayment.getCouponId(); |
| | | Coupon coupon = userConponClient.queryCouponById(couponId); |
| | | List<Integer> couponStoreIds = userConponClient.getCouponStoreIds(couponId); |
| | | if (coupon!=null){ |
| | | if (!couponStoreIds.isEmpty()&&!couponStoreIds.contains(-1)){ |
| | | // 门店 向上查询运营商 |
| | | Store store = storeClient.queryStoreById(couponStoreIds.get(0)); |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(), |
| | | "/base/coupon/aliPaymentCouponCallback"); |
| | | return alipay; |
| | | }else{ |
| | | // 支付宝商户号 |
| | | String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | System.err.println("支付宝商户号"+s2); |
| | | |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(), |
| | | "/base/coupon/aliPaymentCouponCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | Thread.sleep(1000); |
| | | |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code); |
| | | if (userCouponPayment1.getStatus() == 2) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(code); |
| | | if (null != alipayTradeQueryResponse) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | |
| | | String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | userCouponPayment1.setStatus(-1); |
| | | userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | if ("TRADE_FINISHED".equals(s)) { |
| | | // todo 资金结算 |
| | | |
| | | } |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | userCouponPayment1.setStatus(2); |
| | | userCouponPayment1.setPaymentTime(new Date()); |
| | | userCouponPayment1.setOrderNumber(tradeNo); |
| | | userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | UserCoupon userCoupon = new UserCoupon(); |
| | | userCoupon.setCouponId(userCouponPayment1.getCouponId()); |
| | | userCoupon.setUserId(userCouponPayment1.getUserId()); |
| | | userCoupon.setStatus(1); |
| | | userCoupon.setInsertTime(new Date()); |
| | | userConponClient.insertToAppuserCoupon(userCoupon); |
| | | if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){ |
| | | //扣积分 |
| | | TAppUser user = appUserService.getById(userCouponPayment1.getUserId()); |
| | | user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue()); |
| | | appUserService.updateById(user); |
| | | } |
| | | payMoneyUtil.confirm(smid,code,tradeNo,cash.toString()); |
| | | break; |
| | | |
| | | } |
| | | |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | ResultUtil alipay = payMoneyUtil.alipay(s2, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(), |
| | | "/base/coupon/aliPaymentCouponCallback"); |
| | | return alipay; |
| | | } |
| | | } |
| | | }).start(); |
| | | }else { |
| | | // 平台发布 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(), |
| | | "/base/coupon/aliPaymentCouponCallback"); |
| | | return alipay; |
| | | } |
| | | } |
| | | return alipay; |
| | | // ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(), |
| | | // "/base/coupon/aliPaymentCouponCallback"); |
| | | // if (alipay.getCode() == 200) { |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // Thread.sleep(1000); |
| | | // |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code); |
| | | // if (userCouponPayment1.getStatus() == 2) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(code); |
| | | // if (null != alipayTradeQueryResponse) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | // String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | // |
| | | // String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // //有待支付的订单,这里不处理 |
| | | // userCouponPayment1.setStatus(-1); |
| | | // userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | // if ("TRADE_FINISHED".equals(s)) { |
| | | // // todo 资金结算 |
| | | // |
| | | // } |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // userCouponPayment1.setStatus(2); |
| | | // userCouponPayment1.setPaymentTime(new Date()); |
| | | // userCouponPayment1.setOrderNumber(tradeNo); |
| | | // userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1); |
| | | // UserCoupon userCoupon = new UserCoupon(); |
| | | // userCoupon.setCouponId(userCouponPayment1.getCouponId()); |
| | | // userCoupon.setUserId(userCouponPayment1.getUserId()); |
| | | // userCoupon.setStatus(1); |
| | | // userCoupon.setInsertTime(new Date()); |
| | | // userConponClient.insertToAppuserCoupon(userCoupon); |
| | | // if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){ |
| | | // //扣积分 |
| | | // TAppUser user = appUserService.getById(userCouponPayment1.getUserId()); |
| | | // user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue()); |
| | | // appUserService.updateById(user); |
| | | // } |
| | | //// payMoneyUtil.confirm(smid,code,tradeNo,cash.toString()); |
| | | // break; |
| | | // |
| | | // } |
| | | // |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return ResultUtil.error("支付失败"); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | Coupon coupon = ucponClient.queryCouponById(userCouponPayment1.getCouponId()); |
| | | // 创建一个两分钟后执行的线程 |
| | | new Thread(() -> { |
| | | try { |
| | | Thread.sleep(120000); |
| | | if (coupon!=null){ |
| | | if (coupon.getPublisherType()!=null&&coupon.getPublisherType()!=2){ |
| | | if (coupon.getPublisherType() == 1){ |
| | | System.err.println("进入分账逻辑"); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(coupon.getCityManagerId()); |
| | | System.err.println("分账比例:"+proportionByOperatorId); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(coupon.getCityManagerId()); |
| | | System.err.println("商户号"+s2); |
| | | System.err.println("分账比例"+bigDecimal1); |
| | | System.err.println("分账金额"+coupon.getCash().multiply(bigDecimal1)); |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(userCouponPayment1.getOrderNumber(), coupon.getCash().multiply(bigDecimal1), s2,""); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | } |
| | | |
| | | } |
| | | } else if (coupon.getPublisherType() == 3){ |
| | | // 门店 向上查询运营商 |
| | | Store store = storeClient.queryStoreById(coupon.getCityManagerId()); |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台不分账 |
| | | }else{ |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId()); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(userCouponPayment1.getOrderNumber(), coupon.getCash().multiply(bigDecimal1), s2,"购买优惠券分账"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | }else{ |
| | | System.err.println("分账成功"); |
| | | return; |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | }).start(); |
| | | |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private ResultUtil AlipayPayment(Integer type, BigDecimal cash, String code, Long returnId) { |
| | | private ResultUtil AlipayPayment(Integer type, BigDecimal cash, String code, Integer returnId) { |
| | | PointsMerchandise pointsMerchandise = mcClient.selectPointsMerchandiseById(returnId); |
| | | System.err.println("查询积分商品"+pointsMerchandise); |
| | | List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId()); |
| | | String name = (type == 1 ? "购买实体商品" : type == 2 ? "报名运动营": type == 3 ? "购买门票" : "购买优惠券"); |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid,name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | System.out.println("code:" + code); |
| | | List<UserPointsMerchandise> userPointsMerchandises = mcClient.queryUserPointMerchaseByCode(code); |
| | | System.out.println("list-----" + userPointsMerchandises); |
| | | if (userPointsMerchandises.get(0).getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | // ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | if (resultUtil.getCode().equals("10000") && userPointsMerchandises.get(0).getPayStatus() == 1) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | // Map<String, String> data1 = resultUtil.getData(); |
| | | // String s = data1.get("tradeStatus"); |
| | | // String tradeNo = data1.get("tradeNo"); |
| | | switch (type) { |
| | | case 1: |
| | | if (!integers.isEmpty()) { |
| | | Store store1 = storeClient.queryStoreById(integers.get(0)); |
| | | if (store1.getOperatorId() == null || store1.getOperatorId() == 0) { |
| | | // 平台门票 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } else { |
| | | String s2 = storeClient.getmerchantNumberAliByOperatorId(store1.getOperatorId()); |
| | | System.err.println("支付宝商户号" + s2); |
| | | |
| | | String tradeNo = resultUtil.getTradeNo(); |
| | | String tradeStatus = resultUtil.getTradeStatus(); |
| | | System.out.println("ssssss" + tradeStatus); |
| | | |
| | | if ("TRADE_SUCCESS".equals(tradeStatus)) { |
| | | for (UserPointsMerchandise userPointsMerchandise : userPointsMerchandises) { |
| | | userPointsMerchandise.setPayStatus(2); |
| | | userPointsMerchandise.setPaymentTime(new Date()); |
| | | userPointsMerchandise.setOrderNumber(tradeNo); |
| | | userPointsMerchandise.setCashPayType(2); |
| | | userPointsMerchandise.setInsertTime(null); |
| | | userPointsMerchandise.setUserId(null); |
| | | mcClient.updateDetailsUserPointMercase(userPointsMerchandise); |
| | | } |
| | | moneyOut(tradeNo, tradeNo); |
| | | break; |
| | | } |
| | | if ("TRADE_FINISHED".equals(tradeStatus)) { |
| | | // todo 资金结算 |
| | | payMoneyUtil.confirm(smid,code,tradeNo,cash.toString()); |
| | | break; |
| | | } |
| | | // if("REFUND".equals(tradeStatus) || "NOTPAY".equals(tradeStatus) || "CLOSED".equals(tradeStatus) || "REVOKED".equals(tradeStatus) || "PAYERROR".equals(tradeStatus) || num == 10){ |
| | | // mcClient.deletePaymentRecord(code); |
| | | // break; |
| | | // } |
| | | |
| | | if ("WAIT_BUYER_PAY".equals(tradeStatus)) { |
| | | num++; |
| | | } |
| | | } |
| | | if (!StringUtils.hasLength(s2)) { |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | } catch (Exception e) { |
| | | mcClient.deletePaymentRecord(code); |
| | | e.printStackTrace(); |
| | | ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } |
| | | }else{ |
| | | // 平台门票 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | |
| | | } |
| | | }).start(); |
| | | } |
| | | return alipay; |
| | | case 2: |
| | | // 判断课包属于平台还是运营商 |
| | | CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId()); |
| | | Store store = storeClient.queryStoreById(coursePackage.getStoreId()); |
| | | if (store.getOperatorId() == null || store.getOperatorId() == 0) { |
| | | // 平台课包 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } else { |
| | | String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | System.err.println("支付宝商户号" + s2); |
| | | |
| | | if (!StringUtils.hasLength(s2)) { |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } |
| | | case 3: |
| | | if (!integers.isEmpty()) { |
| | | Store store1 = storeClient.queryStoreById(integers.get(0)); |
| | | if (store1.getOperatorId() == null || store1.getOperatorId() == 0) { |
| | | // 平台门票 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } else { |
| | | String s2 = storeClient.getmerchantNumberAliByOperatorId(store1.getOperatorId()); |
| | | System.err.println("支付宝商户号" + s2); |
| | | if (!StringUtils.hasLength(s2)) { |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } |
| | | }else{ |
| | | // 平台门票 |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentAliCallback"); |
| | | return alipay; |
| | | } |
| | | } |
| | | return ResultUtil.error("获取支付失败!"); |
| | | |
| | | } |
| | | |
| | | public void moneyOut(String outNum, String tradeNo) throws AlipayApiException { |
| | |
| | | |
| | | PointsMerchandise pointsMerchandise = mcClient.selectPointsMerchandiseById(id); |
| | | System.err.println("查询积分商品"+pointsMerchandise); |
| | | // 分账表示 0否1是 |
| | | String temp = "0"; |
| | | List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId()); |
| | | |
| | | switch (type){ |
| | | case 1: |
| | | temp = "0"; |
| | | if (!integers.isEmpty()){ |
| | | Store store1 = storeClient.queryStoreById(integers.get(0)); |
| | | if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){ |
| | | // 平台门票 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", ""); |
| | | return weixinpay; |
| | | }else{ |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store1.getOperatorId()); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name, code, |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback1", |
| | | cash.toString()); |
| | | return weixinpay; |
| | | } |
| | | } |
| | | break; |
| | | case 2: |
| | | // 判断课包属于平台还是运营商 |
| | | CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId()); |
| | | Store store = storeClient.queryStoreById(coursePackage.getStoreId()); |
| | | if (store.getOperatorId() ==null || store.getOperatorId() == 0){ |
| | | // 平台课包 不分账 |
| | | // 平台课包 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", ""); |
| | | return weixinpay; |
| | | }else{ |
| | | temp = "1"; |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name, code, |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback1", |
| | | cash.toString()); |
| | | return weixinpay; |
| | | } |
| | | break; |
| | | case 3: |
| | | List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId()); |
| | | if (!integers.isEmpty()){ |
| | | Store store1 = storeClient.queryStoreById(integers.get(0)); |
| | | if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){ |
| | | // 平台门票 不分账 |
| | | // 平台门票 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", ""); |
| | | return weixinpay; |
| | | }else{ |
| | | temp = "1"; |
| | | } |
| | | } |
| | | // 查询门票指定的门店 属于平台还是运营商 |
| | | break; |
| | | } |
| | | System.err.println("支付"); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(name+"-"+temp, "", code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", ""); |
| | | System.err.println("提交支付"+weixinpay); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | UserPointsMerchandise userPointsMerchandise = mcClient.queryUserPointMerchaseByCode(code).get(0); |
| | | if (userPointsMerchandise.getPayStatus() == 2) { |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store1.getOperatorId()); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200 && userPointsMerchandise.getPayStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | mcClient.deletePaymentRecord(code); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | userPointsMerchandise.setPayStatus(2); |
| | | userPointsMerchandise.setPaymentTime(new Date()); |
| | | userPointsMerchandise.setOrderNumber(transaction_id); |
| | | userPointsMerchandise.setCashPayType(1); |
| | | mcClient.updateDetailsUserPointMercase(userPointsMerchandise); |
| | | |
| | | TAppUser tAppUser = baseMapper.selectById(userPointsMerchandise.getUserId()); |
| | | dealAppUserIntegral(userPointsMerchandise.getUserId(), tAppUser, userPointsMerchandise.getIntegral()); |
| | | |
| | | //课程 |
| | | if(type == 2){ |
| | | CoursePackageOrder coursePackageOrder1 = paymentClient.getCoursePackageOrderByCode(code); |
| | | if(null != coursePackageOrder1){ |
| | | coursePackageOrder1.setPayStatus(2); |
| | | coursePackageOrder1.setOrderNumber(transaction_id); |
| | | coursePackageOrder1.setAppUserId(null); |
| | | paymentClient.updateCoursePackageOrder(coursePackageOrder1); |
| | | |
| | | GetCoursePackagePaymentConfig getCoursePackagePaymentConfig = new GetCoursePackagePaymentConfig(); |
| | | getCoursePackagePaymentConfig.setCoursePackageId(coursePackageOrder1.getCoursePackageId()); |
| | | getCoursePackagePaymentConfig.setClassHours(coursePackageOrder1.getClassHours()); |
| | | CoursePackagePaymentConfig coursePackagePaymentConfig = paymentClient.getCoursePackagePaymentConfig(getCoursePackagePaymentConfig); |
| | | |
| | | AddCoursePackageOrderStudent addCoursePackageOrderStudent = new AddCoursePackageOrderStudent(); |
| | | addCoursePackageOrderStudent.setCoursePackageOrderId(coursePackageOrder1.getId()); |
| | | addCoursePackageOrderStudent.setCoursePackagePaymentConfig(coursePackagePaymentConfig); |
| | | paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent); |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name, code, |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback1", |
| | | cash.toString()); |
| | | return weixinpay; |
| | | } |
| | | }else{ |
| | | // 平台门票 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(), |
| | | "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", ""); |
| | | return weixinpay; |
| | | |
| | | } |
| | | }).start(); |
| | | } |
| | | return weixinpay; |
| | | |
| | | return ResultUtil.error("支付失败"); |
| | | } |
| | | |
| | | |
| | | @Override |
| | | public ResultUtil exchangeAddPaymentCallback(String code, String orderNumber) { |
| | |
| | | paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent); |
| | | } |
| | | } |
| | | // 创建一个2分钟后执行的线程 |
| | | new Thread(() -> { |
| | | try { |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(1000 * 60 * 2); |
| | | // 分账表示 0否1是 |
| | | String temp = "0"; |
| | | // 运营商id |
| | | Integer operatorId = 0; |
| | | System.err.println("商品"+pointsMerchandise); |
| | | switch (pointsMerchandise.getType()){ |
| | | case 1: |
| | | temp = "0"; |
| | | break; |
| | | case 2: |
| | | // 判断课包属于平台还是运营商 |
| | | CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId()); |
| | | Store store = storeClient.queryStoreById(coursePackage.getStoreId()); |
| | | if (store.getOperatorId() ==null || store.getOperatorId() == 0){ |
| | | // 平台课包 不分账 |
| | | }else{ |
| | | temp = "1"; |
| | | operatorId = store.getOperatorId(); |
| | | } |
| | | break; |
| | | case 3: |
| | | List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId()); |
| | | System.err.println("门店ids"+integers); |
| | | if (!integers.isEmpty()){ |
| | | Store store1 = storeClient.queryStoreById(integers.get(0)); |
| | | if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){ |
| | | // 平台门票 不分账 |
| | | }else{ |
| | | temp = "1"; |
| | | operatorId = store1.getOperatorId(); |
| | | } |
| | | } |
| | | // 查询门票指定的门店 属于平台还是运营商 |
| | | break; |
| | | } |
| | | System.err.println("运营商id"+operatorId); |
| | | if (operatorId != 0) { |
| | | //课程 |
| | | if(pointsMerchandise.getType() == 2){ |
| | | //课程 |
| | | CoursePackageOrder coursePackageOrder1 = paymentClient.getCoursePackageOrderByCode(code); |
| | | if(null != coursePackageOrder1){ |
| | | coursePackageOrder1.setPayStatus(2); |
| | | coursePackageOrder1.setOrderNumber(orderNumber); |
| | | coursePackageOrder1.setAppUserId(null); |
| | | paymentClient.updateCoursePackageOrder(coursePackageOrder1); |
| | | |
| | | GetCoursePackagePaymentConfig getCoursePackagePaymentConfig = new GetCoursePackagePaymentConfig(); |
| | | getCoursePackagePaymentConfig.setCoursePackageId(coursePackageOrder1.getCoursePackageId()); |
| | | getCoursePackagePaymentConfig.setClassHours(coursePackageOrder1.getClassHours()); |
| | | CoursePackagePaymentConfig coursePackagePaymentConfig = paymentClient.getCoursePackagePaymentConfig(getCoursePackagePaymentConfig); |
| | | |
| | | AddCoursePackageOrderStudent addCoursePackageOrderStudent = new AddCoursePackageOrderStudent(); |
| | | addCoursePackageOrderStudent.setCoursePackageOrderId(coursePackageOrder1.getId()); |
| | | addCoursePackageOrderStudent.setCoursePackagePaymentConfig(coursePackagePaymentConfig); |
| | | paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(operatorId); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(operatorId); |
| | | |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,"运动营商品"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | } |
| | | } |
| | | } |
| | | }else{ |
| | | System.err.println("门票"); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(operatorId); |
| | | System.err.println("分账比例"+proportionByOperatorId); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(operatorId); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, pointsMerchandise.getCash().multiply(bigDecimal1), s2,"门票"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | } catch (InterruptedException e) { |
| | | e.printStackTrace(); |
| | | } catch (Exception e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | }).start(); |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | |
| | | import com.dsh.account.service.TCourseInfoRecordService; |
| | | import com.dsh.account.service.TStudentService; |
| | | import com.dsh.account.util.*; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | |
| | | CoursePackage coursePackage = coursePackageClient.queryCoursePackageById(lessonId); |
| | | Integer storeId = coursePackage.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | // 是否分账 0否1是 |
| | | int isFenZhang= 1; |
| | | String merchantNumber = ""; |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | isFenZhang = 0; |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费"+"-"+isFenZhang, "", code, amount.toString(), |
| | | "/base/coursePackage/wechatPaymentCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | TCoursePackagePayment coursePackagePayment = couPayClient.getCoursePackagePaymentByCode(code); |
| | | if (coursePackagePayment.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200 && coursePackagePayment.getPayStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | coursePackagePayment.setState(3); |
| | | couPayClient.delPaymentCoursePackage(coursePackagePayment.getId()); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | coursePackagePayment.setPayStatus(2); |
| | | coursePackagePayment.setTotalClassHours(hour); |
| | | coursePackagePayment.setLaveClassHours(hour); |
| | | coursePackagePayment.setOrderNumber(transaction_id); |
| | | couPayClient.updatePaymentCoursePackage(coursePackagePayment); |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId()); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (isFenZhang == 1) { |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | System.err.println("微信商户号"+s2); |
| | | |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, amount.multiply(bigDecimal1), s2,"课包续费分账"); |
| | | if (!fenzhang.getCode().equals(500)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | } |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,"课包续费", |
| | | code,"/base/coursePackage/wechatPaymentCallback1", |
| | | amount.toString()); |
| | | return weixinpay; |
| | | |
| | | }else{ |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费", "", code, amount.toString(), |
| | | "/base/coursePackage/wechatPaymentCallback", "APP", ""); |
| | | |
| | | return weixinpay; |
| | | } |
| | | return weixinpay; |
| | | |
| | | } |
| | | private String smid = "2088330203191220";//平台支付宝商户号 |
| | | public ResultUtil AlipayPayment(String code, BigDecimal amount, Integer hour) { |
| | |
| | | Integer storeId = coursePackage.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | String smid1= ""; |
| | | if (store.getOperatorId()==null){ |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | smid1 = smid; |
| | | }else{ |
| | | String smidByOperatorId = storeClient.getSMIDByOperatorId(store.getOperatorId()); |
| | | smid1 = smidByOperatorId; |
| | | smid1 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | } |
| | | |
| | | if (!StringUtils.hasLength(smid1)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid1,"课包续费", "课包续费", "", code, amount.toString(), |
| | | "/base/coursePackage/alipayPaymentCallback"); |
| | | if (alipay.getCode() == 200) { |
| | |
| | | // 平台 |
| | | smid1 = smid; |
| | | // 属于平台 不做资金冻结 不做分账处理 |
| | | payMoneyUtil.confirm(smid1,code,tradeNo,amount.toString()); |
| | | // payMoneyUtil.confirm(smid1,code,tradeNo,amount.toString()); |
| | | }else{ |
| | | String smidByOperatorId = storeClient.getSMIDByOperatorId(store.getOperatorId()); |
| | | smid1 = smidByOperatorId; |
| | |
| | | import com.dsh.account.util.PayMoneyUtil; |
| | | import com.dsh.account.util.RedisUtil; |
| | | import com.dsh.account.util.ResultUtil; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | |
| | | String code = vipPayment.getCode(); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("购买年度会员", "", code, vipPayment.getAmount().toString(), |
| | | "/base/appUser/addVipPaymentWeChatCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | VipPayment vipPayment1 = VipPaymentServiceImpl.this.getOne(new QueryWrapper<VipPayment>().eq("code", code)); |
| | | if (vipPayment1.getPayStatus() == 2) { |
| | | return; |
| | | } |
| | | /** |
| | | * SUCCESS--支付成功 |
| | | * REFUND--转入退款 |
| | | * NOTPAY--未支付 |
| | | * CLOSED--已关闭 |
| | | * REVOKED--已撤销(刷卡支付) |
| | | * USERPAYING--用户支付中 |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | * ACCEPT--已接收,等待扣款 |
| | | */ |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200) { |
| | | Map<String, String> map = resultUtil.getData(); |
| | | String trade_type = map.get("trade_type"); |
| | | String trade_state = map.get("trade_state"); |
| | | String transaction_id = map.get("transaction_id"); |
| | | if ("REFUND".equals(trade_state) || "NOTPAY".equals(trade_state) || "CLOSED".equals(trade_state) || "REVOKED".equals(trade_state) || "PAYERROR".equals(trade_state)) { |
| | | vipPayment1.setState(3); |
| | | VipPaymentServiceImpl.this.updateById(vipPayment1); |
| | | return; |
| | | } |
| | | if ("SUCCESS".equals(trade_state)) { |
| | | vipPayment1.setPayStatus(2); |
| | | vipPayment1.setPayTime(new Date()); |
| | | vipPayment1.setOrderNumber(transaction_id); |
| | | VipPaymentServiceImpl.this.updateById(vipPayment1); |
| | | TAppUser appUser = appUserService.getById(vipPayment1.getAppUserId()); |
| | | Date vipEndTime = appUser.getVipEndTime(); |
| | | if (null == vipEndTime) { |
| | | vipEndTime = new Date(); |
| | | } |
| | | Calendar calendar = Calendar.getInstance(); |
| | | calendar.setTime(vipEndTime); |
| | | calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1); |
| | | appUser.setIsVip(1); |
| | | appUser.setVipEndTime(calendar.getTime()); |
| | | appUserService.updateById(appUser); |
| | | // 注册会员送券 先判断是否有注册送券类型的优惠券 判断优惠券状态 审核是否通过 是否删除 是否在有效期内 是否领取数量达上限 |
| | | List<Long> longs = userConponClient.queryCouponByUser(appUser.getId()); |
| | | redisUtil.setStrValue("VIP_P_" + vipPayment1.getAppUserId(), JSON.toJSONString(longs), 3600); |
| | | return; |
| | | } |
| | | if ("USERPAYING".equals(trade_state) || "ACCEPT".equals(trade_state)) { |
| | | Thread.sleep(wait); |
| | | num++; |
| | | } |
| | | } else { |
| | | Thread.sleep(wait); |
| | | num++; |
| | | } |
| | | if (10 == num) { |
| | | vipPayment1.setState(3); |
| | | VipPaymentServiceImpl.this.updateById(vipPayment1); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // ResultUtil weixinpay = payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"购买年度会员", |
| | | // code,"/base/appUser/addVipPaymentWeChatCallback1", |
| | | // Long.valueOf(vipPayment.getAmount().toString())); |
| | | return weixinpay; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 支付宝 |
| | |
| | | List<Long> longs = userConponClient.queryCouponByUser(appUser.getId()); |
| | | redisUtil.setStrValue("VIP_P_" + vipPayment1.getAppUserId(), JSON.toJSONString(longs), 3600); |
| | | // 如果交易结束 将当前订单金额分配到商户 |
| | | payMoneyUtil.confirm(smid,vipPayment.getCode(),tradeNo,amount); |
| | | // payMoneyUtil.confirm(smid,vipPayment.getCode(),tradeNo,amount); |
| | | return; |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(tradeStatus)) { |
| | |
| | | //import com.github.binarywang.wxpay.service.ProfitSharingV3Service; |
| | | //import com.github.binarywang.wxpay.service.WxPayService; |
| | | |
| | | import com.dsh.account.util.wx.PartnerAppPrepay; |
| | | import com.dsh.account.util.wx.WXPayUtility; |
| | | import com.dsh.account.util.wx.WeChatV3SignUtil; |
| | | import com.dsh.account.util.wx.WxV3PayConfig; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.apache.http.client.methods.CloseableHttpResponse; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | |
| | | /** |
| | | * 支付宝支付 |
| | | */ |
| | | public ResultUtil alipay(String smid,String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | public ResultUtil alipay(String smid,String body, String subject, |
| | | String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | // //构造client |
| | | // CertAlipayRequest certAlipayRequest = new CertAlipayRequest (); |
| | | // //设置网关地址 |
| | |
| | | SubMerchant subMerchant = new SubMerchant(); |
| | | subMerchant.setMerchantId(smid); |
| | | model.setSubMerchant(subMerchant); |
| | | ExtendParams extendParams = new ExtendParams(); |
| | | extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理 |
| | | model.setExtendParams(extendParams); |
| | | //分账 |
| | | // Integer coursePackagePayments = coursePackageClient.queryByCode(outTradeNo); |
| | | // Integer paymentCompetitions = competitionsClient.queryByCode(outTradeNo); |
| | | // Integer siteBookings = siteClient.queryByCode(outTradeNo); |
| | | // List<Integer> stores = new ArrayList<>(); |
| | | // stores.add(coursePackagePayments); |
| | | // stores.add(paymentCompetitions); |
| | | // stores.add(siteBookings); |
| | | // |
| | | // OperatorUser operatorUser = siteClient.queryOperator(stores); |
| | | // |
| | | // String alipayProportion = operatorUser.getAlipayProportion(); |
| | | // String alipayNum = operatorUser.getAlipayNum(); |
| | | // |
| | | // ExtendParams extendParams = new ExtendParams(); |
| | | //// extendParams.setSysServiceProviderId("YOUR_SERVICE_PROVIDER_ID"); |
| | | // model.setExtendParams(extendParams); |
| | | // |
| | | // RoyaltyInfo royaltyInfo = new RoyaltyInfo(); |
| | | //// royaltyInfo.setRoyaltyType("transfer"); |
| | | // |
| | | // |
| | | // RoyaltyDetailInfos royaltyDetailInfo1 = new RoyaltyDetailInfos(); |
| | | // royaltyDetailInfo1.setTransOutType("userId"); |
| | | // royaltyDetailInfo1.setTransOut(aliAppid); |
| | | // royaltyDetailInfo1.setTransInType("loginName"); |
| | | // royaltyDetailInfo1.setTransIn("18398968484"); |
| | | // |
| | | // royaltyDetailInfo1.setDesc("分账描述1"); |
| | | // royaltyDetailInfo1.setAmountPercentage(alipayProportion); |
| | | // List<RoyaltyDetailInfos> royaltyDetailInfos = new ArrayList<>(); |
| | | // |
| | | // |
| | | // |
| | | // royaltyInfo.setRoyaltyDetailInfos(royaltyDetailInfos); |
| | | // model.setRoyaltyInfo(royaltyInfo); |
| | | // System.err.println("=================="+royaltyInfo); |
| | | // |
| | | |
| | | request.setBizModel(model); |
| | | request.setNotifyUrl(callbackPath + notifyUrl); |
| | | try { |
| | |
| | | map.put("appid", appid); |
| | | map.put("mch_id", mchId); |
| | | map.put("nonce_str", nonce_str); |
| | | String temp = ""; |
| | | if (body.split("-").length>1){ |
| | | temp = body.split("-")[1]; |
| | | map.put("body", body.split("-")[0]); |
| | | }else{ |
| | | |
| | | map.put("body", body); |
| | | } |
| | | if (StringUtils.hasLength(temp) && temp.equals("1")){ |
| | | // 添加分账标识 |
| | | map.put("profit_sharing", "Y"); |
| | | } |
| | | |
| | | |
| | | map.put("attach", attach);//存储订单id |
| | | map.put("out_trade_no", out_trade_no);//存储的订单code |
| | | map.put("total_fee", i); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl = callbackPath+notifyUrl;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | String prepayId =""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | |
| | | @Resource |
New file |
| | |
| | | package com.dsh.account.util.wx; |
| | | |
| | | import com.google.gson.annotations.SerializedName; |
| | | import okhttp3.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.account.util.wx; |
| | | |
| | | import com.google.gson.*; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.*; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.account.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.account.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.ArrayList; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.account.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | <name>福利</name> |
| | | <description>福利</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | |
| | | package com.dsh.activity.controller; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.dsh.activity.entity.THuiminAgreement; |
| | |
| | | import com.dsh.activity.util.ResultUtil; |
| | | import com.dsh.activity.util.TokenUtil; |
| | | import com.dsh.activity.util.UUIDUtil; |
| | | import com.dsh.activity.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | |
| | | |
| | | /** |
| | | * 玩湃惠民卡回调 |
| | | * |
| | | * @author zhibing.pu |
| | | * @date 2023/6/24 11:27 |
| | | */ |
| | |
| | | String out_trade_no = map.get("out_trade_no"); |
| | | String transaction_id = map.get("trade_no"); |
| | | TPayHuimin one = payHuiminService.getOne(new LambdaQueryWrapper<TPayHuimin>().eq(TPayHuimin::getCode, out_trade_no)); |
| | | if (one!=null){ |
| | | if (one.getStatus()==2){ |
| | | if (one != null) { |
| | | if (one.getStatus() == 2) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("success"); |
| | | out.flush(); |
| | | out.close(); |
| | | }else if (one.getStatus()==1){ |
| | | } else if (one.getStatus() == 1) { |
| | | one.setOrderNumber(transaction_id); |
| | | one.setStatus(2); |
| | | one.setPaymentTime(new Date()); |
| | |
| | | out.write("success"); |
| | | out.flush(); |
| | | out.close(); |
| | | }else{ |
| | | } else { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("success"); |
| | | out.flush(); |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 玩湃惠民卡微信支付回调接口 |
| | | */ |
| | |
| | | String transaction_id = map.get("transaction_id"); |
| | | String result = map.get("result"); |
| | | TPayHuimin one = payHuiminService.getOne(new LambdaQueryWrapper<TPayHuimin>().eq(TPayHuimin::getCode, out_trade_no)); |
| | | if (one!=null){ |
| | | if (one.getStatus()==2){ |
| | | if (one != null) { |
| | | if (one.getStatus() == 2) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write(result); |
| | | out.flush(); |
| | | out.close(); |
| | | }else if (one.getStatus()==1){ |
| | | } else if (one.getStatus() == 1) { |
| | | one.setOrderNumber(transaction_id); |
| | | one.setStatus(2); |
| | | one.setPaymentTime(new Date()); |
| | |
| | | out.write(result); |
| | | out.flush(); |
| | | out.close(); |
| | | }else{ |
| | | } else { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write(result); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | @PostMapping("/weixinPayHuiminCallback1") |
| | | public void weixinPayHuiminCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | TPayHuimin one = payHuiminService.getOne(new LambdaQueryWrapper<TPayHuimin>().eq(TPayHuimin::getCode, out_trade_no)); |
| | | if (one != null) { |
| | | if (one.getStatus() == 2) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } else if (one.getStatus() == 1) { |
| | | one.setOrderNumber(transaction_id); |
| | | one.setStatus(2); |
| | | one.setPaymentTime(new Date()); |
| | | payHuiminService.updateById(one); |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } else { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 玩湃惠民卡微信退款回调 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/wxRefundHuiminCallback") |
| | | public void wxRefundHuiminCallback(HttpServletRequest request, HttpServletResponse response){ |
| | | public void wxRefundHuiminCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | Map<String, String> map = payMoneyUtil.wxRefundCallback(request); |
| | | if(null != map){ |
| | | if (null != map) { |
| | | String refund_id = map.get("refund_id"); |
| | | String out_refund_no = map.get("out_refund_no"); |
| | | String result = map.get("result"); |
| | |
| | | import com.dsh.activity.model.response.*; |
| | | import com.dsh.activity.service.*; |
| | | import com.dsh.activity.util.*; |
| | | import com.dsh.activity.util.wx.WxV3PayConfig; |
| | | import com.google.gson.Gson; |
| | | import io.swagger.annotations.*; |
| | | import io.swagger.models.auth.In; |
| | |
| | | @Autowired |
| | | private TokenUtil tokenUtil; |
| | | private String smid = "2088330203191220";//平台支付宝商户号 |
| | | // todo 待申请 |
| | | private String smidVx = "2088330203191220";//平台微信商户号 |
| | | |
| | | |
| | | /** |
| | |
| | | BigDecimal bigDecimal = new BigDecimal(studentIds.split(",").length); |
| | | switch (payType) { |
| | | case 1: |
| | | return payMoneyUtil.weixinpay("购买玩湃惠民卡" + "-" + 0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(), |
| | | // todo 惠民卡收入属于平台商户 定死 |
| | | return payMoneyUtil.weixinpay("购买玩湃惠民卡", "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(), |
| | | "/base/huimin/callBack/weixinPayHuiminCallback", "APP", ""); |
| | | // return payMoneyUtil.weixinpayV3("1720719391","购买玩湃惠民卡",tPayHuimin.getCode(), |
| | | // "/base/huimin/callBack/weixinPayHuiminCallback1",tPayHuimin.getSalesMoney().toString()); |
| | | case 2: |
| | | String string = tPayHuimin.getSalesMoney().toString(); |
| | | return payMoneyUtil.alipay(smid, "购买玩湃惠民卡", "购买玩湃惠民卡", "", tPayHuimin.getCode(), string, |
| | | return payMoneyUtil.alipay("2088670241691219", "购买玩湃惠民卡", "购买玩湃惠民卡", "", tPayHuimin.getCode(), string, |
| | | "/base/huimin/callBack/aliPayHuiminCallback"); |
| | | } |
| | | |
| | |
| | | //package com.dsh.activity.controller; |
| | | // |
| | | //import cn.hutool.core.collection.CollUtil; |
| | | //import com.alibaba.fastjson.JSONArray; |
| | | //import com.alibaba.fastjson.JSONObject; |
| | | //import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | //import com.dsh.activity.entity.*; |
| | | //import com.dsh.activity.feignclient.account.StudentClient; |
| | | //import com.dsh.activity.feignclient.other.SiteClient; |
| | | //import com.dsh.activity.feignclient.other.StoreClient; |
| | | //import com.dsh.activity.feignclient.other.model.Site; |
| | | //import com.dsh.activity.feignclient.other.model.Store; |
| | | //import com.dsh.activity.model.response.*; |
| | | //import com.dsh.activity.service.*; |
| | | //import com.dsh.activity.util.*; |
| | | ////import com.dsh.activity.util.wx.WechatPaymentService; |
| | | //import com.dsh.activity.util.wx.WxAppPayService; |
| | | //import io.swagger.annotations.Api; |
| | | //import io.swagger.annotations.ApiImplicitParam; |
| | | //import io.swagger.annotations.ApiImplicitParams; |
| | | //import io.swagger.annotations.ApiOperation; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.web.bind.annotation.PostMapping; |
| | | //import org.springframework.web.bind.annotation.RequestMapping; |
| | | //import org.springframework.web.bind.annotation.ResponseBody; |
| | | //import org.springframework.web.bind.annotation.RestController; |
| | | // |
| | | //import javax.annotation.Resource; |
| | | //import java.math.BigDecimal; |
| | | //import java.text.SimpleDateFormat; |
| | | //import java.time.LocalDate; |
| | | //import java.time.LocalDateTime; |
| | | //import java.time.ZoneId; |
| | | //import java.util.*; |
| | | //import java.util.stream.Collectors; |
| | | // |
| | | ///** |
| | | // * @author zhibing.pu |
| | | // * @date 2023/6/24 11:27 |
| | | // */ |
| | | //@RestController |
| | | //@RequestMapping("/base") |
| | | //public class WeiXinV3Controller { |
| | | // |
| | | //// @Resource |
| | | //// private WechatPaymentService wechatPaymentService; |
| | | package com.dsh.activity.controller; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.dsh.activity.entity.*; |
| | | import com.dsh.activity.feignclient.account.StudentClient; |
| | | import com.dsh.activity.feignclient.other.SiteClient; |
| | | import com.dsh.activity.feignclient.other.StoreClient; |
| | | import com.dsh.activity.feignclient.other.model.Site; |
| | | import com.dsh.activity.feignclient.other.model.Store; |
| | | import com.dsh.activity.model.response.*; |
| | | import com.dsh.activity.service.*; |
| | | import com.dsh.activity.util.*; |
| | | //import com.dsh.activity.util.wx.WechatPaymentService; |
| | | |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.security.PrivateKey; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @author zhibing.pu |
| | | * @date 2023/6/24 11:27 |
| | | */ |
| | | @RestController |
| | | @RequestMapping("/base") |
| | | public class WeiXinV3Controller { |
| | | |
| | | // @Resource |
| | | // private WechatPaymentService wechatPaymentService; |
| | | // @Autowired |
| | | // private WxAppPayService wxAppPayService; |
| | | // @ResponseBody |
| | | // @PostMapping("/testWeiXinV3") |
| | | // @ApiOperation(value = "获取添加人员、选择人员说明文案") |
| | | // @ApiImplicitParams({ |
| | | // @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") |
| | | // }) |
| | | // public ResultUtil getContentForStudent() throws Exception { |
| | | //// Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder(); |
| | | // // ... 在你的下单方法中调用 ... |
| | | @Autowired |
| | | private PayMoneyUtil payMoneyUtil; |
| | | @ResponseBody |
| | | @PostMapping("/testWeiXinV3") |
| | | @ApiOperation(value = "获取添加人员、选择人员说明文案") |
| | | @ApiImplicitParams({ |
| | | @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") |
| | | }) |
| | | public ResultUtil getContentForStudent() throws Exception { |
| | | // Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder(); |
| | | // ... 在你的下单方法中调用 ... |
| | | try { |
| | | ResultUtil resultUtil = payMoneyUtil.weixinpayV3("1720719391","购买玩湃惠民卡","15645654askjak13", |
| | | "/base/huimin/callBack/weixinPayHuiminCallback1","99"); |
| | | return resultUtil; |
| | | |
| | | // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK |
| | | // return ResponseEntity.ok(payParams); // 示例 |
| | | |
| | | } catch (Exception e) { |
| | | // 处理异常 |
| | | // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败"); |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | // @PostMapping("/test-private-key") |
| | | // public String testPrivateKey() { |
| | | // try { |
| | | // String description = "商品描述"; |
| | | // SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | // String outTradeNo =sdf.format(new Date()) + UUIDUtil.getNumberRandom(5); |
| | | // BigDecimal amount = new BigDecimal("0.01"); // 支付金额,例如1分钱 |
| | | // Map<String, String> payParams = wxAppPayService.createOrder(description, outTradeNo, amount); |
| | | // return ResultUtil.success(payParams); |
| | | // |
| | | // // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK |
| | | // // return ResponseEntity.ok(payParams); // 示例 |
| | | // |
| | | // PrivateKey privateKey = WXPaySignatureCertificateUtil.getPrivateKey(); |
| | | // return "私钥加载成功: " + privateKey.getAlgorithm(); |
| | | // } catch (Exception e) { |
| | | // // 处理异常 |
| | | // // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败"); |
| | | // return "私钥加载失败: " + e.getMessage(); |
| | | // } |
| | | // return ResultUtil.success(); |
| | | // } |
| | | // |
| | | //} |
| | | } |
| | |
| | | import com.alipay.api.request.*; |
| | | import com.alipay.api.response.*; |
| | | import com.dsh.activity.util.httpClinet.HttpClientUtil; |
| | | import com.dsh.activity.util.wx.*; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | import java.math.BigDecimal; |
| | | import java.net.InetAddress; |
| | | import java.net.UnknownHostException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.InvalidKeyException; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.security.NoSuchProviderException; |
| | |
| | | |
| | | private String alipay_root_cert_path = "C:/cert/alipay/user/alipay_root_cert_path.crt";//支付宝CA根证书文件路径 |
| | | |
| | | private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书 |
| | | // private String certPath = "D:/apiclient_cert.p12";//微信证书 |
| | | // private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书 |
| | | private String certPath = "E:\\wanpaiapiclient_cert.p12";//微信证书 |
| | | |
| | | public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) { |
| | | public ResultUtil confirm(String smid, String code, String outTradeNo, String amount) { |
| | | AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", |
| | | aliAppid, |
| | | appPrivateKey, |
| | |
| | | "RSA2"); |
| | | AlipayTradeSettleConfirmRequest request = new AlipayTradeSettleConfirmRequest(); |
| | | request.setBizContent("{" + |
| | | " \"out_request_no\":\""+code+"\"," + |
| | | " \"trade_no\":\""+outTradeNo+"\"," + |
| | | " \"out_request_no\":\"" + code + "\"," + |
| | | " \"trade_no\":\"" + outTradeNo + "\"," + |
| | | " \"settle_info\":{" + |
| | | " \"settle_detail_infos\":[" + |
| | | " {" + |
| | | " \"trans_in_type\":\"defaultSettle\"," + |
| | | " \"settle_entity_id\":\""+smid+"\"," + |
| | | " \"settle_entity_id\":\"" + smid + "\"," + |
| | | " \"settle_entity_type\":\"SecondMerchant\"," + |
| | | " \"amount\":"+amount+"," + |
| | | " \"amount\":" + amount + "," + |
| | | " }" + |
| | | " ]" + |
| | | " }," + |
| | |
| | | } catch (AlipayApiException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if(response.isSuccess()){ |
| | | if (response.isSuccess()) { |
| | | System.out.println("调用成功"); |
| | | return ResultUtil.success(); |
| | | } else { |
| | |
| | | return ResultUtil.error("出现问题啦"); |
| | | } |
| | | } |
| | | |
| | | // 属于平台的运营商 因为无需分账不冻结资金 |
| | | public ResultUtil confirm1(String smid,String code, String outTradeNo, String amount) { |
| | | public ResultUtil confirm1(String smid, String code, String outTradeNo, String amount) { |
| | | AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", |
| | | aliAppid, |
| | | appPrivateKey, |
| | |
| | | "RSA2"); |
| | | AlipayTradeSettleConfirmRequest request = new AlipayTradeSettleConfirmRequest(); |
| | | request.setBizContent("{" + |
| | | " \"out_request_no\":\""+code+"\"," + |
| | | " \"trade_no\":\""+outTradeNo+"\"," + |
| | | " \"out_request_no\":\"" + code + "\"," + |
| | | " \"trade_no\":\"" + outTradeNo + "\"," + |
| | | " \"settle_info\":{" + |
| | | " \"settle_detail_infos\":[" + |
| | | " {" + |
| | | " \"trans_in_type\":\"defaultSettle\"," + |
| | | " \"settle_entity_id\":\""+smid+"\"," + |
| | | " \"settle_entity_id\":\"" + smid + "\"," + |
| | | " \"settle_entity_type\":\"SecondMerchant\"," + |
| | | " \"amount\":"+amount+"," + |
| | | " \"amount\":" + amount + "," + |
| | | " }" + |
| | | " ]" + |
| | | " }," + |
| | |
| | | } catch (AlipayApiException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if(response.isSuccess()){ |
| | | if (response.isSuccess()) { |
| | | System.out.println("调用成功"); |
| | | return ResultUtil.success(); |
| | | } else { |
| | |
| | | /** |
| | | * 支付宝支付 |
| | | */ |
| | | public ResultUtil alipay(String smid,String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | public ResultUtil alipay(String smid, String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | // //构造client |
| | | // CertAlipayRequest certAlipayRequest = new CertAlipayRequest (); |
| | | // //设置网关地址 |
| | |
| | | SubMerchant subMerchant = new SubMerchant(); |
| | | subMerchant.setMerchantId(smid); |
| | | model.setSubMerchant(subMerchant); |
| | | ExtendParams extendParams = new ExtendParams(); |
| | | extendParams.setRoyaltyFreeze("false");// 冻结资金 用于后续分账处理 |
| | | model.setExtendParams(extendParams); |
| | | |
| | | request.setBizModel(model); |
| | | request.setNotifyUrl("http://8.137.22.229:5002" + notifyUrl); |
| | | |
| | |
| | | |
| | | |
| | | Map<String, String> map = new HashMap<>(); |
| | | System.err.println("返回码"+params); |
| | | if (params.get("trade_status").equals("TRADE_SUCCESS")){ |
| | | System.err.println("返回码" + params); |
| | | if (params.get("trade_status").equals("TRADE_SUCCESS")) { |
| | | String out_trade_no = params.get("out_trade_no"); |
| | | String subject = params.get("subject"); |
| | | String total_amount = params.get("total_amount"); |
| | |
| | | map.put("passback_params", passback_params);//回传参数 |
| | | System.err.println("回调map"); |
| | | return map; |
| | | }else{ |
| | | } else { |
| | | return null; |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl =callbackPath+ notifyUrl;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | String prepayId =""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | /** |
| | | * 微信统一下单 |
| | |
| | | // map.put("sub_mch_id", "123456"); |
| | | map.put("nonce_str", nonce_str); |
| | | String temp = ""; |
| | | if (body.split("-").length>1){ |
| | | temp = body.split("-")[1]; |
| | | map.put("body", body.split("-")[0]); |
| | | }else{ |
| | | map.put("body", body); |
| | | } |
| | | if (StringUtils.hasLength(temp) && temp.equals("1")){ |
| | | // 添加分账标识 |
| | | map.put("profit_sharing", "Y"); |
| | | } |
| | | map.put("body", body); |
| | | |
| | | map.put("attach", attach);//存储订单id |
| | | map.put("out_trade_no", out_trade_no);//存储的订单code |
| | | map.put("total_fee", i); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | return sb.toString(); |
| | | } |
| | | |
| | | public String weixinSignatureWithSHA256(Map<String, Object> map, String key) { |
| | | try { |
| | | Set<Map.Entry<String, Object>> entries = map.entrySet(); |
| | | List<Map.Entry<String, Object>> infoIds = new ArrayList<>(entries); |
| | | |
| | | // 按 key 的 ASCII 字典序排序 |
| | | Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() { |
| | | public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) { |
| | | return (o1.getKey()).toString().compareTo(o2.getKey()); |
| | | } |
| | | }); |
| | | |
| | | // 构造待签名字符串 |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (Map.Entry<String, Object> item : infoIds) { |
| | | if (item.getKey() != null && !item.getKey().isEmpty()) { |
| | | String keyName = item.getKey(); |
| | | Object val = item.getValue(); |
| | | if (val != null && !val.toString().isEmpty()) { |
| | | sb.append(keyName).append("=").append(val).append("\n"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | sb.deleteCharAt(sb.length() - 1); // 删除最后一个 & |
| | | |
| | | // 使用 SHA256-HMAC 加密 |
| | | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); |
| | | SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); |
| | | sha256_HMAC.init(secretKey); |
| | | byte[] hashBytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8)); |
| | | String sign = byteArrayToHexString(hashBytes).toUpperCase(); |
| | | |
| | | return sign; |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 微信下单的签名算法 |
| | |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String weixinSignature1(Map<String, Object> map) { |
| | | try { |
| | | Set<Map.Entry<String, Object>> entries = map.entrySet(); |
| | |
| | | } |
| | | } |
| | | } |
| | | sb.append("key=" + key); |
| | | String sign = sha256_HMAC(sb.toString(), key).toUpperCase(); |
| | | sb.append("key=" + "1skiujh28376shznxmslwosiusytersq"); |
| | | String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式 |
| | | return sign; |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * sha256_HMAC加密 |
| | | * |
| | | * @param message 消息 |
| | | * @param secret 秘钥 |
| | | * @return 加密后字符串 |
| | | */ |
| | | public String sha256_HMAC(String message, String secret) { |
| | | public String sha256_HMAC(String message, String secret) { |
| | | String hash = ""; |
| | | try { |
| | | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); |
New file |
| | |
| | | package com.dsh.activity.util.wx; |
| | | |
| | | import com.google.gson.annotations.Expose; |
| | | import com.google.gson.annotations.SerializedName; |
| | | |
| | | // 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.ArrayList; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import okhttp3.MediaType; |
| | | import okhttp3.OkHttpClient; |
| | | import okhttp3.Request; |
| | | import okhttp3.RequestBody; |
| | | import okhttp3.Response; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.activity.util.wx; |
| | | |
| | | import com.google.gson.ExclusionStrategy; |
| | | import com.google.gson.FieldAttributes; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.GsonBuilder; |
| | | import com.google.gson.JsonElement; |
| | | import com.google.gson.JsonObject; |
| | | import com.google.gson.JsonParser; |
| | | import com.google.gson.JsonSyntaxException; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.InvalidKeyException; |
| | | import java.security.KeyFactory; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.security.SecureRandom; |
| | | import java.security.Signature; |
| | | import java.security.SignatureException; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.activity.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import javax.crypto.Mac; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import java.io.*; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.*; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.activity.util.wx; |
| | | |
| | | import com.dsh.activity.util.UUIDUtil; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import java.io.*; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.*; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
| | |
| | | //package com.dsh.activity.util.wx; |
| | | // |
| | | //import org.springframework.beans.factory.annotation.Value; |
| | | //import org.springframework.stereotype.Component; |
| | | //import javax.annotation.PostConstruct; // 如果需要静态访问 |
| | | // |
| | | //@Component |
| | | //public class WxV3PayConfig { |
| | | // |
| | | // private String appIdValue = "wx41d32f362ba0f911"; |
| | | // public static String APP_ID= "wx41d32f362ba0f911"; |
| | | // |
| | | // private String mchIdValue= "1681873607"; |
| | | // public static String Mch_ID= "1681873607"; |
| | | // |
| | | // private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | // public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // |
| | | // private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | // public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | // |
| | | // private String privateKeyPathValue= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // public static String privateKeyPath= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // |
| | | // // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | // @PostConstruct |
| | | // public void init() { |
| | | // APP_ID = this.appIdValue; |
| | | // Mch_ID = this.mchIdValue; |
| | | // apiV3Key = this.apiV3KeyValue; |
| | | // mchSerialNo = this.mchSerialNoValue; |
| | | // privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | // |
| | | // // 可以在这里加一些非空检查 |
| | | // if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | // System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | // } else { |
| | | // System.out.println("微信支付V3配置加载完成。"); |
| | | // } |
| | | // } |
| | | // |
| | | // // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | // /* |
| | | // public static PrivateKey getPrivateKey() { |
| | | // if (cachedPrivateKey != null) { |
| | | // return cachedPrivateKey; |
| | | // } |
| | | // try { |
| | | // String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // // ... rest of the method ... |
| | | // } // ... catch blocks ... |
| | | // } |
| | | // */ |
| | | //} |
| | | package com.dsh.activity.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | <name>社区世界杯</name> |
| | | <description>社区世界杯</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-java-core</artifactId> |
| | | <version>0.2.12</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>joda-time</groupId> |
| | | <artifactId>joda-time</artifactId> |
| | | <version>2.10.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <!--日志处理--> |
| | | <dependency> |
| | | <groupId>cn.mb.cloud</groupId> |
| | |
| | | import com.dsh.communityWorldCup.util.PayMoneyUtil; |
| | | import com.dsh.communityWorldCup.util.ResultUtil; |
| | | import com.dsh.communityWorldCup.util.TokenUtil; |
| | | import com.dsh.communityWorldCup.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import groovy.util.logging.Log4j; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/base/worldCup/wxPayWorldCupCallback") |
| | | public void wxPayWorldCupCallback(HttpServletRequest request, HttpServletResponse response){ |
| | | System.err.println("微信回调"); |
| | | try { |
| | | Map<String, String> map = payMoneyUtil.weixinpayCallback(request); |
| | | if (null != map) { |
| | | String code = map.get("out_trade_no"); |
| | | String transaction_id = map.get("transaction_id"); |
| | | String result = map.get("result"); |
| | | ResultUtil resultUtil = worldCupService.paymentWorldCupCallback(code, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.println(result); |
| | | out.flush(); |
| | | out.close(); |
| | | } else { |
| | | log.error("社区世界杯报名微信支付回业务处理异常:" + resultUtil.getMsg()); |
| | | } |
| | | } else { |
| | | log.error("社区世界杯报名微信支付回调解析异常"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 微信支付回调 |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/worldCup/wxPayWorldCupCallback") |
| | | public void wxPayWorldCupCallback(HttpServletRequest request, HttpServletResponse response){ |
| | | @PostMapping("/base/worldCup/wxPayWorldCupCallback1") |
| | | public void wxPayWorldCupCallback1(HttpServletRequest request, HttpServletResponse response){ |
| | | try { |
| | | Map<String, String> map = payMoneyUtil.weixinpayCallback(request); |
| | | if(null != map){ |
| | | String code = map.get("out_trade_no"); |
| | | String transaction_id = map.get("transaction_id"); |
| | | String result = map.get("result"); |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String code = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = worldCupService.paymentWorldCupCallback(code, transaction_id); |
| | | if(resultUtil.getCode() == 200){ |
| | | PrintWriter out = response.getWriter(); |
| | | out.println(result); |
| | | out.println("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | }else{ |
| | |
| | | |
| | | import com.dsh.communityWorldCup.feignclient.other.model.Store; |
| | | import org.springframework.cloud.openfeign.FeignClient; |
| | | import org.springframework.web.bind.annotation.PathVariable; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | |
| | | import java.util.List; |
| | |
| | | */ |
| | | @PostMapping("/store/queryStoreById") |
| | | Store queryStoreById(Integer id); |
| | | @PostMapping("/base/getmerchantNumberByOperatorId/{id}") |
| | | String getmerchantNumberByOperatorId(@PathVariable("id")Integer id); |
| | | @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}") |
| | | String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id); |
| | | } |
| | |
| | | * 状态(1=正常,2=冻结,3=删除) |
| | | */ |
| | | private Integer state; |
| | | /** |
| | | * 运营商id |
| | | */ |
| | | private Integer operatorId; |
| | | } |
| | |
| | | import com.dsh.communityWorldCup.model.*; |
| | | import com.dsh.communityWorldCup.service.*; |
| | | import com.dsh.communityWorldCup.util.*; |
| | | import com.dsh.communityWorldCup.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | |
| | | return payment; |
| | | } |
| | | |
| | | private String smidVx = "2088330203191220";//平台微信商户号 |
| | | |
| | | /** |
| | | * 支付逻辑 |
| | |
| | | worldCupPayment.setAmount(new BigDecimal(num).multiply(worldCup.getCash())); |
| | | worldCupPayment.setUnitPrice(worldCupPayment.getAmount().divide(new BigDecimal(num))); |
| | | worldCupPaymentService.save(worldCupPayment); |
| | | return payMoneyUtil.weixinpay("社区世界杯报名", "", worldCupPayment.getCode(), |
| | | List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, worldCup.getId()).list(); |
| | | if (list.isEmpty()){ |
| | | return payMoneyUtil.weixinpay("社区世界杯报名", "", worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), "/base/worldCup/wxPayWorldCupCallback", "APP", ""); |
| | | }else{ |
| | | Integer storeId = list.get(0).getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | if (store.getOperatorId()==null||store.getOperatorId()==0){ |
| | | // 平台 |
| | | return payMoneyUtil.weixinpay("社区世界杯报名", "", worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), "/base/worldCup/wxPayWorldCupCallback", "APP", ""); |
| | | }else{ |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | System.err.println("微信商户号"+s2); |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(s2,"社区世界杯报名",worldCupPayment.getCode() |
| | | ,"/base/worldCup/wxPayWorldCupCallback1",worldCupPayment.getAmount().toString()); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | | //支付宝 |
| | | if(payType == 2){ |
| | | worldCupPayment.setAmount(new BigDecimal(num).multiply(worldCup.getCash())); |
| | | worldCupPayment.setUnitPrice(worldCupPayment.getAmount().divide(new BigDecimal(num))); |
| | | worldCupPaymentService.save(worldCupPayment); |
| | | return payMoneyUtil.alipay("2088330203191220", "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(), |
| | | List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, worldCup.getId()).list(); |
| | | |
| | | if (list.isEmpty()){ |
| | | return payMoneyUtil.alipay("2088330203191220", "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback"); |
| | | } |
| | | }else{ |
| | | Integer storeId = list.get(0).getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | if (store.getOperatorId()==null||store.getOperatorId()==0){ |
| | | return payMoneyUtil.alipay("2088330203191220", "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback"); |
| | | }else{ |
| | | String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | System.err.println("支付宝商户号"+s2); |
| | | if(!StringUtils.hasLength(s2)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.alipay(s2, "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback"); |
| | | } |
| | | } |
| | | } |
| | | //玩湃币 |
| | | if(payType == 3){ |
| | | AppUser appUser = appUserClient.getAppUser(paymentWorldCup.getUid()); |
| | |
| | | import com.alipay.api.request.*; |
| | | import com.alipay.api.response.*; |
| | | import com.dsh.communityWorldCup.util.httpClinet.HttpClientUtil; |
| | | import com.dsh.communityWorldCup.util.wx.PartnerAppPrepay; |
| | | import com.dsh.communityWorldCup.util.wx.WXPayUtility; |
| | | import com.dsh.communityWorldCup.util.wx.WeChatV3SignUtil; |
| | | import com.dsh.communityWorldCup.util.wx.WxV3PayConfig; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | SubMerchant subMerchant = new SubMerchant(); |
| | | subMerchant.setMerchantId(smid); |
| | | model.setSubMerchant(subMerchant); |
| | | ExtendParams extendParams = new ExtendParams(); |
| | | extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理 |
| | | model.setExtendParams(extendParams); |
| | | request.setBizModel(model); |
| | | request.setNotifyUrl(callbackPath + notifyUrl); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl = callbackPath+notifyUrl;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | String prepayId =""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | /** |
| | | * 微信支付成功后的回调处理 |
New file |
| | |
| | | package com.dsh.communityWorldCup.util.wx; |
| | | |
| | | import com.google.gson.annotations.SerializedName; |
| | | import okhttp3.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.communityWorldCup.util.wx; |
| | | |
| | | import com.google.gson.*; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.*; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.communityWorldCup.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.communityWorldCup.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.ArrayList; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.communityWorldCup.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | <name>赛事</name> |
| | | <description>赛事</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-java-core</artifactId> |
| | | <version>0.2.12</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>joda-time</groupId> |
| | | <artifactId>joda-time</artifactId> |
| | | <version>2.10.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <!--日志处理--> |
| | | <dependency> |
| | | <groupId>cn.mb.cloud</groupId> |
| | |
| | | |
| | | import cn.hutool.core.date.DateUtil; |
| | | import cn.hutool.poi.excel.ExcelUtil; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | |
| | | import com.dsh.competition.service.IPaymentCompetitionService; |
| | | import com.dsh.competition.service.UserCompetitionService; |
| | | import com.dsh.competition.util.*; |
| | | import com.dsh.competition.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.OutputStream; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.DateFormat; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/competition/weChatPaymentCompetitionCallback1") |
| | | public void weChatPaymentCompetitionCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String code = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 1)); |
| | | if (paymentCompetition.getPayStatus() == 1) { |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetition.setPayStatus(2); |
| | | paymentCompetition.setPayTime(new Date()); |
| | | paymentCompetition.setPayOrderNo(transaction_id); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | competition.setApplicantsNumber(competition.getApplicantsNumber() + 1); |
| | | cttService.updateById(competition); |
| | | } |
| | | |
| | | |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 报名赛事微信支付回调 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/competition/weChatPaymentCompetitionCallback") |
| | | public void weChatPaymentCompetitionCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | competition.setApplicantsNumber(competition.getApplicantsNumber() + 1); |
| | | cttService.updateById(competition); |
| | | if (competition.getOperatorId()!=null && competition.getOperatorId()!=0){ |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(competition.getOperatorId()); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(competition.getOperatorId()); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | paymentCompetition.setFenzhangOrderNo(nonce_str); |
| | | BigDecimal bigDecimal2 = new BigDecimal(paymentCompetition.getAmount()); |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"报名赛事分账"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getData()+"-"+fenzhang.getMsg()); |
| | | }else{ |
| | | paymentCompetition.setFenzhangNo(fenzhang.getData().toString()); |
| | | paymentCompetition.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1)); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | PrintWriter out = response.getWriter(); |
| | | out.write(result); |
| | |
| | | import javax.annotation.Resource; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.stream.Collectors; |
| | | |
| | | |
| | | /** |
| | |
| | | List<ParticipantVo> filteredParticipants = new ArrayList<>(); |
| | | |
| | | for (ParticipantVo participant : participantVos) { |
| | | if (participant.getIdcard() != null && !participant.getIdcard().isEmpty()) { |
| | | // if (participant.getIdcard() != null && !participant.getIdcard().isEmpty()) { |
| | | filteredParticipants.add(participant); |
| | | } |
| | | // } |
| | | } |
| | | return ResultUtil.success(filteredParticipants); |
| | | List<ParticipantVo> collect = filteredParticipants.stream().distinct().collect(Collectors.toList()); |
| | | return ResultUtil.success(collect); |
| | | // } |
| | | |
| | | // return ResultUtil.success(participantVos); |
| | |
| | | // 根据运营商id获取微信商户号 |
| | | @PostMapping("/base/getmerchantNumberByOperatorId/{id}") |
| | | String getmerchantNumberByOperatorId(@PathVariable("id")Integer id); |
| | | // 根据运营商id获取支付宝商户号 |
| | | @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}") |
| | | String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id); |
| | | /** |
| | | * 根据名称模糊搜索门店 |
| | | * |
| | |
| | | import com.dsh.competition.service.IPaymentCompetitionService; |
| | | import com.dsh.competition.service.UserCompetitionService; |
| | | import com.dsh.competition.util.*; |
| | | import com.dsh.competition.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | |
| | | if (paymentCompetitionVo.getPayType() == 1) {//微信 |
| | | Competition byId = competitionService.getById(paymentCompetitionVo.getId()); |
| | | Integer operatorId = byId.getOperatorId(); |
| | | int temp = 0; |
| | | if (operatorId!=null && operatorId != 0){ |
| | | temp =1; |
| | | // 运营商 |
| | | String s = storeClient.getmerchantNumberByOperatorId(operatorId); |
| | | if(!StringUtils.hasLength(s)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(s,"报名赛事",code, |
| | | "/base/competition/weChatPaymentCompetitionCallback1", |
| | | money.toString()); |
| | | }else{ |
| | | return payMoneyUtil.weixinpay("报名赛事", "", code, money.toString(), |
| | | "/base/competition/weChatPaymentCompetitionCallback", "APP", ""); |
| | | } |
| | | return weChatPaymentCompetition(code, money,temp,operatorId); |
| | | |
| | | } |
| | | |
| | | if (paymentCompetitionVo.getPayType() == 2) {//支付宝 |
| | |
| | | return aliPaymentCompetition(operatorId,smid,code, money); |
| | | } |
| | | // 获取该运营商的商户号 |
| | | String smid = storeClient.getSMIDByOperatorId(operatorId); |
| | | String smid = storeClient.getmerchantNumberAliByOperatorId(operatorId); |
| | | if (!StringUtils.hasLength(smid)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | return aliPaymentCompetition(operatorId,smid,code, money); |
| | | } |
| | | if (paymentCompetitionVo.getPayType() == 3) {//玩湃币 |
| | |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 赛事微信支付 |
| | | * |
| | |
| | | * @throws Exception |
| | | */ |
| | | public ResultUtil weChatPaymentCompetition(String code, BigDecimal money,Integer isFenZhang,Integer operatorId) throws Exception { |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("报名赛事"+"-"+isFenZhang, "", code, money.toString(), |
| | | // |
| | | // payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"报名赛事",code, |
| | | // "/base/competition/weChatPaymentCompetitionCallback1", |
| | | // Long.valueOf(money.toString())); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("报名赛事"+"-"+isFenZhang, "", code, money.toString(), |
| | | "/base/competition/weChatPaymentCompetitionCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>() |
| | | .eq("code", code).eq("payType", 1)); |
| | | if (paymentCompetition.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200 && paymentCompetition.getPayStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetition.setState(3); |
| | | userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId())); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetition.setPayStatus(2); |
| | | paymentCompetition.setPayTime(new Date()); |
| | | paymentCompetition.setPayOrderNo(transaction_id); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | |
| | | Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | competition.setApplicantsNumber(competition.getApplicantsNumber() + 1); |
| | | cttService.updateById(competition); |
| | | |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | return weixinpay; |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | |
| | | public ResultUtil aliPaymentCompetition(Integer operatorId,String smid,String code, BigDecimal money) throws Exception { |
| | | ResultUtil alipay = payMoneyUtil.alipay |
| | | (smid,"报名赛事", "", "", code, money.toString(), "/base/competition/aliPaymentCompetitionCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 2)); |
| | | if (paymentCompetition.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | if (resultUtil.getCode().equals("10000") && paymentCompetition.getPayStatus() == 1) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | // Map<String, String> data1 = resultUtil.getData(); |
| | | // String s = data1.get("tradeStatus"); |
| | | // String tradeNo = data1.get("tradeNo"); |
| | | String tradeNo = resultUtil.getTradeNo(); |
| | | String s = resultUtil.getTradeStatus(); |
| | | |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetition.setState(3); |
| | | userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId())); |
| | | |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetition.setPayStatus(2); |
| | | paymentCompetition.setPayTime(new Date()); |
| | | paymentCompetition.setPayOrderNo(tradeNo); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | |
| | | Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | competition.setApplicantsNumber(competition.getApplicantsNumber() + 1); |
| | | cttService.updateById(competition); |
| | | |
| | | // 结算资金到商户账号 |
| | | payMoneyUtil.confirm(smid,code,tradeNo,money.toString()); |
| | | if (operatorId == null || operatorId == 0){ |
| | | // 属于平台的赛事 资金不做冻结 不做分账处理 |
| | | payMoneyUtil.confirm1(smid,code,tradeNo,money.toString()); |
| | | }else{ |
| | | // 资金冻结 |
| | | payMoneyUtil.confirm(smid,code,tradeNo,money.toString()); |
| | | // 分账处理 |
| | | extracted(operatorId, money, tradeNo); |
| | | } |
| | | // 结算资金到商户账号 |
| | | //分账 |
| | | // moneyOut(tradeNo, tradeNo,code); |
| | | break; |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // if (alipay.getCode() == 200) { |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 2)); |
| | | // if (paymentCompetition.getPayStatus() == 2) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | // if (resultUtil.getCode().equals("10000") && paymentCompetition.getPayStatus() == 1) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | //// Map<String, String> data1 = resultUtil.getData(); |
| | | //// String s = data1.get("tradeStatus"); |
| | | //// String tradeNo = data1.get("tradeNo"); |
| | | // String tradeNo = resultUtil.getTradeNo(); |
| | | // String s = resultUtil.getTradeStatus(); |
| | | // |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // paymentCompetition.setAppUserId(null); |
| | | // paymentCompetition.setState(3); |
| | | // userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId())); |
| | | // |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // paymentCompetition.setAppUserId(null); |
| | | // paymentCompetition.setPayStatus(2); |
| | | // paymentCompetition.setPayTime(new Date()); |
| | | // paymentCompetition.setPayOrderNo(tradeNo); |
| | | // paymentCompetitionService.updateById(paymentCompetition); |
| | | // |
| | | // Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | // competition.setApplicantsNumber(competition.getApplicantsNumber() + 1); |
| | | // cttService.updateById(competition); |
| | | // |
| | | // // 结算资金到商户账号 |
| | | // payMoneyUtil.confirm(smid,code,tradeNo,money.toString()); |
| | | // if (operatorId == null || operatorId == 0){ |
| | | // // 属于平台的赛事 资金不做冻结 不做分账处理 |
| | | // payMoneyUtil.confirm1(smid,code,tradeNo,money.toString()); |
| | | // }else{ |
| | | // // 资金冻结 |
| | | // payMoneyUtil.confirm(smid,code,tradeNo,money.toString()); |
| | | // // 分账处理 |
| | | // extracted(operatorId, money, tradeNo); |
| | | // } |
| | | // // 结算资金到商户账号 |
| | | // //分账 |
| | | //// moneyOut(tradeNo, tradeNo,code); |
| | | // break; |
| | | // } |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return alipay; |
| | | } |
| | | private void extracted(Integer operatorId, BigDecimal money, String tradeNo) throws Exception { |
| | |
| | | //查出临时表里的数据,也去重 |
| | | List<Participant> list = this.list(new QueryWrapper<Participant>().eq("appUserId", uid).eq("state", 1)); |
| | | List<ParticipantVo> listVo = new ArrayList<>(); |
| | | for (Participant participant : list) { |
| | | ParticipantVo participantVo = new ParticipantVo(); |
| | | participantVo.setId(participant.getId()); |
| | | participantVo.setName(participant.getName()); |
| | | participantVo.setIdcard(participant.getIdcard()); |
| | | if (null != participant.getBirthday()){ |
| | | Integer age = Integer.valueOf(sdf_year.format(new Date())) - Integer.valueOf(sdf_year.format(participant.getBirthday())); |
| | | participantVo.setAge(age); |
| | | }else{ |
| | | participantVo.setAge(18); |
| | | |
| | | } |
| | | Student student = null; |
| | | if(ToolUtil.isNotEmpty(participant.getPhone())){ |
| | | student = studentClient.queryStudentByPhone(participant.getPhone()); |
| | | } |
| | | //设为不是学员 |
| | | participantVo.setIsStudent(0); |
| | | |
| | | if (null != student) { |
| | | Integer integer = coursePackagePaymentClient.queryResidueClassHour(student.getId()); |
| | | participantVo.setResidueClassHour(integer); |
| | | } else { |
| | | participantVo.setResidueClassHour(0); |
| | | } |
| | | participantVo.setPhone(participant.getPhone()); |
| | | participantVo.setHeight(participant.getHeight()); |
| | | participantVo.setWeight(participant.getWeight()); |
| | | participantVo.setHeadImg(participant.getHeadImg()); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); |
| | | if (null != participant.getBirthday()) { |
| | | String format = sdf.format(participant.getBirthday()); |
| | | participantVo.setBirthday(format); |
| | | } |
| | | participantVo.setGender(participant.getGender()); |
| | | if (participant.getIdcard() == null || participant.getIdcard().isEmpty()) { |
| | | listVo1.add(participantVo); |
| | | continue; |
| | | } |
| | | linkedHashMap.put(participant.getIdcard(), participantVo); |
| | | } |
| | | // for (Participant participant : list) { |
| | | // ParticipantVo participantVo = new ParticipantVo(); |
| | | // participantVo.setId(participant.getId()); |
| | | // participantVo.setName(participant.getName()); |
| | | // participantVo.setIdcard(participant.getIdcard()); |
| | | // if (null != participant.getBirthday()){ |
| | | // Integer age = Integer.valueOf(sdf_year.format(new Date())) - Integer.valueOf(sdf_year.format(participant.getBirthday())); |
| | | // participantVo.setAge(age); |
| | | // }else{ |
| | | // participantVo.setAge(18); |
| | | // |
| | | // } |
| | | // Student student = null; |
| | | // if(ToolUtil.isNotEmpty(participant.getPhone())){ |
| | | // student = studentClient.queryStudentByPhone(participant.getPhone()); |
| | | // } |
| | | // //设为不是学员 |
| | | // participantVo.setIsStudent(0); |
| | | // |
| | | // if (null != student) { |
| | | // Integer integer = coursePackagePaymentClient.queryResidueClassHour(student.getId()); |
| | | // participantVo.setResidueClassHour(integer); |
| | | // } else { |
| | | // participantVo.setResidueClassHour(0); |
| | | // } |
| | | // participantVo.setPhone(participant.getPhone()); |
| | | // participantVo.setHeight(participant.getHeight()); |
| | | // participantVo.setWeight(participant.getWeight()); |
| | | // participantVo.setHeadImg(participant.getHeadImg()); |
| | | // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); |
| | | // if (null != participant.getBirthday()) { |
| | | // String format = sdf.format(participant.getBirthday()); |
| | | // participantVo.setBirthday(format); |
| | | // } |
| | | // participantVo.setGender(participant.getGender()); |
| | | // if (participant.getIdcard() == null || participant.getIdcard().isEmpty()) { |
| | | // listVo1.add(participantVo); |
| | | // continue; |
| | | // } |
| | | // linkedHashMap.put(participant.getIdcard(), participantVo); |
| | | // } |
| | | |
| | | for (TStudent tStudent : tStudents) { |
| | | ParticipantVo participantVo = new ParticipantVo(); |
| | |
| | | import com.alipay.api.request.*; |
| | | import com.alipay.api.response.*; |
| | | import com.dsh.competition.util.httpClinet.HttpClientUtil; |
| | | import com.dsh.competition.util.wx.PartnerAppPrepay; |
| | | import com.dsh.competition.util.wx.WXPayUtility; |
| | | import com.dsh.competition.util.wx.WeChatV3SignUtil; |
| | | import com.dsh.competition.util.wx.WxV3PayConfig; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | SubMerchant subMerchant = new SubMerchant(); |
| | | subMerchant.setMerchantId(smid); |
| | | model.setSubMerchant(subMerchant); |
| | | ExtendParams extendParams = new ExtendParams(); |
| | | extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理 |
| | | model.setExtendParams(extendParams); |
| | | |
| | | request.setBizModel(model); |
| | | request.setNotifyUrl(callbackPath + notifyUrl); |
| | | |
| | |
| | | map.put("appid", appid); |
| | | map.put("mch_id", mchId); |
| | | map.put("nonce_str", nonce_str); |
| | | String temp = ""; |
| | | if (body.split("-").length>1){ |
| | | temp = body.split("-")[1]; |
| | | map.put("body", body.split("-")[0]); |
| | | }else{ |
| | | |
| | | map.put("body", body); |
| | | } |
| | | if (StringUtils.hasLength(temp) && temp.equals("1")){ |
| | | // 添加分账标识 |
| | | map.put("profit_sharing", "Y"); |
| | | } |
| | | |
| | | |
| | | |
| | | map.put("attach", attach);//存储订单id |
| | | map.put("out_trade_no", out_trade_no);//存储的订单code |
| | | map.put("total_fee", i); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl =callbackPath+ notifyUrl;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | String prepayId =""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | /** |
| | | * 发起分账 |
New file |
| | |
| | | package com.dsh.competition.util.wx; |
| | | |
| | | import com.google.gson.annotations.SerializedName; |
| | | import okhttp3.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.competition.util.wx; |
| | | |
| | | import com.google.gson.*; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.*; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.competition.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.competition.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.ArrayList; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.competition.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | <description>课程</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-java-core</artifactId> |
| | | <version>0.2.12</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>joda-time</groupId> |
| | | <artifactId>joda-time</artifactId> |
| | | <version>2.10.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>net.sf.json-lib</groupId> |
| | | <artifactId>json-lib</artifactId> |
| | | <version>2.4</version> |
| | |
| | | |
| | | |
| | | import cn.mb.cloud.common.data.controller.BaseController; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | |
| | | import com.dsh.course.model.vo.TQueryBenefitsVideosVO; |
| | | import com.dsh.course.service.*; |
| | | import com.dsh.course.util.*; |
| | | import com.dsh.course.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/course/weChatPaymentCourseCallback1") |
| | | public void weChatPaymentCourseCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String code = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | String attach = jsonInfo.getString("attach"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | |
| | | ResultUtil resultUtil = coursePackageService.paymentCourseCallback(code, transaction_id, attach); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | @ResponseBody |
| | | @PostMapping("/base/course/weChatPaymentCourseCallback") |
| | | public void weChatPaymentCourseCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 购买课程支付宝回调 |
| | |
| | | import com.dsh.course.model.vo.response.*; |
| | | import com.dsh.course.service.*; |
| | | import com.dsh.course.util.*; |
| | | import com.dsh.course.util.wx.WxV3PayConfig; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.obs.services.internal.ServiceException; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.*; |
| | |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | @PostMapping("/base/coursePackage/wechatRegisteredCoursesCallback1") |
| | | public void weChatCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(requestBody.toString()); |
| | | com.alibaba.fastjson.JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | com.alibaba.fastjson.JSONObject jsonInfo = (com.alibaba.fastjson.JSONObject) com.alibaba.fastjson.JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | ResultUtil resultUtil = packagePaymentService.insertVipPaymentCallback(out_trade_no, transaction_id); |
| | | if (resultUtil.getCode() == 200) { |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | /** |
| | | * 课包续课微信支付回调接口 |
| | | */ |
| | | @PostMapping("/base/coursePackage/wechatRegisteredCoursesCallback") |
| | | public void weChatCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 获取学员剩余课时 |
| | | * |
| | |
| | | */ |
| | | @PostMapping("/base/getProportionByOperatorId/{id}") |
| | | String getProportionByOperatorId(@PathVariable("id")Integer id); |
| | | |
| | | @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}") |
| | | String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id); |
| | | /** |
| | | * 根据运营商id获取对应运营商商户号 |
| | | * @return |
| | |
| | | import com.dsh.course.model.vo.response.*; |
| | | import com.dsh.course.service.*; |
| | | import com.dsh.course.util.*; |
| | | import com.dsh.course.util.wx.WxV3PayConfig; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | Integer storeId = byId.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | System.err.println("门店查询"+store); |
| | | // 是否分账 0否1是 |
| | | int isFenZhang= 1; |
| | | String merchantNumber = ""; |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | isFenZhang = 0; |
| | | } |
| | | System.err.println("拉起支付"); |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费"+"-"+isFenZhang, "", code, request.toString(), |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费", "", code, request.toString(), |
| | | "/base/coursePackage/wechatRegisteredCoursesCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | String finalCode = code; |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | .eq("code", finalCode).eq("state", 1)); |
| | | if (coursePackageOrder1.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(finalCode, ""); |
| | | if (resultUtil.getCode() == 200 && coursePackageOrder1.getPayStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | // coursePackageOrder1.setState(3); |
| | | // coursePackageOrderService.updateById(coursePackageOrder1); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | coursePackageOrder1.setPayStatus(2); |
| | | coursePackageOrder1.setOrderNumber(transaction_id); |
| | | coursePackageOrder1.setAppUserId(null); |
| | | coursePackageOrderService.updateById(coursePackageOrder1); |
| | | //修改课时有效期 |
| | | |
| | | CoursePackagePaymentConfig coursePackagePaymentConfig = coursePackagePaymentConfigService.getOne(new QueryWrapper<CoursePackagePaymentConfig>() |
| | | .eq("coursePackageId", coursePackageOrder1.getId()) |
| | | .eq("classHours", coursePackageOrder1.getClassHours()) |
| | | ); |
| | | coursePackageService.addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig); |
| | | |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | return weixinpay; |
| | | }else{ |
| | | String s = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | System.err.println("微信商户号"+s); |
| | | if(!StringUtils.hasLength(s)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(s,"课包续费",code, |
| | | "/base/coursePackage/wechatRegisteredCoursesCallback1",request.toString(),""); |
| | | } |
| | | return weixinpay; |
| | | } |
| | | private String smid = "2088330203191220";//平台支付宝商户号 |
| | | |
| | |
| | | }else if (store.getOperatorId() == 0){ |
| | | smid1 = smid; |
| | | }else{ |
| | | smid1 = storeClient.getSMIDByOperatorId(store.getOperatorId()); |
| | | smid1 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | } |
| | | if (!StringUtils.hasLength(smid1)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid1,"课包购买", "课包购买", "", code, request.toString(), |
| | | "/base/coursePackage/alipayRegisteredCoursesCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | String finalCode = code; |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | Thread.sleep(1000); |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | .eq("code", finalCode).eq("state", 1)); |
| | | if (coursePackageOrder1.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode); |
| | | if (null != alipayTradeQueryResponse) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | |
| | | String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | // coursePackageOrder1.setState(3); |
| | | // coursePackageOrderService.updateById(coursePackageOrder1); |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | coursePackagePayment.setPayStatus(2); |
| | | coursePackagePayment.setOrderNumber(tradeNo); |
| | | baseMapper1.updateById(coursePackagePayment); |
| | | // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号 |
| | | Integer coursePackageId = coursePackagePayment.getCoursePackageId(); |
| | | TCoursePackage byId = coursePackageService.getById(coursePackageId); |
| | | Store store = storeClient.queryStoreById(byId.getStoreId()); |
| | | if (store.getOperatorId() == null ){ |
| | | // 说明是平台的门店 无需冻结资金 不走分账 |
| | | payMoneyUtil.confirm1(smid,code,tradeNo,request.toString()); |
| | | }else if (store.getOperatorId() == 0){ |
| | | // 说明是平台的门店 |
| | | payMoneyUtil.confirm1(smid,code,tradeNo,request.toString()); |
| | | // 发起分账 |
| | | extracted(store, coursePackageOrder1, tradeNo); |
| | | }else{ |
| | | payMoneyUtil.confirm(storeClient.getSMIDByOperatorId(store.getOperatorId()),code,tradeNo,request.toString()); |
| | | // 发起分账 |
| | | extracted(store, coursePackageOrder1, tradeNo); |
| | | } |
| | | break; |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // if (alipay.getCode() == 200) { |
| | | // String finalCode = code; |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // Thread.sleep(1000); |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | // .eq("code", finalCode).eq("state", 1)); |
| | | // if (coursePackageOrder1.getPayStatus() == 2) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode); |
| | | // if (null != alipayTradeQueryResponse) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | // String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | // |
| | | // String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // //有待支付的订单,这里不处理 |
| | | //// coursePackageOrder1.setState(3); |
| | | //// coursePackageOrderService.updateById(coursePackageOrder1); |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // coursePackagePayment.setPayStatus(2); |
| | | // coursePackagePayment.setOrderNumber(tradeNo); |
| | | // baseMapper1.updateById(coursePackagePayment); |
| | | // // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号 |
| | | // Integer coursePackageId = coursePackagePayment.getCoursePackageId(); |
| | | // TCoursePackage byId = coursePackageService.getById(coursePackageId); |
| | | // Store store = storeClient.queryStoreById(byId.getStoreId()); |
| | | // if (store.getOperatorId() == null ){ |
| | | // // 说明是平台的门店 无需冻结资金 不走分账 |
| | | // payMoneyUtil.confirm1(smid,code,tradeNo,request.toString()); |
| | | // }else if (store.getOperatorId() == 0){ |
| | | // // 说明是平台的门店 |
| | | // payMoneyUtil.confirm1(smid,code,tradeNo,request.toString()); |
| | | // // 发起分账 |
| | | // extracted(store, coursePackageOrder1, tradeNo); |
| | | // }else{ |
| | | // payMoneyUtil.confirm(storeClient.getSMIDByOperatorId(store.getOperatorId()),code,tradeNo,request.toString()); |
| | | // // 发起分账 |
| | | // extracted(store, coursePackageOrder1, tradeNo); |
| | | // } |
| | | // break; |
| | | // } |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return alipay; |
| | | } |
| | | |
| | |
| | | Integer storeId = byId.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId()); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,nonce_str); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getData()); |
| | | } |
| | | } |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | |
| | | import com.dsh.course.model.*; |
| | | import com.dsh.course.service.*; |
| | | import com.dsh.course.util.*; |
| | | import com.dsh.course.util.wx.WxV3PayConfig; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | |
| | | TCoursePackage byId = coursePackageService.getById(paymentCourseVo.getId()); |
| | | Integer storeId = byId.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | // 是否分账 0否1是 |
| | | int isFenZhang= 1; |
| | | |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | isFenZhang = 0; |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("报名运动营", "", code, coursePackagePaymentConfig.getId().toString(), |
| | | "/base/course/weChatPaymentCourseCallback", "APP", ""); |
| | | return weixinpay; |
| | | }else{ |
| | | String s = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | System.err.println("微信商户号"+s); |
| | | if(!StringUtils.hasLength(s)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(s,"报名运动营",code, |
| | | "/base/course/weChatPaymentCourseCallback1",paymentPrice.toString(),""); |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("报名运动营"+"-"+isFenZhang, coursePackagePaymentConfig.getId().toString(), code, paymentPrice.toString(), |
| | | "/base/course/weChatPaymentCourseCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | String finalCode = code; |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | .eq("code", finalCode).eq("state", 1)); |
| | | if (coursePackageOrder1.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(finalCode, ""); |
| | | if (resultUtil.getCode() == 200 && coursePackageOrder1.getPayStatus() == 1) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | // coursePackageOrder1.setState(3); |
| | | // coursePackageOrderService.updateById(coursePackageOrder1); |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | coursePackageOrder1.setPayStatus(2); |
| | | coursePackageOrder1.setOrderNumber(transaction_id); |
| | | coursePackageOrder1.setAppUserId(null); |
| | | coursePackageOrderService.updateById(coursePackageOrder1); |
| | | //修改课时有效期 |
| | | addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig); |
| | | |
| | | |
| | | |
| | | |
| | | break; |
| | | |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | return weixinpay; |
| | | // return payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"报名运动营",code, |
| | | // "/base/course/weChatPaymentCourseCallback1",Long.valueOf(paymentPrice.toString()), |
| | | // coursePackagePaymentConfig.getId()+""); |
| | | } |
| | | |
| | | private String smid = "2088330203191220";//平台支付宝商户号 |
| | | private String smidVx = "2088330203191220";//平台微信商户号 |
| | | /** |
| | | * 课程支付宝支付 |
| | | * |
| | |
| | | if (store.getOperatorId()==0){ |
| | | smid1 = smid; |
| | | }else{ |
| | | smid1 = storeClient.getSMIDByOperatorId(store.getOperatorId()); |
| | | smid1 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId()); |
| | | } |
| | | } |
| | | if (!StringUtils.hasLength(smid1)){ |
| | | return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!"); |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid1,"报名运动营", "报名运动营", |
| | | coursePackagePaymentConfig.getId().toString(), |
| | | code, paymentPrice.toString(), "/base/course/aliPaymentCourseCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | String finalCode = code; |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | Thread.sleep(1000); |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | .eq("code", finalCode).eq("state", 1)); |
| | | if (coursePackageOrder1.getPayStatus() == 2) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode); |
| | | if (null != alipayTradeQueryResponse) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | |
| | | String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | //有待支付的订单,这里不处理 |
| | | // coursePackageOrder1.setState(3); |
| | | // if (alipay.getCode() == 200) { |
| | | // String finalCode = code; |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // Thread.sleep(1000); |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>() |
| | | // .eq("code", finalCode).eq("state", 1)); |
| | | // if (coursePackageOrder1.getPayStatus() == 2) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode); |
| | | // if (null != alipayTradeQueryResponse) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | // String s = alipayTradeQueryResponse.getTradeStatus(); |
| | | // |
| | | // String tradeNo = alipayTradeQueryResponse.getTradeNo(); |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // //有待支付的订单,这里不处理 |
| | | //// coursePackageOrder1.setState(3); |
| | | //// coursePackageOrderService.updateById(coursePackageOrder1); |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // coursePackageOrder1.setPayStatus(2); |
| | | // coursePackageOrder1.setOrderNumber(tradeNo); |
| | | // coursePackageOrder1.setAppUserId(null); |
| | | // coursePackageOrderService.updateById(coursePackageOrder1); |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | coursePackageOrder1.setPayStatus(2); |
| | | coursePackageOrder1.setOrderNumber(tradeNo); |
| | | coursePackageOrder1.setAppUserId(null); |
| | | coursePackageOrderService.updateById(coursePackageOrder1); |
| | | addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig); |
| | | moneyOut(tradeNo,tradeNo,finalCode); |
| | | // 根据课程id 查询这个课程属于哪个门店 属于哪个运营商 |
| | | Integer id = paymentCourseVo.getId(); |
| | | TCoursePackage byId = coursePackageService.getById(id); |
| | | Integer storeId = byId.getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | Integer operatorId = store.getOperatorId(); |
| | | if (operatorId==null){ |
| | | String smid ="2088330203191220"; |
| | | // 说明是平台的 不走分账 |
| | | // payMoneyUtil.confirm(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | break; |
| | | }else if (operatorId==0){ |
| | | String smid ="2088330203191220"; |
| | | // 说明是平台的 |
| | | payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | break; |
| | | }else{ |
| | | // 当前课程不属于门店 查询这个课程属于哪个门店 属于哪个运营商 |
| | | String smidByOperatorId = storeClient.getSMIDByOperatorId(operatorId); |
| | | payMoneyUtil.confirm(smidByOperatorId,finalCode,tradeNo,paymentPrice.toString()); |
| | | // 分账处理 |
| | | extracted(store, coursePackageOrder1, tradeNo); |
| | | break; |
| | | } |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig); |
| | | // moneyOut(tradeNo,tradeNo,finalCode); |
| | | // // 根据课程id 查询这个课程属于哪个门店 属于哪个运营商 |
| | | // Integer id = paymentCourseVo.getId(); |
| | | // TCoursePackage byId = coursePackageService.getById(id); |
| | | // Integer storeId = byId.getStoreId(); |
| | | // Store store = storeClient.queryStoreById(storeId); |
| | | // Integer operatorId = store.getOperatorId(); |
| | | // if (operatorId==null){ |
| | | // String smid ="2088330203191220"; |
| | | // // 说明是平台的 不走分账 |
| | | //// payMoneyUtil.confirm(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | // payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | // break; |
| | | // }else if (operatorId==0){ |
| | | // String smid ="2088330203191220"; |
| | | // // 说明是平台的 |
| | | // payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString()); |
| | | // break; |
| | | // }else{ |
| | | // // 当前课程不属于门店 查询这个课程属于哪个门店 属于哪个运营商 |
| | | // String smidByOperatorId = storeClient.getSMIDByOperatorId(operatorId); |
| | | // payMoneyUtil.confirm(smidByOperatorId,finalCode,tradeNo,paymentPrice.toString()); |
| | | // // 分账处理 |
| | | // extracted(store, coursePackageOrder1, tradeNo); |
| | | // break; |
| | | // } |
| | | // } |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return alipay; |
| | | } |
| | | |
| | |
| | | TCoursePackage byId = coursePackageService.getById(coursePackageOrder1.getCoursePackageId()); |
| | | Store store = storeClient.queryStoreById(byId.getStoreId()); |
| | | // moneyOut(trade_no,trade_no,code); |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId()); |
| | | String[] split = proportionByOperatorId.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(trade_no, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,nonce_str); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getData().toString()); |
| | | }else{ |
| | | coursePackageOrder1.setFenzhangNo(fenzhang.getData().toString()); |
| | | coursePackageOrder1.setFenzhangOrderNo(nonce_str); |
| | | coursePackageOrderService.updateById(coursePackageOrder1); |
| | | } |
| | | } |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | |
| | | import com.alipay.api.request.*; |
| | | import com.alipay.api.response.*; |
| | | import com.dsh.course.util.httpClinet.HttpClientUtil; |
| | | import com.dsh.course.util.wx.PartnerAppPrepay; |
| | | import com.dsh.course.util.wx.WXPayUtility; |
| | | import com.dsh.course.util.wx.WeChatV3SignUtil; |
| | | import com.dsh.course.util.wx.WxV3PayConfig; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | SubMerchant subMerchant = new SubMerchant(); |
| | | subMerchant.setMerchantId(smid); |
| | | model.setSubMerchant(subMerchant); |
| | | ExtendParams extendParams = new ExtendParams(); |
| | | extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理 |
| | | model.setExtendParams(extendParams); |
| | | request.setBizModel(model); |
| | | request.setNotifyUrl(callbackPath + notifyUrl); |
| | | try { |
| | |
| | | map.put("appid", appid); |
| | | map.put("mch_id", mchId); |
| | | map.put("nonce_str", nonce_str); |
| | | String temp = ""; |
| | | if (body.split("-").length>1){ |
| | | temp = body.split("-")[1]; |
| | | map.put("body", body.split("-")[0]); |
| | | }else{ |
| | | map.put("body", body); |
| | | } |
| | | if (StringUtils.hasLength(temp) && temp.equals("1")){ |
| | | // 添加分账标识 |
| | | map.put("profit_sharing", "Y"); |
| | | } |
| | | |
| | | |
| | | map.put("attach", attach);//存储订单id |
| | | map.put("out_trade_no", out_trade_no);//存储的订单code |
| | | map.put("total_fee", i); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee, String attach) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl =callbackPath+ notifyUrl;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | request.attach = attach; |
| | | String prepayId =""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | |
| | | /** |
New file |
| | |
| | | package com.dsh.course.util.wx; |
| | | |
| | | import com.google.gson.annotations.SerializedName; |
| | | import okhttp3.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.course.util.wx; |
| | | |
| | | import com.google.gson.*; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.*; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.course.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.course.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.ArrayList; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.course.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | } |
| | | @RequestMapping("/bindWx/{id}") |
| | | public String bindWx(Model model,@PathVariable("id") Integer id) { |
| | | |
| | | TOperator operator = operatorService.getById(id); |
| | | model.addAttribute("id",id); |
| | | model.addAttribute("merchantNumber",operatorService.getById(id).getMerchantNumber()); |
| | | model.addAttribute("name",operatorService.getById(id).getMerchantName()); |
| | | OperatorUser one = operatorUserService.lambdaQuery() |
| | | .eq(OperatorUser::getOperatorId, id).one(); |
| | | if (one!=null){ |
| | | model.addAttribute("wechatProportion",one.getWechatProportion()); |
| | | } |
| | | model.addAttribute("merchantNumber",operator.getMerchantNumber()); |
| | | model.addAttribute("merchantNumberAli",operator.getMerchantNumberAli()); |
| | | return PREFIX + "Operator_wx.html"; |
| | | } |
| | | /** |
| | |
| | | */ |
| | | @RequestMapping(value = "/merchantNumberWx") |
| | | @ResponseBody |
| | | public Object listAll(Integer id, String merchantNumber,String name,String wechatProportion) throws Exception { |
| | | public Object listAll(Integer id, String merchantNumber,String merchantNumberAli) throws Exception { |
| | | TOperator byId = operatorService.getById(id); |
| | | byId.setMerchantNumber(merchantNumber); |
| | | byId.setMerchantName(name); |
| | | byId.setMerchantNumberAli(merchantNumberAli); |
| | | operatorService.updateById(byId); |
| | | OperatorUser one = operatorUserService.lambdaQuery() |
| | | .eq(OperatorUser::getOperatorId, id).one(); |
| | | if (one!=null){ |
| | | one.setWechatProportion(wechatProportion); |
| | | operatorUserService.updateById(one); |
| | | } |
| | | ResultUtil resultUtil = payMoneyUtil.addReceiver(merchantNumber, name); |
| | | if (resultUtil.getCode() == 500){ |
| | | return ResultUtil.error(resultUtil.getMsg()); |
| | | } |
| | | return SUCCESS_TIP; |
| | | } |
| | | |
| | |
| | | @TableField("merchantNumber") |
| | | private String merchantNumber; |
| | | /** |
| | | * 支付宝商户号 |
| | | */ |
| | | @TableField("merchantNumberAli") |
| | | private String merchantNumberAli; |
| | | /** |
| | | * 微信商户全称 |
| | | */ |
| | | @TableField("merchantName") |
| | |
| | | <#button name="冻结" icon="fa-remove" clickFun="TSite.offShelf()" space="true"/> |
| | | <#button name="解冻" icon="fa-check" clickFun="TSite.onShelf()" space="true"/> |
| | | <#button name="重置密码" icon="fa-search" clickFun="TSite.reload()" space="true"/> |
| | | @if(shiro.hasPermission("/operator/merchantNumberWx")){ |
| | | <#button name="绑定微信商户号" icon="fa-search" clickFun="TSite.bindWx()" space="true"/> |
| | | @} |
| | | <#button name="绑定商户号" icon="fa-search" clickFun="TSite.bindWx()" space="true"/> |
| | | </div> |
| | | <#table id="TSiteTable"/> |
| | | </div> |
| | |
| | | <div class="col-sm-9"> |
| | | <input style="width: 300px" class="form-control" id="merchantNumber" value="${merchantNumber}" placeholder="请输入" type="number"> |
| | | </div> |
| | | <label class="col-sm-3 control-label">*微信商户全称:</label> |
| | | <label class="col-sm-3 control-label">*支付宝商户号:</label> |
| | | <div class="col-sm-9"> |
| | | <input style="width: 300px" class="form-control" id="name" value="${name}" placeholder="请输入" type="text"> |
| | | <input style="width: 300px" class="form-control" id="merchantNumberAli" value="${merchantNumberAli}" placeholder="请输入" type="number"> |
| | | </div> |
| | | <div class="form-group" > |
| | | |
| | | <label class="col-sm-3 control-label">*微信分账比例(%):</label> |
| | | <div class="col-sm-9"> |
| | | <input style="width: 300px" class="form-control" id="wechatProportion" value="${wechatProportion}" placeholder="请输入" type="number"> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | return {1:"全国",2:"指定区域"}[data] |
| | | } |
| | | }, |
| | | {title: '商户绑定状态', field: 'status', visible: true, align: 'center', valign: 'middle', |
| | | formatter: function (data,row) { |
| | | var btn = ""; |
| | | if(data==1) { |
| | | var str = '<button class="btn btn-outline btn-primary" onclick="TSite.Allocationratio('+row.id+')" >去绑定</button>' |
| | | btn = ['<p class="toolTip" style="overflow:hidden;white-space:nowrap;text-overflow:ellipsis;" title="" onfocus="TUser.tooltip()">' + str + '</p>'] |
| | | }else{ |
| | | var str = '<h3>已绑定</h3>' |
| | | btn = [str] |
| | | } |
| | | return btn; |
| | | } |
| | | }, |
| | | {title: '状态', field: 'state', visible: true, align: 'center', valign: 'middle', |
| | | formatter:function (data) { |
| | | return {1:"正常",2:"冻结",3:"删除"}[data] |
| | |
| | | } |
| | | |
| | | TSite.merchantNumber = function(){ |
| | | if($("#merchantNumber").val() == "" || $("#merchantNumber").val() == null){ |
| | | Feng.error("请填写微信商户号") |
| | | return; |
| | | } |
| | | if($("#name").val() == "" || $("#name").val() == null){ |
| | | Feng.error("请填写微信商户全称") |
| | | return; |
| | | } |
| | | if($("#wechatProportion").val() == "" || $("#wechatProportion").val() == null){ |
| | | Feng.error("请填写微信分账比例") |
| | | return; |
| | | } |
| | | |
| | | //提交信息 |
| | | var ajax = new $ax(Feng.ctxPath + "/operator/merchantNumberWx", function(data){ |
| | | console.log(data) |
| | |
| | | }); |
| | | ajax.set("id",$("#id").val()); |
| | | ajax.set("merchantNumber",$("#merchantNumber").val()); |
| | | ajax.set("name",$("#name").val()); |
| | | ajax.set("wechatProportion",$("#wechatProportion").val()); |
| | | ajax.set("merchantNumberAli",$("#merchantNumberAli").val()); |
| | | ajax.start(); |
| | | }; |
| | | TSite.oneChange = function (e) { |
| | |
| | | <name>其他</name> |
| | | <description>其他</description> |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-java-core</artifactId> |
| | | <version>0.2.12</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | <!-- 微信支付V3 目前新版本--> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.9</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>joda-time</groupId> |
| | | <artifactId>joda-time</artifactId> |
| | | <version>2.10.10</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.google.code.gson</groupId> |
| | | <artifactId>gson</artifactId> |
| | | </dependency> |
| | | <!-- OkHttp --> |
| | | <dependency> |
| | | <groupId>com.squareup.okhttp3</groupId> |
| | | <artifactId>okhttp</artifactId> |
| | | </dependency> |
| | | <!--日志处理--> |
| | | <dependency> |
| | | <groupId>cn.mb.cloud</groupId> |
| | |
| | | import com.dsh.other.service.*; |
| | | import com.dsh.other.util.*; |
| | | import com.dsh.other.util.httpClinet.HttpResult; |
| | | import com.dsh.other.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | |
| | | |
| | | |
| | | |
| | | private String smidVx = "2088330203191220";//平台微信商户号 |
| | | |
| | | @Autowired |
| | | private IOperatorUserService operatorUserService; |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/api/game/payGame") |
| | | @ApiOperation(value = "支付游戏", tags = {"用户—游戏"}) |
| | |
| | | if (null == uid) { |
| | | return ResultUtil.tokenErr(); |
| | | } |
| | | Site byId = siteService.getById(spaceId); |
| | | Store byId1 = storeService.getById(byId.getStoreId()); |
| | | Site site = siteService.getById(spaceId); |
| | | Store store = storeService.getById(site.getStoreId()); |
| | | |
| | | // 是否分账 0否1是 |
| | | int isFenZhang= 1; |
| | | String merchantNumber = ""; |
| | | if (byId1.getOperatorId()==null || byId1.getOperatorId()==0){ |
| | | if (store.getOperatorId()==null || store.getOperatorId()==0){ |
| | | // 平台 |
| | | isFenZhang = 0; |
| | | } |
| | |
| | | tGameRecord.setMoney(config.getCash()); |
| | | gameRecordService.updateById(tGameRecord); |
| | | String params = uid + "_" + gameId + "_" + spaceId + "_" + sutuId+"_"+code+"_"+configId + "_" + gameType; |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付-"+isFenZhang, params, code, config.getCash().toString(), |
| | | "/base/game/wechatPaymentGameCallback", "APP", ""); |
| | | // ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付-"+isFenZhang, params, code, config.getCash().toString(), |
| | | // "/base/game/wechatPaymentGameCallback", "APP", ""); |
| | | System.err.println("启动游戏支付"); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 15) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | List<TGameRecord> list = gameRecordService |
| | | .list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 1)); |
| | | TGameRecord one = list.get(0); |
| | | |
| | | if (one.getStatus() == 1) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, ""); |
| | | if (resultUtil.getCode() == 200 && one.getStatus() == 0) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | for (TGameRecord gameRecord : list) { |
| | | gameRecord.setStatus(1); |
| | | gameRecord.setOrderNo(transaction_id); |
| | | } |
| | | gameRecordService.updateBatchById(list); |
| | | Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId); |
| | | |
| | | break; |
| | | |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | if (store.getOperatorId()==null||store.getOperatorId()==0){ |
| | | // 平台 |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付", params, code, config.getCash().toString(), |
| | | "/base/game/wechatPaymentGameCallback", "APP", ""); |
| | | return weixinpay; |
| | | }else{ |
| | | String smidVx= operatorUserService.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(smidVx,"游戏支付" |
| | | ,code,"/base/huimin/callBack/wechatPaymentGameCallback1", |
| | | config.getCash().toString(),params); |
| | | } |
| | | return weixinpay; |
| | | |
| | | |
| | | |
| | | |
| | | } else if (type == 2) { |
| | | tGameRecord.setMoney(config.getCash()); |
| | | gameRecordService.updateById(tGameRecord); |
| | |
| | | // 是平台的 |
| | | smid1 = smid; |
| | | }else{ |
| | | String smidByOperatorId = siteService.getSMIDByOperatorId(operationId); |
| | | String smidByOperatorId = siteService.getmerchantNumberAliByOperatorId(operationId); |
| | | smid1 = smidByOperatorId; |
| | | } |
| | | |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid1,"游戏支付", "游戏支付", params, code, config.getCash().toString(), |
| | | "/base/game/aliPaymentGameCallback"); |
| | | if (alipay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 2)); |
| | | TGameRecord one = list.get(0); |
| | | if (one.getStatus() == 1) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | if (resultUtil.getCode().equals("10000") && one.getStatus() == 0) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | String tradeNo = resultUtil.getTradeNo(); |
| | | String s = resultUtil.getTradeStatus(); |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | for (TGameRecord gameRecord : list) { |
| | | gameRecord.setStatus(1); |
| | | gameRecord.setOrderNo(tradeNo); |
| | | |
| | | CourseCounsum courseCounsum = new CourseCounsum(); |
| | | courseCounsum.setChangeType(3); |
| | | courseCounsum.setInsertTime(new Date()); |
| | | courseCounsum.setReason("智慧球场;" + config.getCash()); |
| | | courseCounsum.setAppUserId(uid); |
| | | courseRecordClient.save(courseCounsum); |
| | | |
| | | } |
| | | gameRecordService.updateBatchById(list); |
| | | Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId); |
| | | // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号 |
| | | TGame game = gameService.getById(gameId); |
| | | String smid2=""; |
| | | Integer operationId = game.getOperationId(); |
| | | if (operationId==0){ |
| | | // 是平台的 |
| | | smid2 = smid; |
| | | }else{ |
| | | String smidByOperatorId = siteService.getSMIDByOperatorId(operationId); |
| | | smid2 = smidByOperatorId; |
| | | } |
| | | payMoneyUtil.confirm(smid2,code,tradeNo,config.getCash().toString()); |
| | | break; |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // if (alipay.getCode() == 200) { |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 2)); |
| | | // TGameRecord one = list.get(0); |
| | | // if (one.getStatus() == 1) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | // if (resultUtil.getCode().equals("10000") && one.getStatus() == 0) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | // String tradeNo = resultUtil.getTradeNo(); |
| | | // String s = resultUtil.getTradeStatus(); |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // for (TGameRecord gameRecord : list) { |
| | | // gameRecord.setStatus(1); |
| | | // gameRecord.setOrderNo(tradeNo); |
| | | // |
| | | // CourseCounsum courseCounsum = new CourseCounsum(); |
| | | // courseCounsum.setChangeType(3); |
| | | // courseCounsum.setInsertTime(new Date()); |
| | | // courseCounsum.setReason("智慧球场;" + config.getCash()); |
| | | // courseCounsum.setAppUserId(uid); |
| | | // courseRecordClient.save(courseCounsum); |
| | | // |
| | | // } |
| | | // gameRecordService.updateBatchById(list); |
| | | // Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId); |
| | | // // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号 |
| | | // TGame game = gameService.getById(gameId); |
| | | // String smid2=""; |
| | | // Integer operationId = game.getOperationId(); |
| | | // if (operationId==0){ |
| | | // // 是平台的 |
| | | // smid2 = smid; |
| | | // }else{ |
| | | // String smidByOperatorId = siteService.getSMIDByOperatorId(operationId); |
| | | // smid2 = smidByOperatorId; |
| | | // } |
| | | // payMoneyUtil.confirm(smid2,code,tradeNo,config.getCash().toString()); |
| | | // break; |
| | | // } |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return alipay; |
| | | } else if (type == 3) { |
| | | tGameRecord.setMoney(config.getPlayCoin()); |
| | |
| | | Integer integer = startGame(Integer.valueOf(s[0]), Integer.valueOf(s[6]), Integer.valueOf(s[1]), Integer.valueOf(s[2]), Integer.valueOf(s[3])); |
| | | TGameRecord one = gameRecordService.getOne(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1)); |
| | | System.err.println("游戏支付记录"+one); |
| | | if (one!=null){ |
| | | Integer gameId = one.getGameId(); |
| | | Site byId1 = siteService.getById(one.getSiteId()); |
| | | Store byId = storeService.getById(byId1.getStoreId()); |
| | | if (byId!=null){ |
| | | if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){ |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | OperatorUser operatorId = operatorUserService.getOne( |
| | | new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId()) |
| | | ); |
| | | if (operatorId.getWechatProportion() == null){ |
| | | operatorId.setWechatProportion("0"); |
| | | } |
| | | if (operatorId.getAlipayProportion() == null){ |
| | | operatorId.setAlipayProportion("0"); |
| | | } |
| | | String proportion= operatorId.getWechatProportion()+","+operatorId.getAlipayProportion(); |
| | | String[] split = proportion.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, one.getMoney().multiply(bigDecimal1), s2,nonce_str,"玩游戏分账"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | }else{ |
| | | one.setFenzhangNo(fenzhang.getData().toString()); |
| | | one.setFenzhangOrderNo(nonce_str); |
| | | one.setFenzhangAmount(one.getMoney().multiply(bigDecimal1)); |
| | | gameRecordService.updateById(one); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | // if (one!=null){ |
| | | // Integer gameId = one.getGameId(); |
| | | // Site byId1 = siteService.getById(one.getSiteId()); |
| | | // Store byId = storeService.getById(byId1.getStoreId()); |
| | | // if (byId!=null){ |
| | | // if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){ |
| | | // // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | // Thread.sleep(120000); |
| | | // // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | // OperatorUser operatorId = operatorUserService.getOne( |
| | | // new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId()) |
| | | // ); |
| | | // if (operatorId.getWechatProportion() == null){ |
| | | // operatorId.setWechatProportion("0"); |
| | | // } |
| | | // if (operatorId.getAlipayProportion() == null){ |
| | | // operatorId.setAlipayProportion("0"); |
| | | // } |
| | | // String proportion= operatorId.getWechatProportion()+","+operatorId.getAlipayProportion(); |
| | | // String[] split = proportion.split(","); |
| | | // String s1 = split[0]; |
| | | // if (!s1.equals("未设置")){ |
| | | // BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // // 分账比例 |
| | | // BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // // 微信商户号 |
| | | // String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | // String nonce_str = UUIDUtil.getRandomCode(16); |
| | | // |
| | | // ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, one.getMoney().multiply(bigDecimal1), s2,nonce_str,"玩游戏分账"); |
| | | // if (!fenzhang.getCode().equals(200)){ |
| | | // System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | // }else{ |
| | | // one.setFenzhangNo(fenzhang.getData().toString()); |
| | | // one.setFenzhangOrderNo(nonce_str); |
| | | // one.setFenzhangAmount(one.getMoney().multiply(bigDecimal1)); |
| | | // gameRecordService.updateById(one); |
| | | // } |
| | | // } |
| | | // } |
| | | // } |
| | | // |
| | | // } |
| | | |
| | | PrintWriter out = null; |
| | | try { |
| | |
| | | out.close(); |
| | | } |
| | | } |
| | | @ResponseBody |
| | | @PostMapping("/base/game/wechatPaymentGameCallback1") |
| | | public void wechatPaymentGameCallback1(HttpServletRequest request, HttpServletResponse response) throws Exception { |
| | | System.err.println("进入游戏回调"); |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String out_trade_no = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | String attach = jsonInfo.getString("attach"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1)); |
| | | for (TGameRecord gameRecord : list) { |
| | | gameRecord.setStatus(1); |
| | | gameRecord.setOrderNo(transaction_id); |
| | | CourseCounsum courseCounsum = new CourseCounsum(); |
| | | courseCounsum.setChangeType(3); |
| | | courseCounsum.setInsertTime(new Date()); |
| | | courseCounsum.setReason("智慧球场;" + gameRecord.getMoney()); |
| | | courseCounsum.setAppUserId(gameRecord.getUserId()); |
| | | courseRecordClient.save(courseCounsum); |
| | | |
| | | } |
| | | gameRecordService.updateBatchById(list); |
| | | String[] s = attach.split("_"); |
| | | Integer integer = startGame(Integer.valueOf(s[0]), Integer.valueOf(s[6]), Integer.valueOf(s[1]), Integer.valueOf(s[2]), Integer.valueOf(s[3])); |
| | | TGameRecord one = gameRecordService.getOne(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1)); |
| | | System.err.println("游戏支付记录" + one); |
| | | |
| | | PrintWriter out = null; |
| | | try { |
| | | out = response.getWriter(); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | out.print("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | }catch (Exception e){ |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/base/game/aliPaymentGameCallback") |
| | |
| | | import com.dsh.other.model.vo.siteVo.SiteSearchVO; |
| | | import com.dsh.other.service.*; |
| | | import com.dsh.other.util.*; |
| | | import com.dsh.other.util.wx.WxV3PayConfig; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; |
| | | import io.swagger.annotations.ApiImplicitParam; |
| | | import io.swagger.annotations.ApiImplicitParams; |
| | | import io.swagger.annotations.ApiOperation; |
| | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.BufferedReader; |
| | | import java.io.PrintWriter; |
| | | import java.math.BigDecimal; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | |
| | | @ResponseBody |
| | | public String getmerchantNumberByOperatorId(@PathVariable("id")Integer id) { |
| | | return siteService.getmerchantNumberByOperatorId(id); |
| | | } |
| | | |
| | | /** |
| | | * 通过运营商id 获取支付宝商户号 |
| | | * @param id |
| | | * @return |
| | | */ |
| | | @RequestMapping("/base/getmerchantNumberAliByOperatorId/{id}") |
| | | @ResponseBody |
| | | public String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id) { |
| | | return siteService.getmerchantNumberAliByOperatorId(id); |
| | | } |
| | | /** |
| | | * 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/site/weChatPaymentSiteCallback1") |
| | | public void weChatPaymentSiteCallback1(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | | System.err.println("微信回调"); |
| | | System.err.println("请求" + request); |
| | | BufferedReader reader = request.getReader(); |
| | | String string1 = reader.toString(); |
| | | System.err.println("请求reader" + string1); |
| | | StringBuilder requestBody = new StringBuilder(); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | requestBody.append(line); |
| | | } |
| | | System.err.println("全部请求体" + requestBody); |
| | | JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); |
| | | JSONObject resource = jsonObject.getJSONObject("resource"); |
| | | |
| | | AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); |
| | | String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), |
| | | resource.getString("ciphertext")); |
| | | System.err.println("微信解密的字符串信息" + decryptedData); |
| | | JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData); |
| | | String code = jsonInfo.getString("out_trade_no"); |
| | | String transaction_id = jsonInfo.getString("transaction_id"); |
| | | String trade_state = jsonInfo.getString("trade_state"); |
| | | if (trade_state.equals("SUCCESS")) { |
| | | SiteBooking siteBooking = siteBookingService.getOne(new QueryWrapper<SiteBooking>().eq("orderNo", code).eq("state", 1)); |
| | | if (siteBooking.getStatus() == 0) { |
| | | siteBooking.setPayTime(new Date()); |
| | | siteBooking.setStatus(1); |
| | | siteBooking.setPayOrderNo(transaction_id); |
| | | siteBookingService.updateById(siteBooking); |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 购买课程微信支付回调 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/site/weChatPaymentSiteCallback") |
| | | public void weChatPaymentSiteCallback(HttpServletRequest request, HttpServletResponse response) { |
| | | try { |
| | |
| | | siteBooking.setPayOrderNo(transaction_id); |
| | | siteBookingService.updateById(siteBooking); |
| | | Store byId = service.getById(siteBooking.getStoreId()); |
| | | if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){ |
| | | System.err.println("预约场地分账"); |
| | | // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | Thread.sleep(120000); |
| | | // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | OperatorUser operatorId = operatorUserService.getOne( |
| | | new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId()) |
| | | ); |
| | | if (operatorId.getWechatProportion() == null){ |
| | | operatorId.setWechatProportion("0"); |
| | | } |
| | | if (operatorId.getAlipayProportion() == null){ |
| | | operatorId.setAlipayProportion("0"); |
| | | } |
| | | String proportion= operatorId.getWechatProportion()+","+operatorId.getAlipayProportion(); |
| | | String[] split = proportion.split(","); |
| | | String s1 = split[0]; |
| | | if (!s1.equals("未设置")){ |
| | | BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // 分账比例 |
| | | BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // 微信商户号 |
| | | String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | BigDecimal bigDecimal2 = new BigDecimal(siteBooking.getPayMoney()); |
| | | ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"预约场地分账"); |
| | | if (!fenzhang.getCode().equals(200)){ |
| | | System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | }else{ |
| | | siteBooking.setFenzhangNo(fenzhang.getData().toString()); |
| | | siteBooking.setFenzhangOrderNo(nonce_str); |
| | | siteBooking.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1)); |
| | | siteBookingService.updateById(siteBooking); |
| | | } |
| | | } |
| | | } |
| | | // if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){ |
| | | // System.err.println("预约场地分账"); |
| | | // // 休眠两分钟后再调用分账接口 避免提示订单正在处理中 |
| | | // Thread.sleep(120000); |
| | | // // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例 |
| | | // OperatorUser operatorId = operatorUserService.getOne( |
| | | // new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId()) |
| | | // ); |
| | | // if (operatorId.getWechatProportion() == null){ |
| | | // operatorId.setWechatProportion("0"); |
| | | // } |
| | | // if (operatorId.getAlipayProportion() == null){ |
| | | // operatorId.setAlipayProportion("0"); |
| | | // } |
| | | // String proportion= operatorId.getWechatProportion()+","+operatorId.getAlipayProportion(); |
| | | // String[] split = proportion.split(","); |
| | | // String s1 = split[0]; |
| | | // if (!s1.equals("未设置")){ |
| | | // BigDecimal bigDecimal = new BigDecimal(s1); |
| | | // // 分账比例 |
| | | // BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2); |
| | | // // 微信商户号 |
| | | // String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | // String nonce_str = UUIDUtil.getRandomCode(16); |
| | | // BigDecimal bigDecimal2 = new BigDecimal(siteBooking.getPayMoney()); |
| | | // ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"预约场地分账"); |
| | | // if (!fenzhang.getCode().equals(200)){ |
| | | // System.err.println("分账失败 原因是:"+fenzhang.getMsg()); |
| | | // }else{ |
| | | // siteBooking.setFenzhangNo(fenzhang.getData().toString()); |
| | | // siteBooking.setFenzhangOrderNo(nonce_str); |
| | | // siteBooking.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1)); |
| | | // siteBookingService.updateById(siteBooking); |
| | | // } |
| | | // } |
| | | // } |
| | | PrintWriter out = response.getWriter(); |
| | | out.write(result); |
| | | out.flush(); |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 预约场地支付宝回调 |
| | | * |
| | |
| | | String getSMIDByOperatorId(@Param("id")Integer id); |
| | | String getmerchantNumberByOperatorId(@Param("id")Integer id); |
| | | |
| | | String getmerchantNumberAliByOperatorId(@Param("id")Integer id); |
| | | } |
| | |
| | | */ |
| | | public interface TOperatorUserMapper extends BaseMapper<OperatorUser> { |
| | | |
| | | List<Map<String, Object>> listAll(@Param("page") Page<Map<String, Object>> page, |
| | | @Param("province") String province, |
| | | @Param("city") String city, |
| | | @Param("userName") String userName, |
| | | @Param("phone") String phone, |
| | | @Param("platform") Integer platform, |
| | | @Param("type") Integer type, |
| | | @Param("state") Integer state); |
| | | |
| | | |
| | | String getmerchantNumberByOperatorId(@Param("operatorId") Integer operatorId); |
| | | } |
| | |
| | | */ |
| | | public interface IOperatorUserService extends IService<OperatorUser> { |
| | | |
| | | List<Map<String, Object>> listAll(Page<Map<String, Object>> page, String province, String city, String userName, String phone, Integer platform, Integer type, Integer state); |
| | | |
| | | String getmerchantNumberByOperatorId(Integer operatorId); |
| | | |
| | | } |
| | |
| | | String getSMIDByOperatorId(Integer id); |
| | | String getmerchantNumberByOperatorId(Integer id); |
| | | |
| | | String getmerchantNumberAliByOperatorId(Integer id); |
| | | } |
| | |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | | |
| | | private String smidVx = "2088330203191220";//平台微信商户号 |
| | | /** |
| | | * 课程微信支付 |
| | | * |
| | |
| | | String merchantNumber = ""; |
| | | if (byId.getOperatorId()==null || byId.getOperatorId()==0){ |
| | | // 平台 |
| | | isFenZhang = 0; |
| | | } |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("预约场地-"+isFenZhang, id.toString(), code, paymentPrice.toString(), |
| | | ResultUtil weixinpay = payMoneyUtil.weixinpay("预约场地-"+isFenZhang, id.toString(), code, paymentPrice.toString(), |
| | | "/base/site/weChatPaymentSiteCallback", "APP", ""); |
| | | if (weixinpay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | SiteBooking siteBooking = siteBookingService.getById(id); |
| | | if (siteBooking.getStatus() != 0) { |
| | | break; |
| | | } |
| | | ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(siteBooking.getOrderNo(), ""); |
| | | if (resultUtil.getCode() == 200 && siteBooking.getStatus() == 0) { |
| | | /** |
| | | * SUCCESS—支付成功, |
| | | * REFUND—转入退款, |
| | | * NOTPAY—未支付, |
| | | * CLOSED—已关闭, |
| | | * REVOKED—已撤销(刷卡支付), |
| | | * USERPAYING--用户支付中, |
| | | * PAYERROR--支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | Map<String, String> data1 = resultUtil.getData(); |
| | | String s = data1.get("trade_state"); |
| | | String transaction_id = data1.get("transaction_id"); |
| | | if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) { |
| | | break; |
| | | } |
| | | if ("SUCCESS".equals(s)) { |
| | | siteBooking.setPayTime(new Date()); |
| | | siteBooking.setStatus(1); |
| | | siteBooking.setPayOrderNo(transaction_id); |
| | | siteBookingService.updateById(siteBooking); |
| | | |
| | | |
| | | |
| | | break; |
| | | } |
| | | if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | return weixinpay; |
| | | }else{ |
| | | String smidVx= operatorUserService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | return payMoneyUtil.weixinpayV3(smidVx,"预约场地" |
| | | ,code,"/base/site/weChatPaymentSiteCallback1", |
| | | paymentPrice.toString(),id.toString()); |
| | | } |
| | | return weixinpay; |
| | | |
| | | |
| | | } |
| | | |
| | | private String smid = "2088330203191220";//平台支付宝商户号 |
| | |
| | | }else if (operatorId == 0){ |
| | | smid1 = smid; |
| | | }else{ |
| | | smid1 = siteService.getSMIDByOperatorId(operatorId); |
| | | smid1 = siteService.getmerchantNumberAliByOperatorId(operatorId); |
| | | |
| | | } |
| | | ResultUtil alipay = payMoneyUtil.alipay(smid1,"预约场地", "预约场地", id.toString(), code, paymentPrice.toString(), "/base/site/aliPaymentSiteCallback"); |
| | | System.out.println("预约场地----" + alipay.getCode()); |
| | | if (alipay.getCode() == 200) { |
| | | new Thread(new Runnable() { |
| | | @Override |
| | | public void run() { |
| | | try { |
| | | int num = 1; |
| | | int wait = 0; |
| | | while (num <= 10) { |
| | | int min = 5000; |
| | | wait += (min * num); |
| | | Thread.sleep(wait); |
| | | SiteBooking siteBooking = siteBookingService.getById(id); |
| | | if (siteBooking.getStatus() != 0) { |
| | | break; |
| | | } |
| | | AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | if (resultUtil.getCode().equals("10000") && siteBooking.getStatus() == 0) { |
| | | /** |
| | | * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | * TRADE_SUCCESS(交易支付成功)、 |
| | | * TRADE_FINISHED(交易结束,不可退款) |
| | | */ |
| | | // Map<String, String> data1 = resultUtil.getData(); |
| | | // String s = data1.get("tradeStatus"); |
| | | String tradeNo = resultUtil.getTradeNo(); |
| | | String s = resultUtil.getTradeStatus(); |
| | | System.out.println("ssssss" + s); |
| | | if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | break; |
| | | } |
| | | if ("TRADE_SUCCESS".equals(s)) { |
| | | siteBooking.setPayTime(new Date()); |
| | | siteBooking.setStatus(1); |
| | | siteBooking.setPayOrderNo(tradeNo); |
| | | siteBookingService.updateById(siteBooking); |
| | | System.err.println("======完成支付"); |
| | | // 判断预约的门店 属于哪个运营商 |
| | | Integer storeId = siteBooking.getStoreId(); |
| | | Store byId = storeService.getById(storeId); |
| | | Integer operatorId = byId.getOperatorId(); |
| | | String smid1 = ""; |
| | | if (operatorId == null || operatorId == 0){ |
| | | // 平台的门店 不冻结资金不做分账处理 |
| | | payMoneyUtil.confirm1(smid1,code,tradeNo,paymentPrice.toString()); |
| | | }else{ |
| | | smid1 = siteService.getSMIDByOperatorId(operatorId); |
| | | payMoneyUtil.confirm(smid1,code,tradeNo,paymentPrice.toString()); |
| | | // 分账 |
| | | extracted(operatorId, new BigDecimal(paymentPrice.toString()), tradeNo); |
| | | } |
| | | |
| | | break; |
| | | } |
| | | if ("WAIT_BUYER_PAY".equals(s)) { |
| | | num++; |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | }).start(); |
| | | } |
| | | // if (alipay.getCode() == 200) { |
| | | // new Thread(new Runnable() { |
| | | // @Override |
| | | // public void run() { |
| | | // try { |
| | | // int num = 1; |
| | | // int wait = 0; |
| | | // while (num <= 10) { |
| | | // int min = 5000; |
| | | // wait += (min * num); |
| | | // Thread.sleep(wait); |
| | | // SiteBooking siteBooking = siteBookingService.getById(id); |
| | | // if (siteBooking.getStatus() != 0) { |
| | | // break; |
| | | // } |
| | | // AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code); |
| | | // if (resultUtil.getCode().equals("10000") && siteBooking.getStatus() == 0) { |
| | | // /** |
| | | // * WAIT_BUYER_PAY(交易创建,等待买家付款)、 |
| | | // * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、 |
| | | // * TRADE_SUCCESS(交易支付成功)、 |
| | | // * TRADE_FINISHED(交易结束,不可退款) |
| | | // */ |
| | | //// Map<String, String> data1 = resultUtil.getData(); |
| | | //// String s = data1.get("tradeStatus"); |
| | | // String tradeNo = resultUtil.getTradeNo(); |
| | | // String s = resultUtil.getTradeStatus(); |
| | | // System.out.println("ssssss" + s); |
| | | // if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) { |
| | | // break; |
| | | // } |
| | | // if ("TRADE_SUCCESS".equals(s)) { |
| | | // siteBooking.setPayTime(new Date()); |
| | | // siteBooking.setStatus(1); |
| | | // siteBooking.setPayOrderNo(tradeNo); |
| | | // siteBookingService.updateById(siteBooking); |
| | | // System.err.println("======完成支付"); |
| | | // // 判断预约的门店 属于哪个运营商 |
| | | // Integer storeId = siteBooking.getStoreId(); |
| | | // Store byId = storeService.getById(storeId); |
| | | // Integer operatorId = byId.getOperatorId(); |
| | | // String smid1 = ""; |
| | | // if (operatorId == null || operatorId == 0){ |
| | | // // 平台的门店 不冻结资金不做分账处理 |
| | | // payMoneyUtil.confirm1(smid1,code,tradeNo,paymentPrice.toString()); |
| | | // }else{ |
| | | // smid1 = siteService.getSMIDByOperatorId(operatorId); |
| | | // payMoneyUtil.confirm(smid1,code,tradeNo,paymentPrice.toString()); |
| | | // // 分账 |
| | | // extracted(operatorId, new BigDecimal(paymentPrice.toString()), tradeNo); |
| | | // } |
| | | // |
| | | // break; |
| | | // } |
| | | // if ("WAIT_BUYER_PAY".equals(s)) { |
| | | // num++; |
| | | // } |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // }).start(); |
| | | // } |
| | | return alipay; |
| | | } |
| | | |
| | |
| | | public String getmerchantNumberByOperatorId(Integer id) { |
| | | return siteMapper.getmerchantNumberByOperatorId(id); |
| | | } |
| | | |
| | | @Override |
| | | public String getmerchantNumberAliByOperatorId(Integer id) { |
| | | return siteMapper.getmerchantNumberAliByOperatorId(id); |
| | | } |
| | | } |
| | |
| | | */ |
| | | @Service |
| | | public class TOperatorUserServiceImpl extends ServiceImpl<TOperatorUserMapper, OperatorUser> implements IOperatorUserService { |
| | | |
| | | @Override |
| | | public List<Map<String, Object>> listAll(Page<Map<String, Object>> page, String province, String city, String userName, String phone, Integer platform, Integer type, Integer state) { |
| | | return this.baseMapper.listAll(page, province, city, userName, phone, platform, type, state); |
| | | public String getmerchantNumberByOperatorId(Integer operatorId) { |
| | | return this.baseMapper.getmerchantNumberByOperatorId(operatorId); |
| | | } |
| | | } |
| | |
| | | import com.alipay.api.msg.MsgHandler; |
| | | import com.alipay.api.request.*; |
| | | import com.alipay.api.response.*; |
| | | import com.dsh.other.util.wx.PartnerAppPrepay; |
| | | import com.dsh.other.util.wx.WXPayUtility; |
| | | import com.dsh.other.util.wx.WeChatV3SignUtil; |
| | | import com.dsh.other.util.wx.WxV3PayConfig; |
| | | import lombok.Synchronized; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | |
| | | private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书 |
| | | |
| | | |
| | | |
| | | |
| | | public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) { |
| | | public ResultUtil confirm(String smid, String code, String outTradeNo, String amount) { |
| | | |
| | | AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", |
| | | aliAppid, |
| | |
| | | "RSA2"); |
| | | AlipayTradeSettleConfirmRequest request = new AlipayTradeSettleConfirmRequest(); |
| | | request.setBizContent("{" + |
| | | " \"out_request_no\":\""+code+"\"," + |
| | | " \"trade_no\":\""+outTradeNo+"\"," + |
| | | " \"out_request_no\":\"" + code + "\"," + |
| | | " \"trade_no\":\"" + outTradeNo + "\"," + |
| | | " \"settle_info\":{" + |
| | | " \"settle_detail_infos\":[" + |
| | | " {" + |
| | | " \"trans_in_type\":\"defaultSettle\"," + |
| | | " \"settle_entity_id\":\""+smid+"\"," + |
| | | " \"settle_entity_id\":\"" + smid + "\"," + |
| | | " \"settle_entity_type\":\"SecondMerchant\"," + |
| | | " \"amount\":"+amount+"," + |
| | | " \"amount\":" + amount + "," + |
| | | " }" + |
| | | " ]" + |
| | | " }," + |
| | |
| | | } catch (AlipayApiException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if(response.isSuccess()){ |
| | | if (response.isSuccess()) { |
| | | System.out.println("调用成功"); |
| | | return ResultUtil.success(); |
| | | } else { |
| | |
| | | return ResultUtil.error("出现问题啦"); |
| | | } |
| | | } |
| | | |
| | | // 属于平台的运营商 因为无需分账不冻结资金 |
| | | public ResultUtil confirm1(String smid,String code, String outTradeNo, String amount) { |
| | | public ResultUtil confirm1(String smid, String code, String outTradeNo, String amount) { |
| | | AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", |
| | | aliAppid, |
| | | appPrivateKey, |
| | |
| | | "RSA2"); |
| | | AlipayTradeSettleConfirmRequest request = new AlipayTradeSettleConfirmRequest(); |
| | | request.setBizContent("{" + |
| | | " \"out_request_no\":\""+code+"\"," + |
| | | " \"trade_no\":\""+outTradeNo+"\"," + |
| | | " \"out_request_no\":\"" + code + "\"," + |
| | | " \"trade_no\":\"" + outTradeNo + "\"," + |
| | | " \"settle_info\":{" + |
| | | " \"settle_detail_infos\":[" + |
| | | " {" + |
| | | " \"trans_in_type\":\"defaultSettle\"," + |
| | | " \"settle_entity_id\":\""+smid+"\"," + |
| | | " \"settle_entity_id\":\"" + smid + "\"," + |
| | | " \"settle_entity_type\":\"SecondMerchant\"," + |
| | | " \"amount\":"+amount+"," + |
| | | " \"amount\":" + amount + "," + |
| | | " }" + |
| | | " ]" + |
| | | " }," + |
| | |
| | | } catch (AlipayApiException e) { |
| | | e.printStackTrace(); |
| | | } |
| | | if(response.isSuccess()){ |
| | | if (response.isSuccess()) { |
| | | System.out.println("调用成功"); |
| | | return ResultUtil.success(); |
| | | } else { |
| | |
| | | return ResultUtil.error("出现问题啦"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 支付宝支付 |
| | | */ |
| | | public ResultUtil alipay(String smid,String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | public ResultUtil alipay(String smid, String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) { |
| | | //// //构造client |
| | | // CertAlipayRequest certAlipayRequest = new CertAlipayRequest (); |
| | | // //设置网关地址 |
| | |
| | | |
| | | |
| | | Map<String, String> map = new HashMap<>(); |
| | | System.err.println("返回码"+params); |
| | | if (params.get("trade_status").equals("TRADE_SUCCESS")){ |
| | | System.err.println("返回码" + params); |
| | | if (params.get("trade_status").equals("TRADE_SUCCESS")) { |
| | | String out_trade_no = params.get("out_trade_no"); |
| | | String subject = params.get("subject"); |
| | | String total_amount = params.get("total_amount"); |
| | |
| | | map.put("passback_params", passback_params);//回传参数 |
| | | System.err.println("回调map"); |
| | | return map; |
| | | }else{ |
| | | } else { |
| | | return null; |
| | | } |
| | | } |
| | |
| | | map.put("mch_id", mchId); |
| | | map.put("nonce_str", nonce_str); |
| | | String temp = ""; |
| | | if (body.split("-").length>1){ |
| | | temp = body.split("-")[1]; |
| | | map.put("body", body.split("-")[0]); |
| | | }else{ |
| | | map.put("body", body); |
| | | } |
| | | if (StringUtils.hasLength(temp) && temp.equals("1")){ |
| | | // 添加分账标识 |
| | | map.put("profit_sharing", "Y"); |
| | | } |
| | | map.put("body", body); |
| | | map.put("attach", attach);//存储订单id |
| | | map.put("out_trade_no", out_trade_no);//存储的订单code |
| | | map.put("total_fee", i); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | |
| | | public ResultUtil weixinpayV3(String subMchid, String description, String outTradeNo, String notifyUrl, String totalFee, String attach) throws Exception { |
| | | int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue(); |
| | | |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest(); |
| | | request.spAppid = appid;// appid |
| | | request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号 |
| | | request.subMchid = subMchid;// 子商户号 |
| | | request.description = description;// 描述 |
| | | request.outTradeNo = outTradeNo;// 订单号 |
| | | request.notifyUrl = callbackPath+notifyUrl;// 回调地址 |
| | | request.attach = attach;// 回调地址 |
| | | request.amount = new PartnerAppPrepay.CommonAmountInfo(); |
| | | request.amount.total = (long) i;// 金额 单位分 |
| | | request.amount.currency = "CNY"; |
| | | String prepayId = ""; |
| | | Map<String, Object> map3 = new HashMap<>(); |
| | | try { |
| | | PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | prepayId = response.prepayId; |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | map3.put("appid", appid); |
| | | map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); |
| | | String nonce_str = UUIDUtil.getRandomCode(16); |
| | | map3.put("noncestr", nonce_str); |
| | | map3.put("prepayid", prepayId); |
| | | // 构造待签名字符串 |
| | | String message = WeChatV3SignUtil.buildSignMessage(map3); |
| | | // 私钥路径(pem 文件) |
| | | String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | // String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem"; |
| | | // 生成签名 |
| | | String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath); |
| | | map3.put("sign", sign); |
| | | map3.put("package", "Sign=WXPay"); |
| | | map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号 |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | |
| | | /** |
| | | * 发起分账 |
| | | * |
| | | * @param order 微信订单号 |
| | | * @return |
| | | */ |
| | | public ResultUtil fenzhang(String order,BigDecimal amount,String merchantNumber, |
| | | String nonce_str,String description) throws Exception { |
| | | public ResultUtil fenzhang(String order, BigDecimal amount, String merchantNumber, |
| | | String nonce_str, String description) throws Exception { |
| | | Map<String, Object> map = new HashMap<>(); |
| | | map.put("mch_id", mchId); |
| | | map.put("appid", appid); |
| | |
| | | JSONObject jsonObject = new JSONObject(body); |
| | | JSONArray objects = new JSONArray(); |
| | | objects.add(jsonObject); |
| | | map.put("receivers",objects.toJSONString()); |
| | | map.put("receivers", objects.toJSONString()); |
| | | String s = this.weixinSignature1(map); |
| | | map.put("sign", s); |
| | | String url = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing"; |
| | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | System.err.println("分账请求"+body1); |
| | | System.err.println("分账请求" + body1); |
| | | //将结果xml解析成map |
| | | body1 = body1.replaceAll("<!\\[CDATA\\[", ""); |
| | | body1 = body1.replaceAll("]]>", ""); |
| | |
| | | return ResultUtil.error(map1.get("return_msg"), new JSONObject()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 发起分账回退 |
| | | * |
| | | * @param order 微信订单号 |
| | | * @return |
| | | */ |
| | | public ResultUtil fenzhangRefund(String order,BigDecimal amount,String merchantNumber, |
| | | public ResultUtil fenzhangRefund(String order, BigDecimal amount, String merchantNumber, |
| | | String nonce_str, |
| | | String fenzhangRefundNo) throws Exception { |
| | | Map<String, Object> map = new HashMap<>(); |
| | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | System.err.println("分账金额回退"+body1); |
| | | System.err.println("分账金额回退" + body1); |
| | | //将结果xml解析成map |
| | | body1 = body1.replaceAll("<!\\[CDATA\\[", ""); |
| | | body1 = body1.replaceAll("]]>", ""); |
| | |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * sha256_HMAC加密 |
| | | * |
| | | * @param message 消息 |
| | | * @param secret 秘钥 |
| | | * @return 加密后字符串 |
| | | */ |
| | | public String sha256_HMAC(String message, String secret) { |
| | | public String sha256_HMAC(String message, String secret) { |
| | | String hash = ""; |
| | | try { |
| | | Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import com.google.gson.annotations.SerializedName; |
| | | import okhttp3.*; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.security.PrivateKey; |
| | | import java.security.PublicKey; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * App下单 |
| | | */ |
| | | public class PartnerAppPrepay { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/pay/partner/transactions/app"; |
| | | |
| | | // public static void main(String[] args) { |
| | | // // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // PartnerAppPrepay client = new PartnerAppPrepay( |
| | | // "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | // "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924 |
| | | // "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | // "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589 |
| | | // "E:\\wanpai\\pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | // ); |
| | | // |
| | | // PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest(); |
| | | // request.spAppid = "wx41d32f362ba0f911"; |
| | | // request.spMchid = WxV3PayConfig.Mch_ID; |
| | | // request.subMchid = "1720719391"; |
| | | // request.description = "Image形象店-深圳腾大-QQ公仔"; |
| | | // request.outTradeNo = "12177525012014070332333680182"; |
| | | // request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php"; |
| | | //// request.goodsTag = "WXG"; |
| | | //// request.settleInfo = new PartnerSettleInfo(); |
| | | //// request.settleInfo.profitSharing = false; |
| | | // request.amount = new CommonAmountInfo(); |
| | | // request.amount.total = 100L; |
| | | // request.amount.currency = "CNY"; |
| | | //// request.detail = new CouponInfo(); |
| | | //// request.detail.costPrice = 1L; |
| | | //// request.detail.invoiceId = "wx123"; |
| | | //// request.detail.goodsDetail = new ArrayList<>(); |
| | | //// { |
| | | //// GoodsDetail item0 = new GoodsDetail(); |
| | | //// item0.merchantGoodsId = "1246464644"; |
| | | //// item0.wechatpayGoodsId = "1001"; |
| | | //// item0.goodsName = "iPhone6s 16G"; |
| | | //// item0.quantity = 1L; |
| | | //// item0.unitPrice = 528800L; |
| | | //// request.detail.goodsDetail.add(item0); |
| | | //// }; |
| | | //// request.sceneInfo = new CommonSceneInfo(); |
| | | //// request.sceneInfo.payerClientIp = "14.23.150.211"; |
| | | //// request.sceneInfo.deviceId = "013467007045764"; |
| | | //// request.sceneInfo.storeInfo = new StoreInfo(); |
| | | //// request.sceneInfo.storeInfo.id = "0001"; |
| | | //// request.sceneInfo.storeInfo.name = "腾讯大厦分店"; |
| | | //// request.sceneInfo.storeInfo.areaCode = "440305"; |
| | | //// request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号"; |
| | | // try { |
| | | // PartnerAPIv3AppPrepayResponse response = client.run(request); |
| | | // |
| | | // // TODO: 请求成功,继续业务逻辑 |
| | | // System.err.println("微信申请成功,预支付ID: " + response.prepayId); |
| | | // } catch (WXPayUtility.ApiException e) { |
| | | // // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | |
| | | public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) { |
| | | String uri = PATH; |
| | | String reqBody = WXPayUtility.toJson(request); |
| | | |
| | | Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); |
| | | reqBuilder.addHeader("Accept", "application/json"); |
| | | reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); |
| | | reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody)); |
| | | reqBuilder.addHeader("Content-Type", "application/json"); |
| | | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody); |
| | | reqBuilder.method(METHOD, requestBody); |
| | | Request httpRequest = reqBuilder.build(); |
| | | |
| | | // 发送HTTP请求 |
| | | OkHttpClient client = new OkHttpClient.Builder().build(); |
| | | try (Response httpResponse = client.newCall(httpRequest).execute()) { |
| | | String respBody = WXPayUtility.extractBody(httpResponse); |
| | | if (httpResponse.code() >= 200 && httpResponse.code() < 300) { |
| | | // 2XX 成功,验证应答签名 |
| | | WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, |
| | | httpResponse.headers(), respBody); |
| | | // 从HTTP应答报文构建返回数据 |
| | | return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class); |
| | | } else { |
| | | throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException("Sending request to " + uri + " failed.", e); |
| | | } |
| | | } |
| | | |
| | | private final String mchid; |
| | | private final String certificateSerialNo; |
| | | private final PrivateKey privateKey; |
| | | private final String wechatPayPublicKeyId; |
| | | private final PublicKey wechatPayPublicKey; |
| | | |
| | | public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { |
| | | this.mchid = mchid; |
| | | this.certificateSerialNo = certificateSerialNo; |
| | | this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); |
| | | this.wechatPayPublicKeyId = wechatPayPublicKeyId; |
| | | this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); |
| | | } |
| | | |
| | | public static class PartnerSettleInfo { |
| | | @SerializedName("profit_sharing") |
| | | public Boolean profitSharing; |
| | | } |
| | | |
| | | public static class CommonAmountInfo { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public static class CommonSceneInfo { |
| | | @SerializedName("payer_client_ip") |
| | | public String payerClientIp; |
| | | |
| | | @SerializedName("device_id") |
| | | public String deviceId; |
| | | |
| | | @SerializedName("store_info") |
| | | public StoreInfo storeInfo; |
| | | } |
| | | |
| | | public static class GoodsDetail { |
| | | @SerializedName("merchant_goods_id") |
| | | public String merchantGoodsId; |
| | | |
| | | @SerializedName("wechatpay_goods_id") |
| | | public String wechatpayGoodsId; |
| | | |
| | | @SerializedName("goods_name") |
| | | public String goodsName; |
| | | |
| | | @SerializedName("quantity") |
| | | public Long quantity; |
| | | |
| | | @SerializedName("unit_price") |
| | | public Long unitPrice; |
| | | } |
| | | |
| | | public static class PartnerAPIv3AppPrepayResponse { |
| | | @SerializedName("prepay_id") |
| | | public String prepayId; |
| | | } |
| | | |
| | | public static class PartnerAPIv3CommonPrepayRequest { |
| | | @SerializedName("sp_appid") |
| | | public String spAppid; |
| | | |
| | | @SerializedName("sp_mchid") |
| | | public String spMchid; |
| | | |
| | | @SerializedName("sub_appid") |
| | | public String subAppid; |
| | | |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("description") |
| | | public String description; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("time_expire") |
| | | public String timeExpire; |
| | | |
| | | @SerializedName("attach") |
| | | public String attach; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("goods_tag") |
| | | public String goodsTag; |
| | | |
| | | @SerializedName("settle_info") |
| | | public PartnerSettleInfo settleInfo; |
| | | |
| | | @SerializedName("support_fapiao") |
| | | public Boolean supportFapiao; |
| | | |
| | | @SerializedName("amount") |
| | | public CommonAmountInfo amount; |
| | | |
| | | @SerializedName("detail") |
| | | public CouponInfo detail; |
| | | |
| | | @SerializedName("scene_info") |
| | | public CommonSceneInfo sceneInfo; |
| | | } |
| | | |
| | | public static class CouponInfo { |
| | | @SerializedName("cost_price") |
| | | public Long costPrice; |
| | | |
| | | @SerializedName("invoice_id") |
| | | public String invoiceId; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class StoreInfo { |
| | | @SerializedName("id") |
| | | public String id; |
| | | |
| | | @SerializedName("name") |
| | | public String name; |
| | | |
| | | @SerializedName("area_code") |
| | | public String areaCode; |
| | | |
| | | @SerializedName("address") |
| | | public String address; |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import com.google.gson.*; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.wechat.pay.java.core.util.GsonUtil; |
| | | import okhttp3.Headers; |
| | | import okhttp3.Response; |
| | | import okio.BufferedSource; |
| | | |
| | | import javax.crypto.BadPaddingException; |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.IllegalBlockSizeException; |
| | | import javax.crypto.NoSuchPaddingException; |
| | | import java.io.IOException; |
| | | import java.io.UncheckedIOException; |
| | | import java.io.UnsupportedEncodingException; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.Paths; |
| | | import java.security.*; |
| | | import java.security.spec.InvalidKeySpecException; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.security.spec.X509EncodedKeySpec; |
| | | import java.time.DateTimeException; |
| | | import java.time.Duration; |
| | | import java.time.Instant; |
| | | import java.util.Base64; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | public class WXPayUtility { |
| | | private static final Gson gson = new GsonBuilder() |
| | | .disableHtmlEscaping() |
| | | .addSerializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.serialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .addDeserializationExclusionStrategy(new ExclusionStrategy() { |
| | | @Override |
| | | public boolean shouldSkipField(FieldAttributes fieldAttributes) { |
| | | final Expose expose = fieldAttributes.getAnnotation(Expose.class); |
| | | return expose != null && !expose.deserialize(); |
| | | } |
| | | |
| | | @Override |
| | | public boolean shouldSkipClass(Class<?> aClass) { |
| | | return false; |
| | | } |
| | | }) |
| | | .create(); |
| | | private static final char[] SYMBOLS = |
| | | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); |
| | | private static final SecureRandom random = new SecureRandom(); |
| | | |
| | | /** |
| | | * 将 Object 转换为 JSON 字符串 |
| | | */ |
| | | public static String toJson(Object object) { |
| | | return gson.toJson(object); |
| | | } |
| | | |
| | | /** |
| | | * 将 JSON 字符串解析为特定类型的实例 |
| | | */ |
| | | public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException { |
| | | return gson.fromJson(json, classOfT); |
| | | } |
| | | |
| | | /** |
| | | * 从公私钥文件路径中读取文件内容 |
| | | * |
| | | * @param keyPath 文件路径 |
| | | * @return 文件内容 |
| | | */ |
| | | private static String readKeyStringFromPath(String keyPath) { |
| | | try { |
| | | return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8); |
| | | } catch (IOException e) { |
| | | throw new UncheckedIOException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象 |
| | | * |
| | | * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePrivate( |
| | | new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的私钥文件中加载私钥 |
| | | * |
| | | * @param keyPath 私钥文件路径 |
| | | * @return PrivateKey 对象 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPath(String keyPath) { |
| | | return loadPrivateKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象 |
| | | * |
| | | * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromString(String keyString) { |
| | | try { |
| | | keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "") |
| | | .replace("-----END PUBLIC KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | return KeyFactory.getInstance("RSA").generatePublic( |
| | | new X509EncodedKeySpec(Base64.getDecoder().decode(keyString))); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException(e); |
| | | } catch (InvalidKeySpecException e) { |
| | | throw new IllegalArgumentException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从 PKCS#8 格式的公钥文件中加载公钥 |
| | | * |
| | | * @param keyPath 公钥文件路径 |
| | | * @return PublicKey 对象 |
| | | */ |
| | | public static PublicKey loadPublicKeyFromPath(String keyPath) { |
| | | return loadPublicKeyFromString(readKeyStringFromPath(keyPath)); |
| | | } |
| | | |
| | | /** |
| | | * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途 |
| | | */ |
| | | public static String createNonce(int length) { |
| | | char[] buf = new char[length]; |
| | | for (int i = 0; i < length; ++i) { |
| | | buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)]; |
| | | } |
| | | return new String(buf); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密 |
| | | * |
| | | * @param publicKey 加密用公钥对象 |
| | | * @param plaintext 待加密明文 |
| | | * @return 加密后密文 |
| | | */ |
| | | public static String encrypt(PublicKey publicKey, String plaintext) { |
| | | final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; |
| | | |
| | | try { |
| | | Cipher cipher = Cipher.getInstance(transformation); |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey); |
| | | return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8))); |
| | | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { |
| | | throw new IllegalArgumentException("The current Java environment does not support " + transformation, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e); |
| | | } catch (BadPaddingException | IllegalBlockSizeException e) { |
| | | throw new IllegalArgumentException("Plaintext is too long", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 使用私钥按照指定算法进行签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param algorithm 签名算法,如 SHA256withRSA |
| | | * @param privateKey 签名用私钥对象 |
| | | * @return 签名结果 |
| | | */ |
| | | public static String sign(String message, String algorithm, PrivateKey privateKey) { |
| | | byte[] sign; |
| | | try { |
| | | Signature signature = Signature.getInstance(algorithm); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | sign = signature.sign(); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e); |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e); |
| | | } catch (SignatureException e) { |
| | | throw new RuntimeException("An error occurred during the sign process.", e); |
| | | } |
| | | return Base64.getEncoder().encodeToString(sign); |
| | | } |
| | | |
| | | /** |
| | | * 使用公钥按照特定算法验证签名 |
| | | * |
| | | * @param message 待签名串 |
| | | * @param signature 待验证的签名内容 |
| | | * @param algorithm 签名算法,如:SHA256withRSA |
| | | * @param publicKey 验签用公钥对象 |
| | | * @return 签名验证是否通过 |
| | | */ |
| | | public static boolean verify(String message, String signature, String algorithm, |
| | | PublicKey publicKey) { |
| | | try { |
| | | Signature sign = Signature.getInstance(algorithm); |
| | | sign.initVerify(publicKey); |
| | | sign.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | return sign.verify(Base64.getDecoder().decode(signature)); |
| | | } catch (SignatureException e) { |
| | | return false; |
| | | } catch (InvalidKeyException e) { |
| | | throw new IllegalArgumentException("verify uses an illegal publickey.", e); |
| | | } catch (NoSuchAlgorithmException e) { |
| | | throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3请求签名规则构造 Authorization 签名 |
| | | * |
| | | * @param mchid 商户号 |
| | | * @param certificateSerialNo 商户API证书序列号 |
| | | * @param privateKey 商户API证书私钥 |
| | | * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE |
| | | * @param uri 请求接口的URL |
| | | * @param body 请求接口的Body |
| | | * @return 构造好的微信支付APIv3 Authorization 头 |
| | | */ |
| | | public static String buildAuthorization(String mchid, String certificateSerialNo, |
| | | PrivateKey privateKey, |
| | | String method, String uri, String body) { |
| | | String nonce = createNonce(32); |
| | | long timestamp = Instant.now().getEpochSecond(); |
| | | |
| | | String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce, |
| | | body == null ? "" : body); |
| | | |
| | | String signature = sign(message, "SHA256withRSA", privateKey); |
| | | |
| | | return String.format( |
| | | "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," + |
| | | "timestamp=\"%d\",serial_no=\"%s\"", |
| | | mchid, nonce, signature, timestamp, certificateSerialNo); |
| | | } |
| | | |
| | | /** |
| | | * 对参数进行 URL 编码 |
| | | * |
| | | * @param content 参数内容 |
| | | * @return 编码后的内容 |
| | | */ |
| | | public static String urlEncode(String content) { |
| | | try { |
| | | return URLEncoder.encode(content, StandardCharsets.UTF_8.name()); |
| | | } catch (UnsupportedEncodingException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 对参数Map进行 URL 编码,生成 QueryString |
| | | * |
| | | * @param params Query参数Map |
| | | * @return QueryString |
| | | */ |
| | | public static String urlEncode(Map<String, Object> params) { |
| | | if (params == null || params.isEmpty()) { |
| | | return ""; |
| | | } |
| | | |
| | | int index = 0; |
| | | StringBuilder result = new StringBuilder(); |
| | | for (Map.Entry<String, Object> entry : params.entrySet()) { |
| | | result.append(entry.getKey()) |
| | | .append("=") |
| | | .append(urlEncode(entry.getValue().toString())); |
| | | index++; |
| | | if (index < params.size()) { |
| | | result.append("&"); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 从应答中提取 Body |
| | | * |
| | | * @param response HTTP 请求应答对象 |
| | | * @return 应答中的Body内容,Body为空时返回空字符串 |
| | | */ |
| | | public static String extractBody(Response response) { |
| | | if (response.body() == null) { |
| | | return ""; |
| | | } |
| | | |
| | | try { |
| | | BufferedSource source = response.body().source(); |
| | | return source.readUtf8(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常 |
| | | * |
| | | * @param wechatpayPublicKeyId 微信支付公钥ID |
| | | * @param wechatpayPublicKey 微信支付公钥对象 |
| | | * @param headers 微信支付应答 Header 列表 |
| | | * @param body 微信支付应答 Body |
| | | */ |
| | | public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey, |
| | | Headers headers, |
| | | String body) { |
| | | String timestamp = headers.get("Wechatpay-Timestamp"); |
| | | try { |
| | | Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp)); |
| | | // 拒绝过期请求 |
| | | if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is expires, " |
| | | + "request-id[%s]", |
| | | timestamp, headers.get("Request-ID"))); |
| | | } |
| | | } catch (DateTimeException | NumberFormatException e) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " + |
| | | "request-id[%s]", timestamp, |
| | | headers.get("Request-ID"))); |
| | | } |
| | | String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"), |
| | | body == null ? "" : body); |
| | | String serialNumber = headers.get("Wechatpay-Serial"); |
| | | if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId, |
| | | serialNumber)); |
| | | } |
| | | String signature = headers.get("Wechatpay-Signature"); |
| | | |
| | | boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey); |
| | | if (!success) { |
| | | throw new IllegalArgumentException( |
| | | String.format("Validate response failed,the WechatPay signature is incorrect.%n" |
| | | + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]", |
| | | headers.get("Request-ID"), headers, body)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常 |
| | | */ |
| | | public static class ApiException extends RuntimeException { |
| | | private static final long serialVersionUID = 2261086748874802175L; |
| | | |
| | | private final int statusCode; |
| | | private final String body; |
| | | private final Headers headers; |
| | | private final String errorCode; |
| | | private final String errorMessage; |
| | | |
| | | public ApiException(int statusCode, String body, Headers headers) { |
| | | super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers)); |
| | | this.statusCode = statusCode; |
| | | this.body = body; |
| | | this.headers = headers; |
| | | |
| | | if (body != null && !body.isEmpty()) { |
| | | JsonElement code; |
| | | JsonElement message; |
| | | |
| | | try { |
| | | JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class); |
| | | code = jsonObject.get("code"); |
| | | message = jsonObject.get("message"); |
| | | } catch (JsonSyntaxException ignored) { |
| | | code = null; |
| | | message = null; |
| | | } |
| | | this.errorCode = code == null ? null : code.getAsString(); |
| | | this.errorMessage = message == null ? null : message.getAsString(); |
| | | } else { |
| | | this.errorCode = null; |
| | | this.errorMessage = null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答状态码 |
| | | */ |
| | | public int getStatusCode() { |
| | | return statusCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答包体内容 |
| | | */ |
| | | public String getBody() { |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 获取 HTTP 应答 Header |
| | | */ |
| | | public Headers getHeaders() { |
| | | return headers; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误码 (错误应答中的 code 字段) |
| | | */ |
| | | public String getErrorCode() { |
| | | return errorCode; |
| | | } |
| | | |
| | | /** |
| | | * 获取 错误消息 (错误应答中的 message 字段) |
| | | */ |
| | | public String getErrorMessage() { |
| | | return errorMessage; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.io.IOException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.*; |
| | | |
| | | public class WeChatSignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥对字符串进行 SHA256withRSA 签名 |
| | | * |
| | | * @param content 待签名字符串 |
| | | * @param privateKeyPath 商户私钥文件路径(pem 格式) |
| | | * @return 签名结果(Base64 编码) |
| | | */ |
| | | public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception { |
| | | String privateKeyPEM = readPemFile(privateKeyPath); |
| | | String privateKeyContent = privateKeyPEM |
| | | .replace("-----BEGIN PRIVATE KEY-----", "") |
| | | .replace("-----END PRIVATE KEY-----", "") |
| | | .replaceAll("\\s+", ""); |
| | | |
| | | byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey); |
| | | KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(content.getBytes(StandardCharsets.UTF_8)); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } |
| | | |
| | | private static String readPemFile(String filePath) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
| | | String line; |
| | | while ((line = br.readLine()) != null) { |
| | | sb.append(line).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 按照微信 V3 规范生成待签名字符串 |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (String key : keys) { |
| | | Object value = map.get(key); |
| | | if (value != null && !value.toString().isEmpty()) { |
| | | sb.append(key).append("=").append(value).append("\n"); |
| | | } |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.FileReader; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Security; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.util.ArrayList; |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | public class WeChatV3SignUtil { |
| | | |
| | | static { |
| | | Security.addProvider(new BouncyCastleProvider()); |
| | | } |
| | | |
| | | /** |
| | | * 从 PEM 文件中读取私钥内容 |
| | | */ |
| | | public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | if (!line.startsWith("-----") && !line.endsWith("-----")) { |
| | | sb.append(line); |
| | | } |
| | | } |
| | | } |
| | | |
| | | byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString()); |
| | | |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes); |
| | | KeyFactory kf = KeyFactory.getInstance("RSA", "BC"); |
| | | return kf.generatePrivate(keySpec); |
| | | } |
| | | |
| | | /** |
| | | * 构造待签名字符串(按 key 字典序排序) |
| | | */ |
| | | public static String buildSignMessage(Map<String, Object> map) { |
| | | List<String> keys = new ArrayList<>(map.keySet()); |
| | | // Collections.sort(keys); |
| | | |
| | | StringBuilder sb = new StringBuilder(); |
| | | |
| | | sb.append(map.get("appid")).append("\n"); |
| | | sb.append(map.get("timestamp")).append("\n"); |
| | | sb.append(map.get("noncestr")).append("\n"); |
| | | sb.append(map.get("prepayid")).append("\n"); |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 使用商户私钥生成签名(SHA256withRSA + Base64) |
| | | */ |
| | | public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception { |
| | | PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath); |
| | | |
| | | Signature signature = Signature.getInstance("SHA256withRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(message.getBytes(StandardCharsets.UTF_8)); |
| | | byte[] signedBytes = signature.sign(); |
| | | |
| | | return Base64.getEncoder().encodeToString(signedBytes); |
| | | } |
| | | } |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import javax.annotation.PostConstruct; |
| | | |
| | | @Component |
| | | public class WxV3PayConfig { |
| | | // 服务商AppId |
| | | |
| | | private String appIdValue = "wx41d32f362ba0f911"; |
| | | public static String APP_ID= "wx41d32f362ba0f911"; |
| | | |
| | | // 服务商商户号 |
| | | private String mchIdValue= "1681873607"; |
| | | public static String Mch_ID= "1681873607"; |
| | | |
| | | // 平台收款商户号 todo 待申请 |
| | | public static String smidVx= "2088330203191220"; |
| | | private String smidVxValue= "2088330203191220"; |
| | | |
| | | // 服务商商户私钥 |
| | | private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; |
| | | public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; |
| | | // 证书序列号 |
| | | |
| | | private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; |
| | | |
| | | private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem"; |
| | | |
| | | // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 |
| | | @PostConstruct |
| | | public void init() { |
| | | APP_ID = this.appIdValue; |
| | | APP_ID = this.appIdValue; |
| | | smidVx = this.smidVxValue; |
| | | apiV3Key = this.apiV3KeyValue; |
| | | mchSerialNo = this.mchSerialNoValue; |
| | | privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 |
| | | |
| | | |
| | | // 可以在这里加一些非空检查 |
| | | if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { |
| | | System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); |
| | | // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 |
| | | } else { |
| | | System.out.println("微信支付V3配置加载完成。"); |
| | | } |
| | | } |
| | | |
| | | // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath |
| | | // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: |
| | | /* |
| | | public static PrivateKey getPrivateKey() { |
| | | if (cachedPrivateKey != null) { |
| | | return cachedPrivateKey; |
| | | } |
| | | try { |
| | | String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 |
| | | // ... rest of the method ... |
| | | } // ... catch blocks ... |
| | | } |
| | | */ |
| | | } |
| | |
| | | ORDER BY insertTime desc |
| | | </select> |
| | | <select id="getSMIDByOperatorId" resultType="java.lang.String"> |
| | | select alipayNum from t_operator_user where operatorId = #{id} |
| | | select merchantNumberAli from t_operator where operatorId = #{id} |
| | | </select> |
| | | <select id="getmerchantNumberByOperatorId" resultType="java.lang.String"> |
| | | select merchantNumber from t_operator where id = #{id} |
| | | </select> |
| | | <select id="getmerchantNumberAliByOperatorId" resultType="java.lang.String"> |
| | | select merchantNumberAli from t_operator where id = #{id} |
| | | </select> |
| | | </mapper> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="com.dsh.other.mapper.TOperatorUserMapper"> |
| | | |
| | | |
| | | <select id="getmerchantNumberByOperatorId" resultType="java.lang.String"> |
| | | select merchantNumber from t_operator_user where id=#{operatorId} |
| | | </select> |
| | | </mapper> |