目录

前言

一、起调代码如下:

1.1 config配置类信息

1.2 AlipayController层

1.3 AlipayService层

二、支付宝回调接口

2.1 AlipayCallbackController层

2.2 异步回调实体类参数

2.3 AlipayCallbackService层

总结


前言

首先我们要登录支付宝的开放平台:https://developers.alipay.com/platform/home.htm 

支付宝一共包含两个接口地址:起调地址、回调地址。

起调地址:主要实现我们将对应的数据参数提供给支付宝,支付宝给我们返回对应扫码界面。

回调地址:主要实现的是我们的支付状态,是否对刚才的订单已支付。

对接支付宝沙箱开发步骤及其注意事项,以及开发流程。

springboot 整合沙箱未支付订单进行支付 springboot支付宝沙箱支付_支付宝

 

 

 

  1. 要注册企业账号或者个人支付宝账号,使用企业账号或者个人账号进行登录的开放平台。
  2. 沙箱应用栏目中可以看到系统自动分配的信息,这里APPID和支付宝网关是自动生成的,这些参数都是使用在配置文件里面,点击查看应用公钥,第一次进入是要自己填写的。这里使用了RSA2加密算法,进行加密,从而保证数据的安全性。
  3. 下载对应并下载沙箱支付宝APP测试版即可(目前只支持安卓版本
  4. 进行相关代码开发以及商户信息配置等,支付宝公钥Alipay_Public_Key一定是系统自动生成的那个支付宝公钥查看支付宝公钥,而不是在RSA工具中生成对应商户应用公钥。
  5. 根据系统提供的沙箱账号进行支付测试,
  6. 核心配置参数:APPID、商家私钥、支付宝公钥、支付宝回调地址、网关地址、加密签名算法RSA2。

一、起调代码如下:

起调地址:我们将对应的参数传给支付宝。通过配置文件调用。

1.1 config配置类信息

package com.ft.blog.config;

/**
 * @Author: ft
 * @Date: 2021/4/11 17:32
 */
public class AlipayConfig {

    //商品appid
    public String APPID = "填写自己的APPID";

    //私钥pkcs8格式的
    public String RSA_PRIVATE_KEY = "填写自己的支付宝私钥";

    //服务器异步通知页面路径 需 http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public String notify_url = "异步回调地址,支付可以判断支付状态";

    //服务器返回地址
    public String return_url = "相当于一个重定向的过程";

    //请求网关地址
    public String URL = "https://openapi.alipaydev.com/gateway.do";

    //编码
    public String CHARSET = "UTF-8";

    //返回格式
    public String FORMAT = "json";

    //支付宝公钥
    public String ALIPAY_PUBLIC_KEY = "自己的支付宝公钥";

    //RSA2
    public String SIGTYPE = "RSA2";


}

1.2 AlipayController层

/**
     * 支付宝起调接口
     *
     * @param total_amount 支付金额
     * @param openId 支付人员的Id
     * @param httpRequest
     * @param httpResponse
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping("/alipay")
    public void goAlipay(@RequestParam(value = "total_amount", required = true) BigDecimal total_amount,
                         @RequestParam(value = "openId", required = true) String openId,
                         HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception {
        alipayService.goAlipay(total_amount, openId, httpRequest, httpResponse);
    }

1.3 AlipayService层

public void goAlipay(BigDecimal total_amount, String openId, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
        //订单保存
        //省略业务代码
        //商户订单号,商户网站订单系统中唯一订单号,必填
        String out_trade_no = IdGenerator.getId(); //商品ID
        AlipayConfig alipayConfig = new AlipayConfig();
        //获得初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.URL, alipayConfig.APPID, alipayConfig.RSA_PRIVATE_KEY, alipayConfig.FORMAT, alipayConfig.CHARSET, alipayConfig.ALIPAY_PUBLIC_KEY, alipayConfig.SIGTYPE);
        //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        //这里设置支付后跳转的地址
        alipayRequest.setReturnUrl(alipayConfig.return_url);
        alipayRequest.setNotifyUrl(alipayConfig.notify_url);
        alipayRequest.setBizContent(JSON.toJSONString(alipayClient));
        //付款金额,必填
//        total_amount = String.valueOf("0.01");
        //订单名称,必填

        String subject = "whycode博客第" + count + "次充值";
        //商品描述,可空
        String body = "K币充值";
        // 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
        String timeout_express = "3m";
        alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\","
                + "\"total_amount\":\"" + total_amount + "\","
                + "\"subject\":\"" + subject + "\","
                + "\"body\":\"" + body + "\","
                + "\"timeout_express\":\"" + timeout_express + "\","
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

        //请求
        //  String result = alipayClient.pageExecute(alipayRequest).getBody();
        AlipayTradePagePayResponse response = null;
        try {
            response = alipayClient.pageExecute(alipayRequest);
            String body1 = response.getBody();  // 注意 这个是调起支付扫码的,但是不是获取二维码的,他这个返回来的是个form标签
            //  我们只需要将返回的这个标签进行跳转就行了,二维码是跳转之后就出来的,不用考虑获取二维码
            httpResponse.setContentType("text/html;charset=" + alipayConfig.CHARSET);
            HashMap<String, Object> map = new HashMap<>();
            map.put("status", true);
            map.put("success", true);
            map.put("masage", "充值成功");
            map.put("data", total_amount);
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(map);
            httpResponse.getWriter().write(body1);
            httpResponse.getWriter().flush();
            httpResponse.getWriter().close();
        } catch (Exception e) {
            e.printStackTrace();
        }


        if (response.isSuccess()) {
            System.out.println("调起成功");
//            return new Result(true,StatusCode.OK,"调起成功",out_trade_no);
            Alipay alipay = new Alipay();
            alipay.setOutTradeNo(out_trade_no);
            alipay.setSubject(subject);
            alipay.setTotalAmount(total_amount);
            alipay.setBody(body);
            alipay.setProductCode("FAST_INSTANT_TRADE_PAY");

//            alipay.setProductCode(product_code);
            alipay.setOpenId(openId);
            alipay.setStatus(2);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

            alipay.setCreateTime(sdf.format(new Date()));
            insertAlipay(alipay);//这里做一个数据入库的操作,目前的状态为2表示未支付。
            count++;



        } else {
            System.out.println("调起失败");


        }
    }

二、支付宝回调接口

这个时候就是支付宝调用我们的线上接口就是我们在配置类里notify_url

2.1 AlipayCallbackController层

@RequestMapping("alipay_callback")
    @ResponseBody
    public String callback(HttpServletRequest request) {
        String callback = alipayService.callback(request);
        return callback;
    }

通过回调地址,我们可以通过HttpServletRequest对象来解析回调参数。

2.2 异步回调实体类参数

@Data
public class AlipayNotifyParam {

    private String appId;
    private String tradeNo; // 支付宝交易凭证号
    private String out_trade_no; // 原支付请求的商户订单号
    private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流水号
    private String buyerId; // 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
    private String buyerLogonId; // 买家支付宝账号
    private String sellerId; // 卖家支付宝用户号
    private String sellerEmail; // 卖家支付宝账号
    private String trade_status; // 交易目前所处的状态,见交易状态说明
    private BigDecimal totalAmount; // 本次交易支付的订单金额
    private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
    private BigDecimal buyerPayAmount; // 用户在交易中支付的金额
    private BigDecimal refundFee; // 退款通知中,返回总退款金额,单位为元,支持两位小数
    private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
    private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
    private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
    private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
    private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
    private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
    private String fundBillList; // 支付成功的各个渠道金额信息,array
    private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。

}

2.3 AlipayCallbackService层

public String callback(HttpServletRequest request) {
        Map<String, String> params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中
        String paramsJson = JSON.toJSONString(params);
        logger.info("支付宝回调,{}", paramsJson);
        try {
            AlipayConfig alipayConfig = new AlipayConfig();// 支付宝配置
            // 调用SDK验证签名
            boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.ALIPAY_PUBLIC_KEY,
                    alipayConfig.CHARSET, alipayConfig.SIGTYPE);
            if (signVerified) {
                logger.info("支付宝回调签名认证成功");
                // 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
                this.check(params);
                // 另起线程处理业务
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        AlipayNotifyParam param = buildAlipayNotifyParam(params);
                        String trade_status = param.getTrade_status();
                        // 支付成功
                        System.out.println("trade_status==" + trade_status);
                        if ("TRADE_FINISHED".equals(trade_status) || "TRADE_SUCCESS".equals(trade_status)) {
                            // 处理支付成功逻辑
                            try {
                                String outTradeNo = param.getOut_trade_no();
                                updateAlipayStatus(outTradeNo); //更新支付状态

                                System.out.println("数据回调成功");

                            } catch (Exception e) {
                                logger.error("支付宝回调业务处理报错,params:" + paramsJson, e);
                            }
                        } else {
                            logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}", trade_status, paramsJson);
                        }
                    }
                });
                // 如果签名验证正确,立即返回success,后续业务另起线程单独处理
                // 业务处理失败,可查看日志进行补偿,跟支付宝已经没多大关系。
                return "success";
            } else {
                logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
                return "failure";
            }
        } catch (AlipayApiException e) {
            logger.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
            return "failure";
        }
    }

    // 将request中的参数转换成Map
    private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
        Map<String, String> retMap = new HashMap<String, String>();

        Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();

        for (Map.Entry<String, String[]> entry : entrySet) {
            String name = entry.getKey();
            String[] values = entry.getValue();
            int valLen = values.length;

            if (valLen == 1) {
                retMap.put(name, values[0]);
            } else if (valLen > 1) {
                StringBuilder sb = new StringBuilder();
                for (String val : values) {
                    sb.append(",").append(val);
                }
                retMap.put(name, sb.toString().substring(1));
            } else {
                retMap.put(name, "");
            }
        }

        return retMap;
    }

    private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
        String json = JSON.toJSONString(params);
        return JSON.parseObject(json, AlipayNotifyParam.class);
    }

    /**
     * 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
     * @throws AlipayApiException
     */
    private void check(Map<String, String> params) throws AlipayApiException {
        String outTradeNo = params.get("out_trade_no");

        // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        //这个方法自己实现
        Alipay orderByOutTradeNo = getOrderByOutTradeNo(outTradeNo);


        if (orderByOutTradeNo == null) {
            throw new AlipayApiException("out_trade_no错误");
        }

        // 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
//        long total_amount = new BigDecimal(params.get("total_amount")).multiply(new BigDecimal(100)).longValue();
        BigDecimal total_amount = new BigDecimal(params.get("total_amount"));
        System.out.println("total_amount======" + total_amount);
        System.out.println("数据库里面金额" + orderByOutTradeNo.getTotalAmount());
        if (!total_amount.equals(orderByOutTradeNo.getTotalAmount())) {
            throw new AlipayApiException("error total_amount");
        }

        // 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
        // 第三步可根据实际情况省略

        AlipayConfig alipayConfig = new AlipayConfig();
        // 4、验证app_id是否为该商户本身。
        if (!params.get("app_id").equals(alipayConfig.APPID)) {
            throw new AlipayApiException("app_id不一致");
        }
    }

总结

这只是一个简单的demo,支付宝支付还需要考虑到很多因为涉及到资金的问题,比如:支付宝支付要考虑事务、幂等性、失败重试机制等。