123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- package cn.sikey.pay.service.alipay;
- import cn.hutool.core.util.StrUtil;
- import cn.hutool.http.HttpUtil;
- import cn.hutool.json.JSONUtil;
- import cn.sikey.framework.common.exception.ServiceException;
- import cn.sikey.framework.common.pojo.CommonResult;
- import cn.sikey.hmd.api.hmd.vo.payments.PaymentsRespVO;
- import cn.sikey.hmd.api.hmd.vo.plans.PlansRespVO;
- import cn.sikey.order.api.order.OrderApi;
- import cn.sikey.order.api.order.vo.GenerateOrderReqVO;
- import cn.sikey.pay.api.alipay.dto.AliPayTradeDTO;
- import cn.sikey.pay.controller.app.alipay.vo.AlipayConfigExtendReqVO;
- import cn.sikey.pay.controller.app.alipay.vo.TcmPulseDiagnosisGateWayReqVO;
- import cn.sikey.pay.entity.alipay.AlipayTradeQuery;
- import cn.sikey.pay.enums.AlipayPayStatusEnum;
- import cn.sikey.pay.enums.PayChannelEnum;
- import cn.sikey.pay.enums.PayStatusEnum;
- import cn.sikey.pay.service.AbstractPaymentService;
- import cn.sikey.pay.util.MapUtil;
- import com.alipay.v3.ApiClient;
- import com.alipay.v3.ApiException;
- import com.alipay.v3.api.AlipayTradeApi;
- import com.alipay.v3.model.AlipayTradeQueryDefaultResponse;
- import com.alipay.v3.model.AlipayTradeQueryModel;
- import com.alipay.v3.model.AlipayTradeQueryResponseModel;
- import com.alipay.v3.util.AlipaySignature;
- import com.alipay.v3.util.GenericExecuteApi;
- import jakarta.annotation.Resource;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.http.HttpStatus;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
- import org.springframework.stereotype.Service;
- import java.math.BigDecimal;
- import java.net.URLEncoder;
- import java.nio.charset.StandardCharsets;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Objects;
- /**
- * @Author: qin
- * @Date: 2025/3/31
- * @Description: alipay
- */
- @Service("alipayPaymentService")
- @Slf4j
- public class AlipayPaymentServiceImpl extends AbstractPaymentService implements AlipayPaymentService {
- private static final String PRODUCT_CODE = "QUICK_WAP_WAY";
- private static final String TCM_PULSE_DIAGNOSIS = "2:tcmPulseDiagnosis:1";
- private static final String APP_ID = "app_id";
- @Resource
- private OrderApi orderApi;
- @Qualifier("myTaskExecutor")
- @Autowired
- private ThreadPoolTaskExecutor myTaskExecutor;
- @Resource
- private Map<String, ApiClient> alipayClients;
- @Resource
- private Map<String, String> alipayPublicKeys;
- @Resource
- private Map<String, AlipayConfigExtendReqVO> alipayParamConfigs;
- private static final String PASSBACK_PARAMS = "passback_params";
- private static final String MERCHANT_PROJECT_APPLICATION = "merchantProjectApplication";
- private static final String ALIPAY_TRADE_WAP_PAY = "alipay.trade.wap.pay";
- @Value(value = "${tcmPulseDiagnosis.url}")
- private String tcmPulseDiagnosisUrl;
- private static final String PAY_TYPE = "支付宝";
- // TODO 暂时这么写
- private Map<String, String> merchantAppIdConfig = new HashMap<>() {{
- put("1:hmd:1", "2021003175691070");
- put("2:tcmPulseDiagnosis:1", "2021003175691070");
- }};
- /**
- * 手机网站支付
- *
- * @param aliPayTradeDTO alipay
- * @return
- */
- @Override
- public CommonResult<String> alipayTradeWapPay(AliPayTradeDTO aliPayTradeDTO) {
- String merchantProjectApplication = aliPayTradeDTO.getMerchantProjectApplication();
- if (StrUtil.isBlank(merchantProjectApplication)) {
- log.warn("[手机网站支付-支付宝]请配置商户配置");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请配置商户配置");
- }
- // 构造请求参数以调用接口
- Map<String, Object> bizParams = new HashMap<>();
- Map<String, Object> bizContent = new HashMap<>();
- // 设置商户订单号
- bizContent.put("out_trade_no", aliPayTradeDTO.getOut_trade_no());
- // 设置订单总金额
- bizContent.put("total_amount", aliPayTradeDTO.getTotal_amount());
- // 设置订单标题
- bizContent.put("subject", aliPayTradeDTO.getSubject());
- // 设置产品码
- bizContent.put("product_code", PRODUCT_CODE);
- // 设置公用回传参数
- bizContent.put(PASSBACK_PARAMS, URLEncoder.encode(MERCHANT_PROJECT_APPLICATION + "=" + merchantProjectApplication, StandardCharsets.UTF_8));
- // 设置订单附加信息
- // bizContent.put("body", aliPayTrade.getBody());
- bizParams.put("biz_content", bizContent);
- String appId = merchantAppIdConfig.get(merchantProjectApplication);
- bizParams.put("notify_url", alipayParamConfigs.get(appId).getNotifyUrl());
- bizParams.put("return_url", alipayParamConfigs.get(appId).getReturnUrl());
- try {
- ApiClient client = alipayClients.get(merchantAppIdConfig.get(merchantProjectApplication));
- GenericExecuteApi api = new GenericExecuteApi(client);
- // 如果是第三方代调用模式,请设置app_auth_token(应用授权令牌)
- String pageRedirectionData = api.pageExecute(ALIPAY_TRADE_WAP_PAY, "POST", bizParams, null, null, null);
- // 如果需要返回GET请求,请使用
- // String pageRedirectionData = api.pageExecute("alipay.trade.wap.pay", "GET", bizParams);
- log.info("[手机网站支付-支付宝]alipay html:{}", pageRedirectionData);
- return CommonResult.success(pageRedirectionData);
- } catch (Exception e) {
- log.error("[手机网站支付-支付宝]初始化alipay支付参数失败,{0}", e);
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "初始化alipay支付参数失败");
- }
- }
- /**
- * 手机网站支付,回调
- * <p>
- * 交易状态:WAIT_BUYER_PAY(交易创建,等待买家付款)、
- * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
- * TRADE_SUCCESS(交易支付成功)、
- * TRADE_FINISHED(交易结束,不可退款)
- *
- * @param request 参数
- * @param response 参数
- * @return
- */
- @Override
- public String tradeWapPayGateway(HttpServletRequest request, HttpServletResponse response) {
- // 根据 商户唯一标识-项目名-应用类型 调用对应服务
- Map<String, String> paramsMap = MapUtil.convertRequestToMap(request);
- log.info("[手机网站支付-支付宝,回调]处理支付成功,支付信息:{}", paramsMap);
- String passBackParams = paramsMap.get(PASSBACK_PARAMS);
- if (StrUtil.isBlank(passBackParams)) {
- log.warn("[手机网站支付-支付宝,回调] 回传参数是空");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "回传参数是空");
- }
- // passBackParams = URLDecoder.decode(passBackParams, StandardCharsets.UTF_8);
- String[] parts = StrUtil.splitToArray(passBackParams, '=');
- String tradeStatus = paramsMap.get("trade_status");
- String outTradeNo = String.valueOf(paramsMap.get("out_trade_no"));
- // 商户项目应用
- String merchantProjectApplication = parts[1];
- if (StrUtil.equals(merchantProjectApplication, TCM_PULSE_DIAGNOSIS)) {
- log.info("[手机网站支付-支付宝,回调]调用中医诊断服务");
- // 中医诊断服务
- TcmPulseDiagnosisGateWayReqVO reqVO = new TcmPulseDiagnosisGateWayReqVO();
- reqVO.setOut_trade_no(outTradeNo);
- reqVO.setGmt_payment(paramsMap.get("gmt_payment"));
- reqVO.setTrade_status(tradeStatus);
- String result = HttpUtil.post(tcmPulseDiagnosisUrl, JSONUtil.toJsonStr(reqVO));
- log.info("[手机网站支付-支付宝,回调]调用中医诊断服务返回结果:{}", result);
- if (StrUtil.equals(result, "success")) {
- return "success";
- } else {
- return "failure";
- }
- }
- CommonResult<GenerateOrderReqVO> queryOrder = orderApi.queryOrder(outTradeNo);
- if (!queryOrder.getCode().equals(HttpStatus.SC_OK)) {
- log.warn("[手机网站支付-支付宝,回调]调用订单服务失败");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "调用订单服务失败");
- }
- // 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。
- GenerateOrderReqVO generateOrderReqVO = queryOrder.getData();
- if (Objects.isNull(generateOrderReqVO)) {
- log.warn("[手机网站支付-支付宝,回调]不存在该订单,订单号:{}", outTradeNo);
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "不存在该订单");
- }
- Long orderId = generateOrderReqVO.getId();
- CommonResult<PaymentsRespVO> respVOCommonResult = paymentApi.queryPayments(orderId);
- if (!respVOCommonResult.getCode().equals(HttpStatus.SC_OK)) {
- log.warn("[手机网站支付-支付宝,回调]调用hmd付款交易服务查询失败,订单id:{}", orderId);
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "调用hmd付款交易服务查询失败");
- }
- PaymentsRespVO paymentsRespVO = respVOCommonResult.getData();
- // 幂等处理
- if (Objects.nonNull(paymentsRespVO) && Objects.nonNull(paymentsRespVO.getStatus())) {
- if (paymentsRespVO.getStatus().equals(PayStatusEnum.SUCCESS.getValue())) {
- log.warn("[手机网站支付-支付宝,回调]重复回调,订单号:{}", outTradeNo);
- return "success";
- }
- }
- // 支付宝appId
- String appId = merchantAppIdConfig.get(merchantProjectApplication);
- // 支付宝公钥
- String alipayPublicKey = alipayPublicKeys.get(appId);
- try {
- boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, alipayPublicKey, "UTF-8", "RSA2");
- if (signVerified) {
- log.info("[手机网站支付-支付宝,回调]验签成功");
- myTaskExecutor.execute(() -> {
- AlipayPayStatusEnum status = AlipayPayStatusEnum.fromStatusCode(tradeStatus);
- switch (status) {
- case TRADE_SUCCESS:
- case TRADE_FINISHED:
- // 更新支付记录
- super.updatePayments(generateOrderReqVO.getId(), paramsMap.get("trade_no"), PAY_TYPE);
- handlePaymentSuccess(paramsMap, generateOrderReqVO);
- case TRADE_CLOSED:
- log.info("[手机网站支付-支付宝,回调]支付宝交易关闭: outTradeNo:{}", outTradeNo);
- break;
- case WAIT_BUYER_PAY:
- log.info("[手机网站支付-支付宝,回调]等待买家付款: outTradeNo:{}", outTradeNo);
- break;
- default:
- log.info("[手机网站支付-支付宝,回调]未知交易状态: outTradeNo:{}", outTradeNo);
- }
- log.info("[手机网站支付-支付宝,回调]处理支付成功,状态:{}", status);
- });
- return "success";
- } else {
- log.info("[手机网站支付-支付宝,回调]验签失败");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "验签失败");
- }
- } catch (Exception e) {
- log.error("[手机网站支付-支付宝,回调]异常:{1}", e);
- super.savePaymentExceptionLogs(paymentsRespVO, PayChannelEnum.ALI_PAY.getValue(), merchantProjectApplication, e);
- }
- return "success";
- }
- /**
- * 统一收单交易查询
- *
- * @param alipayTradeQuery alipay
- * @return
- */
- @Override
- public CommonResult<String> alipayTradeQuery(AlipayTradeQuery alipayTradeQuery) {
- String merchantProjectApplication = alipayTradeQuery.getMerchantProjectApplication();
- if (StrUtil.isBlank(merchantProjectApplication)) {
- log.warn("[统一收单交易查询]请配置商户配置");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "请配置商户配置");
- }
- ApiClient client = alipayClients.get(merchantAppIdConfig.get(merchantProjectApplication));
- // 构造请求参数以调用接口
- AlipayTradeApi api = new AlipayTradeApi(client);
- AlipayTradeQueryModel data = new AlipayTradeQueryModel();
- // 设置订单支付时传入的商户订单号
- data.setOutTradeNo(alipayTradeQuery.getOutTradeNo());
- try {
- AlipayTradeQueryResponseModel response = api.query(data);
- String tradeStatus = response.getTradeStatus();
- log.info("[[统一收单交易查询-支付宝]]交易状态:{}", tradeStatus);
- return CommonResult.success(tradeStatus);
- } catch (ApiException e) {
- AlipayTradeQueryDefaultResponse errorObject = (AlipayTradeQueryDefaultResponse) e.getErrorObject();
- log.error("[统一收单交易查询-支付宝]异常:{}", errorObject);
- return CommonResult.error(HttpStatus.SC_INTERNAL_SERVER_ERROR, errorObject.getAlipayTradeQueryErrorResponseModel().getMessage());
- }
- }
- /**
- * 手机网站支付,return返回,验签
- *
- * @param params 参数
- * @return
- */
- @Override
- public CommonResult<Integer> verifySignVerified(Map<String, String> params) {
- try {
- // 支付宝公钥
- String alipayPublicKey = alipayPublicKeys.get(params.get(APP_ID));
- // 验签
- boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2");
- if (!signVerified) {
- log.warn("[手机网站支付,return返回,验签]签名验证失败");
- return CommonResult.success(0);
- }
- log.info("[手机网站支付,return返回,验签]验签完成");
- return CommonResult.success(1);
- } catch (Exception e) {
- log.warn("[手机网站支付,return返回,验签]签名验证失败:{0}", e);
- return CommonResult.success(0);
- }
- }
- /**
- * 处理支付成功
- *
- * @param paramsMap 回调参数
- * @param generateOrderReqVO 订单
- * @return
- */
- private void handlePaymentSuccess(Map<String, String> paramsMap, GenerateOrderReqVO generateOrderReqVO) {
- // 判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)。
- Integer orderTotalAmount = generateOrderReqVO.getTotalAmount();
- String deviceId = generateOrderReqVO.getDeviceId();
- if (StrUtil.isBlank(paramsMap.get("total_amount")) || Objects.isNull(orderTotalAmount)) {
- log.warn("[手机网站支付-支付宝,回调]订单金额是空");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "订单金额是空");
- }
- BigDecimal total = new BigDecimal(paramsMap.get("total_amount"));
- int totalAmount = total.multiply(new BigDecimal(100)).intValue();
- if (totalAmount != orderTotalAmount.intValue()) {
- log.warn("[手机网站支付-支付宝,回调]订单金额与支付金额不匹配");
- throw new ServiceException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "订单金额与支付金额不匹配");
- }
- PlansRespVO plansRespVO = super.queryPlans(generateOrderReqVO.getPlansId(), PAY_TYPE);
- super.updateRemainingTimesExpireTime(plansRespVO.getValidity(), deviceId, PAY_TYPE);
- log.info("[手机网站支付-支付宝,回调]处理支付成功");
- }
- }
|