Bootstrap

Java 实现支付宝支付、退款、订单查询

最在开发一款APP,需要实现支付宝支付,记录一下实现过程

流程整体交互图如下所示
在这里插入图片描述

一、引入pom依赖

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>4.0.3</version>
            </dependency>

二、初始化请求对象

@SneakyThrows(value = AlipayApiException.class)
    public AlipayClient initAlipay() {
        //构造client
        CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
        //设置网关地址
        certAlipayRequest.setServerUrl(AliPayUtil.SERVER_URL);
        //设置应用Id
        certAlipayRequest.setAppId(AliPayUtil.APP_ID);
        //设置应用私钥
        certAlipayRequest.setPrivateKey(AliPayUtil.PRIVATE_KEY);
        //设置请求格式,固定值json
        certAlipayRequest.setFormat(AliPayUtil.FORMAT);
        //设置字符集
        certAlipayRequest.setCharset(AliPayUtil.CHARSET);
        //设置签名类型
        certAlipayRequest.setSignType(AliPayUtil.SIGN_TYPE);
        //设置应用公钥证书路径
        certAlipayRequest.setCertPath(AliPayUtil.CERT_PATH);
        //设置支付宝公钥证书路径
        certAlipayRequest.setAlipayPublicCertPath(AliPayUtil.ALIPAY_PUBLIC_CER_PATH);
        //设置支付宝根证书路径
        certAlipayRequest.setRootCertPath(AliPayUtil.ROOT_CER_PATH);
        //构造client
        return new DefaultAlipayClient(certAlipayRequest);
    }

三、生成支付宝订单

注意,这里返回orderStr需要返回前端、用于唤起支付宝APP

@SneakyThrows(value = AlipayApiException.class)
    public String alipayRecharge(AliPayModel aliPayModel) {

        //初始化配置信息
        AlipayClient alipayClient = initAlipay();

        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

        //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        String orderNumber = aliPayModel.getOrderNumber();

        model.setBody(aliPayModel.getBody());
        model.setSubject(aliPayModel.getSubject());
        model.setOutTradeNo(orderNumber);
        model.setTimeoutExpress(AliPayUtil.TIME_OUT_EXPRESS);
        model.setTotalAmount(aliPayModel.getTotalAmount());
        request.setBizModel(model);

        //异步通知地址
        request.setNotifyUrl(AliPayUtil.NOTIFY_URL);
        try  {
            //业务处理
            //dosomething
            //这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

            if (!response.isSuccess()) {
                log.error("接口调用错误:{}",response.getBody());
                throw new AlipayApiException("接口调用错误");
            }

            AliPayUtil.NOT_CLOSE_ORDER.put(orderNumber,aliPayModel);

            return response.getBody(); //就是orderString 可以直接给客户端请求,无需再做处理。
        }catch(AlipayApiException e) {
            log.error("生成支付订单错误:",e);
            return null;
        }
    }

三、回调方法

用户在支付宝支付成功之后会回调此方法,接口在步骤二设置。

public String aliPayNotify(HttpServletRequest request) {

        // 将异步通知中收到的待验证所有参数都存放到map中
        Map<String, String> params = AliPayUtil.convertRequestParamsToMapWith(request);
        String paramsJson = JSON.toJSONString(params);
        log.info("支付宝回调,{}", paramsJson);
        try {
            // 调用SDK验证签名
            boolean signVerified = AlipaySignature.rsaCertCheckV1(params, AliPayUtil.ALIPAY_PUBLIC_CER_PATH,
                    AliPayUtil.CHARSET, AliPayUtil.SIGN_TYPE);
            if (signVerified) {
                // 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
                AliPayUtil.check(params);
                String tradeStatus = params.get("trade_status");
                String outTradeNo = params.get("out_trade_no");
                String tradeNo = params.get("trade_no");
                //通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功
                if(tradeStatus.equals(AliPayUtil.TRADE_SUCCESS) || tradeStatus.equals(AliPayUtil.TRADE_FINISHED)){
                    //业务逻辑处理
                    //do something

                }
                return "success";
            } else {
                log.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
                return "failure";
            }
        } catch (AlipayApiException e) {
            log.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
            return "failure";
        }
    }

注意:此处需返回给支付宝状态 success 或 failure

四、查询订单

   @SneakyThrows(value = AlipayApiException.class)
    public String getAlipayOutTradeNo(Long outTradeNo, Long tradeNo) {

        //初始化支付宝配置
        AlipayClient alipayClient = initAlipay();
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        AlipayTradeQueryModel model = new AlipayTradeQueryModel();
        model.setOutTradeNo(outTradeNo.toString());
        if(tradeNo != null){
            model.setTradeNo(tradeNo.toString());
        }
        request.setBizModel(model);
        AlipayTradeQueryResponse response = alipayClient.certificateExecute(request);
        return response.getBody();
    }

注意:查询订单时可用商品订单号(自己生成的订单号)或支付订单号(支付宝返回的订单号)进行查询,两个都有的情况下优先使用支付宝订单号~

五、退款

    @SneakyThrows(value = AlipayApiException.class)
    public Boolean refund(String outTradeNo,String refundAmount,String refundReason) {

        AlipayClient alipayClient = initAlipay();

        AlipayTradeRefundRequest alipayTradeCloseRequest =new AlipayTradeRefundRequest();
        //请求参数集合对象,除了公共参数之外,所有参数都可通过此对象传递
        AlipayTradeRefundModel alipayTradeRefundModel =new AlipayTradeRefundModel();
        //退款的订单号,传入生成支付订单时的订单号即可
        alipayTradeRefundModel.setOutTradeNo(outTradeNo);
        //退款金额
        alipayTradeRefundModel.setRefundAmount(refundAmount);
        //退款的原因
        alipayTradeRefundModel.setRefundReason(refundReason);
        alipayTradeCloseRequest.setBizModel(alipayTradeRefundModel);

        //退款的执行流程与支付不太一样,支付时成功之后,需要通知回调接口,而退款则不需要,只需判断响应			参数 refundResponse.getFundChange().equals("Y") 判断是否发生了资金变化, equals("Y")表示资金发生了变化,退款成功
        AlipayTradeRefundResponse response = alipayClient.execute(alipayTradeCloseRequest);
        return response.isSuccess() && response.getFundChange().equals("Y");
    }

注意:退款需要传三个参数,商品订单号(自己生成的订单号)、退款原因和退款金额

六、使用到的工具类(仅供参考)

工具类包含支付宝初始化参数订单号生成方法解析支付宝消息方法订单金额验证方法和签名验证方法

@Slf4j
public class AliPayUtil {

    public static Map<String, AliPayModel> NOT_CLOSE_ORDER = new HashMap<>();

    public static String TIME_OUT_EXPRESS = "30m";
    public static String APP_ID = "";
    public static String SERVER_URL = "";
    public static String PRIVATE_KEY = "";
    public static String NOTIFY_URL = "";
    public static String CERT_PATH = "";
    public static String ALIPAY_PUBLIC_CER_PATH = "";
    public static String ROOT_CER_PATH = "";

    public static String CHARSET = "utf-8";

    public static String FORMAT = "json";

    public static String SIGN_TYPE = "RSA2";
    public static String TRADE_SUCCESS = "TRADE_SUCCESS";
    public static String TRADE_FINISHED = "TRADE_FINISHED";


    private static final Random random = new Random();

    public static String generateOrderNumber(int userId, int productId) {
        // 获取当前时间戳
        long timestamp = System.currentTimeMillis();

        // 生成6位随机数
        int randomNum = random.nextInt(900000) + 100000;

        // 组装订单号
        return String.format("%d%d%d%d", timestamp, randomNum, userId, productId);
    }


    /**
     * request 转 map
     *
     * @param request
     * @return
     */
    public static Map<String, String> convertRequestParamsToMapWith(HttpServletRequest request) {
        Map<String, String> params = new HashMap<String, String>();
        Map requestParams = request.getParameterMap();
        for (Iterator 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);
        }
        return params;
    }

    /**
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
     * 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
     * 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
     * 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
     * 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
     * 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
     *
     * @param params 支付宝回调参数
     */

    @SneakyThrows(value = AlipayApiException.class)
    public static void check(Map<String, String> params) {
        //订单号
        String outTradeNo = params.get("out_trade_no");
        //商户订单实际金额
        String totalAmount = params.get("total_amount");
        //APPId
        String appId = params.get("app_id");

        //验证订单号
        AliPayModel aliPayModel = NOT_CLOSE_ORDER.get(outTradeNo);

        if (aliPayModel == null) {
            throw new AlipayApiException("out_trade_no错误");
        }
        //验证订单实际金额
        if (!totalAmount.equals(aliPayModel.getTotalAmount())) {
            throw new AlipayApiException("支付金额不一致");
        }

        //验证app_id是否为商户本身
        if (!appId.equals(AliPayUtil.APP_ID)) {
            throw new AlipayApiException("app_id不一致");
        }
    }


    /**
     * 客户端同步验签
     *
     * @param request
     * @return true:验证成功,false:验证失败
     * @throws AlipayApiException
     */
    public Boolean getVerifySignResult(HttpServletRequest request) throws AlipayApiException {
        Map<String, String> params = AliPayUtil.convertRequestParamsToMapWith(request);
        String paramsJson = JSON.toJSONString(params);
        log.info("支付宝回调,{}", paramsJson);
        return AlipaySignature.rsaCertCheckV1(params, AliPayUtil.ALIPAY_PUBLIC_CER_PATH,
                AliPayUtil.CHARSET, AliPayUtil.SIGN_TYPE);
    }

大功告成,文章中若有不对请指出,望见谅~

;