AlipayPaymentServiceImpl.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. package cn.sikey.pay.service.alipay;
  2. import cn.hutool.core.util.StrUtil;
  3. import cn.hutool.http.HttpUtil;
  4. import cn.hutool.json.JSONUtil;
  5. import cn.sikey.framework.common.exception.ServiceException;
  6. import cn.sikey.framework.common.pojo.CommonResult;
  7. import cn.sikey.hmd.api.hmd.vo.payments.PaymentsRespVO;
  8. import cn.sikey.hmd.api.hmd.vo.plans.PlansRespVO;
  9. import cn.sikey.order.api.order.OrderApi;
  10. import cn.sikey.order.api.order.vo.GenerateOrderReqVO;
  11. import cn.sikey.pay.api.alipay.dto.AliPayTradeDTO;
  12. import cn.sikey.pay.controller.app.alipay.vo.AlipayConfigExtendReqVO;
  13. import cn.sikey.pay.controller.app.alipay.vo.TcmPulseDiagnosisGateWayReqVO;
  14. import cn.sikey.pay.entity.alipay.AlipayTradeQuery;
  15. import cn.sikey.pay.enums.AlipayPayStatusEnum;
  16. import cn.sikey.pay.enums.PayChannelEnum;
  17. import cn.sikey.pay.enums.PayStatusEnum;
  18. import cn.sikey.pay.service.AbstractPaymentService;
  19. import cn.sikey.pay.util.MapUtil;
  20. import com.alipay.v3.ApiClient;
  21. import com.alipay.v3.ApiException;
  22. import com.alipay.v3.api.AlipayTradeApi;
  23. import com.alipay.v3.model.AlipayTradeQueryDefaultResponse;
  24. import com.alipay.v3.model.AlipayTradeQueryModel;
  25. import com.alipay.v3.model.AlipayTradeQueryResponseModel;
  26. import com.alipay.v3.util.AlipaySignature;
  27. import com.alipay.v3.util.GenericExecuteApi;
  28. import jakarta.annotation.Resource;
  29. import jakarta.servlet.http.HttpServletRequest;
  30. import jakarta.servlet.http.HttpServletResponse;
  31. import lombok.extern.slf4j.Slf4j;
  32. import org.apache.http.HttpStatus;
  33. import org.springframework.beans.factory.annotation.Autowired;
  34. import org.springframework.beans.factory.annotation.Qualifier;
  35. import org.springframework.beans.factory.annotation.Value;
  36. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  37. import org.springframework.stereotype.Service;
  38. import java.math.BigDecimal;
  39. import java.net.URLEncoder;
  40. import java.nio.charset.StandardCharsets;
  41. import java.util.HashMap;
  42. import java.util.Map;
  43. import java.util.Objects;
  44. /**
  45. * @Author: qin
  46. * @Date: 2025/3/31
  47. * @Description: alipay
  48. */
  49. @Service("alipayPaymentService")
  50. @Slf4j
  51. public class AlipayPaymentServiceImpl extends AbstractPaymentService implements AlipayPaymentService {
  52. private static final String PRODUCT_CODE = "QUICK_WAP_WAY";
  53. private static final String TCM_PULSE_DIAGNOSIS = "2:tcmPulseDiagnosis:1";
  54. private static final String APP_ID = "app_id";
  55. @Resource
  56. private OrderApi orderApi;
  57. @Qualifier("myTaskExecutor")
  58. @Autowired
  59. private ThreadPoolTaskExecutor myTaskExecutor;
  60. @Resource
  61. private Map<String, ApiClient> alipayClients;
  62. @Resource
  63. private Map<String, String> alipayPublicKeys;
  64. @Resource
  65. private Map<String, AlipayConfigExtendReqVO> alipayParamConfigs;
  66. private static final String PASSBACK_PARAMS = "passback_params";
  67. private static final String MERCHANT_PROJECT_APPLICATION = "merchantProjectApplication";
  68. private static final String ALIPAY_TRADE_WAP_PAY = "alipay.trade.wap.pay";
  69. @Value(value = "${tcmPulseDiagnosis.url}")
  70. private String tcmPulseDiagnosisUrl;
  71. private static final String PAY_TYPE = "支付宝";
  72. // TODO 暂时这么写
  73. private Map<String, String> merchantAppIdConfig = new HashMap<>() {{
  74. put("1:hmd:1", "2021003175691070");
  75. put("2:tcmPulseDiagnosis:1", "2021003175691070");
  76. }};
  77. /**
  78. * 手机网站支付
  79. *
  80. * @param aliPayTradeDTO alipay
  81. * @return
  82. */
  83. @Override
  84. public CommonResult<String> alipayTradeWapPay(AliPayTradeDTO aliPayTradeDTO) {
  85. String merchantProjectApplication = aliPayTradeDTO.getMerchantProjectApplication();
  86. if (StrUtil.isBlank(merchantProjectApplication)) {
  87. log.warn("[手机网站支付-支付宝]请配置商户配置");
  88. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请配置商户配置");
  89. }
  90. // 构造请求参数以调用接口
  91. Map<String, Object> bizParams = new HashMap<>();
  92. Map<String, Object> bizContent = new HashMap<>();
  93. // 设置商户订单号
  94. bizContent.put("out_trade_no", aliPayTradeDTO.getOut_trade_no());
  95. // 设置订单总金额
  96. bizContent.put("total_amount", aliPayTradeDTO.getTotal_amount());
  97. // 设置订单标题
  98. bizContent.put("subject", aliPayTradeDTO.getSubject());
  99. // 设置产品码
  100. bizContent.put("product_code", PRODUCT_CODE);
  101. // 设置公用回传参数
  102. bizContent.put(PASSBACK_PARAMS, URLEncoder.encode(MERCHANT_PROJECT_APPLICATION + "=" + merchantProjectApplication, StandardCharsets.UTF_8));
  103. // 设置订单附加信息
  104. // bizContent.put("body", aliPayTrade.getBody());
  105. bizParams.put("biz_content", bizContent);
  106. String appId = merchantAppIdConfig.get(merchantProjectApplication);
  107. bizParams.put("notify_url", alipayParamConfigs.get(appId).getNotifyUrl());
  108. bizParams.put("return_url", alipayParamConfigs.get(appId).getReturnUrl());
  109. try {
  110. ApiClient client = alipayClients.get(merchantAppIdConfig.get(merchantProjectApplication));
  111. GenericExecuteApi api = new GenericExecuteApi(client);
  112. // 如果是第三方代调用模式,请设置app_auth_token(应用授权令牌)
  113. String pageRedirectionData = api.pageExecute(ALIPAY_TRADE_WAP_PAY, "POST", bizParams, null, null, null);
  114. // 如果需要返回GET请求,请使用
  115. // String pageRedirectionData = api.pageExecute("alipay.trade.wap.pay", "GET", bizParams);
  116. log.info("[手机网站支付-支付宝]alipay html:{}", pageRedirectionData);
  117. return CommonResult.success(pageRedirectionData);
  118. } catch (Exception e) {
  119. log.error("[手机网站支付-支付宝]初始化alipay支付参数失败,{0}", e);
  120. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "初始化alipay支付参数失败");
  121. }
  122. }
  123. /**
  124. * 手机网站支付,回调
  125. * <p>
  126. * 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、
  127. * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
  128. * TRADE_SUCCESS(交易支付成功)、
  129. * TRADE_FINISHED(交易结束,不可退款)
  130. *
  131. * @param request 参数
  132. * @param response 参数
  133. * @return
  134. */
  135. @Override
  136. public String tradeWapPayGateway(HttpServletRequest request, HttpServletResponse response) {
  137. // 根据 商户唯一标识-项目名-应用类型 调用对应服务
  138. Map<String, String> paramsMap = MapUtil.convertRequestToMap(request);
  139. log.info("[手机网站支付-支付宝,回调]处理支付成功,支付信息:{}", paramsMap);
  140. String passBackParams = paramsMap.get(PASSBACK_PARAMS);
  141. if (StrUtil.isBlank(passBackParams)) {
  142. log.warn("[手机网站支付-支付宝,回调] 回传参数是空");
  143. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "回传参数是空");
  144. }
  145. // passBackParams = URLDecoder.decode(passBackParams, StandardCharsets.UTF_8);
  146. String[] parts = StrUtil.splitToArray(passBackParams, '=');
  147. String tradeStatus = paramsMap.get("trade_status");
  148. String outTradeNo = String.valueOf(paramsMap.get("out_trade_no"));
  149. // 商户项目应用
  150. String merchantProjectApplication = parts[1];
  151. if (StrUtil.equals(merchantProjectApplication, TCM_PULSE_DIAGNOSIS)) {
  152. log.info("[手机网站支付-支付宝,回调]调用中医诊断服务");
  153. // 中医诊断服务
  154. TcmPulseDiagnosisGateWayReqVO reqVO = new TcmPulseDiagnosisGateWayReqVO();
  155. reqVO.setOut_trade_no(outTradeNo);
  156. reqVO.setGmt_payment(paramsMap.get("gmt_payment"));
  157. reqVO.setTrade_status(tradeStatus);
  158. String result = HttpUtil.post(tcmPulseDiagnosisUrl, JSONUtil.toJsonStr(reqVO));
  159. log.info("[手机网站支付-支付宝,回调]调用中医诊断服务返回结果:{}", result);
  160. if (StrUtil.equals(result, "success")) {
  161. return "success";
  162. } else {
  163. return "failure";
  164. }
  165. }
  166. CommonResult<GenerateOrderReqVO> queryOrder = orderApi.queryOrder(outTradeNo);
  167. if (!queryOrder.getCode().equals(HttpStatus.SC_OK)) {
  168. log.warn("[手机网站支付-支付宝,回调]调用订单服务失败");
  169. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "调用订单服务失败");
  170. }
  171. // 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
  172. GenerateOrderReqVO generateOrderReqVO = queryOrder.getData();
  173. if (Objects.isNull(generateOrderReqVO)) {
  174. log.warn("[手机网站支付-支付宝,回调]不存在该订单,订单号:{}", outTradeNo);
  175. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "不存在该订单");
  176. }
  177. Long orderId = generateOrderReqVO.getId();
  178. CommonResult<PaymentsRespVO> respVOCommonResult = paymentApi.queryPayments(orderId);
  179. if (!respVOCommonResult.getCode().equals(HttpStatus.SC_OK)) {
  180. log.warn("[手机网站支付-支付宝,回调]调用hmd付款交易服务查询失败,订单id:{}", orderId);
  181. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "调用hmd付款交易服务查询失败");
  182. }
  183. PaymentsRespVO paymentsRespVO = respVOCommonResult.getData();
  184. // 幂等处理
  185. if (Objects.nonNull(paymentsRespVO) && Objects.nonNull(paymentsRespVO.getStatus())) {
  186. if (paymentsRespVO.getStatus().equals(PayStatusEnum.SUCCESS.getValue())) {
  187. log.warn("[手机网站支付-支付宝,回调]重复回调,订单号:{}", outTradeNo);
  188. return "success";
  189. }
  190. }
  191. // 支付宝appId
  192. String appId = merchantAppIdConfig.get(merchantProjectApplication);
  193. // 支付宝公钥
  194. String alipayPublicKey = alipayPublicKeys.get(appId);
  195. try {
  196. boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, alipayPublicKey, "UTF-8", "RSA2");
  197. if (signVerified) {
  198. log.info("[手机网站支付-支付宝,回调]验签成功");
  199. myTaskExecutor.execute(() -> {
  200. AlipayPayStatusEnum status = AlipayPayStatusEnum.fromStatusCode(tradeStatus);
  201. switch (status) {
  202. case TRADE_SUCCESS:
  203. case TRADE_FINISHED:
  204. // 更新支付记录
  205. super.updatePayments(generateOrderReqVO.getId(), paramsMap.get("trade_no"), PAY_TYPE);
  206. handlePaymentSuccess(paramsMap, generateOrderReqVO);
  207. case TRADE_CLOSED:
  208. log.info("[手机网站支付-支付宝,回调]支付宝交易关闭: outTradeNo:{}", outTradeNo);
  209. break;
  210. case WAIT_BUYER_PAY:
  211. log.info("[手机网站支付-支付宝,回调]等待买家付款: outTradeNo:{}", outTradeNo);
  212. break;
  213. default:
  214. log.info("[手机网站支付-支付宝,回调]未知交易状态: outTradeNo:{}", outTradeNo);
  215. }
  216. log.info("[手机网站支付-支付宝,回调]处理支付成功,状态:{}", status);
  217. });
  218. return "success";
  219. } else {
  220. log.info("[手机网站支付-支付宝,回调]验签失败");
  221. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "验签失败");
  222. }
  223. } catch (Exception e) {
  224. log.error("[手机网站支付-支付宝,回调]异常:{1}", e);
  225. super.savePaymentExceptionLogs(paymentsRespVO, PayChannelEnum.ALI_PAY.getValue(), merchantProjectApplication, e);
  226. }
  227. return "success";
  228. }
  229. /**
  230. * 统一收单交易查询
  231. *
  232. * @param alipayTradeQuery alipay
  233. * @return
  234. */
  235. @Override
  236. public CommonResult<String> alipayTradeQuery(AlipayTradeQuery alipayTradeQuery) {
  237. String merchantProjectApplication = alipayTradeQuery.getMerchantProjectApplication();
  238. if (StrUtil.isBlank(merchantProjectApplication)) {
  239. log.warn("[统一收单交易查询]请配置商户配置");
  240. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请配置商户配置");
  241. }
  242. ApiClient client = alipayClients.get(merchantAppIdConfig.get(merchantProjectApplication));
  243. // 构造请求参数以调用接口
  244. AlipayTradeApi api = new AlipayTradeApi(client);
  245. AlipayTradeQueryModel data = new AlipayTradeQueryModel();
  246. // 设置订单支付时传入的商户订单号
  247. data.setOutTradeNo(alipayTradeQuery.getOutTradeNo());
  248. try {
  249. AlipayTradeQueryResponseModel response = api.query(data);
  250. String tradeStatus = response.getTradeStatus();
  251. log.info("[[统一收单交易查询-支付宝]]交易状态:{}", tradeStatus);
  252. return CommonResult.success(tradeStatus);
  253. } catch (ApiException e) {
  254. AlipayTradeQueryDefaultResponse errorObject = (AlipayTradeQueryDefaultResponse) e.getErrorObject();
  255. log.error("[统一收单交易查询-支付宝]异常:{}", errorObject);
  256. return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, errorObject.getAlipayTradeQueryErrorResponseModel().getMessage());
  257. }
  258. }
  259. /**
  260. * 手机网站支付,return返回,验签
  261. *
  262. * @param params 参数
  263. * @return
  264. */
  265. @Override
  266. public CommonResult<Integer> verifySignVerified(Map<String, String> params) {
  267. try {
  268. // 支付宝公钥
  269. String alipayPublicKey = alipayPublicKeys.get(params.get(APP_ID));
  270. // 验签
  271. boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2");
  272. if (!signVerified) {
  273. log.warn("[手机网站支付,return返回,验签]签名验证失败");
  274. return CommonResult.success(0);
  275. }
  276. log.info("[手机网站支付,return返回,验签]验签完成");
  277. return CommonResult.success(1);
  278. } catch (Exception e) {
  279. log.warn("[手机网站支付,return返回,验签]签名验证失败:{0}", e);
  280. return CommonResult.success(0);
  281. }
  282. }
  283. /**
  284. * 处理支付成功
  285. *
  286. * @param paramsMap 回调参数
  287. * @param generateOrderReqVO 订单
  288. * @return
  289. */
  290. private void handlePaymentSuccess(Map<String, String> paramsMap, GenerateOrderReqVO generateOrderReqVO) {
  291. // 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)。
  292. Integer orderTotalAmount = generateOrderReqVO.getTotalAmount();
  293. String deviceId = generateOrderReqVO.getDeviceId();
  294. if (StrUtil.isBlank(paramsMap.get("total_amount")) || Objects.isNull(orderTotalAmount)) {
  295. log.warn("[手机网站支付-支付宝,回调]订单金额是空");
  296. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "订单金额是空");
  297. }
  298. BigDecimal total = new BigDecimal(paramsMap.get("total_amount"));
  299. int totalAmount = total.multiply(new BigDecimal(100)).intValue();
  300. if (totalAmount != orderTotalAmount.intValue()) {
  301. log.warn("[手机网站支付-支付宝,回调]订单金额与支付金额不匹配");
  302. throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "订单金额与支付金额不匹配");
  303. }
  304. PlansRespVO plansRespVO = super.queryPlans(generateOrderReqVO.getPlansId(), PAY_TYPE);
  305. super.updateRemainingTimesExpireTime(plansRespVO.getValidity(), deviceId, PAY_TYPE);
  306. log.info("[手机网站支付-支付宝,回调]处理支付成功");
  307. }
  308. }