准备工作:

支付宝开放平台注册认证自己的账号、去创建自己的应用(准备自己产品的相关图片、自己产品的宣传官网)、让自己的产品签约支付服务(APP、WEB),整理完成就可以去相应的编码了!

线上环境如果遇到问题,请先创建向导:

https://opensupport.alipay.com/support/codelab/home.htm

非常好用!

介绍编码前的准备工作

  • APPID:每个应用都有自己的应用公钥!
  • 应用私:由 支付宝开放平台开发助手 登录自己的账号 – 生成密钥得到!
  • 支付宝公钥:由 支付宝开放平台助手 生成密钥 与 自己创建的应用绑定后,就可以得到!
  • 应用公钥:没啥用! 由 支付宝开放平台开发助手 登录自己的账号 – 生成密钥得到!

编码

Maven依赖!

        <!-- Hutool 工具 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.7</version>
        </dependency>



        <!-- 支付宝SDK -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.16.2.ALL</version>
        </dependency>

创建一个SpringBean 将来用于注入

说明:生产环境与沙箱环境不一样的地方就是 服务地址 SERVER_URL_Test 是沙箱环境地址!APPID、 应用私钥、支付宝公钥都会变

里面的密钥需要生成好,再来写相关的代码!


    private final String APP_ID = "你的AppID";
    // 服务地址
    private final String SERVER_URL = "https://openapi.alipay.com/gateway.do";
    private final String SERVER_URL_Test = "https://openapi.alipaydev.com/gateway.do";
    // 应用私钥 在"支付宝开放平台开发助手"里生成,然后更换支付宝的“应用公钥”即可,这样“应用私钥”就能生效
    private final String APP_PRIVATE_KEY = "你的应用私钥";

    // 支付宝公钥
    private final String ALIPAY_PUBLIC_KEY = "你的支付宝公钥";
    private final String CHARSET = "utf-8";

    @Bean
    public AlipayClient alipayClient() {
        //实例化客户端
        AlipayClient alipayClient = new DefaultAlipayClient(SERVER_URL, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");
        return alipayClient;
    }

APP支付

    // 注入 alipayClient
    @Autowired
    AlipayClient alipayClient;

    /**
     * 如果需要特别的要求,请去支付宝开放平台文档查看一些自定义参数 https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay#%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0
     *
     * 完全可以将String的Body作为Json直接返回给APP,APP可以唤起支付宝支付。如果用支付宝单独请求这个接口,只会得到Body字符串,不会唤起支付宝支付.
     * 我们能做的就是给APP这个Body,由APP自己唤起手机系统底层调用支付宝,完成支付等!
     * 为了更好的复习Servlet,练练手,还是用Servlet返回,注意使用httpServletResponse 不能加入@ResponseBody,否则会报错!
     * Uniapp不支持支付宝沙箱环境
     * @param httpServletResponse
     */
    @GetMapping("/doapppay")
    public void doAppPay(HttpServletResponse httpServletResponse) {
        // 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

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

        // 自定义参数 依次是订单描述、订单标题、商户平台订单号、超时时间、总金额、公共回传参数(会返回到回调request里)、商家和支付宝签约的产品码
        model.setBody("订单描述");
        model.setSubject("订单标题");
        model.setOutTradeNo(new Snowflake(1, 1).nextIdStr());
        model.setTimeoutExpress("30m");
        model.setTotalAmount("0.01");
        model.setPassbackParams(UUID.fastUUID().toString());
        model.setProductCode("QUICK_MSECURITY_PAY");

        // 将自定义参数绑定到请求上
        request.setBizModel(model);

        // 设置回调地址 你可以参考我的文章内网穿透!简单作为自己的内网穿透!
        request.setNotifyUrl("http://740969606.vaiwan.com/emailclock/pay/reback");

        try {
            // 这里和普通的接口调用不同,APP支付使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

            // 指定ContentType
            httpServletResponse.setContentType("text/html;charset=" + CHARSET);

            // 给APP写入数据
            if (response.isSuccess()) {
                httpServletResponse.getWriter().write(response.getBody());
                System.out.println("调用成功成功");
            } else {
                httpServletResponse.getWriter().write("抱歉APP支付唤起失败!请去Java后台查看相关报错信息!");
                System.out.println("调用失败,原因:" + response.getMsg() + "," + response.getSubMsg());
            }

            httpServletResponse.getWriter().flush();
            httpServletResponse.getWriter().close();
        } catch (Exception e) {
            e.printStackTrace();
            // TODO 这里你可做任何事情
        }
    }

WEB支付

    // 注入 alipayClient
    @Autowired
    AlipayClient alipayClient;

    /**
     * 官方文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay#%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0
     * 扫码支付,将会返回一个二维码地址,只需要让二维码跳转Java返回的地址即可!
     * 回调地址会根据沙箱中的配置为准!
     *
     * @return 返回前端 制作的二维码内容 填写这个URL,支付宝扫码,就跳到支付页面了!
     */
    @GetMapping("/dowebpay")
    @ResponseBody
    public String doWebPay() {
        // 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();

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

        // 自定义参数 以此是 订单信息、订单描述、自己平台订单号、交易超时时间、订单金额、公共回传参数(会返回到回调request里)
        model.setSubject("卫龙辣条");
        model.setBody("中国科学院小卖部");
        model.setOutTradeNo(new Snowflake(1, 1).nextIdStr());
        model.setTimeoutExpress("30m");
        model.setTotalAmount("1000");
        model.setPassbackParams(UUID.fastUUID().toString());

        // 将自定义参数绑定到请求上
        request.setBizModel(model);

        // 设置回调地址 你可以参考我的文章内网穿透!简单作为自己的内网穿透!
        request.setNotifyUrl("http://740969606.vaiwan.com/emailclock/pay/reback");

        // 发起请求
        try {
            //这里和普通的接口调用不同,使用的是execute
            AlipayTradePrecreateResponse response = alipayClient.execute(request);
            
            if (response.isSuccess()) {
                System.out.println("调用成功:" + response.getMsg() + "," + response.getSubMsg());
                String qrCodeUrl = response.getQrCode();
                return qrCodeUrl;
            } else {
                System.out.println("调用失败,原因:" + response.getMsg() + "," + response.getSubMsg());
                return "老铁,下单失败,报错信息请在后端控制台查看";
            }
           
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "null";
    }

支付宝回调接收(不确定支付宝回调的次数与间隔时间)

建议参考支付宝的回调机制:https://opendocs.alipay.com/open/58/103594

我们回调接受的时候,是请求支付的时候添加的一个自定义参数: notify_url,如果没有这个参数,将不会自己的触发回调。

支付宝官方文章中说到,重定向,也就是自定义参数:return_url,支付成功后,会自动跳转到这个网页(也就是发请求)! 如果没有这个参数,将不会自己的触发回调 。

    /**
     * 支付宝支付成功回调
     * 不同的支付方式回调参数不完全相同,如果查看APP响应参数,请求查看响应的官方文档
     * 例如:APP响应的参数有:https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay#%E5%93%8D%E5%BA%94%E5%8F%82%E6%95%B0
     *
     * @param httpServletRequest
     */
    @RequestMapping("/reback")
    @ResponseBody
    public void payReback(HttpServletRequest httpServletRequest) {
        System.out.println("回调来了,当前时间是:" + new Date());
        // 最终的结果集
        Map<String, String> params = new HashMap<>();
        // 尚未整理的结果集合
        Map<String, String[]> requestParams = httpServletRequest.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = iter.next();
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            params.put(name, valueStr);
        }
        boolean verifyResult = false;
        try {
            // 验证支付是否成功
            verifyResult = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, "UTF-8", "RSA2");
            if (verifyResult) {
                // TODO 请在这里加上商户的业务逻辑程序代码 异步通知可能出现订单重复通知 需要做去重处理
                params.forEach((k, v) -> {
                    if (k.equals("out_trade_no")) {
                        System.out.println("支付成功的自己平台的订单号:" + v);
                    }
                    if (k.equals("total_amount")) {
                        System.out.println("收入的金额是:" + v + "元");
                    }
                });
                System.out.println("notify_url 验证成功succcess");
            } else {
                System.out.println("notify_url 验证失败");
                // TODO 执行失败的相关逻辑
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这里给出一个线上版本的一个测试APP支付的回调参数,以便于参考!也可作为商户订单号 或者 支付宝订单号查询(红字展示),下面有查询代码

gmt_create : 2021-08-31 19:28:29
charset : utf-8
seller_email : 这是商家支付宝号我隐藏了
subject : 银卡充值
body : 56081689,1
buyer_id : 用户id号 2088开头的 我隐藏了

invoice_amount : 0.01
notify_id : 2021083100222192830050911412782255
fund_bill_list : [{"amount":"0.01","fundChannel":"ALIPAYACCOUNT"}]
notify_type : trade_status_sync
trade_status : TRADE_SUCCESS
receipt_amount : 0.01
app_id : 这个应用ID这里我隐藏了!
buyer_pay_amount : 0.01
seller_id : 2088241356978595
gmt_payment : 2021-08-31 19:28:29
notify_time : 2021-08-31 20:54:48
passback_params : f3551c9e-b888-4d07-9ffd-cf7bc8f6de7f
version : 1.0
out_trade_no : 20210831192824882346104249917440
total_amount : 0.01
trade_no : 2021083122001450911439518753
auth_app_id : 这个应用ID这里我隐藏了!

buyer_logon_id : 614***@qq.com
point_amount : 0.00

查询订单状态接口 (上面有参数,自己查询)

    /**
     * 交易查询 可以单独查询一个订单号中的所有信息 具体需要什么自己去变动
     * 这里是APP支付的响应结果 https://opendocs.alipay.com/apis/028xq9#%E5%93%8D%E5%BA%94%E5%8F%82%E6%95%B0
     * 不清楚WEB支付是否一样,建议去查看WEB的支付的查询接口文档
     * 
     * 传入任意一个参数都可以查询,但是一个都不传入,报错!
     * @param outTradeNo 商户订单号
     * @param tradeNo   支付宝订单号
     * @return
     */
    @RequestMapping(value = "/query")
    @ResponseBody
    public String tradeQuery(@RequestParam(required = false, name = "outTradeNo") String outTradeNo,
                             @RequestParam(required = false, name = "tradeNo") String tradeNo) {
        try {
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            AlipayTradeQueryModel model = new AlipayTradeQueryModel();
            if (StringUtils.isNotEmpty(outTradeNo)) {
                model.setOutTradeNo(outTradeNo);
            }
            if (StringUtils.isNotEmpty(tradeNo)) {
                model.setTradeNo(tradeNo);
            }
            request.setBizModel(model);
            AlipayTradeQueryResponse execute = alipayClient.execute(request);
            String info = "商户订单号:" + execute.getOutTradeNo() +
                            "<br>支付宝订单号:" + execute.getTradeNo() +
                            "<br>订单状态:" + execute.getTradeStatus() +
                            "<br>支付用户ID:" + execute.getBuyerUserId() +
                            "<br>支付用户名:" + execute.getBuyerUserName() +
                            "<br>订单支付时间(不确定订单支付时间):" + execute.getSendPayDate() +
                            "<br>订单支付金额:" + execute.getTotalAmount();
            return info;
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return "啥也没查到!";
    }

测试:http://127.0.0.1:18888/emailclock/pay/query?tradeNo=2021083122001450911439518753

其实说白了,一个支付系统不可能这么简单的。必须要有自己的消息队列来辅助订单的有效性!