| | |
| | | } |
| | | return ResultUtil.success(response.getBody()); |
| | | } |
| | | public static void main(String[] args) throws AlipayApiException { |
| | | confirm9("2088670241691219", "20250706132544057730", "2025070622001467281419691745", "0.1"); |
| | | confirm9("2088670241691219", "20250706132448647822", "2025070622001467281421616221", "0.1"); |
| | | confirm9("2088670241691219", "20250706132703365228", "2025070622001467281418986854", "0.1"); |
| | | confirm9("2088670241691219", "20250706132812692528", "2025070622001467281421628990", "0.1"); |
| | | confirm9("2088670241691219", "20250706133122769531", "2025070622001467281421682297", "0.1"); |
| | | confirm9("2088670241691219", "20250706155632714326", "2025070622001423881412880254", "0.01"); |
| | | confirm9("2088670241691219", "20250707124157706846", "2025070722001479841407246616", "0.01"); |
| | | confirm9("2088670241691219", "20250709182513843937", "2025070922001467281434141170", "0.01"); |
| | | confirm9("2088670241691219", "20250709183148107861", "2025070922001467281433375653", "0.01"); |
| | | confirm9("2088670241691219", "20250715203444033496", "2025071522001457851416341513", "5"); |
| | | |
| | | } |
| | | // public static void main(String[] args) throws AlipayApiException { |
| | | // confirm9("2088670241691219", "20250706132544057730", "2025070622001467281419691745", "0.1"); |
| | | // confirm9("2088670241691219", "20250706132448647822", "2025070622001467281421616221", "0.1"); |
| | | // confirm9("2088670241691219", "20250706132703365228", "2025070622001467281418986854", "0.1"); |
| | | // confirm9("2088670241691219", "20250706132812692528", "2025070622001467281421628990", "0.1"); |
| | | // confirm9("2088670241691219", "20250706133122769531", "2025070622001467281421682297", "0.1"); |
| | | // confirm9("2088670241691219", "20250706155632714326", "2025070622001423881412880254", "0.01"); |
| | | // confirm9("2088670241691219", "20250707124157706846", "2025070722001479841407246616", "0.01"); |
| | | // confirm9("2088670241691219", "20250709182513843937", "2025070922001467281434141170", "0.01"); |
| | | // confirm9("2088670241691219", "20250709183148107861", "2025070922001467281433375653", "0.01"); |
| | | // confirm9("2088670241691219", "20250715203444033496", "2025071522001457851416341513", "5"); |
| | | // |
| | | // } |
| | | private static AlipayConfig getAlipayConfig() { |
| | | String privateKey = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCi5i9nW/hGLJ3A06cZxTQdviFC7THpdSihoTYGLr9q006hu0V26ecBMY/o4w5bvIX0Ok/yofmZsVcCJpAPvbXL/uqVrIjnRRxXiaeBFThlxoBUTdunvbUSDYfzlEhJr5NvUKI6H6lz2niXlQGx4qy8Hau4ccWit9kM8jwUvsBVQoFgJA+xrjMvooA7YLopQtpOD+UJr5thApTSf1xrnr1W12yolTLEH15JmNV372cqXrYUuqnY0QsaPtxeqJUGAOcGdVLllQ7easEznP8DFBvDdHATcmp2SHNQDUEWN6MCVPbMgY06NQVqAXxqjTAYSVh+6TRu6bofPmpYC3TZB003AgMBAAECggEBAJAcR2+PA3NBYUYHeFrqBRMS8uX8ZR19kjZ7IgoSLTFaQsP9opRylPSPXhrPVBKAE5leRQAHn4MCSlESwHvMfxo7KFjFTFAc6dffZZpipYQUOc9bGampwJh58/3e/pyBgVMG6J23CPf/HJQtNFSkjd/V9+ayb/9l2dUEL3bC0fAZ/dbx8HsxdLw8wn3fLlWLj68hOMqa2deCZe3JdSVsPbeWqkh56FFsMLug0Nd+Ar4TgRl9/jnhXF0JWiD0LmPUYLhboY7EfUBzN4w1iYbDi1P+3zvoOYsiVKAXox9GMhQ2VzOO2UcSTuizSza2e98mGpabl/GpKmCz+RDFjtkX6eECgYEA2MyCij65eO3aGIm3FUe93DULRBYTfX8qJQSJq2WOWA3mmQlEW6L3O2B5/lG2h+8WmN6iLEs9eHpgycGYp7vAqgrANEn16ACVcuyx0scFtrZfZ+kmHMzFfiUWxJjVYk/6YngsGVBLdw6ueM42C8TTP67X9tU5TdVGoGWuqEj4W98CgYEAwFqwprXOch5Pqk/RPbb49r0Ou03K/UbciWnWWKzUhFFNS8MdlQPoDvQZbMwHLeWsa2VhaKITK3x5biLQb3U+0GLOn6lTvEyrEUH+ucREyLgVYTRAvwBPtnvlrzpyxPk2HnslQjju8WrvvLLBMKWUjlTrTOzhaHT21gz3pHMiOakCgYEAhLmfaXdBITGshb054sNLDtdCkGpbgEcrzAHdLps769iGxkYQHXHFngpQZUwtTUcoNGqIKknd1jZFrv7gsD+XkgKG7PwimehRlkwmCX5ilxtLiVgJRzRt6+5U5AMVD90a0tHzXYP0z2yjj73fBJF5KtGl0a10KZxaYrQdm1UhB00CgYBZZgzx/k9rtHC8LAqIj1CYhHejT92G53c6Gkl3vyOqN4sgKhfGmSEySfrDGPRBPZxr8ZtbIPCd5mUdberH0osWGMYFaJI1UsCy7aQwvGpniz7MhZeN7dweaOjwDs8mgtjHQ96mL4XGCDhR0BZ/wIURvZ/6iaGdhbbu9unlsWj3uQKBgQCmZYdsbbZkd3ev6f8rwyvMz+DrCQyYpY44cegBYuJgrZiQnL2fJioeN7ixX0UM48SfwsZEIrzshP/LGAwnc2MdjxKUl4jLN8SEe0NAjXOnz9Zaw740+aOmLpXcLWdP4uM2gIhWsvW1tEkQZCXmm7c9s/RsU8Pmzv+YL3+fSijOzA=="; |
| | | String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmu8n/4yTHWbn7VOrNc9OsLtDL1bEQ8gC1dHkj8Wy5z0mkaOsjJRIG/28ze12M0V8jdCKuuDr5Z1OPKiqf+XO3ypguEh+mYUVMBM/cZodDFQfTY1TKLWjvQCuaqlA+QUTCK6f7T7stsgyQ1o9Jj0rXZDz6PM4QHSTzjrLIBaeqM5WIBvH+fy/X+QG5Utd+/UT0kc0JyvuKhZ65yVUd/C9VcwJJAPliRsAQNrqYterwAJ9zvw9tF11wj9W0XgJ8Ccu4x3gR1vrlLRJJo/OA97RmxPQ+5hSacWQZCUd1dwiBq+YCrKVHGTj14izRHXrLc0yBlRXo7tBOIqcy3IsvKVthQIDAQAB"; |
| | |
| | | <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> |
| | | <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> |
| | | <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> |
| | | <level>debug</level> |
| | | <level>info</level> |
| | | </filter> |
| | | <encoder> |
| | | <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> |
| | |
| | | datasource.names=master0 |
| | | datasource.master0.type=com.alibaba.druid.pool.DruidDataSource |
| | | datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver |
| | | datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai |
| | | datasource.master0.url=jdbc:mysql://127.0.0.1/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai |
| | | datasource.master0.username=root |
| | | datasource.master0.password=playpai2023! |
| | | datasource.master0.maxActive=20 |
| | |
| | | |
| | | /** |
| | | * 查询社区世界杯收入--管理后台 |
| | | * @param storeId |
| | | * @return |
| | | */ |
| | | @ResponseBody |
| | |
| | | @ApiImplicitParam(name = "id", value = "列表中的id", required = true, dataType = "Long"), |
| | | @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") |
| | | }) |
| | | public ResultUtil cancelMyWorldCup(Long id){ |
| | | public ResultUtil cancelMyWorldCup(Long id) throws Exception { |
| | | return worldCupPaymentService.cancelMyWorldCup(id); |
| | | } |
| | | |
| | |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/worldCup/cancelWorldCupRefund") |
| | | public void cancelWorldCupRefund(@RequestBody Integer id){ |
| | | public void cancelWorldCupRefund(@RequestBody Integer id) throws Exception { |
| | | worldCupService.cancelWorldCupRefund(id); |
| | | } |
| | | |
| | |
| | | out.close(); |
| | | } |
| | | } |
| | | /** |
| | | * 取消赛事后微信退款回调微信V3回调 |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/worldCup/wxRefundWorldCupCallback1") |
| | | public void wxRefundWorldCupCallback1(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 refund_status = jsonInfo.getString("refund_status"); |
| | | String out_refund_no = jsonInfo.getString("out_refund_no"); |
| | | if (refund_status.equals("SUCCESS")) { |
| | | WorldCupPayment worldCupPayment = worldCupPaymentService.getOne(new QueryWrapper<WorldCupPayment>().eq("code", out_refund_no)); |
| | | worldCupPayment.setRefundOrderNo(code); |
| | | worldCupPayment.setRefundTime(new Date()); |
| | | worldCupPayment.setPayStatus(3); |
| | | worldCupPayment.setWorldCupId(null); |
| | | worldCupPaymentService.updateById(worldCupPayment); |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | |
| | | * @param id |
| | | * @return |
| | | */ |
| | | ResultUtil cancelMyWorldCup(Long id); |
| | | ResultUtil cancelMyWorldCup(Long id) throws Exception; |
| | | } |
| | |
| | | * 取消赛事后退还费用 |
| | | * @param id |
| | | */ |
| | | void cancelWorldCupRefund(Integer id); |
| | | void cancelWorldCupRefund(Integer id) throws Exception; |
| | | |
| | | |
| | | /** |
| | |
| | | import com.dsh.communityWorldCup.entity.WorldCup; |
| | | import com.dsh.communityWorldCup.entity.WorldCupPayment; |
| | | import com.dsh.communityWorldCup.entity.WorldCupPaymentParticipant; |
| | | import com.dsh.communityWorldCup.entity.WorldCupStore; |
| | | import com.dsh.communityWorldCup.feignclient.account.AppUserClient; |
| | | import com.dsh.communityWorldCup.feignclient.account.model.AppUser; |
| | | import com.dsh.communityWorldCup.feignclient.course.CoursePackageOrderStudentClient; |
| | | import com.dsh.communityWorldCup.feignclient.course.model.CoursePackageOrderStudent; |
| | | import com.dsh.communityWorldCup.feignclient.other.StoreClient; |
| | | import com.dsh.communityWorldCup.feignclient.other.model.Store; |
| | | import com.dsh.communityWorldCup.mapper.WorldCupPaymentMapper; |
| | | import com.dsh.communityWorldCup.model.DeductionClassHourList; |
| | | import com.dsh.communityWorldCup.service.IWorldCupPaymentParticipantService; |
| | | import com.dsh.communityWorldCup.service.IWorldCupPaymentService; |
| | | import com.dsh.communityWorldCup.service.IWorldCupService; |
| | | import com.dsh.communityWorldCup.service.IWorldCupStoreService; |
| | | import com.dsh.communityWorldCup.util.PayMoneyUtil; |
| | | import com.dsh.communityWorldCup.util.ResultUtil; |
| | | import com.dsh.communityWorldCup.util.UUIDUtil; |
| | | import net.bytebuddy.asm.Advice; |
| | | 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; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | |
| | | @Autowired |
| | | private IWorldCupPaymentParticipantService worldCupPaymentParticipantService; |
| | | @Autowired |
| | | private IWorldCupStoreService worldCupStoreService; |
| | | |
| | | @Autowired |
| | | private IWorldCupService worldCupService; |
| | |
| | | |
| | | @Resource |
| | | private AppUserClient appUserClient; |
| | | @Resource |
| | | private StoreClient storeClient; |
| | | |
| | | @Resource |
| | | private CoursePackageOrderStudentClient coursePackageOrderStudentClient; |
| | |
| | | * @return |
| | | */ |
| | | @Override |
| | | public ResultUtil cancelMyWorldCup(Long id) { |
| | | public ResultUtil cancelMyWorldCup(Long id) throws Exception { |
| | | WorldCupPaymentParticipant worldCupPaymentParticipant = worldCupPaymentParticipantService.getById(id); |
| | | WorldCupPayment worldCupPayment = this.getById(worldCupPaymentParticipant.getWorldCupPaymentId()); |
| | | WorldCup worldCup = worldCupService.getById(worldCupPayment.getWorldCupId()); |
| | |
| | | //总排款金额(排除已经参加比赛的数据) |
| | | BigDecimal multiply = worldCupPayment.getUnitPrice().multiply(new BigDecimal(list1.size())); |
| | | //微信支付 |
| | | |
| | | List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, worldCup.getId()).list(); |
| | | Integer storeId = list.get(0).getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | if(worldCupPayment.getPayType() == 1){ |
| | | Map<String, String> map = payMoneyUtil.wxRefund(worldCupPayment.getPayOrderNo(), worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), multiply.toString(), "/base/worldCup/wxRefundWorldCupCallback"); |
| | | if(!"SUCCESS".equals(map.get("return_code"))){ |
| | | System.err.println("-------------微信退款失败---------"); |
| | | System.err.println(map.get("return_msg")); |
| | | return ResultUtil.error("微信退款失败"); |
| | | }else{ |
| | | worldCupPayment.setRefundAmount(multiply); |
| | | worldCupPayment.setWorldCupId(null); |
| | | this.updateById(worldCupPayment); |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | String smidVx= storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | System.err.println("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | String codeRefund = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);// 退款单号 |
| | | // 运营商退款 |
| | | payMoneyUtil.weixinRefundV3(smidVx, codeRefund, worldCupPayment.getPayOrderNo(), worldCupPayment.getAmount().toString(), "/base/worldCup/wxRefundWorldCupCallback1"); |
| | | }else { |
| | | Map<String, String> map = payMoneyUtil.wxRefund(worldCupPayment.getPayOrderNo(), worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), multiply.toString(), "/base/worldCup/wxRefundWorldCupCallback"); |
| | | if (!"SUCCESS".equals(map.get("return_code"))) { |
| | | System.err.println("-------------微信退款失败---------"); |
| | | System.err.println(map.get("return_msg")); |
| | | return ResultUtil.error("微信退款失败"); |
| | | } else { |
| | | worldCupPayment.setRefundAmount(multiply); |
| | | worldCupPayment.setWorldCupId(null); |
| | | this.updateById(worldCupPayment); |
| | | } |
| | | } |
| | | } |
| | | //支付宝支付 |
| | |
| | | * @param id |
| | | */ |
| | | @Override |
| | | public void cancelWorldCupRefund(Integer id) { |
| | | public void cancelWorldCupRefund(Integer id) throws Exception { |
| | | //免费除外 |
| | | List<WorldCupPayment> list2 = worldCupPaymentService.list(new QueryWrapper<WorldCupPayment>().eq("worldCupId", id) |
| | | .ne("payType", 0).eq("payStatus", 2).eq("state", 1)); |
| | | List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, id).list(); |
| | | Integer storeId = list.get(0).getStoreId(); |
| | | Store store = storeClient.queryStoreById(storeId); |
| | | for (WorldCupPayment worldCupPayment : list2) { |
| | | List<WorldCupPaymentParticipant> list1 = worldCupPaymentParticipantService.list(new QueryWrapper<WorldCupPaymentParticipant>() |
| | | .eq("worldCupId", id).eq("worldCupPaymentId", worldCupPayment.getId()).eq("alreadyEntered", 0)); |
| | |
| | | BigDecimal multiply = worldCupPayment.getUnitPrice().multiply(new BigDecimal(list1.size())); |
| | | //微信支付 |
| | | if(worldCupPayment.getPayType() == 1){ |
| | | Map<String, String> map = payMoneyUtil.wxRefund(worldCupPayment.getPayOrderNo(), worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), multiply.toString(), "/base/worldCup/wxRefundWorldCupCallback"); |
| | | if(!"SUCCESS".equals(map.get("return_code"))){ |
| | | System.err.println("-------------微信退款失败---------"); |
| | | System.err.println(map.get("return_msg")); |
| | | }else{ |
| | | worldCupPayment.setRefundAmount(multiply); |
| | | worldCupPaymentService.updateById(worldCupPayment); |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | String smidVx= storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | System.err.println("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | String codeRefund = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);// 退款单号 |
| | | // 运营商退款 |
| | | payMoneyUtil.weixinRefundV3(smidVx, codeRefund, worldCupPayment.getPayOrderNo(), worldCupPayment.getAmount().toString(), "/base/worldCup/wxRefundWorldCupCallback1"); |
| | | }else { |
| | | Map<String, String> map = payMoneyUtil.wxRefund(worldCupPayment.getPayOrderNo(), worldCupPayment.getCode(), |
| | | worldCupPayment.getAmount().toString(), multiply.toString(), "/base/worldCup/wxRefundWorldCupCallback"); |
| | | if (!"SUCCESS".equals(map.get("return_code"))) { |
| | | System.err.println("-------------微信退款失败---------"); |
| | | System.err.println(map.get("return_msg")); |
| | | } else { |
| | | worldCupPayment.setRefundAmount(multiply); |
| | | worldCupPaymentService.updateById(worldCupPayment); |
| | | } |
| | | } |
| | | } |
| | | //支付宝支付 |
| | |
| | | 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 com.dsh.communityWorldCup.util.wx.*; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | } |
| | | } |
| | | |
| | | public ResultUtil weixinRefundV3(String subMchid,String outRefundNo, String transactionId, String totalFee, String notifyUrl) throws Exception { |
| | | BigDecimal bigDecimal = new BigDecimal(totalFee); |
| | | int i = bigDecimal.multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | Create client = new Create( |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | Create.CreateRequest request = new Create.CreateRequest(); |
| | | request.subMchid = subMchid; |
| | | request.transactionId = transactionId; |
| | | request.outRefundNo = outRefundNo; |
| | | request.reason = "退款"; |
| | | request.notifyUrl = callbackPath+notifyUrl; |
| | | request.amount = new Create.AmountReq(); |
| | | request.amount.refund = (long) i; |
| | | request.amount.total = (long) i; |
| | | request.amount.currency = "CNY"; |
| | | try { |
| | | Create.Refund response = client.run(request); |
| | | |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.out.println(response); |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | | /** |
| | | * 微信退款成功后的回调处理 |
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; |
| | | |
| | | /** |
| | | * 退款申请 |
| | | */ |
| | | public class Create { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/refund/domestic/refunds"; |
| | | |
| | | |
| | | public Refund run(CreateRequest 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, Refund.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 Create(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 enum Status { |
| | | @SerializedName("SUCCESS") |
| | | SUCCESS, |
| | | @SerializedName("CLOSED") |
| | | CLOSED, |
| | | @SerializedName("PROCESSING") |
| | | PROCESSING, |
| | | @SerializedName("ABNORMAL") |
| | | ABNORMAL |
| | | } |
| | | |
| | | public enum Account { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE |
| | | } |
| | | |
| | | public enum PromotionType { |
| | | @SerializedName("COUPON") |
| | | COUPON, |
| | | @SerializedName("DISCOUNT") |
| | | DISCOUNT |
| | | } |
| | | |
| | | 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("unit_price") |
| | | public Long unitPrice; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("refund_quantity") |
| | | public Integer refundQuantity; |
| | | } |
| | | |
| | | public static class CreateRequest { |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("reason") |
| | | public String reason; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("funds_account") |
| | | public ReqFundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public AmountReq amount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public enum Channel { |
| | | @SerializedName("ORIGINAL") |
| | | ORIGINAL, |
| | | @SerializedName("BALANCE") |
| | | BALANCE, |
| | | @SerializedName("OTHER_BALANCE") |
| | | OTHER_BALANCE, |
| | | @SerializedName("OTHER_BANKCARD") |
| | | OTHER_BANKCARD |
| | | } |
| | | |
| | | public static class Amount { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("payer_total") |
| | | public Long payerTotal; |
| | | |
| | | @SerializedName("payer_refund") |
| | | public Long payerRefund; |
| | | |
| | | @SerializedName("settlement_refund") |
| | | public Long settlementRefund; |
| | | |
| | | @SerializedName("settlement_total") |
| | | public Long settlementTotal; |
| | | |
| | | @SerializedName("discount_refund") |
| | | public Long discountRefund; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | |
| | | @SerializedName("refund_fee") |
| | | public Long refundFee; |
| | | } |
| | | |
| | | public enum ReqFundsAccount { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED |
| | | } |
| | | |
| | | public enum FundsAccount { |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED, |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE, |
| | | @SerializedName("OPERATION") |
| | | OPERATION, |
| | | @SerializedName("BASIC") |
| | | BASIC, |
| | | @SerializedName("ECNY_BASIC") |
| | | ECNY_BASIC |
| | | } |
| | | |
| | | public static class Promotion { |
| | | @SerializedName("promotion_id") |
| | | public String promotionId; |
| | | |
| | | @SerializedName("scope") |
| | | public PromotionScope scope; |
| | | |
| | | @SerializedName("type") |
| | | public PromotionType type; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class Refund { |
| | | @SerializedName("refund_id") |
| | | public String refundId; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("channel") |
| | | public Channel channel; |
| | | |
| | | @SerializedName("user_received_account") |
| | | public String userReceivedAccount; |
| | | |
| | | @SerializedName("success_time") |
| | | public String successTime; |
| | | |
| | | @SerializedName("create_time") |
| | | public String createTime; |
| | | |
| | | @SerializedName("status") |
| | | public Status status; |
| | | |
| | | @SerializedName("funds_account") |
| | | public FundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public Amount amount; |
| | | |
| | | @SerializedName("promotion_detail") |
| | | public List<Promotion> promotionDetail; |
| | | } |
| | | |
| | | public static class FundsFromItem { |
| | | @SerializedName("account") |
| | | public Account account; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | } |
| | | |
| | | public static class AmountReq { |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public enum PromotionScope { |
| | | @SerializedName("GLOBAL") |
| | | GLOBAL, |
| | | @SerializedName("SINGLE") |
| | | SINGLE |
| | | } |
| | | } |
| | |
| | | datasource.names=master0 |
| | | datasource.master0.type=com.alibaba.druid.pool.DruidDataSource |
| | | datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver |
| | | datasource.master0.url=jdbc:mysql://8.137.22.229:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai |
| | | datasource.master0.username=root |
| | | datasource.master0.password=playpai2023! |
| | | datasource.master0.maxActive=20 |
| | | datasource.master0.maxWait=60000 |
| | | datasource.master0.minIdle=5 |
| | | datasource.master0.initialSize=2 |
| | | |
| | | |
| | | |
| | | #datasource.names=master0 |
| | | #datasource.master0.type=com.alibaba.druid.pool.DruidDataSource |
| | |
| | | #datasource.master0.initialSize=2 |
| | | |
| | | |
| | | |
| | | datasource.names=master0 |
| | | datasource.master0.type=com.alibaba.druid.pool.DruidDataSource |
| | | datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver |
| | | datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai |
| | | datasource.master0.username=root |
| | | datasource.master0.password=playpai2023! |
| | | datasource.master0.maxActive=20 |
| | | datasource.master0.maxWait=60000 |
| | | datasource.master0.minIdle=5 |
| | | datasource.master0.initialSize=2 |
| | |
| | | import com.dsh.competition.feignclient.course.model.PaymentDeductionClassHour; |
| | | import com.dsh.competition.feignclient.model.*; |
| | | import com.dsh.competition.feignclient.other.StoreClient; |
| | | import com.dsh.competition.feignclient.other.model.Store; |
| | | import com.dsh.competition.model.*; |
| | | import com.dsh.competition.service.CompetitionService; |
| | | import com.dsh.competition.service.IParticipantService; |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 取消已报名赛事后微信回退金额回调微信V3服务商版本 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/competition/weChatCancelPaymentCompetitionCallback1") |
| | | public void weChatCancelPaymentCompetitionCallback1(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 refund_status = jsonInfo.getString("refund_status"); |
| | | String out_refund_no = jsonInfo.getString("out_refund_no"); |
| | | if (refund_status.equals("SUCCESS")) { |
| | | PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>() |
| | | .eq("code", code).eq("payType", 1)); |
| | | if (paymentCompetition.getPayStatus() == 2) { |
| | | paymentCompetition.setPayStatus(3); |
| | | paymentCompetition.setRefundTime(new Date()); |
| | | paymentCompetition.setRefundOrderNo(out_refund_no); |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | |
| | | Competition competition = cttService.getById(paymentCompetition.getCompetitionId()); |
| | | competition.setApplicantsNumber(competition.getApplicantsNumber() - 1); |
| | | cttService.updateById(competition); |
| | | storeClient.addBackRecord(paymentCompetition.getAmount() + "_" + paymentCompetition.getAppUserId()); |
| | | |
| | | } |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | @PostMapping("/base/competition/queryById") |
| | |
| | | String code = paymentCompetition.getCode(); |
| | | Double amount = paymentCompetition.getAmount(); |
| | | Competition competition = competitionService.getById(paymentCompetition.getCompetitionId()); |
| | | |
| | | |
| | | Store store = storeClient.queryStoreById(Integer.valueOf(competition.getStoreId().split(",")[0])); |
| | | String payOrderNo = paymentCompetition.getPayOrderNo(); |
| | | if (paymentCompetition.getPayType() == 1) {//微信支付 |
| | | if (StringUtils.hasLength(paymentCompetition.getFenzhangNo())){ |
| | | // 是分账订单 如果分账金额不为0 那么回退分账金额 |
| | | if (paymentCompetition.getFenzhangAmount()!=null && paymentCompetition.getFenzhangAmount().compareTo(BigDecimal.ZERO)>0){ |
| | | String randomCode = UUIDUtil.getRandomCode(16); |
| | | String randomCode1 = UUIDUtil.getRandomCode(16); |
| | | if (competition.getOperatorId()!=null && competition.getOperatorId()!=0){ |
| | | // 微信商户号 |
| | | String s2 =storeClient.getmerchantNumberByOperatorId(competition.getOperatorId()); |
| | | ResultUtil resultUtil = payMoneyUtil.fenzhangRefund(paymentCompetition.getFenzhangNo(), paymentCompetition.getFenzhangAmount(), s2, randomCode, randomCode1); |
| | | if (!resultUtil.getCode().equals(200)){ |
| | | System.err.println("分账回退失败 原因是:"+resultUtil.getMsg()); |
| | | }else{ |
| | | paymentCompetition.setFenzhangRefundNo(resultUtil.getData().toString()); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | } |
| | | |
| | | } |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | String smidVx= storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | System.err.println("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | Map<String, String> map = payMoneyUtil.wxRefund(payOrderNo, code, amount.toString(), amount.toString(), |
| | | "/base/competition/weChatCancelPaymentCompetitionCallback"); |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | String codeRefund = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);// 退款单号 |
| | | // 运营商退款 |
| | | payMoneyUtil.weixinRefundV3(smidVx, codeRefund, pay.getPayOrderNo(), pay.getAmount().toString(), "/base/competition/weChatCancelPaymentCompetitionCallback1"); |
| | | }else { |
| | | Map<String, String> map = payMoneyUtil.wxRefund(payOrderNo, code, amount.toString(), amount.toString(), |
| | | "/base/competition/weChatCancelPaymentCompetitionCallback"); |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | // return ResultUtil.error(map.get("return_msg")); |
| | | continue; |
| | | continue; |
| | | } |
| | | String refund_id = map.get("refund_id"); |
| | | paymentCompetition.setRefundOrderNo(refund_id); |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | storeClient.addBackRecord(paymentCompetition.getAmount() + "_" + paymentCompetition.getAppUserId()); |
| | | } |
| | | String refund_id = map.get("refund_id"); |
| | | paymentCompetition.setRefundOrderNo(refund_id); |
| | | paymentCompetition.setAppUserId(null); |
| | | paymentCompetitionService.updateById(paymentCompetition); |
| | | storeClient.addBackRecord(paymentCompetition.getAmount() + "_" + paymentCompetition.getAppUserId()); |
| | | |
| | | } |
| | | if (paymentCompetition.getPayType() == 2) {//支付宝支付 |
| | | Map<String, String> map = payMoneyUtil.aliRefund(payOrderNo, amount.toString()); |
| | |
| | | } |
| | | |
| | | String payOrderNo = paymentCompetition.getPayOrderNo(); |
| | | Store store = storeClient.queryStoreById(Integer.valueOf(competition.getStoreId().split(",")[0])); |
| | | |
| | | if (paymentCompetition.getPayType() == 1) {//微信支付 |
| | | if (StringUtils.hasLength(paymentCompetition.getFenzhangNo())){ |
| | | // 是分账订单 如果分账金额不为0 那么回退分账金额 |
| | | if (paymentCompetition.getFenzhangAmount()!=null && paymentCompetition.getFenzhangAmount().compareTo(BigDecimal.ZERO)>0){ |
| | | String randomCode = UUIDUtil.getRandomCode(16); |
| | | String randomCode1 = UUIDUtil.getRandomCode(16); |
| | | if (competition.getOperatorId()!=null && competition.getOperatorId()!=0){ |
| | | // 微信商户号 |
| | | String s2 =storeClient.getmerchantNumberByOperatorId(competition.getOperatorId()); |
| | | ResultUtil resultUtil = payMoneyUtil.fenzhangRefund(paymentCompetition.getFenzhangNo(), paymentCompetition.getFenzhangAmount(), s2, randomCode, randomCode1); |
| | | if (!resultUtil.getCode().equals(200)){ |
| | | System.err.println("分账回退失败 原因是:"+resultUtil.getData().toString()); |
| | | }else{ |
| | | paymentCompetition.setFenzhangRefundNo(resultUtil.getData().toString()); |
| | | paymentCompetitionMapper.updateById(paymentCompetition); |
| | | } |
| | | |
| | | } |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | String smidVx= storeClient.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | System.err.println("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | } |
| | | Map<String, String> map = payMoneyUtil.wxRefund(payOrderNo, code, amount.toString(), amount.toString(), |
| | | "/base/competition/weChatCancelPaymentCompetitionCallback"); |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | return ResultUtil.error(map.get("return_msg")); |
| | | } |
| | | String refund_id = map.get("refund_id"); |
| | | paymentCompetition.setRefundOrderNo(refund_id); |
| | | paymentCompetition.setAppUserId(null); |
| | | this.updateById(paymentCompetition); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | String codeRefund = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);// 退款单号 |
| | | // 运营商退款 |
| | | payMoneyUtil.weixinRefundV3(smidVx, codeRefund, paymentCompetition.getPayOrderNo(), paymentCompetition.getAmount().toString(), "/base/competition/weChatCancelPaymentCompetitionCallback1"); |
| | | }else { |
| | | Map<String, String> map = payMoneyUtil.wxRefund(payOrderNo, code, amount.toString(), amount.toString(), |
| | | "/base/competition/weChatCancelPaymentCompetitionCallback"); |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | return ResultUtil.error(map.get("return_msg")); |
| | | } |
| | | String refund_id = map.get("refund_id"); |
| | | paymentCompetition.setRefundOrderNo(refund_id); |
| | | paymentCompetition.setAppUserId(null); |
| | | this.updateById(paymentCompetition); |
| | | |
| | | storeClient.addBackRecord(paymentCompetition.getAmount() + "_" + paymentCompetition.getAppUserId()); |
| | | storeClient.addBackRecord(paymentCompetition.getAmount() + "_" + paymentCompetition.getAppUserId()); |
| | | } |
| | | } |
| | | if (paymentCompetition.getPayType() == 2) {//支付宝支付 |
| | | Map<String, String> map = payMoneyUtil.aliRefund(payOrderNo, amount.toString()); |
| | |
| | | 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 com.dsh.competition.util.wx.*; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | return map2; |
| | | } |
| | | } |
| | | public ResultUtil weixinRefundV3(String subMchid,String outRefundNo, String transactionId, String totalFee, String notifyUrl) throws Exception { |
| | | BigDecimal bigDecimal = new BigDecimal(totalFee); |
| | | int i = bigDecimal.multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | Create client = new Create( |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | Create.CreateRequest request = new Create.CreateRequest(); |
| | | request.subMchid = subMchid; |
| | | request.transactionId = transactionId; |
| | | request.outRefundNo = outRefundNo; |
| | | request.reason = "退款"; |
| | | request.notifyUrl = callbackPath+notifyUrl; |
| | | request.amount = new Create.AmountReq(); |
| | | request.amount.refund = (long) i; |
| | | request.amount.total = (long) i; |
| | | request.amount.currency = "CNY"; |
| | | try { |
| | | Create.Refund response = client.run(request); |
| | | |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.out.println(response); |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | /** |
| | | * 发起分账回退 |
| | | * @param order 微信订单号 |
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; |
| | | |
| | | /** |
| | | * 退款申请 |
| | | */ |
| | | public class Create { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/refund/domestic/refunds"; |
| | | |
| | | |
| | | public Refund run(CreateRequest 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, Refund.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 Create(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 enum Status { |
| | | @SerializedName("SUCCESS") |
| | | SUCCESS, |
| | | @SerializedName("CLOSED") |
| | | CLOSED, |
| | | @SerializedName("PROCESSING") |
| | | PROCESSING, |
| | | @SerializedName("ABNORMAL") |
| | | ABNORMAL |
| | | } |
| | | |
| | | public enum Account { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE |
| | | } |
| | | |
| | | public enum PromotionType { |
| | | @SerializedName("COUPON") |
| | | COUPON, |
| | | @SerializedName("DISCOUNT") |
| | | DISCOUNT |
| | | } |
| | | |
| | | 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("unit_price") |
| | | public Long unitPrice; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("refund_quantity") |
| | | public Integer refundQuantity; |
| | | } |
| | | |
| | | public static class CreateRequest { |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("reason") |
| | | public String reason; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("funds_account") |
| | | public ReqFundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public AmountReq amount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public enum Channel { |
| | | @SerializedName("ORIGINAL") |
| | | ORIGINAL, |
| | | @SerializedName("BALANCE") |
| | | BALANCE, |
| | | @SerializedName("OTHER_BALANCE") |
| | | OTHER_BALANCE, |
| | | @SerializedName("OTHER_BANKCARD") |
| | | OTHER_BANKCARD |
| | | } |
| | | |
| | | public static class Amount { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("payer_total") |
| | | public Long payerTotal; |
| | | |
| | | @SerializedName("payer_refund") |
| | | public Long payerRefund; |
| | | |
| | | @SerializedName("settlement_refund") |
| | | public Long settlementRefund; |
| | | |
| | | @SerializedName("settlement_total") |
| | | public Long settlementTotal; |
| | | |
| | | @SerializedName("discount_refund") |
| | | public Long discountRefund; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | |
| | | @SerializedName("refund_fee") |
| | | public Long refundFee; |
| | | } |
| | | |
| | | public enum ReqFundsAccount { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED |
| | | } |
| | | |
| | | public enum FundsAccount { |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED, |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE, |
| | | @SerializedName("OPERATION") |
| | | OPERATION, |
| | | @SerializedName("BASIC") |
| | | BASIC, |
| | | @SerializedName("ECNY_BASIC") |
| | | ECNY_BASIC |
| | | } |
| | | |
| | | public static class Promotion { |
| | | @SerializedName("promotion_id") |
| | | public String promotionId; |
| | | |
| | | @SerializedName("scope") |
| | | public PromotionScope scope; |
| | | |
| | | @SerializedName("type") |
| | | public PromotionType type; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class Refund { |
| | | @SerializedName("refund_id") |
| | | public String refundId; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("channel") |
| | | public Channel channel; |
| | | |
| | | @SerializedName("user_received_account") |
| | | public String userReceivedAccount; |
| | | |
| | | @SerializedName("success_time") |
| | | public String successTime; |
| | | |
| | | @SerializedName("create_time") |
| | | public String createTime; |
| | | |
| | | @SerializedName("status") |
| | | public Status status; |
| | | |
| | | @SerializedName("funds_account") |
| | | public FundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public Amount amount; |
| | | |
| | | @SerializedName("promotion_detail") |
| | | public List<Promotion> promotionDetail; |
| | | } |
| | | |
| | | public static class FundsFromItem { |
| | | @SerializedName("account") |
| | | public Account account; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | } |
| | | |
| | | public static class AmountReq { |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public enum PromotionScope { |
| | | @SerializedName("GLOBAL") |
| | | GLOBAL, |
| | | @SerializedName("SINGLE") |
| | | SINGLE |
| | | } |
| | | } |
| | |
| | | }) |
| | | public ResultUtil cancelMySite(Integer id) { |
| | | try { |
| | | System.err.println("预约数据id"); |
| | | Integer uid = tokenUtil.getUserIdFormRedis(); |
| | | if (null == uid) { |
| | | return ResultUtil.tokenErr(); |
| | |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | /** |
| | | * 微信退款回调V3服务商版本 |
| | | * |
| | | * @param request |
| | | * @param response |
| | | */ |
| | | @ResponseBody |
| | | @PostMapping("/base/site/cancelMySiteCallback1") |
| | | public void cancelMySiteCallback1(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 refund_status = jsonInfo.getString("refund_status"); |
| | | String out_refund_no = jsonInfo.getString("out_refund_no"); |
| | | if (refund_status.equals("SUCCESS")) { |
| | | SiteBooking siteBooking = siteBookingService.getOne(new QueryWrapper<SiteBooking>().eq("orderNo", code).eq("state", 1)); |
| | | siteBooking.setStatus(5); |
| | | siteBooking.setCancelTime(new Date()); |
| | | siteBooking.setRefundOrderNo(out_refund_no); |
| | | siteBookingService.updateById(siteBooking); |
| | | if (null != siteBooking.getUserCouponId()) { |
| | | UserCoupon userCoupon = userCouponClient.queryUserCouponById(new QueryUserCouponByIdAndUserId(siteBooking.getUserCouponId(), siteBooking.getAppUserId())); |
| | | userCoupon.setStatus(1); |
| | | userCouponClient.updateUserCoupon(userCoupon); |
| | | } |
| | | PrintWriter out = response.getWriter(); |
| | | out.write("SUCCESS"); |
| | | out.flush(); |
| | | out.close(); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | @ResponseBody |
| | | @PostMapping("/api/site/continuePaymentMySite") |
| | |
| | | @Resource |
| | | private UserCouponClient userCouponClient; |
| | | |
| | | |
| | | @Resource |
| | | private CouponClient couponClient; |
| | | |
| | |
| | | if (siteBooking.getStatus() != 1 && siteBooking.getStatus() != 2) { |
| | | return ResultUtil.error("不能进行取消操作"); |
| | | } |
| | | Integer storeId = siteBooking.getStoreId(); |
| | | Store store = storeService.getById(storeId); |
| | | |
| | | if (siteBooking.getPayType() == 1) {//微信支付 |
| | | if (StringUtils.hasLength(siteBooking.getFenzhangNo())){ |
| | | // 是分账订单 如果分账金额不为0 那么回退分账金额 |
| | | if (siteBooking.getFenzhangAmount()!=null && siteBooking.getFenzhangAmount().compareTo(BigDecimal.ZERO)>0){ |
| | | String randomCode = UUIDUtil.getRandomCode(16); |
| | | String randomCode1 = UUIDUtil.getRandomCode(16); |
| | | if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){ |
| | | // 微信商户号 |
| | | String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId()); |
| | | ResultUtil resultUtil = payMoneyUtil.fenzhangRefund(siteBooking.getFenzhangNo(), siteBooking.getFenzhangAmount().multiply(new BigDecimal("100")), s2, randomCode, randomCode1); |
| | | if (!resultUtil.getCode().equals(200)){ |
| | | System.err.println("分账回退失败 原因是:"+resultUtil.getMsg()+resultUtil.getData()); |
| | | }else{ |
| | | siteBooking.setFenzhangRefundNo(resultUtil.getMsg()); |
| | | siteBookingService.updateById(siteBooking); |
| | | } |
| | | |
| | | } |
| | | if (store.getOperatorId()!=null && store.getOperatorId()!=0){ |
| | | String smidVx= operatorUserService.getmerchantNumberByOperatorId(store.getOperatorId()); |
| | | if (!StringUtils.hasLength(smidVx)){ |
| | | return ResultUtil.error("运营商未配置微信商户号,获取支付失败!"); |
| | | } |
| | | } |
| | | Map<String, String> map = payMoneyUtil.wxRefund(siteBooking.getPayOrderNo(), siteBooking.getOrderNo(), siteBooking.getPayMoney().toString(), |
| | | siteBooking.getPayMoney().toString(), "/base/site/cancelMySiteCallback"); |
| | | if (null == map) { |
| | | return ResultUtil.error("取消退款异常"); |
| | | } |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | return ResultUtil.error(map.get("return_msg")); |
| | | } |
| | | siteBooking.setCancelUserId(uid); |
| | | siteBookingService.updateById(siteBooking); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | String code = sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);// 退款单号 |
| | | // 运营商退款 |
| | | payMoneyUtil.weixinRefundV3(smidVx, code, siteBooking.getPayOrderNo(), siteBooking.getPayMoney().toString(), "/base/site/cancelMySiteCallback1"); |
| | | }else{ |
| | | Map<String, String> map = payMoneyUtil.wxRefund(siteBooking.getPayOrderNo(), siteBooking.getOrderNo(), siteBooking.getPayMoney().toString(), |
| | | siteBooking.getPayMoney().toString(), "/base/site/cancelMySiteCallback"); |
| | | if (null == map) { |
| | | return ResultUtil.error("取消退款异常"); |
| | | } |
| | | String return_code = map.get("return_code"); |
| | | if (!"SUCCESS".equals(return_code)) { |
| | | return ResultUtil.error(map.get("return_msg")); |
| | | } |
| | | siteBooking.setCancelUserId(uid); |
| | | siteBookingService.updateById(siteBooking); |
| | | |
| | | TBackRecord tBackRecord = new TBackRecord(); |
| | | tBackRecord.setMoney(siteBooking.getPayMoney()); |
| | | tBackRecord.setUserId(siteBooking.getAppUserId()); |
| | | tBackRecord.setTime(new Date()); |
| | | backRecordService.save(tBackRecord); |
| | | TBackRecord tBackRecord = new TBackRecord(); |
| | | tBackRecord.setMoney(siteBooking.getPayMoney()); |
| | | tBackRecord.setUserId(siteBooking.getAppUserId()); |
| | | tBackRecord.setTime(new Date()); |
| | | backRecordService.save(tBackRecord); |
| | | } |
| | | |
| | | |
| | | } |
| | | if (siteBooking.getPayType() == 2) {//支付宝 |
| | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.alipay.api.*; |
| | | import com.alipay.api.domain.*; |
| | | import com.alipay.api.msg.AlipayMsgClient; |
| | | 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 com.dsh.other.util.wx.*; |
| | | import org.apache.commons.collections.map.HashedMap; |
| | | import org.bouncycastle.jce.provider.BouncyCastleProvider; |
| | | import org.dom4j.Document; |
| | |
| | | import org.springframework.http.HttpHeaders; |
| | | import org.springframework.http.MediaType; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.crypto.*; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import javax.servlet.http.HttpServletRequest; |
| | |
| | | System.err.println(map3); |
| | | return ResultUtil.success(map3); |
| | | } |
| | | public ResultUtil weixinRefundV3(String subMchid,String outRefundNo, String transactionId, String totalFee, String notifyUrl) throws Exception { |
| | | BigDecimal bigDecimal = new BigDecimal(totalFee); |
| | | int i = bigDecimal.multiply(new BigDecimal("100")).intValue(); |
| | | // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340 |
| | | Create client = new Create( |
| | | "1681873607", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 |
| | | "55714944F7A7E52526F708280B176DCC838F371A", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 |
| | | "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 |
| | | "PUB_KEY_ID_0116818736072025042400351694002605", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 |
| | | "/usr/playpai/server/wxV3/pub_key.pem" // 微信支付公钥文件路径,本地文件路径 |
| | | ); |
| | | |
| | | Create.CreateRequest request = new Create.CreateRequest(); |
| | | request.subMchid = subMchid; |
| | | request.transactionId = transactionId; |
| | | request.outRefundNo = outRefundNo; |
| | | request.reason = "退款"; |
| | | request.notifyUrl = callbackPath+notifyUrl; |
| | | request.amount = new Create.AmountReq(); |
| | | request.amount.refund = (long) i; |
| | | request.amount.total = (long) i; |
| | | request.amount.currency = "CNY"; |
| | | try { |
| | | Create.Refund response = client.run(request); |
| | | |
| | | // TODO: 请求成功,继续业务逻辑 |
| | | System.out.println(response); |
| | | } catch (WXPayUtility.ApiException e) { |
| | | // TODO: 请求失败,根据状态码执行不同的逻辑 |
| | | e.printStackTrace(); |
| | | } |
| | | return ResultUtil.success(); |
| | | } |
| | | |
| | | /** |
| | | * 发起分账 |
New file |
| | |
| | | package com.dsh.other.util.wx; |
| | | |
| | | import com.dsh.other.util.wx.WXPayUtility; |
| | | import com.google.gson.annotations.Expose; |
| | | import com.google.gson.annotations.SerializedName; |
| | | 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; |
| | | |
| | | /** |
| | | * 退款申请 |
| | | */ |
| | | public class Create { |
| | | private static String HOST = "https://api.mch.weixin.qq.com"; |
| | | private static String METHOD = "POST"; |
| | | private static String PATH = "/v3/refund/domestic/refunds"; |
| | | |
| | | |
| | | public Refund run(CreateRequest 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, Refund.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 Create(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 enum Status { |
| | | @SerializedName("SUCCESS") |
| | | SUCCESS, |
| | | @SerializedName("CLOSED") |
| | | CLOSED, |
| | | @SerializedName("PROCESSING") |
| | | PROCESSING, |
| | | @SerializedName("ABNORMAL") |
| | | ABNORMAL |
| | | } |
| | | |
| | | public enum Account { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE |
| | | } |
| | | |
| | | public enum PromotionType { |
| | | @SerializedName("COUPON") |
| | | COUPON, |
| | | @SerializedName("DISCOUNT") |
| | | DISCOUNT |
| | | } |
| | | |
| | | 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("unit_price") |
| | | public Long unitPrice; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("refund_quantity") |
| | | public Integer refundQuantity; |
| | | } |
| | | |
| | | public static class CreateRequest { |
| | | @SerializedName("sub_mchid") |
| | | public String subMchid; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("reason") |
| | | public String reason; |
| | | |
| | | @SerializedName("notify_url") |
| | | public String notifyUrl; |
| | | |
| | | @SerializedName("funds_account") |
| | | public ReqFundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public AmountReq amount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public enum Channel { |
| | | @SerializedName("ORIGINAL") |
| | | ORIGINAL, |
| | | @SerializedName("BALANCE") |
| | | BALANCE, |
| | | @SerializedName("OTHER_BALANCE") |
| | | OTHER_BALANCE, |
| | | @SerializedName("OTHER_BANKCARD") |
| | | OTHER_BANKCARD |
| | | } |
| | | |
| | | public static class Amount { |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("payer_total") |
| | | public Long payerTotal; |
| | | |
| | | @SerializedName("payer_refund") |
| | | public Long payerRefund; |
| | | |
| | | @SerializedName("settlement_refund") |
| | | public Long settlementRefund; |
| | | |
| | | @SerializedName("settlement_total") |
| | | public Long settlementTotal; |
| | | |
| | | @SerializedName("discount_refund") |
| | | public Long discountRefund; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | |
| | | @SerializedName("refund_fee") |
| | | public Long refundFee; |
| | | } |
| | | |
| | | public enum ReqFundsAccount { |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED |
| | | } |
| | | |
| | | public enum FundsAccount { |
| | | @SerializedName("UNSETTLED") |
| | | UNSETTLED, |
| | | @SerializedName("AVAILABLE") |
| | | AVAILABLE, |
| | | @SerializedName("UNAVAILABLE") |
| | | UNAVAILABLE, |
| | | @SerializedName("OPERATION") |
| | | OPERATION, |
| | | @SerializedName("BASIC") |
| | | BASIC, |
| | | @SerializedName("ECNY_BASIC") |
| | | ECNY_BASIC |
| | | } |
| | | |
| | | public static class Promotion { |
| | | @SerializedName("promotion_id") |
| | | public String promotionId; |
| | | |
| | | @SerializedName("scope") |
| | | public PromotionScope scope; |
| | | |
| | | @SerializedName("type") |
| | | public PromotionType type; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | |
| | | @SerializedName("refund_amount") |
| | | public Long refundAmount; |
| | | |
| | | @SerializedName("goods_detail") |
| | | public List<GoodsDetail> goodsDetail; |
| | | } |
| | | |
| | | public static class Refund { |
| | | @SerializedName("refund_id") |
| | | public String refundId; |
| | | |
| | | @SerializedName("out_refund_no") |
| | | public String outRefundNo; |
| | | |
| | | @SerializedName("transaction_id") |
| | | public String transactionId; |
| | | |
| | | @SerializedName("out_trade_no") |
| | | public String outTradeNo; |
| | | |
| | | @SerializedName("channel") |
| | | public Channel channel; |
| | | |
| | | @SerializedName("user_received_account") |
| | | public String userReceivedAccount; |
| | | |
| | | @SerializedName("success_time") |
| | | public String successTime; |
| | | |
| | | @SerializedName("create_time") |
| | | public String createTime; |
| | | |
| | | @SerializedName("status") |
| | | public Status status; |
| | | |
| | | @SerializedName("funds_account") |
| | | public FundsAccount fundsAccount; |
| | | |
| | | @SerializedName("amount") |
| | | public Amount amount; |
| | | |
| | | @SerializedName("promotion_detail") |
| | | public List<Promotion> promotionDetail; |
| | | } |
| | | |
| | | public static class FundsFromItem { |
| | | @SerializedName("account") |
| | | public Account account; |
| | | |
| | | @SerializedName("amount") |
| | | public Long amount; |
| | | } |
| | | |
| | | public static class AmountReq { |
| | | @SerializedName("refund") |
| | | public Long refund; |
| | | |
| | | @SerializedName("from") |
| | | public List<FundsFromItem> from; |
| | | |
| | | @SerializedName("total") |
| | | public Long total; |
| | | |
| | | @SerializedName("currency") |
| | | public String currency; |
| | | } |
| | | |
| | | public enum PromotionScope { |
| | | @SerializedName("GLOBAL") |
| | | GLOBAL, |
| | | @SerializedName("SINGLE") |
| | | SINGLE |
| | | } |
| | | } |