支付宝支付服务端对接记录(小程序/APP 预支付统一下单及回调处理)

个人使用的是IJPay的开源支付工具类https://javen205.gitee.io/ijpay/,不用也行,支付宝的sdk本身也很方便了。

1.在配置文件中定义好各种常量配置,如appid/证书路径等


# 支付宝小程序支付
alipay.miniapp.appId=
alipay.miniapp.privateKey=
alipay.miniapp.aesEncryptKey=
alipay.miniapp.aliPayCertPath=/alipayCertPublicKey_RSA2.crt
alipay.miniapp.appCertPath=/appCertPublicKey.crt
alipay.miniapp.rootCertPath=/alipayRootCert.crt
alipay.miniapp.onlineSignUpNotifyUrl=https://xx.com.cn/alipayNotify/doProcess?invokeMethod=xxProcessor

# 支付宝APP支付 (一个账户下的不同应用支付宝证书和根证书都相同)
alipay.app.appId=
alipay.app.appCertPath=/appCertPublicKey.crt

2.APP支付

1.新建配置实体类,用于接收配置文件的各种配置

import com.alipay.api.AlipayApiException;
import com.ijpay.alipay.AliPayApiConfig;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

/**
 * 支付宝APP支付配置
 *
 * @author yz
 * @className AliPayAppConfig
 * @date 2020/7/29 13:53
 */
@Component
@Data
@Validated
public class AliPayAppConfig {
    private final static Logger LOGGER = LoggerFactory.getLogger(AliPayAppConfig.class);

    /**
     * 支付宝支付 APP appId
     */
    @NotEmpty
    @Value("${alipay.app.appId}")
    private String appId;
    /**
     * 支付宝支付 APP app证书
     */
    @NotEmpty
    @Value("${alipay.app.appCertPath}")
    private String appAppCertPath;

    /**
     * 支付宝支付 支付宝证书
     */
    @NotEmpty
    @Value("${alipay.miniapp.aliPayCertPath}")
    private String miniAliPayCertPath;

    /**
     * 支付宝支付 根证书
     */
    @NotEmpty
    @Value("${alipay.miniapp.rootCertPath}")
    private String miniRootCertPath;
    /**
     * 支付宝支付 小程序私钥
     */
    @NotEmpty
    @Value("${alipay.miniapp.privateKey}")
    private String miniPrivateKey;

    /**
     * 支付宝支付 线上报名回调URL
     */
    @NotEmpty
    @Value("${alipay.miniapp.onlineSignUpNotifyUrl}")
    private String onlineSignUpNotifyUrl;

    /**
     * 支付宝APP支付配置
     *
     * @return {@link AliPayApiConfig}
     * @author yz
     * @date 2020/7/30 15:28
     */
    public AliPayApiConfig getConfig() throws AlipayApiException {
            return AliPayApiConfig.builder()
                    .setAppId(this.appId)
                    .setAliPayCertPath(this.miniAliPayCertPath)
                    .setAliPayRootCertPath(this.miniRootCertPath)
                    .setAppCertPath(this.appAppCertPath)
                    .setCharset("UTF-8")
                    .setPrivateKey(this.miniPrivateKey)
                    .setServiceUrl("https://openapi.alipay.com/gateway.do")
                    .setSignType("RSA2")
                    .buildByCert();
    }

}

2.APP预支付订单下单接口

import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @author yz
 * @className AliPayAppPayStrategy
 * @description 支付宝APP支付策略
 * @date 2020/7/29 10:49
 */
@Component("AliPayAppPayStrategy")
public class AliPayAppPayStrategy<T> implements IPayChannelStrategy<T> {
    private final static Logger LOGGER = LoggerFactory.getLogger(AliPayApiConfig.class);

    /**
     * @description 创建预交易支付单方法
     * @author yz
     * @date 2020/7/29 11:22
     */
    @Override
    public Map<String, Object> createTrade(AllPayConfigObj allPayConfigObj, CommonPayRequest<T> payReq) throws AlipayApiException {
        if (allPayConfigObj == null || allPayConfigObj.getAliPayApiConfigApp() == null) {
            throw new BusiException(AppErrCode.INVALID_PARAMETER.getCode(), "支付宝APP支付出错![Invalid pay config]");
        }
        AliPayApiConfigKit.setThreadLocalAliPayApiConfig(allPayConfigObj.getAliPayApiConfigApp());

        //发起下单请求
        AlipayTradeAppPayModel alipayTradeAppPayModel = generateAlipayAppTradeCreateModel(payReq);
        AlipayTradeAppPayResponse appPayResponse = AliPayApi.appPayToResponse(alipayTradeAppPayModel,
                allPayConfigObj.getAlipayNotifyUrl());

        //如果请求失败
        if (!appPayResponse.isSuccess()) {
            throw new BusiException(AppErrCode.SYSTEM_ERROR.getCode(), "支付宝APP支付出错!" +
                    appPayResponse.getSubCode() + appPayResponse.getSubMsg());
        }

        return MapUtil.builder("orderStr", (Object) appPayResponse.getBody()).build();
    }

    /**
     * @param payReq {@link CommonPayRequest}
     * @return {@link AlipayTradeCreateModel}
     * @description 构造支付宝交易创建单Dto
     * @author yz
     * @date 2020/7/30 17:57
     */
    public AlipayTradeAppPayModel generateAlipayAppTradeCreateModel(CommonPayRequest<T> payReq) {
        AlipayTradeAppPayModel alipayTradeAppPayModel = new AlipayTradeAppPayModel();
        alipayTradeAppPayModel.setSubject(payReq.getOrderName());
        alipayTradeAppPayModel.setTotalAmount(String.valueOf(payReq.getOrderAmount()));
        alipayTradeAppPayModel.setOutTradeNo(payReq.getOrderId());
        //销售产品码,商家和支付宝签约的产品码,APP支付功能中该值固定为下面这个
        alipayTradeAppPayModel.setProductCode("QUICK_MSECURITY_PAY");
        return alipayTradeAppPayModel;
    }
}

3.小程序支付

1.新建配置实体类,用于接收配置文件的各种配置


/**
 * 支付宝小程序支付配置
 *
 * @author yz
 * @className AliPayMiniAppConfig
 * @date 2020/7/29 13:53
 */
@Component
@Data
@Validated
public class AliPayMiniAppConfig {
    private final static Logger LOGGER = LoggerFactory.getLogger(AliPayMiniAppConfig.class);

    /**
     * 支付宝支付 小程序 appId
     */
    @NotEmpty
    @Value("${alipay.miniapp.appId}")
    private String appId;
    /**
     * 支付宝支付 小程序 app证书
     */
    @NotEmpty
    @Value("${alipay.miniapp.appCertPath}")
    private String miniAppCertPath;

    /**
     * 支付宝支付 支付宝证书
     */
    @NotEmpty
    @Value("${alipay.miniapp.aliPayCertPath}")
    private String miniAliPayCertPath;

    /**
     * 支付宝支付 根证书
     */
    @NotEmpty
    @Value("${alipay.miniapp.rootCertPath}")
    private String miniRootCertPath;
    /**
     * 支付宝支付 小程序私钥
     */
    @NotEmpty
    @Value("${alipay.miniapp.privateKey}")
    private String miniPrivateKey;

    /**
     * 支付宝支付 报名回调URL
     */
    @NotEmpty
    @Value("${alipay.miniapp.onlineSignUpNotifyUrl}")
    private String onlineSignUpNotifyUrl;

    /**
     * 支付宝小程序支付配置
     *
     * @return {@link AliPayApiConfig}
     * @author yz
     * @date 2020/7/30 15:28
     */
    public AliPayApiConfig getConfig() throws AlipayApiException {
            return AliPayApiConfig.builder()
                    .setAppId(this.appId)
                    .setAliPayCertPath(this.miniAliPayCertPath)
                    .setAliPayRootCertPath(this.miniRootCertPath)
                    .setAppCertPath(this.miniAppCertPath)
                    .setCharset("UTF-8")
                    .setPrivateKey(this.miniPrivateKey)
                    .setServiceUrl("https://openapi.alipay.com/gateway.do")
                    .setSignType("RSA2")
                    .buildByCert();
    }
}

2.小程序预支付订单下单接口

import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.response.AlipayTradeCreateResponse;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @author yz
 * @className AliPayMiniPayStrategy
 * @description 支付宝小程序支付策略
 * @date 2020/7/29 10:49
 */
@Component("AliPayMiniPayStrategy")
public class AliPayMiniPayStrategy<T> implements IPayChannelStrategy<T> {
    private final static Logger LOGGER = LoggerFactory.getLogger(AliPayApiConfig.class);
    @Autowired
    private PayFeeDao payFeeDao;

    /**
     * @description 创建预交易支付单方法
     * @author yz
     * @date 2020/7/29 11:22
     */
    @Override
    public Map<String, Object> createTrade(AllPayConfigObj allPayConfigObj, CommonPayRequest<T> payReq) throws AlipayApiException {
        if (allPayConfigObj == null || allPayConfigObj.getAliPayApiConfigMini() == null) {
            throw new BusiException(AppErrCode.INVALID_PARAMETER.getCode(), "支付宝小程序支付出错![Invalid pay config]");
        }
        AliPayApiConfigKit.setThreadLocalAliPayApiConfig(allPayConfigObj.getAliPayApiConfigMini());

        //发起下单请求
        AlipayTradeCreateModel alipayTradeCreateModel = generateAlipayTradeCreateModel(payReq);
        AlipayTradeCreateResponse alipayTradeCreateResponse = AliPayApi.tradeCreateToResponse(alipayTradeCreateModel,
                allPayConfigObj.getAlipayNotifyUrl());
        //如果请求失败
        if (!alipayTradeCreateResponse.isSuccess()) {
            throw new BusiException(AppErrCode.SYSTEM_ERROR.getCode(), "支付宝小程序支付出错!" + alipayTradeCreateResponse.getSubCode() + alipayTradeCreateResponse.getSubMsg());
        }

        // 录入支付预支付订单记录
        PayWxOrderRecDto dto = generatePrepayOrderRecDto(payReq, alipayTradeCreateResponse);
        if (payFeeDao.insertPayWxOrderRec(dto) != 1) {
            throw new BusiException(AppErrCode.DATABASE_INSERT_ERROR.getCode(), "录入支付宝支付预支付订单记录失败");
        }
        return MapUtil.builder("tradeNo", (Object) alipayTradeCreateResponse.getTradeNo()).build();
    }

    /**
     * @param payReq {@link CommonPayRequest}
     * @return {@link com.alipay.api.domain.AlipayTradeCreateModel}
     * @description 构造支付宝统一下单交易创建单Dto
     * @author yz
     * @date 2020/7/30 17:57
     */
    public AlipayTradeCreateModel generateAlipayTradeCreateModel(CommonPayRequest<T> payReq) {
        AlipayTradeCreateModel alipayTradeCreateModel = new AlipayTradeCreateModel();
        alipayTradeCreateModel.setSubject(payReq.getOrderName());
        alipayTradeCreateModel.setBuyerLogonId(payReq.getBuyerLogonId());
        alipayTradeCreateModel.setBuyerId(payReq.getBuyerId());
        alipayTradeCreateModel.setTotalAmount(String.valueOf(payReq.getOrderAmount()));
        alipayTradeCreateModel.setOutTradeNo(payReq.getOrderId());
        return alipayTradeCreateModel;
    }
}

回调处理(APP/小程序共用)

在上面指定回调URL及在自定义参数中指定处理方法,这里使用反射来调用处理方法

import cn.hutool.core.map.MapUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.ijpay.alipay.AliPayApiConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * 支付宝异步通知处理服务
 *
 * @author yz
 * @className AlipayNotifyProcessSvc
 * @date 2020/8/2 21:38
 */
@Controller
@RequestMapping("/alipayNotify")
public class AlipayNotifyProcessSvc {
    private final static Logger LOGGER = LoggerFactory.getLogger(AlipayNotifyProcessSvc.class);
    /**
     * 成功返回
     */
    private final static String RTN_SUCCESS = "success";
    /**
     * 失败返回
     */
    private final static String RTN_ERROR = "error";

    @Autowired
    private AliPayMiniAppConfig aliPayMiniAppConfig;

    @Autowired
    private OrderBusiDao orderBusiDao;

    @Autowired
    private SignUpSvc signUpSvc;

    @Autowired
    private PayBusiDao payBusiDao;

    /**
     * 支付宝支付费用统一回调处理接口,后续只需在该服务中定义与invokeMethod值同名的方法即可
     *
     * @param request  {@link HttpServletRequest}
     * @param response {@link HttpServletResponse}
     * @author yz
     * @date 2020/8/3 11:22
     */
    @RequestMapping("/doProcess")
    @ResponseBody
    public String doProcess(HttpServletRequest request, HttpServletResponse response) {
        //1.获取支付宝POST过来反馈信息
        Map<String, String> params = getNotifyParaMap(request);
        LOGGER.info("接收到支付宝异步通知:{}", params);
        //调用方法名
        String invokeMethodName = params.get("invokeMethod");

        AliPayApiConfig config;
        try {
            config = aliPayMiniAppConfig.getConfig();
        } catch (AlipayApiException e) {
            LOGGER.error("获取支付宝支付配置出错!", e);
            return RTN_ERROR;
        }
        // 2.验证合法性
        if (!verifyLegitimacy(config, params)) {
            return RTN_ERROR;
        }

        //3.校验通过,进行订单处理
        PayWxNotifyRecDto payWxNotifyRecDto = PayWxNotifyRecDto.generatePayWxNotifyRecDto(params);
        // 插入支付支付通知记录
        if (payBusiDao.insertPayWxNotifyRec(payWxNotifyRecDto) != 1) {
            LOGGER.error("插入pay_wx_notify_rec支付通知记录失败,请检查!{}", payWxNotifyRecDto);
        }

        //4.根据notify_url中传入的invokeMethod来调用对应的同名方法
        if (StringUtils.isBlank(invokeMethodName)) {
            LOGGER.error("支付宝回调url中未配置invokeMethod,请检查!");
            return RTN_ERROR;
        }
        try {
            //获取同名且参数为Map的方法
            Method method = this.getClass().getMethod(invokeMethodName, Map.class);
            // 4.返回执行成功的标识与支付宝服务器通信,否则支付宝服务器会多次再POST数据
            return ((boolean) method.invoke(this, params)) ? RTN_SUCCESS : RTN_ERROR;
        } catch (NoSuchMethodException e) {
            LOGGER.error("支付宝回调处理找不到对应方法,请检查!{}-{}", this.getClass().getSimpleName(), invokeMethodName);
        } catch (IllegalAccessException e) {
            LOGGER.error("支付宝回调处理调用方法失败,请检查!{}-{}", this.getClass().getSimpleName(), invokeMethodName);
        } catch (InvocationTargetException e) {
            LOGGER.error("支付宝异步通知处理出错!", e);
        }
        return RTN_ERROR;
    }

    /**
     * 报名回调处理方法(需在notify_url中指定 invokeMethod = onlineSignUpProcessor)
     *
     * @param params 支付宝通知参数
     * @author yz
     * @date 2020/8/7 14:40
     */
    public boolean onlineSignUpProcessor(Map<String, String> params) throws Exception {
        // 处理业务逻辑
        //.....
        return true;
    }


    /**
     * *
     * 校验合法性
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
     * 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
     * 3、验证app_id是否为该商户本身。
     * 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
     * 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS,支付宝才会认定为买家付款成功。
     *
     * @param config 支付宝支付配置
     * @param params 异步通知携带参数
     * @return {@link boolean}
     * @author yz
     * @date 2020/8/2 22:14
     */
    private boolean verifyLegitimacy(AliPayApiConfig config, Map<String, String> params) {
        String orderId = params.get("out_trade_no");
        String appId = params.get("app_id");
        //订单金额
        String totalAmount = params.get("total_amount");
        if (StringUtils.isEmpty(orderId)) {
            LOGGER.error("未能获取到回调订单ID");
            return false;
        }

        //1.验签。
        try {
            //注意:验签前需把notify_url中的自定义参数去除
            params.remove("invokeMethod");
            boolean flag = AlipaySignature.rsaCertCheckV1(params, config.getAliPayCertPath(), config.getCharset(), "RSA2");
            if (!flag) {
                LOGGER.error("验签失败!orderId={}", orderId);
                return false;
            }
        } catch (AlipayApiException e) {
            LOGGER.error("验签失败!orderId={}", orderId, e);
            return false;
        }

        //2.判断APPID是否正确
        // if (!config.getAppId().equals(appId)) {
        //     LOGGER.error("appId错误,appId={}", appId);
        //     return false;
        // }

        //3.判断该订单是否已经完成(是否是重复回调)
        //判断交易状态是不是TRADE_SUCCESS
        if (!"TRADE_SUCCESS".equals(params.get("trade_status"))) {
            return false;
        }
        Map<String, Object> orderMap = MapUtil.builder("orderId", (Object) orderId).build();
        // 获取订单数据
        OrderDataEntity orderDataDto = orderBusiDao.queryOrderData(orderMap);
        if (null != orderDataDto && OrderDataDto.OrderStatus.PAID.getCode().equals(orderDataDto.getStatus())) {
            // 已完成 此处应为重复回调所致
            return false;
        }

        // if (!(order.getInformationFee().compareTo(new BigDecimal(totalAmount)) == 0)) {
        //     LOGGER.error("订单金额错误");
        //     return false;
        // }
        return true;
    }

    /**
     * 获取传入的通知参数并转为Map
     *
     * @param request HttpServletRequest
     * @return {@link Map<String,String>}
     * @author yz
     * @date 2020/8/2 22:01
     */
    private Map<String, String> getNotifyParaMap(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Object o : requestParams.keySet()) {
            String name = (String) o;
            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);
        }
        return params;
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值