掘金 后端 ( ) • 2024-05-07 18:06

theme: yu highlight: a11y-dark

前言

在移动支付飞速发展的今天,支付宝作为国内领先的第三方支付平台,以其安全、便捷、普及的特点,深受广大用户的喜爱和信赖。支付宝支付已经成为我们生活中不可或缺的一部分,无论是在线购物、线下消费还是转账支付,都离不开支付宝的便利服务。本文将带领读者初步了解支付宝的调用以及一些需要注意的场景问题。

环境准备

正式版

需要到支付宝开发平台创建应用,并且提交相关证件审核。 支付宝开放平台

image.png

点击你需要的应用,此处以网页/移动应用为例。

image.png

创建完成并且审核完毕后,去到设置界面设置密钥,这密钥需要我们记住,再代码里面需要用到,并且妥善保管,请勿泄露

image.png

由于营业执照等资质原因,正式版笔者在这里一笔带过,具体请参考下面的沙箱支付。

沙箱

我们来到沙箱环境,会看到支付宝已经给了我们一些参数进行调试。

这里主要是使用网页/移动应用进行测试

image.png

部分参数说明

APPID: 我们的应用id

支付宝网关:支付宝沙箱网关地址,开发者在沙箱环境调用 OpenAPI 发送 http(s) 请求的目标地址,需配置在AlipayClient中。

商家账号:收款方的账号。

应用网关:用于接收支付宝沙箱异步通知消息(需要传入http(s)公网可访问的网页地址。选填,若不设置,则无法接收相应的异步通知消息。

配置密钥

此处可以使用系统默认提供的密钥,也可以使用我们自己生成的密钥。

系统默认:

image.png

点击查看系统提供的密钥,记住妥善保管,切勿外漏

私钥:

image.png

公钥: image.png

自定义:

首先下载支付宝密钥生成器进行密钥生成

支付宝密钥生成器

打开密钥生成工具

image.png

如果你是公司开发,请同时设置证书。

生成后如下图

image.png

然后把公钥和私钥配置到沙箱或者正式版的app里面。

此时,我们的准备就准备妥当了。

代码调试

maven依赖

<!--        支付宝依赖-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.11.33.ALL</version>
        </dependency>

支付代码调试

public class AliPayTest {
    /**
     * 线上应用同步到沙箱后的APPID,该APPID跟线上应用一致
     *
     */
    static String APP_ID = "";
    /**
     * 沙箱分配的默认应用私钥
     */
    static String APP_PRIVATE_KEY = "";
    /**
     * 沙箱分配的默认支付宝公钥
     */
    static String ALIPAY_PUBLIC_KEY = "";
    /**
     * 建议使用开发平台默认分配的沙箱账号下面的买家信息
     */
    static String BUYER_ID = "";
    /**
     * 沙箱支付网关
     */
    static String gateway = "https://openapi-sandbox.dl.alipaydev.com/gateway.do";
    public static void main(String[] args) throws AlipayApiException {
        sandAliPay("10086",10.50,"测试的订单名","测试的body","","");

    }
    /**
     * 初始化支付宝客户端
     */
    private static AlipayClient initAlipayClient() {
        AlipayClient alipayClient = new DefaultAlipayClient(
                gateway,
                APP_ID,
                APP_PRIVATE_KEY,
                "json",
                "utf-8",
                ALIPAY_PUBLIC_KEY,
                "RSA2");
        return alipayClient;
    }

    /**
     * 初始化支付宝请求参数
     */
    private static AlipayTradePagePayRequest initAlipayTradePagePayRequest() {
        //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        //公网回调函数url,通常对此订单进行数据库处理
        alipayRequest.setReturnUrl("https://www.bing.com/");
        //异步通知url
        alipayRequest.setNotifyUrl("");
        return alipayRequest;
    }
    
    /**
     * @param orderid              商户订单号,商户网站订单系统中唯一订单号,必填
     * @param amount               付款金额,必填
     * @param ordername            订单名称,必填
     * @param body                 商品描述,可空
     * @throws AlipayApiException
     */
    public static String sandAliPay(
                                    String orderid,
                                    double amount,
                                    String ordername,
                                    String body,
                                    String return_url,
                                    String notify_url) throws AlipayApiException {
        //获得初始化的AlipayClient
        AlipayClient alipayClient = initAlipayClient();
        //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(return_url);
        alipayRequest.setNotifyUrl(notify_url);
        //商户订单号,商户网站订单系统中唯一订单号,必填
        /* String out_trade_no = "123456789";*/
        //付款金额,必填
        //String total_amount = new String("0.01");
        //订单名称,必填
        //String subject = new String("测试");
        //商品描述,可空
        //String body = new String("商品描述");

        alipayRequest.setBizContent("{"out_trade_no":"" + orderid + "","
                + ""total_amount":"" + amount + "","
                + ""subject":"" + ordername + "","
                + ""body":"" + body + "","
                + ""product_code":"FAST_INSTANT_TRADE_PAY"}");
        //请求
        AlipayTradePagePayResponse response = alipayClient.pageExecute(alipayRequest, "POST");
        String result = response.getBody();
        System.out.println(result);
        if (response.isSuccess()) {
            System.out.println("调用成功");
        } else {
            System.out.println("调用失败");
            // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
            // String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
            // System.out.println(diagnosisUrl);
        }
        return result;

    }
}

填写你的相关密钥以及沙箱提供的买家账号,然后启动main方法进行调试。

image.png

可以看到调用成功了,并且返回了一大串东西,第一眼看进去是个form,没错,这个角色支付宝的扫码支付页面。

为了让这个页面能在浏览器示出来,我们需要对此进行一些改造。

代码改造

新建一个controller,把代码复制进去,然后对请求部分进行以下改造

//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
String myresult = result.replace("&quot;", "");
// 提取out_trade_no的开始和结束索引
Pattern pattern = Pattern.compile("out_trade_no:" + "(.*?)" + ",total_amount:");
Matcher matcher = pattern.matcher(myresult);
if (matcher.find()) {
    // TODO: 2024/5/6 此处进行入库操作,并且将订单状态设置为”待付款“
    System.out.println(matcher.group(1));
} else {
    System.out.println("fail");
}
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(result);//直接将完整的表单html输出到页面
response.getWriter().flush();
response.getWriter().close();

笔者这里没有官网ip接受回调结果,是拿不到订单号的,所以在它返回表单的时候进行了一些处理,拿到了相关的订单号。

启动应用,访问对应url,如图所示。

image.png

注意,注意,注意 此处使用我们平时使用的支付宝扫码是没有结果的,需要使用沙箱版支付宝,并且登录买家账号进行扫码。 扫码完成后,等待一段时间她会自动跳转到alipayRequest.setReturnUrl("https://www.bing.com/");这个方法设置的url里面,一般为我们系统内部的支付成功页面。

退款代码调试

public static void refund() throws AlipayApiException {
    //获得初始化的AlipayClient
    AlipayClient alipayClient = initAlipayClient();
    AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
    AlipayTradeRefundModel model = new AlipayTradeRefundModel();
    //订单号
    model.setOutTradeNo("123456789");
    model.setRefundAmount("0.01");
    request.setBizModel(model);
    AlipayTradeRefundResponse response = alipayClient.execute(request);
    System.out.println(response.getBody());
    if (response.isSuccess()) {
        System.out.println("调用成功");
    } else {
        System.out.println("调用失败");
        // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接
        // String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response);
        // System.out.println(diagnosisUrl);
    }
}

控制台输出

image.png

然后再看看我们沙箱支付宝的账单详细:

image.png

ok,测试都成功了,最后我们看看我们的账号余额

image.png

可惜,这些钱都是虚拟的!!

场景问题

假如我们接收的支付结果通知的机器在接受通知前的一瞬间宕机了,那么会接受不了支付宝那边的返回信息,导致用户明明下单付款了,钱扣了,然后订单还是显示"待支付"状态。

这种问题被称为“掉单”。

概念图:

image.png

要解决这种情况,笔者想到有两种方法

方法一:异步通知

概念图:

image.png

就是我们 alipayRequest.setNotifyUrl(""); 这个方法设置的url值,这样我们只需接受通知消息再进行解析就能对数据进行更新保证支付前后一致性。

方法二:定时查询

创建一个定时任务,每隔一段时间找出未支付的订单,向支付宝进行查询,根据结果对订单进行状态更新

概念图: image.png

总结

注意开发时候的参数配置,比如公钥私钥和网关。网关有两个

沙箱网关

https://openapi-sandbox.dl.alipaydev.com/gateway.do

正式版网关

https://openapi.alipay.com/gateway.do

还有就是注意掉单问题以及其他订单问题。

欢迎大家评论区讨论,让我们一起进步,谢谢