commit
This commit is contained in:
@@ -25,6 +25,7 @@ public enum BpmProcessConstant {
|
||||
GRASSROOTS_CONGRESS_MATERIALS("基层双代会会议资料"),
|
||||
|
||||
MEMBER_SINGLE_APPLY("会员入会申请"),
|
||||
MEMBER_CHANGE("会员入会申请"),
|
||||
|
||||
MEMBER_BRANCH_UNION_CHANGE("会员分工会变更"),
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ public class RedisConstant {
|
||||
//企业微信token
|
||||
public final static String REDIS_KEY_WX_WORK_TOKEN = PLATFORM_REDIS_PREFIX + "wxwork:token:";
|
||||
|
||||
//健步走小程序TOKEN
|
||||
public final static String REDIS_KEY_WE_APP_ACCESS_TOKEN = PLATFORM_REDIS_PREFIX + "weapp:token:";
|
||||
|
||||
//图形验证码
|
||||
public final static String REDIS_CAPTCHA_KEY = PLATFORM_REDIS_PREFIX + "platfrom:captcha:";
|
||||
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.budwk.app.base.payment.alipay.controller;
|
||||
|
||||
import com.budwk.app.base.payment.alipay.service.AlipayService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @ClassName AlipayClientController
|
||||
* @Description TODO
|
||||
* @Author zhf
|
||||
* @Date 2024/1/25 15:36
|
||||
*/
|
||||
@IocBean
|
||||
@Slf4j
|
||||
@Ok("json:full")
|
||||
@At("/mobile/memberCost/AlipayClient")
|
||||
public class AlipayClientController {
|
||||
|
||||
|
||||
@Inject
|
||||
private AlipayService alipayService;
|
||||
|
||||
|
||||
@At("/tradePagePay")
|
||||
public String tradePagePay(String money, String paymentProjectId) throws Exception {
|
||||
log.info("统一收单下单并支付接口调用");
|
||||
String formStr = alipayService.tradeCreate(money, paymentProjectId);
|
||||
return formStr;
|
||||
}
|
||||
|
||||
@At("/tradeNotify")
|
||||
@Ok("re")
|
||||
public String tradeNotify(HttpServletRequest request) {
|
||||
try {
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
Map<String, String[]> requestParams = request.getParameterMap();
|
||||
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String) iter.next();
|
||||
String[] values = (String[]) requestParams.get(name);
|
||||
String valueStr = "";
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
valueStr = (i == values.length - 1) ? valueStr + values[i]
|
||||
: valueStr + values[i] + ",";
|
||||
}
|
||||
//乱码解决,这段代码在出现乱码时使用
|
||||
|
||||
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
|
||||
|
||||
params.put(name, valueStr);
|
||||
}
|
||||
log.info("支付通知,正在执行,通知参数:{}");
|
||||
return alipayService.tradeNotify(params);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.budwk.app.base.payment.alipay.service;
|
||||
|
||||
/**
|
||||
* @ClassName AlipayClientConfig
|
||||
* @Description TODO
|
||||
* @Author zhf
|
||||
* @Date 2024/1/25 15:33
|
||||
*/
|
||||
public interface AlipayClientConfig {
|
||||
String serverUrl = "https://openapi.alipay.com/gateway.do";
|
||||
String appId = "2021004133670087";
|
||||
String sellerId = "2088002638762620";
|
||||
String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCEJBWNlUIY/2Tave1OtjTgvA21GZ5tdvZZD3jFmixIFTkEdZaRUpXCvlKuZ9heoL1IQwpbuOErJ4dM2f2Ju/Oc7QtUOjEOX6CnElR73KK5OSY8d8f5822LUOgp2zg5FHI6C7olYmjN4Nw26G6BGyqQBmWs4/sKzghKelEaH9AqkR8DZ+yOvuYtIg5KEvVixsM27TMhd6/3BuNXDIiQDVH9ZeQVb9IKuynZ6hnUJS0T4MkS41qehS1u8LgplGU5yvrNuUNeQtybADsiGHDNmNkl5lVt0QNh+N/lo56//duf5PJwh8ApDB22eXptiS6p321PSrYowy5VeFpHC07Tref9AgMBAAECggEAPQddXPKegGUT82LcgwnsCg9QVV/rp6rVw/Q9VyYJZZFHsCOO0rdNq4+zKBGWAL5ELWepFQuiEwhCmN/lFQPVU5i3LsNhfL31uLPiiQIxtVILssmvdmVTJpDLaOlyYgprZxubcUCuHsfHbSSURNSDJ9mMg/kXLLpTYfJ0Gpiuz7lkrMuAxncdz88Sp2r0O9k2T+TcnHFpRXHs+C/QAaa15EF18ieDSBLPkY0ti1R8J91LGAzDrjwcP7pNlq4u7TasZ7djv47ZFwQ3yjWoo13d9ab0MBYeQx9Grf5N+JGNf33YLyulQ0IshcuwNyB+RYhyWGjNmjnJdytzKRMOYSpPgQKBgQC6iqMPX+LGunyI+3T9l/AwAVWRJXzEHPflNxDYfQbicB5awlRixxH5T1qyZ46KZb/pBt01qDULXy0bEObVFSjAn1wXPzC9Fwb+BtVZUroYBTKV3tOAMpVCz2AAC34OZwG/3X//sRtW9Sr0Dr+yCcx8ULMiFS1BuCZfhSNXA516NQKBgQC1V+rge1pYKGnjtWqML4qJy4FEIDI4fSpGIizxumYDMxdXoUdNKoG2NHQkznHU4fIHJPVvIN/Tm82mfj2rhH9eFuu1FB8TL2soe5nPgzSJ9AmP7rZ7KBo3/SczvZqUEpii8+PjHu+Gx95BmgTzOFMnYz8xzitb4IBGTEGxVzavqQKBgCL28XFkS6FsOwOB/n+6Bu84kM1P9X7c3yo7lBC//pZa5ZWlbyPEpH3SffGKr5EJWVtYSgZQrQCgqle0Z0/xU14ngJ9q3LvUoR7kMQE5HGB8OOsWhRY2ZPvM2vWnMc2JSS6pWeCXVcSK164/RXcdlxKGHc15oq360lZ9Ig6hQtcxAoGAIYijd2V4HGtEwYXvsQKtlLOcMNPfVZDXu2Zgd0bnzFmrTFoXjE569A2ZGKFQbXRmxClydV1X5pKeIdagYtI6l1SXu8F/yZVwNGk+1R/HHGeTSRl0hyuwRYA7upaqOg2CSkfdcUdsNblKAUmQ9sUIo3T336bnX7MRJW6+iyO8ihkCgYBG8vG1fVUwPmqlRK+QanHxicR1+UyjRAN+aW6dgJUNMlC+tmPeHj21+WGkDdKPdhinHziLihLmzbmpwWSDwuGMMQPNQm8gSjiaqy1tWcP6XKDxjpG5biTzwEyHNuCIY6odS4HTeDOrrU4LvN6PxirN8xKBIpq+A2cgpGo+Mn4x+g==";
|
||||
String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjO/QUy+ujNBlToL6Kt8YjvTRGpEwmXjqc9pFe7GIORVkzBXP3F/UC/w5mPiwXhhyymXAe7bUwLANL/QMUZhvpjjCdLENtFWC9bd6hq3m2oHiTu0oicbzqCuaoQkzik2NybrNjuH+aYVi4RU0eNYpdewQoHHW+hbfxkSYUWmx7NF4SuG93edYlpc/G/ImdrweATLE0b6W9il4H/BxVUweCraPrispiOM8r6TN3fiIKUQRP15HKQo8Xsr4pMJzfi37I9aN8AThN/xBf6sual8IZZsH5hC3PS37LrYkXbUjt0dK5tqKl3SsZPjvvHGyz9b9JC225+0q09Y6B8/axOlihwIDAQAB";
|
||||
|
||||
/* String serverUrl = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
|
||||
String appId = "9021000134641646";
|
||||
String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCBFTF8qFbK+NiqUbKf1/jWHL/s0hfUXK6cyKwZCEXkEMZH1Lu6kZQl3kQye9Fsasawxn1vLmKz+OKjMiE+TdTytHDu5ULdLlSHN3bY1qf91i8L+RJfmZpb1p+NQOk9zImhhSauuk+itMkTC3bTiwwAkg/utOxkNB7YaynpAb3evhXnlX3LUVTHaMnCQFeV7ithh7CDKx0LPlxOpOvxnkWDBR3nsdcfjDqYNDVHXnUxEk0KQV4Ik6fA40+Gq/HXTByJolCSAaTk0OaH2ULp1RZAklWLqhiNEhytTZAlJUx8cSm36mm0c4jhgSXNdRz0Yi1BlPY9yxQGStpH5ue3+EfvAgMBAAECggEAda/D4Zp9HysIJyi7SZ6XwWaO2TSnAzfI/yq6NUxkpE1i6cSbr7rgrjHaTYiz5DvpkOw3GnHZDarhvtaKlopRl0JKIJSnhI6kBG/eDE6Q9Q4c8LaSONULdaXU6+z9ehmMddW28eDtkDNxi8qM+MuyEqcYnZMYXzdh1ADIQicTXArxo+9fpvlKUMNxOTO5LoOf0DtJg/xE1CD9PtWVc+l2G8S3+C52+RhfZ214YQdxwayMlvJQlWzLTbfzJCp8w4F2up1nJdwp7EY8k8xDUngEnG6BWm8ztWUVTTg7qp/+SC9yMTD2dl86wDkJt1lTtwcKaWxHYh2ZIn8a8+icHFykaQKBgQDgBEWAmuj0pgpny5Q0GRsUYPq42SwpN70u4rmD5UYFCeDHaKb4U3kyvA1V+1Tp0XWzFi7GU855h1n8dnQX64J3Vm4GfhftWwTJgB5ibbY53cZVEoV5nZe0bOO4hxxMdNy3BKjPovkT0REPpCkVas/RRLAFCdnvi6vmDkFxvv9JewKBgQCTgx9NZZCqSzzCJYpLTDOCitooOQmR8kAlgBiDCcHeH2wNVMh5O2Dl/OyTGauKUJFWIOSD8FXVTrNUbOEmx58msnS/+Mc5SnbEka5ptVEDp73gf6ctB+eZAMdO1kLsBT3iKnuyNa7pRyR/D2A692RHOTQZIvRHE1N+kM1r+iBPHQKBgCZRVLocxUAjrp/2eJE4wiMIO5qmuyEJwbhlW6ot634tJF+3jOfpD1AYToeoh+u+wk0xvCp8Z1LuTfwbHtyAkj5rcEJ4Cwu0h+sXmRro/USEpMokcuRMioh4H7OOmM6TJrW/fEiX7dbv6eUtBUH+lTGMeDdEahzJXUvqoFMegnhFAoGAa2eflRUMflIFzT7UMPsszjIXog3VoASJHETmlBK4q+ipHhPLXCoV5Z+BCaI5Op5rFBEjEcYeWQ/Slvgn/7p9cQmi5FxZgqdrTrjcC2/Pqx+pqxqHOMx2nvroI8jyNTOj2VchdaKM1qHMOw+Q+55Bz3IhMuQ2hMHXm2IHkEUw3SkCgYBjcqcWbJcq26L8yYVpU2JFzOvu7iHuV99TpEvF9Ym3cAcSNuoR5kGXzGS0MadYY87cd4HK9HvmXQmthLY14buKYxq27ZFfNbK/Auz8vk0Wbrm1Ym4Z1yMqClQqxKdr4hV29ijn1nPProRqgnVsGH3EtFmNxDfilZoVzh9ERaZe7w==";
|
||||
String alipayPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArhElUiFMpxqFCs+8zDHa4OVuhdWgKUUGVKgWnfLktPgy9mDZkuGPKk295k8ktWQSUZsnFvT8ikJIKdGYfbSWzlXvCAn6lDDDlsjfOJWMQ6zLmb9xygCih2OjwN5xt9QFIIthnWq8sOXpWvqmoRpIFQHolxxmvvCmUZ/VwK6mwgwU/yVvVypruQ/FGrJA70LAcJ764spWAMy/pWIoIs5UewOlJa3TAcZQIqRnjupL2/sI/TpIWFWF90a/rahwLfBgZ83VH7MA4Tl9uFLc3aeNFLllDF4X7lRdzaHIMYgUUKte9yvlDy5Ihrt+ssjsgdRhqLHvFn00u/g7BCvO5ZmIUwIDAQAB";
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.base.payment.alipay.service;
|
||||
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface AlipayService extends BaseService {
|
||||
/**
|
||||
* 支付宝开放平台接收 request请求对象后
|
||||
* @return
|
||||
*/
|
||||
String tradeCreate( String money, String paymentProjectId) throws Exception;
|
||||
String tradeNotify(Map<String, String> params) ;
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.budwk.app.base.payment.alipay.service.impl;
|
||||
|
||||
import com.alipay.api.AlipayApiException;
|
||||
import com.alipay.api.AlipayConstants;
|
||||
import com.alipay.api.DefaultAlipayClient;
|
||||
import com.alipay.api.domain.AlipayTradeWapPayModel;
|
||||
import com.alipay.api.internal.util.AlipaySignature;
|
||||
import com.alipay.api.request.AlipayTradeWapPayRequest;
|
||||
import com.alipay.api.response.AlipayTradeWapPayResponse;
|
||||
import com.budwk.app.base.payment.alipay.service.AlipayClientConfig;
|
||||
import com.budwk.app.base.payment.alipay.service.AlipayService;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @ClassName AlipayNewServiceImpl
|
||||
* @Description TODO
|
||||
* @Author zhf
|
||||
* @Date 2024/1/25 15:38
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class AlipayNewServiceImpl extends BaseServiceImpl implements AlipayService {
|
||||
public AlipayNewServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
|
||||
DefaultAlipayClient alipayClient = new DefaultAlipayClient(AlipayClientConfig.serverUrl,
|
||||
AlipayClientConfig.appId,
|
||||
AlipayClientConfig.privateKey,
|
||||
AlipayConstants.FORMAT_JSON,
|
||||
AlipayConstants.CHARSET_UTF8,
|
||||
AlipayClientConfig.alipayPublicKey,
|
||||
AlipayConstants.SIGN_TYPE_RSA2);
|
||||
|
||||
@Override
|
||||
public String tradeCreate(String money, String paymentProjectId) throws Exception {
|
||||
String outTradeNo = String.valueOf(new Date().getTime());
|
||||
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
|
||||
//支付宝公共参数
|
||||
request.setReturnUrl("http://sansi.ltd/mobile/memberCost/AlipayClient/tradeNotify");
|
||||
|
||||
|
||||
//面向对象封装业务参数
|
||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||
model.setOutTradeNo(outTradeNo);
|
||||
model.setTotalAmount(money);
|
||||
model.setSubject("会员入会会费");
|
||||
model.setProductCode("QUICK_WAP_WAY");
|
||||
model.setSellerId(AlipayClientConfig.sellerId);
|
||||
model.setTimeoutExpress("30m");
|
||||
model.setQuitUrl("http://sansi.ltd/mobile/memberCost/list");
|
||||
|
||||
request.setBizModel(model);
|
||||
|
||||
//执行请求,调用支付宝
|
||||
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request, "POST");
|
||||
if (response.isSuccess()) {
|
||||
dao().update(MemberPaymentInfo.class, Chain.make("payId", outTradeNo), Cnd.where("paymentProjectId", "=", paymentProjectId)
|
||||
.and("userId", "=", SecurityUtil.getUserId()));
|
||||
log.info("调用成功,返回结果:[{}]", response.getBody());
|
||||
return response.getBody();
|
||||
} else {
|
||||
log.info("调用失败!!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String tradeNotify(Map<String, String> params) {
|
||||
String total_amount = params.get("total_amount");
|
||||
String out_trade_no = params.get("out_trade_no");
|
||||
String trade_no = params.get("trade_no");
|
||||
|
||||
//
|
||||
String result = "failure";
|
||||
try {
|
||||
//异步通知验签
|
||||
boolean signVerified = AlipaySignature.rsaCheckV1(params,
|
||||
AlipayClientConfig.alipayPublicKey,
|
||||
AlipayConstants.CHARSET_UTF8,
|
||||
AlipayConstants.SIGN_TYPE_RSA2);
|
||||
if (!signVerified) {
|
||||
// TODO 验签失败则记录异常日志,并在response中返回failure.
|
||||
log.error("支付成功,异步通知验签失败!");
|
||||
return result;
|
||||
}
|
||||
log.info("支付成功,异步通知验签成功!");
|
||||
//TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验
|
||||
//1.验证out_trade_no 是否为商家系统中创建的订单号
|
||||
String outTradeNo = params.get("out_trade_no");
|
||||
if (!outTradeNo.equals(out_trade_no)) {
|
||||
log.error("订单号不一致");
|
||||
return result;
|
||||
}
|
||||
//2.判断 total_amount 是否确实为该订单的实际金额
|
||||
String totalAmount = params.get("total_amount");
|
||||
if (!totalAmount.equals(total_amount)) {
|
||||
log.error("订单的实际金额不一致");
|
||||
return result;
|
||||
}
|
||||
//3.校验通知中的 seller_id是否为 out_trade_no 这笔单据的对应的操作方
|
||||
String sellerId = params.get("seller_id");
|
||||
if (!sellerId.equals(AlipayClientConfig.sellerId)) {
|
||||
log.error("商家PID校验失败");
|
||||
return result;
|
||||
}
|
||||
//4.验证 app_id 是否为该商家本身
|
||||
String appId = params.get("app_id");
|
||||
if (!appId.equals(AlipayClientConfig.appId)) {
|
||||
log.error("app_id校验失败");
|
||||
return result;
|
||||
}
|
||||
|
||||
dao().update(MemberPaymentInfo.class,
|
||||
Chain.make("isPayment", true)
|
||||
.add("paymentMoney", total_amount)
|
||||
.add("paymentTime", new Date())
|
||||
.add("tradeNo", trade_no),
|
||||
Cnd.where("userId", "=", SecurityUtil.getUserId()).and("payId", "=", out_trade_no));
|
||||
|
||||
//TODO 处理自身业务
|
||||
result = "->:/mobile/memberCost/list/paySuccess";
|
||||
} catch (AlipayApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.budwk.app.base.payment.enums;
|
||||
|
||||
import com.budwk.app.base.annotation.DictEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 退款状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@DictEnum(key = "RefundStateEnum", name = "退款状态枚举")
|
||||
public enum RefundStateEnum {
|
||||
|
||||
INITIATE_REFUND("发起退款"),
|
||||
REFUNDING("退款中"),
|
||||
REFUND_SUCCESS("退款成功"),
|
||||
REFUND_FAILED("退款失败");
|
||||
|
||||
/**
|
||||
* 避免和枚举类的name冲突
|
||||
*/
|
||||
public final String refundStateName;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.budwk.app.base.payment.enums;
|
||||
|
||||
import com.budwk.app.base.annotation.DictEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 交易渠道枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@DictEnum(key = "TradingChannelEnum", name = "交易渠道枚举")
|
||||
public enum TradingChannelEnum {
|
||||
|
||||
QQ_PAY("QQ"),
|
||||
WX_PAY("微信"),
|
||||
JD_PAY("京东"),
|
||||
ALI_PAY("支付宝"),
|
||||
UNION_PAY("云闪付");
|
||||
|
||||
/**
|
||||
* 避免和枚举类的name冲突
|
||||
*/
|
||||
public final String tradingChannelName;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.budwk.app.base.payment.enums;
|
||||
|
||||
import com.budwk.app.base.annotation.DictEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
/**
|
||||
* 支付状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@DictEnum(key = "TradingStateEnum", name = "支付状态枚举")
|
||||
public enum TradingStateEnum {
|
||||
|
||||
WAITING_FOR_PAYMENT("等待支付"),
|
||||
PAY_SUCCESS("支付成功"),
|
||||
CANCEL_PAYMENT("取消支付");
|
||||
|
||||
/**
|
||||
* 避免和枚举类的name冲突
|
||||
*/
|
||||
public final String tradingStateName;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.budwk.app.base.payment.enums;
|
||||
|
||||
import com.budwk.app.base.annotation.DictEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 交易类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@DictEnum(key = "TradingTypeEnum", name = "交易类型枚举")
|
||||
public enum TradingTypeEnum {
|
||||
|
||||
PAYMENT("付款"),
|
||||
REFUND("退款");
|
||||
|
||||
/**
|
||||
* 避免和枚举类的name冲突
|
||||
*/
|
||||
public final String tradingTypeName;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.budwk.app.base.payment.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayResultCode
|
||||
* @Date 2025/3/10 10:27
|
||||
* @注释 微信支付返回码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum WxPayResultCode {
|
||||
|
||||
SUCCESS(200, "操作成功"),
|
||||
APPID_MCHID_NOT_MATCH(400, "商户ID和服务号或企业微信号不匹配"),
|
||||
INVALID_REQUEST(400, "无效请求"),
|
||||
MCH_NOT_EXISTS(400, "商户号不存在"),
|
||||
PARAM_ERROR(400, "参数错误"),
|
||||
SIGN_ERROR(401, "签名错误"),
|
||||
NO_AUTH(403, "商户无权限"),
|
||||
OUT_TRADE_NO_USED(403, "商户订单号重复"),
|
||||
FREQUENCY_LIMITED(429, "频率超限"),
|
||||
SYSTEM_ERROR(500, "系统错误");
|
||||
|
||||
final int code;
|
||||
final String msg;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.budwk.app.base.payment.model;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 退款记录表
|
||||
*/
|
||||
@Data
|
||||
@Table
|
||||
@Comment("退款记录表")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaymentRefundRecord extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("关联系统交易订单表Id")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 32)
|
||||
private String paymentTransactionId;
|
||||
|
||||
@Column
|
||||
@Comment("本次退款订单号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String outRefundTradeNo;
|
||||
|
||||
@Column
|
||||
@Comment("第三方支付的退款单号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String refundNo;
|
||||
|
||||
@Column
|
||||
@Comment("退款状态")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 30)
|
||||
private String refundState;
|
||||
|
||||
@Column
|
||||
@Comment("本次退款金额")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer refundAmount;
|
||||
|
||||
@Column
|
||||
@Comment("原订单金额")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer total;
|
||||
|
||||
@Column
|
||||
@Comment("交易返回数据")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private String resultJson;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.budwk.app.base.payment.model;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 交易单表
|
||||
*/
|
||||
@Data
|
||||
@Table
|
||||
@Comment("交易订单表")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PaymentTransaction extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("商户号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String mchId;
|
||||
|
||||
@Column
|
||||
@Comment("订单号,传递给交易系统,交易系统订单号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String outTradeNo;
|
||||
|
||||
@Column
|
||||
@Comment("交易系统交易号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String transactionId;
|
||||
|
||||
@Column
|
||||
@Comment("交易渠道")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String tradingChannel;
|
||||
|
||||
@Column
|
||||
@Comment("交易类型")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 30)
|
||||
private String tradingType;
|
||||
|
||||
@Column
|
||||
@Comment("交易状态")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String tradingState;
|
||||
|
||||
@Column
|
||||
@Comment("用户ID")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 32)
|
||||
private String userId;
|
||||
|
||||
@Column
|
||||
@Comment("用户工号")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String loginName;
|
||||
|
||||
@Column
|
||||
@Comment("用户姓名")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String userName;
|
||||
|
||||
@Column
|
||||
@Comment("工会id")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 32)
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("用户工会")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String unionName;
|
||||
|
||||
@Column
|
||||
@Comment("单位id")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 32)
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
@Comment("用户单位")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String unitName;
|
||||
|
||||
@Column
|
||||
@Comment("交易金额")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer tradingAmount;
|
||||
|
||||
@Column
|
||||
@Comment("是否退款")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean isRefund;
|
||||
|
||||
@Column
|
||||
@Comment("退款金额")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer refundAmount;
|
||||
|
||||
@Column
|
||||
@Comment("订单描述")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 128)
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
@Comment("附加详细描述")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String attach;
|
||||
|
||||
@Column
|
||||
@Comment("微信支付用户openId")
|
||||
@ColDefine(type = ColType.VARCHAR,width = 50)
|
||||
private String openId;
|
||||
|
||||
@Column
|
||||
@Comment("交易返回数据")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private String resultJson;
|
||||
|
||||
@Column
|
||||
@Comment("交易名单的主键id")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String memberPaymentInfoId;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.budwk.app.base.payment.wxpay.bean;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayV3Bean
|
||||
* @Date 2025/3/6 9:54
|
||||
* @注释
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class WxPayV3Bean {
|
||||
|
||||
/**
|
||||
* 服务号
|
||||
* appid: wxf12fa6175ec7c75f
|
||||
* app_secret: f65bbb41d3de4a93c8c93d8e2fd50369
|
||||
*/
|
||||
|
||||
private String appId;
|
||||
private String keyPath;
|
||||
private String publicKeyPath;
|
||||
private String certPath;
|
||||
private String certP12Path;
|
||||
private String platformCertPath;
|
||||
private String mchId;
|
||||
private String apiKey;
|
||||
private String apiKey3;
|
||||
private String domain;
|
||||
private String publicKeyId;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WxPayV3Bean{" +
|
||||
"appId='" + appId + '\'' +
|
||||
", keyPath='" + keyPath + '\'' +
|
||||
", publicKeyPath='" + publicKeyPath + '\'' +
|
||||
", certPath='" + certPath + '\'' +
|
||||
", certP12Path='" + certP12Path + '\'' +
|
||||
", platformCertPath='" + platformCertPath + '\'' +
|
||||
", mchId='" + mchId + '\'' +
|
||||
", apiKey='" + apiKey + '\'' +
|
||||
", apiKey3='" + apiKey3 + '\'' +
|
||||
", domain='" + domain + '\'' +
|
||||
", publicKeyId='" + publicKeyId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.budwk.app.base.payment.wxpay.controller;
|
||||
|
||||
import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayCommonController
|
||||
* @Date 2025/3/10 19:28
|
||||
* @注释 微信支付回调
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/wxPay")
|
||||
public class WxPayCommonController {
|
||||
|
||||
@Inject
|
||||
private WxPayCommonService wxPayCommonService;
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("微信支付-付款回调")
|
||||
public void payNotify(HttpServletRequest request, HttpServletResponse response){
|
||||
wxPayCommonService.payNotify(request, response);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
public void refundNotify(HttpServletRequest request, HttpServletResponse response){
|
||||
wxPayCommonService.refundNotify(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEKDCCAxCgAwIBAgIUONYpzvxVfV/RrWnLGPlL6/nb/AcwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjCBgTETMBEGA1UEAwwK
|
||||
MTY2NjkzMDk4MDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
|
||||
DCTmrabmsYnlnLDlpKfkuInmgJ3np5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYT
|
||||
AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBALlDML+FJEW+m2hXp/qUH1cU6xlwFllLU4LjFusJkxwyzzcZO4nY0mOi
|
||||
3woz8xIJipvUWj2Y413WYwMkfcJcRgghupZkfYwdQtSyPfUu7xQBUm8TLWGH88Re
|
||||
kyyVABEB5T+ne6r3LesXS4IuZknm5N/iaa4z9mbbscGqF0GLvMUqZCRNyTkyfK9r
|
||||
Bkvs/vspi+Jcjx9I7aU++h/vwzsGB6Bf/uB3kLMb+lj/i3xY20sdrrW8JD2sqONQ
|
||||
NcRIKmNXSp4zsNy+d6kIKWE3P5609ZTta1ogkeGBNdGXMRNNy6vLIudQc7zny/9H
|
||||
S4LxGsIrsYmZNhGljRng56X+PG3w1CUCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsG
|
||||
A1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2Eu
|
||||
aXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRC
|
||||
MDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdB
|
||||
OUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ5e8U
|
||||
1pQ40gfRoZyO0s+QF16L8pAcVLVwHffo0mmJpIRB4bu8W5IJ9kKhksJ0CzZ0YEwB
|
||||
Yv7/LTC1qpi7PE73nZ4Ytm9oZeH1Kk5KIlQS+2H3IwOpoV4wucMc34y6MVQJjC7E
|
||||
u5OnglBPSAopwNP4p+Us9A3ah7Ks48XCw+LCEWpA/setJwo9C13Qq2AQm8qLpogK
|
||||
pNBwm/oylxb8/kZK1lLPgcXd7geLDhl/Str+aOzBXyd7YSEMxS8zWiZhWozu6rCq
|
||||
dg10QFKicPGDkDzNQwBz7cDXvlJXSWRDBK5r1li1YgX+QD5WJYPchATo5SlvF4Lr
|
||||
5/V/dZzI7BJTinr1
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5QzC/hSRFvpto
|
||||
V6f6lB9XFOsZcBZZS1OC4xbrCZMcMs83GTuJ2NJjot8KM/MSCYqb1Fo9mONd1mMD
|
||||
JH3CXEYIIbqWZH2MHULUsj31Lu8UAVJvEy1hh/PEXpMslQARAeU/p3uq9y3rF0uC
|
||||
LmZJ5uTf4mmuM/Zm27HBqhdBi7zFKmQkTck5MnyvawZL7P77KYviXI8fSO2lPvof
|
||||
78M7BgegX/7gd5CzG/pY/4t8WNtLHa61vCQ9rKjjUDXESCpjV0qeM7DcvnepCClh
|
||||
Nz+etPWU7WtaIJHhgTXRlzETTcuryyLnUHO858v/R0uC8RrCK7GJmTYRpY0Z4Oel
|
||||
/jxt8NQlAgMBAAECggEBAKGQqTi9Ii741Ysftsze1J4bDhS3HaZt/LyO0vPS+8qB
|
||||
vRiuFrBWuNRPu/en6nl2fLz9al2Bsfep+dtJvar80c5Rq+lwZdUFx7y6XTIv5iwq
|
||||
IvvfW5pM+Bfu6qbcopwybP0gUMd+nTsCq8UyNd6JUFy3lbZodqCpQ9JDRdjRxqSi
|
||||
oMonve36Q9kJyDs0mS2F0w+Y9oYFtpE9WvGMBthwT7VfzWuDa+YC/Gh0RhT2V9OG
|
||||
uDC8kxmfaIbk0IITDVFWVMMhScXiqmJL1II9yQWsCHH73dZS+dDxwwH0ObRD3kOx
|
||||
hHptxpRzhtuNxLjCOZDGhneyvUHIftpqf4wdOgQ8iUECgYEA6TDo36s3XDFIs8SH
|
||||
lpVYrhie0nmaNET50CtOc5C2M1OotrkMUrQTMoa4F263UloJfqweW9AKZX11wPp2
|
||||
8h7af2sZsGawjuwv7mMthZJoi0fc5J2+z6BnVGgFo7uZbcs1Y/+B0tNrDKIHVrqb
|
||||
Lywmh18iKDXrMiMDMRp8mtiNyU0CgYEAy2Im0oGb1rmAh9gPvQIENmNEgRsyIU4x
|
||||
WPAFb5KO7jnseO9KrC9uiRSO5JuwZu/QNGlga2w77Gu7n+Q4HfSU2KFlNhR6pKjb
|
||||
mqXzPq1quryzbtWay520VsRKz3F/Tfp1FUpIuZvj8x7FrL8z8vjbLkbW4p+Z7HE4
|
||||
heYdmk2KCjkCgYBC6xU48pJtc3swk2pxjSQoucoCDoFxFIY39eWKhnGy4wQcg5gE
|
||||
t8Z2jnCP0ObszAfDgTMpF9eT7l/3ZCj0LGR5gkuZ8q4iJadPmsh6d1xm89VxCc+z
|
||||
F43vi/ZqWTCbcGA+9RjVXnRu6KSAgAZ+kAXyfRt5PJflCGUnFznA2fo+VQKBgG6s
|
||||
n7Gno/UKBv5lPdM9mKs8A5g6A2KKWWIrYtnfJ/ZNAPiiRsFCImVoEYYnjqvFTkhn
|
||||
G3f5cvZpfbU5jKwIX0MvOJNgO4kE9lPGgIKX/d9WNlJW3KB21h7tPER8X6lAZJxM
|
||||
SxztzvKKyUkC1zMd5q/dDqxN64X16qs6mNtR3I9JAoGBAJbqQtg59Szi2m4jZ9Xo
|
||||
0cKvP7+PSKgysdWsemTkU0ji1rcMpGRTtcUGRWlawLjRSInQS+xNdL6GVU5mHZWe
|
||||
gNwXIXEpape4IJta0B1PYAhNcIvOSh5CzcUnVTeo6q0CTSVQPdsivlrObLKZ4uGQ
|
||||
eewDw82Ej6YoSgczjf99ug0x
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEFDCCAvygAwIBAgIUKN05Gm0Q9n9mLPPltUh8qjKKegQwDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjUwMzA2MTQwNDAzWhcNMzAwMzA1MTQwNDAzWjBuMRgwFgYDVQQDDA9U
|
||||
ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
|
||||
bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
|
||||
ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbMenF0GNlcUYErPpj
|
||||
Aj+WOuzRFz+PJcrtgkI81S0WDh8flMtq1A2nwKARNyPXx+UPd9it657nH5G8E9kL
|
||||
Ea5u5rENpn+JZc4cAIvZqazteSAUg2pfQH9ewW+/CxNOWarjXPNRYa0OjZWf+5F8
|
||||
ZBOT/x8sJ2+jou9lxKNe1skha+jzmiQ8m3B2vPXqR87qgL/fyQjjpLvBGpvearh/
|
||||
hxTMBEfGeB4F6Fu52Fz3/VCWe38ubxwj8EMcRSPp4xsl2BTaRVMMT525Hul2N49C
|
||||
vXCSVsCNt3ND8UDffXnb19Zgpir4KqpECTbfqcT7YX+U34P1Av8gcDl6rRm+7Wpj
|
||||
PY4RAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
|
||||
kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
|
||||
aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
|
||||
RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
|
||||
MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAUAvXCx55ZGmd3ATi28W4mirnUHQb2RAw
|
||||
X5/0OC2SkuBdzxI5RbY4YAuugP6nQIAU8TXCANzno2oqZ6vmFZ4dBnJmLewVFMoM
|
||||
qp18A4RWmnsGnmEIgMm6N1kB+RbM11oLCLdy2KK0SvmqDmTBFJyjyUGqEmRshnV/
|
||||
QfKn4UUzJ4suDFAolGX4DyDIOtM0D+SCHwwxGI4yf2+nj4IB8XgmmOeschxoSvu9
|
||||
T3E6BgQ271YVDwg+3r9CiFVI5JdyftvQagOEQmZJMPINEKgFn5LzrZJW7mH42+N+
|
||||
i4RFz04wpGUsrSFDESh3JxYSNhq/HulpMLinntWxEBHFhpkjyyGDGg==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.budwk.app.base.payment.wxpay.service;
|
||||
|
||||
|
||||
import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
|
||||
import com.budwk.app.base.result.Result;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayCommonService
|
||||
* @Date 2025/3/6 11:40
|
||||
* @注释
|
||||
*/
|
||||
public interface WxPayCommonService {
|
||||
|
||||
|
||||
/**
|
||||
* 二维码统一下单,返回支付二维码链接
|
||||
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
|
||||
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
|
||||
* @return
|
||||
*/
|
||||
Result nativePay(WxPayNeedParamVo paramVo);
|
||||
|
||||
|
||||
/**
|
||||
* JSAPI支付,返回支付参数
|
||||
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
|
||||
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
|
||||
* JsAPI支付,需要传递openId,请自行获取
|
||||
* @return
|
||||
*/
|
||||
Result jsApiPay(WxPayNeedParamVo paramVo);
|
||||
|
||||
/**
|
||||
* WAP支付,返回支付参数
|
||||
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
|
||||
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
|
||||
* @return
|
||||
*/
|
||||
Result wapPay(WxPayNeedParamVo paramVo);
|
||||
|
||||
|
||||
/**
|
||||
* 支付回调,验证是否支付成功
|
||||
* @param request 请求
|
||||
* @param response 响应
|
||||
*/
|
||||
void payNotify(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
|
||||
/**
|
||||
* 退款
|
||||
* @param transactionId 微信订单号
|
||||
* @param outTradeNo 商户订单号
|
||||
* @return
|
||||
*/
|
||||
Result refund(String transactionId, String outTradeNo);
|
||||
|
||||
|
||||
/**
|
||||
* 退款回调
|
||||
* @param request 请求
|
||||
* @param response 响应
|
||||
*/
|
||||
void refundNotify(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
+612
@@ -0,0 +1,612 @@
|
||||
package com.budwk.app.base.payment.wxpay.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.payment.enums.*;
|
||||
import com.budwk.app.base.payment.model.PaymentRefundRecord;
|
||||
import com.budwk.app.base.payment.model.PaymentTransaction;
|
||||
import com.budwk.app.base.payment.wxpay.bean.WxPayV3Bean;
|
||||
import com.budwk.app.base.payment.wxpay.service.WxPayCommonService;
|
||||
import com.budwk.app.base.payment.wxpay.vo.WxPayNeedParamVo;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.staffmanage.memberCost.model.MemberPaymentInfo;
|
||||
import com.ijpay.core.IJPayHttpResponse;
|
||||
import com.ijpay.core.enums.AuthTypeEnum;
|
||||
import com.ijpay.core.enums.RequestMethodEnum;
|
||||
import com.ijpay.core.kit.AesUtil;
|
||||
import com.ijpay.core.kit.HttpKit;
|
||||
import com.ijpay.core.kit.PayKit;
|
||||
import com.ijpay.core.kit.WxPayKit;
|
||||
import com.ijpay.wxpay.WxPayApi;
|
||||
import com.ijpay.wxpay.enums.WxDomainEnum;
|
||||
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
|
||||
import com.ijpay.wxpay.model.v3.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.random.R;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayCommonServiceImpl
|
||||
* @Date 2025/3/6 11:41
|
||||
* @注释
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
public class WxPayCommonServiceImpl implements WxPayCommonService {
|
||||
|
||||
// 判断下单接口成功返回码
|
||||
private final static int OK = 200;
|
||||
|
||||
// 1666930980 商户号
|
||||
// ww3eda050a1b5ad96e 企业微信
|
||||
// wxf12fa6175ec7c75f 微信服务号
|
||||
// 微信V3必要的支付参数,后期优化
|
||||
private final static WxPayV3Bean WX_PAY_V3_BEAN = new WxPayV3Bean(
|
||||
"wxf12fa6175ec7c75f",
|
||||
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
|
||||
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_key.pem",
|
||||
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
|
||||
"src/main/java/com/budwk/app/base/payment/wxpay/pem/apiclient_cert.pem",
|
||||
"src/main/java/com/budwk/app/base/payment/wxpay/pem/wechatpay.pem",
|
||||
"1666930980",
|
||||
"",
|
||||
"4ebd020883285d698c44ec50939c0967",
|
||||
"https://schoolhd.top/platform",
|
||||
"");
|
||||
|
||||
// 自定义参数,内容是统一的,但会被多次调用,直接声明
|
||||
private String serialNo;
|
||||
private String platSerialNo;
|
||||
private final String payNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/payNotify");
|
||||
private final String refundNotifyUrl = WX_PAY_V3_BEAN.getDomain().concat("/wxPay/refundNotify");
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private SysUserService sysUserService;
|
||||
|
||||
/**
|
||||
* 微信扫描二维码支付
|
||||
* @param paramVo 需要传递的信息,请自定义付款金额Amount 和 付款描述Description,其他关键信息请勿修改
|
||||
* 注意Amount的总价total是int类型,会自动乘以0.01,例如传递100元,会自动转换为1元
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result nativePay(WxPayNeedParamVo paramVo) {
|
||||
try {
|
||||
// 商户订单号,存储当前用户的订单
|
||||
String outTradeNo = PayKit.generateStr();
|
||||
|
||||
String timeExpire = getNowTimeExpire();
|
||||
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
|
||||
.setAppid(WX_PAY_V3_BEAN.getAppId())
|
||||
.setMchid(WX_PAY_V3_BEAN.getMchId())
|
||||
// 描述信息
|
||||
.setDescription(paramVo.getDescription())
|
||||
.setOut_trade_no(outTradeNo)
|
||||
.setTime_expire(timeExpire)
|
||||
.setAttach(paramVo.getAttach())
|
||||
.setNotify_url(payNotifyUrl)
|
||||
// 订单金额
|
||||
.setAmount(paramVo.getAmount());
|
||||
|
||||
log.info("二维码支付统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
|
||||
IJPayHttpResponse response = WxPayApi.v3(
|
||||
RequestMethodEnum.POST,
|
||||
WxDomainEnum.CHINA.toString(),
|
||||
BasePayApiEnum.NATIVE_PAY.toString(),
|
||||
WX_PAY_V3_BEAN.getMchId(),
|
||||
getSerialNumber(),
|
||||
null,
|
||||
WX_PAY_V3_BEAN.getKeyPath(),
|
||||
JSONUtil.toJsonStr(unifiedOrderModel),
|
||||
AuthTypeEnum.RSA.getCode()
|
||||
);
|
||||
log.info("统一下单响应 {}", response);
|
||||
// 根据证书序列号查询对应的证书来验证签名结果
|
||||
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
log.info("verifySignature: {}", verifySignature);
|
||||
// 下单返回数据
|
||||
String result = response.getBody();
|
||||
|
||||
JSONObject jsonObject = JSONUtil.parseObj(result);
|
||||
|
||||
if (response.getStatus() == OK) {
|
||||
// 统一下单成功,向数据库插入订单记录
|
||||
insertTradingOrder(paramVo, outTradeNo, result);
|
||||
return Result.success("请在三分钟内完成支付,超时将自动取消订单").addData(result);
|
||||
} else {
|
||||
WxPayResultCode code = WxPayResultCode.valueOf(WxPayResultCode.class, jsonObject.getStr("code"));
|
||||
return Result.error(code.getMsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result jsApiPay(WxPayNeedParamVo paramVo) {
|
||||
try {
|
||||
Sys_user user = dao.fetch(Sys_user.class, SecurityUtil.getUserId());
|
||||
if (StrUtil.isBlank(user.getOpenId())) {
|
||||
return Result.error("未获取到当前用户的openid,请重新登录");
|
||||
}
|
||||
// 商户订单号,存储当前用户的订单
|
||||
String outTradeNo = PayKit.generateStr();
|
||||
// 订单关闭时间
|
||||
String timeExpire = getNowTimeExpire();
|
||||
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
|
||||
.setAppid(WX_PAY_V3_BEAN.getAppId())
|
||||
.setMchid(WX_PAY_V3_BEAN.getMchId())
|
||||
.setDescription(paramVo.getDescription())
|
||||
.setOut_trade_no(outTradeNo)
|
||||
.setTime_expire(timeExpire)
|
||||
.setAttach(paramVo.getAttach())
|
||||
.setNotify_url(payNotifyUrl)
|
||||
.setAmount(paramVo.getAmount())
|
||||
.setPayer(new Payer().setOpenid(user.getOpenId()));
|
||||
|
||||
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
|
||||
IJPayHttpResponse response = WxPayApi.v3(
|
||||
RequestMethodEnum.POST,
|
||||
WxDomainEnum.CHINA.toString(),
|
||||
BasePayApiEnum.JS_API_PAY.toString(),
|
||||
WX_PAY_V3_BEAN.getMchId(),
|
||||
getSerialNumber(),
|
||||
null,
|
||||
WX_PAY_V3_BEAN.getKeyPath(),
|
||||
JSONUtil.toJsonStr(unifiedOrderModel)
|
||||
);
|
||||
log.info("统一下单响应 {}", response);
|
||||
// 根据证书序列号查询对应的证书来验证签名结果
|
||||
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
log.info("verifySignature: {}", verifySignature);
|
||||
if (response.getStatus() == OK && verifySignature) {
|
||||
String body = response.getBody();
|
||||
JSONObject jsonObject = JSONUtil.parseObj(body);
|
||||
String prepayId = jsonObject.getStr("prepay_id");
|
||||
Map<String, String> map = WxPayKit.jsApiCreateSign(WX_PAY_V3_BEAN.getAppId(), prepayId, WX_PAY_V3_BEAN.getKeyPath());
|
||||
log.info("唤起支付参数:{}", map);
|
||||
// 唤起支付成功,向数据库插入订单参数
|
||||
insertTradingOrder(paramVo, outTradeNo, body);
|
||||
return Result.success().addData(JSONUtil.toJsonStr(map));
|
||||
}
|
||||
// return JSONUtil.toJsonStr(response);
|
||||
return Result.error("唤起支付失败");
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result wapPay(WxPayNeedParamVo paramVo) {
|
||||
try {
|
||||
// 商户订单号,存储当前用户的订单
|
||||
String outTradeNo = PayKit.generateStr();
|
||||
// 订单关闭时间
|
||||
String timeExpire = getNowTimeExpire();
|
||||
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
|
||||
.setAppid(WX_PAY_V3_BEAN.getAppId())
|
||||
.setMchid(WX_PAY_V3_BEAN.getMchId())
|
||||
.setDescription(paramVo.getDescription())
|
||||
.setOut_trade_no(outTradeNo)
|
||||
.setTime_expire(timeExpire)
|
||||
.setAttach(paramVo.getAttach())
|
||||
.setNotify_url(payNotifyUrl)
|
||||
.setAmount(paramVo.getAmount())
|
||||
.setScene_info(
|
||||
new SceneInfo()
|
||||
.setPayer_client_ip("").
|
||||
setH5_info(new H5Info()
|
||||
.setType("Wap")));
|
||||
|
||||
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
|
||||
IJPayHttpResponse response = WxPayApi.v3(
|
||||
RequestMethodEnum.POST,
|
||||
WxDomainEnum.CHINA.toString(),
|
||||
BasePayApiEnum.H5_PAY.toString(),
|
||||
WX_PAY_V3_BEAN.getMchId(),
|
||||
getSerialNumber(),
|
||||
null,
|
||||
WX_PAY_V3_BEAN.getKeyPath(),
|
||||
JSONUtil.toJsonStr(unifiedOrderModel)
|
||||
);
|
||||
log.info("统一下单响应 {}", response);
|
||||
// 根据证书序列号查询对应的证书来验证签名结果
|
||||
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
log.info("verifySignature: {}", verifySignature);
|
||||
if (response.getStatus() == OK && verifySignature) {
|
||||
String body = response.getBody();
|
||||
insertTradingOrder(paramVo, outTradeNo, body);
|
||||
return Result.success(JSONUtil.toJsonStr(body));
|
||||
}
|
||||
// return JSONUtil.toJsonStr(response);
|
||||
return Result.error();
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付回调
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
@Override
|
||||
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, String> map = new HashMap<>(12);
|
||||
try {
|
||||
String timestamp = request.getHeader("Wechatpay-Timestamp");
|
||||
String nonce = request.getHeader("Wechatpay-Nonce");
|
||||
String serialNo = request.getHeader("Wechatpay-Serial");
|
||||
String signature = request.getHeader("Wechatpay-Signature");
|
||||
|
||||
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
|
||||
String result = HttpKit.readData(request);
|
||||
log.info("支付通知密文 {}", result);
|
||||
// String plainText = null;
|
||||
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
|
||||
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
|
||||
WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
// 微信公钥验证签名并解密
|
||||
// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
|
||||
// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
|
||||
// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
// }
|
||||
log.info("支付通知明文 {}", plainText);
|
||||
if (StrUtil.isNotEmpty(plainText)) {
|
||||
// 支付结果
|
||||
JSONObject jsonResult = JSONUtil.parseObj(plainText);
|
||||
// 获取缴费记录,并修改缴费结果
|
||||
PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", jsonResult.getStr("out_trade_no")));
|
||||
transaction.setTradingState(TradingStateEnum.PAY_SUCCESS.name());
|
||||
transaction.setTransactionId(jsonResult.getStr("transaction_id"));
|
||||
sysUserService.dao().update(transaction);
|
||||
|
||||
sysUserService.dao().update(MemberPaymentInfo.class,
|
||||
Chain.make("isPayment", true).add("paymentTime", DateUtil.date())
|
||||
.add("paymentMoney", transaction.getTradingAmount()),
|
||||
Cnd.where("id", "=", transaction.getMemberPaymentInfoId())
|
||||
);
|
||||
|
||||
response.setStatus(200);
|
||||
map.put("code", "SUCCESS");
|
||||
map.put("message", "SUCCESS");
|
||||
} else {
|
||||
response.setStatus(500);
|
||||
map.put("code", "ERROR");
|
||||
map.put("message", "签名错误");
|
||||
log.error("签名错误");
|
||||
}
|
||||
|
||||
// 支付回调,返回给微信,自己也接收不到结果,这里的返回没用
|
||||
response.setHeader("Content-type", ContentType.JSON.toString());
|
||||
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
|
||||
response.flushBuffer();
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退款
|
||||
* @param transactionId 微信订单号
|
||||
* @param outTradeNo 商户订单号
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Result refund(String transactionId, String outTradeNo) {
|
||||
try {
|
||||
// 查询商户订单
|
||||
PaymentTransaction transaction = sysUserService.dao().fetch(PaymentTransaction.class, Cnd.where("outTradeNo", "=", outTradeNo));
|
||||
Integer tradingAmount = transaction.getTradingAmount();
|
||||
String outRefundNo = PayKit.generateStr();
|
||||
log.info("商户退款单号: {}", outRefundNo);
|
||||
|
||||
// 没用
|
||||
// List<RefundGoodsDetail> list = new ArrayList<>();
|
||||
// RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
|
||||
// .setMerchant_goods_id("123")
|
||||
// .setGoods_name("测试")
|
||||
// .setUnit_price(1)
|
||||
// .setRefund_amount(1)
|
||||
// .setRefund_quantity(1);
|
||||
// list.add(refundGoodsDetail);
|
||||
|
||||
RefundModel refundModel = new RefundModel()
|
||||
.setOut_refund_no(outRefundNo)
|
||||
.setReason(transaction.getDescription() + "退款")
|
||||
.setNotify_url(refundNotifyUrl)
|
||||
.setAmount(new RefundAmount().setRefund(tradingAmount).setTotal(tradingAmount).setCurrency("CNY"));
|
||||
// .setGoods_detail(list);
|
||||
|
||||
if (StrUtil.isNotEmpty(transactionId)) {
|
||||
refundModel.setTransaction_id(transactionId);
|
||||
}
|
||||
if (StrUtil.isNotEmpty(outTradeNo)) {
|
||||
refundModel.setOut_trade_no(outTradeNo);
|
||||
}
|
||||
log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel));
|
||||
IJPayHttpResponse response = WxPayApi.v3(
|
||||
RequestMethodEnum.POST,
|
||||
WxDomainEnum.CHINA.toString(),
|
||||
BasePayApiEnum.REFUND.toString(),
|
||||
WX_PAY_V3_BEAN.getMchId(),
|
||||
getSerialNumber(),
|
||||
null,
|
||||
WX_PAY_V3_BEAN.getKeyPath(),
|
||||
JSONUtil.toJsonStr(refundModel)
|
||||
);
|
||||
// 根据证书序列号查询对应的证书来验证签名结果
|
||||
boolean verifySignature = WxPayKit.verifySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
// 微信支付公钥验证签名
|
||||
// boolean verifySignature = WxPayKit.verifyPublicKeySignature(response, WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
log.info("verifySignature: {}", verifySignature);
|
||||
log.info("退款响应 {}", response);
|
||||
|
||||
if (verifySignature) {
|
||||
// 用户发起退款,存一个退款记录
|
||||
PaymentRefundRecord record = new PaymentRefundRecord();
|
||||
record.setId(R.UU32());
|
||||
record.setPaymentTransactionId(transaction.getId());
|
||||
record.setOutRefundTradeNo(outRefundNo);
|
||||
record.setRefundNo(null);
|
||||
record.setRefundState(RefundStateEnum.REFUNDING.name());
|
||||
record.setRefundAmount(tradingAmount);
|
||||
record.setTotal(tradingAmount);
|
||||
record.setResultJson(response.getBody());
|
||||
dao.insert(record);
|
||||
|
||||
dao.update(MemberPaymentInfo.class, Chain.make("isRefund", true)
|
||||
.add("refundState", RefundStateEnum.REFUNDING.name()),
|
||||
Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
|
||||
|
||||
// 返回退款下单结果
|
||||
return Result.success();
|
||||
}
|
||||
return Result.error("退款失败");
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退款回调
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, String> map = new HashMap<>(12);
|
||||
try {
|
||||
String timestamp = request.getHeader("Wechatpay-Timestamp");
|
||||
String nonce = request.getHeader("Wechatpay-Nonce");
|
||||
String serialNo = request.getHeader("Wechatpay-Serial");
|
||||
String signature = request.getHeader("Wechatpay-Signature");
|
||||
|
||||
log.info("退款通知 timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
|
||||
String result = HttpKit.readData(request);
|
||||
log.info("退款通知密文 {}", result);
|
||||
// String plainText = null;
|
||||
// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
|
||||
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
|
||||
WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
// 微信公钥验证签名并解密
|
||||
// if (ObjectUtil.equals(serialNo, WX_PAY_V3_BEAN.getPublicKeyId())) {
|
||||
// plainText = WxPayKit.verifyPublicKeyNotify(result, signature, nonce, timestamp,
|
||||
// WX_PAY_V3_BEAN.getApiKey3(), WX_PAY_V3_BEAN.getPlatformCertPath());
|
||||
// }
|
||||
log.info("退款通知明文 {}", plainText);
|
||||
|
||||
if (StrUtil.isNotEmpty(plainText)) {
|
||||
// 取出明文,修改退款的结果
|
||||
JSONObject jsonObject = JSONUtil.parseObj(plainText);
|
||||
// 查询出退款记录,存储关键信息并修改
|
||||
PaymentRefundRecord record = dao.fetch(PaymentRefundRecord.class, Cnd.where("outRefundTradeNo", "=", jsonObject.getStr("out_refund_no")));
|
||||
record.setRefundNo(jsonObject.getStr("refund_id"));
|
||||
record.setRefundState(RefundStateEnum.REFUND_SUCCESS.name());
|
||||
// 到此是退款成功了,还要去修改订单表的数据
|
||||
PaymentTransaction transaction = dao.fetch(PaymentTransaction.class, record.getPaymentTransactionId());
|
||||
dao.update(PaymentTransaction.class, Chain.make("isRefund", true)
|
||||
.add("refundAmount", record.getRefundAmount()),
|
||||
Cnd.where("id", "=", record.getPaymentTransactionId()));
|
||||
dao.update(MemberPaymentInfo.class, Chain.make("isPayment", null).add("paymentMoney", null)
|
||||
.add("refundState", RefundStateEnum.REFUND_SUCCESS.name()),
|
||||
Cnd.where("id", "=", transaction.getMemberPaymentInfoId()));
|
||||
|
||||
dao.update(record);
|
||||
response.setStatus(200);
|
||||
map.put("code", "SUCCESS");
|
||||
map.put("message", "SUCCESS");
|
||||
} else {
|
||||
response.setStatus(500);
|
||||
map.put("code", "ERROR");
|
||||
map.put("message", "签名错误");
|
||||
}
|
||||
// 没用
|
||||
response.setHeader("Content-type", ContentType.JSON.toString());
|
||||
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
|
||||
response.flushBuffer();
|
||||
} catch (Exception e) {
|
||||
log.error("系统异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共方法,获取证书序列号
|
||||
* @return
|
||||
*/
|
||||
private String getSerialNumber() {
|
||||
if (StrUtil.isEmpty(serialNo)) {
|
||||
// 获取证书序列号
|
||||
X509Certificate certificate = PayKit.getCertificate(WX_PAY_V3_BEAN.getCertPath());
|
||||
if (null != certificate) {
|
||||
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
// 提前两天检查证书是否有效
|
||||
boolean isValid = PayKit.checkCertificateIsValid(certificate, WX_PAY_V3_BEAN.getMchId(), -2);
|
||||
log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
|
||||
}
|
||||
// System.out.println("输出证书信息:\n" + certificate.toString());
|
||||
// // 输出关键信息,截取部分并进行标记
|
||||
// System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
|
||||
// System.out.println("版本号:" + certificate.getVersion());
|
||||
// System.out.println("签发者:" + certificate.getIssuerDN());
|
||||
// System.out.println("有效起始日期:" + certificate.getNotBefore());
|
||||
// System.out.println("有效终止日期:" + certificate.getNotAfter());
|
||||
// System.out.println("主体名:" + certificate.getSubjectDN());
|
||||
// System.out.println("签名算法:" + certificate.getSigAlgName());
|
||||
// System.out.println("签名:" + certificate.getSignature().toString());
|
||||
}
|
||||
System.out.println("serialNo:" + serialNo);
|
||||
return serialNo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共方法,获取平台证书序列号
|
||||
* @return
|
||||
*/
|
||||
private String getPlatSerialNumber() {
|
||||
if (StrUtil.isEmpty(platSerialNo)) {
|
||||
// 获取平台证书序列号
|
||||
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(WX_PAY_V3_BEAN.getPlatformCertPath()));
|
||||
platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
}
|
||||
System.out.println("platSerialNo:" + platSerialNo);
|
||||
return platSerialNo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共方法,保存平台证书
|
||||
* @param associatedData
|
||||
* @param nonce
|
||||
* @param cipherText
|
||||
* @param algorithm
|
||||
* @param certPath
|
||||
* @return
|
||||
*/
|
||||
private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
|
||||
try {
|
||||
String key3 = WX_PAY_V3_BEAN.getApiKey3();
|
||||
String publicKey;
|
||||
if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
|
||||
publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
|
||||
} else {
|
||||
AesUtil aesUtil = new AesUtil(WX_PAY_V3_BEAN.getApiKey3().getBytes(StandardCharsets.UTF_8));
|
||||
// 平台证书密文解密
|
||||
// encrypt_certificate 中的 associated_data nonce ciphertext
|
||||
publicKey = aesUtil.decryptToString(
|
||||
associatedData.getBytes(StandardCharsets.UTF_8),
|
||||
nonce.getBytes(StandardCharsets.UTF_8),
|
||||
cipherText
|
||||
);
|
||||
}
|
||||
if (StrUtil.isNotEmpty(publicKey)) {
|
||||
// 保存证书
|
||||
FileWriter writer = new FileWriter(certPath);
|
||||
writer.write(publicKey);
|
||||
// 获取平台证书序列号
|
||||
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
|
||||
return certificate.getSerialNumber().toString(16).toUpperCase();
|
||||
}
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
log.error("保存平台证书异常", e);
|
||||
return e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共方法,构建订单关闭时间,默认3分钟
|
||||
* @return
|
||||
*/
|
||||
private String getNowTimeExpire() {
|
||||
long currentTimeMillis = System.currentTimeMillis() + 1000 * 60 * 3;
|
||||
// 将时间戳转换为 DateTime 对象
|
||||
DateTime dateTime = DateUtil.date(currentTimeMillis);
|
||||
return DateUtil.format(dateTime, "yyyy-MM-dd'T'HH:mm:ssXXX");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共方法,插入交易订单
|
||||
* @param paramVo 交易必填参数Vo
|
||||
* @param outTradeNo 商户交易号
|
||||
* @param result 下单返回结构
|
||||
*/
|
||||
private void insertTradingOrder(WxPayNeedParamVo paramVo, String outTradeNo, String result){
|
||||
View_user user = sysUserService.dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
PaymentTransaction transaction = new PaymentTransaction();
|
||||
transaction.setId(R.UU32());
|
||||
transaction.setMchId(WX_PAY_V3_BEAN.getMchId());
|
||||
transaction.setOutTradeNo(outTradeNo);
|
||||
transaction.setTransactionId(null);
|
||||
transaction.setTradingChannel(TradingChannelEnum.WX_PAY.name());
|
||||
transaction.setTradingType(TradingTypeEnum.PAYMENT.name());
|
||||
transaction.setUserId(user.getId());
|
||||
transaction.setLoginName(user.getLoginname());
|
||||
transaction.setUserName(user.getUsername());
|
||||
transaction.setUnitId(user.getUnitId());
|
||||
transaction.setUnitName(user.getUnitName());
|
||||
transaction.setUnionId(user.getUnionId());
|
||||
transaction.setUnionName(user.getUnionName());
|
||||
transaction.setTradingState(TradingStateEnum.WAITING_FOR_PAYMENT.name());
|
||||
// 获取订单金额
|
||||
transaction.setTradingAmount(paramVo.getAmount().getTotal());
|
||||
transaction.setIsRefund(false);
|
||||
transaction.setRefundAmount(null);
|
||||
transaction.setDescription(paramVo.getDescription());
|
||||
transaction.setAttach(paramVo.getAttach());
|
||||
transaction.setOpenId(null);
|
||||
transaction.setResultJson(result);
|
||||
transaction.setMemberPaymentInfoId(paramVo.getMemberPaymentInfoId());
|
||||
sysUserService.dao().insert(transaction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.budwk.app.base.payment.wxpay.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.budwk.app.base.constant.RedisConstant;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.http.Http;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WeChatUtils
|
||||
* @Date 2025/3/10 14:13
|
||||
* @注释
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean
|
||||
public class WeChatUtils {
|
||||
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String KEY = "4eXvXq1ZJmZj8z5z6yXvXq1ZJmZj8z5z";
|
||||
|
||||
@Inject
|
||||
private RedisService redisService;
|
||||
|
||||
public static final String APPID = "wxf12fa6175ec7c75f";
|
||||
|
||||
public static final String SECRET = "f65bbb41d3de4a93c8c93d8e2fd50369";
|
||||
|
||||
// 获取code
|
||||
public final static String CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
|
||||
|
||||
//获取网页access_token的接口地址
|
||||
public final static String WEB_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
|
||||
|
||||
|
||||
//获取用户信息地址
|
||||
public final static String USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
|
||||
|
||||
// 获取access_token的接口地址(GET) 限2000(次/天)
|
||||
public final static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
|
||||
// 发送模板消息
|
||||
public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
|
||||
|
||||
|
||||
/**
|
||||
* 生成获取code的url
|
||||
*
|
||||
* @param redirectUrl
|
||||
* @return
|
||||
*/
|
||||
public String getCodeUrl(String redirectUrl, String state) {
|
||||
String requestUrl = null;
|
||||
state = state == null ? "" : state;
|
||||
try {
|
||||
requestUrl = CODE_URL.replace("APPID", APPID).replace("REDIRECT_URI", URLEncoder.encode(redirectUrl, "UTF-8")).replace("STATE", state);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信网页access_token 和 openid
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public JSONObject getWebAccessTokenAndOpenid(String code) {
|
||||
String requestUrl = WEB_ACCESS_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", SECRET).replace("CODE", code);
|
||||
return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了
|
||||
* <a href="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html">...</a>
|
||||
*
|
||||
* @param webAccessToken
|
||||
* @param openid
|
||||
* @return
|
||||
*/
|
||||
public JSONObject getUserInfo(String webAccessToken, String openid) {
|
||||
String requestUrl = USERINFO_URL.replace("ACCESS_TOKEN", webAccessToken).replace("OPENID", openid);
|
||||
return JSONUtil.parseObj(HttpUtil.post(requestUrl, new JSONObject()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取access_token 先从Redis获取,如果没有再调微信获取并存Redis
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
String token = redisService.get(RedisConstant.REDIS_KEY_WX_TOKEN);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
return token;
|
||||
}
|
||||
String accessTokenUrl = ACCESS_TOKEN_URL.formatted(APPID, SECRET);
|
||||
String accessToken = Json.fromJson(NutMap.class, Http.get(accessTokenUrl).getContent()).getString("access_token");
|
||||
redisService.setex(RedisConstant.REDIS_KEY_WX_TOKEN, 60 * 60 * 2, accessToken);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
|
||||
public String encrypt(String data) throws Exception {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
||||
byte[] encryptedBytes = cipher.doFinal(data.getBytes());
|
||||
return Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
}
|
||||
|
||||
public String decrypt(String encryptedData) throws Exception {
|
||||
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec);
|
||||
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
|
||||
return new String(decryptedBytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.budwk.app.base.payment.wxpay.vo;
|
||||
|
||||
import com.ijpay.wxpay.model.v3.Amount;
|
||||
import com.ijpay.wxpay.model.v3.Payer;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @version 1.0
|
||||
* @Author zzr
|
||||
* @name:WxPayNeedParamVo
|
||||
* @Date 2025/3/7 18:43
|
||||
* @注释
|
||||
*/
|
||||
@Data
|
||||
public class WxPayNeedParamVo {
|
||||
|
||||
/**
|
||||
* 订单描述,会显示在支付界面的标题,最大长度127,必填
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 附加数据,订单的详细描述信息,不必填
|
||||
*/
|
||||
private String attach;
|
||||
|
||||
/**
|
||||
* 金额对象,对象中的 total 是金额,必填;
|
||||
* 其余参数可不传递也可不填
|
||||
*/
|
||||
private Amount amount;
|
||||
|
||||
/**
|
||||
* 会员支付信息ID,必填
|
||||
*/
|
||||
private String memberPaymentInfoId;
|
||||
}
|
||||
Reference in New Issue
Block a user