惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
I
InfoQ
Y
Y Combinator Blog
The Last Watchdog
The Last Watchdog
博客园_首页
The Cloudflare Blog
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
罗磊的独立博客
月光博客
月光博客
V
V2EX
大猫的无限游戏
大猫的无限游戏
腾讯CDC
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Attack and Defense Labs
Attack and Defense Labs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
B
Blog RSS Feed
Webroot Blog
Webroot Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
量子位
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
C
CERT Recently Published Vulnerability Notes
人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Announcements
Recent Announcements
Cyberwarzone
Cyberwarzone
G
Google Developers Blog
H
Heimdal Security Blog
MyScale Blog
MyScale Blog
The Register - Security
The Register - Security
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
T
Tenable Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
C
Check Point Blog
Forbes - Security
Forbes - Security
SecWiki News
SecWiki News
K
Kaspersky official blog
The GitHub Blog
The GitHub Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
F
Full Disclosure
阮一峰的网络日志
阮一峰的网络日志

牛牛技术客栈

设计模式-备忘录模式 - OXOXTECH 牛牛技术客栈 设计模式-中介者模式 - OXOXTECH 牛牛技术客栈 Linux【Ubuntu】修改ssh默认端口 - OXOXTECH 牛牛技术客栈 设计模式-迭代器模式 - OXOXTECH 牛牛技术客栈 scheduled定时任务的三种基本实现方式 - OXOXTECH 牛牛技术客栈 Apriori - 基于关联规则的推荐算法(三) - OXOXTECH 牛牛技术客栈 Apriori - 基于关联规则的推荐算法(二) - OXOXTECH 牛牛技术客栈 Apriori - 基于关联规则的推荐算法(一) - OXOXTECH 牛牛技术客栈 基于JavaFX的桌面端网络调试工具 - OXOXTECH 牛牛技术客栈 Golang Channel的原理介绍 - OXOXTECH 牛牛技术客栈 Go语言Map的原理分析 - OXOXTECH 牛牛技术客栈 Go语言错误处理(panic)的最佳实践 - OXOXTECH 牛牛技术客栈 设计模式-解释器模式 - OXOXTECH 牛牛技术客栈 Redis报错Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. go-webpbin库在Linux报错failed to encode image to WebP: exit status 1.......的问题 exe4j 打包加密的jar - OXOXTECH 牛牛技术客栈 Go生成图形验证码示例 - OXOXTECH 牛牛技术客栈 澳门一天游:一日尽享东方与西方的交融之美 - OXOXTECH 牛牛技术客栈 设计模式-命令模式 - OXOXTECH 牛牛技术客栈 别再自己瞎写工具类了,SpringBoot内置工具类应有尽有 - OXOXTECH 牛牛技术客栈 中山一日游 - OXOXTECH 牛牛技术客栈 设计模式-责任链模式 - OXOXTECH 牛牛技术客栈 起舞吧,齐舞吧 - OXOXTECH 牛牛技术客栈 设计模式-组合模式 - OXOXTECH 牛牛技术客栈 Go语言Web开发|GoFrame框架入门笔记 - OXOXTECH 牛牛技术客栈 Java打包exe教程 - OXOXTECH 牛牛技术客栈 设计模式-代理模式 - OXOXTECH 牛牛技术客栈 MySQL存储过程的优缺点有哪些? - OXOXTECH 牛牛技术客栈 前端渲染优化有哪些? - OXOXTECH 牛牛技术客栈 HTTP状态码及其含义 - OXOXTECH 牛牛技术客栈 从浏览器地址栏输入url到显示页面的步骤 - OXOXTECH 牛牛技术客栈 TypeScript事件派发管理器 - OXOXTECH 牛牛技术客栈 MQTT保留消息的使用方法 - OXOXTECH 牛牛技术客栈 世界工程-港珠澳大桥游 - OXOXTECH 牛牛技术客栈 Golang逃逸分析 - OXOXTECH 牛牛技术客栈 设计模式-享元模式 - OXOXTECH 牛牛技术客栈 牛牛成长记录 - OXOXTECH 牛牛技术客栈 ffmpeg常用命令 - OXOXTECH 牛牛技术客栈 设计模式-外观模式 - OXOXTECH 牛牛技术客栈 设计模式-装饰器模式 - OXOXTECH 牛牛技术客栈 设计模式-桥接模式 - OXOXTECH 牛牛技术客栈 5周年恋爱纪念日 - OXOXTECH 牛牛技术客栈 2024新年快乐,龙腾四海 - OXOXTECH 牛牛技术客栈 迎接新年:除夕的美好时刻 - OXOXTECH 牛牛技术客栈 设计模式-适配器模式 - OXOXTECH 牛牛技术客栈 设计模式-原型模式 - OXOXTECH 牛牛技术客栈 设计模式-建造者模式 - OXOXTECH 牛牛技术客栈 设计模式-工厂模式 - OXOXTECH 牛牛技术客栈 设计模式-单例模式 - OXOXTECH 牛牛技术客栈 SpringBoot在Linux环境下发送163邮件失败(No appropriate protocol (protocol is disabled or cipher suites are inappropriate)) 海与日落 - OXOXTECH 牛牛技术客栈 Swagger比较常用的注解 - OXOXTECH 牛牛技术客栈 猫🐱牛 - OXOXTECH 牛牛技术客栈 2023年最后一个晚霞 - OXOXTECH 牛牛技术客栈 Linux(Centos)部署Nginx教程 - OXOXTECH 牛牛技术客栈 Linux MySQL下载安装详细教程(CentOS版) - OXOXTECH 牛牛技术客栈 JavaFx打包成exe - OXOXTECH 牛牛技术客栈 Flux脚本语言入门教程 - OXOXTECH 牛牛技术客栈 演唱会出图 - OXOXTECH 牛牛技术客栈 Netty TCP解决粘包拆包 - OXOXTECH 牛牛技术客栈 SpringBoot实现订单超时取消的几种方案 - OXOXTECH 牛牛技术客栈 详解Java并发中的各种锁 - OXOXTECH 牛牛技术客栈 雪花算法:分布式系统唯一ID生成算法 - OXOXTECH 牛牛技术客栈 Java解决空指针的神器Optional - OXOXTECH 牛牛技术客栈 与兴一起 - OXOXTECH 牛牛技术客栈 Java17新特性详解与安装 - OXOXTECH 牛牛技术客栈 Jdk17安装+环境配置详细教程 - OXOXTECH 牛牛技术客栈 孤注一掷 - OXOXTECH 牛牛技术客栈 解决WinSCP经常断线重连 - OXOXTECH 牛牛技术客栈 内存不足导致Tomcat崩溃问题排查与解决办法 - OXOXTECH 牛牛技术客栈 influxDB初识,一个高效的时序数据库 - OXOXTECH 牛牛技术客栈 SpringBoot 服务接口限流方案 - OXOXTECH 牛牛技术客栈 Docker 安装 Portainer - OXOXTECH 牛牛技术客栈 Linux 安装Docker - OXOXTECH 牛牛技术客栈 物料宣传 - OXOXTECH 牛牛技术客栈 Java使用EMQX实现MQTT通信 - OXOXTECH 牛牛技术客栈 Java实现常见的排序算法 - OXOXTECH 牛牛技术客栈 FreeSwitch Windows安装教程 - OXOXTECH 牛牛技术客栈 MQTT单向SSL数据加密 - OXOXTECH 牛牛技术客栈 随性 - OXOXTECH 牛牛技术客栈 mysql报错Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre的解决方案 Git Push项目报 push to origin/master was rejected 错误解决方案 游行记——珠海金沙滩与金湖公园之行 - OXOXTECH 牛牛技术客栈 Tomcat:解决Tomcat启动警告:"无法将资源添加到Web应用程序缓存中....请考虑增加缓存空间" 的问题 - OXOXTECH 牛牛技术客栈 励骏庞都广场,迷一般的皇宫 - OXOXTECH 牛牛技术客栈 Docker 常用命令集合 - OXOXTECH 牛牛技术客栈 ElasticSearch Windows版-安装教程 - OXOXTECH 牛牛技术客栈 Java去除对象中为null的字段 - OXOXTECH 牛牛技术客栈 我和我的青春 - OXOXTECH 牛牛技术客栈 Java实现螺旋矩阵算法: - OXOXTECH 牛牛技术客栈 Java直接内存分配和释放的理解 - OXOXTECH 牛牛技术客栈 FreeSwitch将默认数据库迁移至MySQL - OXOXTECH 牛牛技术客栈 别错过路上的风景,别错过刹那间的深情! - OXOXTECH 牛牛技术客栈 Viewer.js:一款强大的图片预览组件 - OXOXTECH 牛牛技术客栈 Java JDK Proxy和CGLib动态代理示例 - OXOXTECH 牛牛技术客栈 redis常用命令 - OXOXTECH 牛牛技术客栈 SpringBoot查询IP归属地 - OXOXTECH 牛牛技术客栈 Spring 事务失效的六种情况 - OXOXTECH 牛牛技术客栈 #张艺兴每时每刻# - OXOXTECH 牛牛技术客栈 Nginx常用命令及具体应用 - OXOXTECH 牛牛技术客栈
SpringBoot集成支付宝支付 - OXOXTECH 牛牛技术客栈
Mr.Potato · 2023-09-27 · via 牛牛技术客栈

前言

最近在做一个支付小demo,后端采用了SpringBoot,需要集成支付宝进行线上支付,在这个过程中研究了大量支付宝的集成资料,也走了一些弯路,现在总结出来,相信你读完也能轻松集成支付宝支付。

在开始集成支付宝支付之前,我们需要准备一个支付宝商家账户,如果是个人开发者,可以通过注册公司或者让有公司资质的单位进行授权,后续在集成相关API的时候需要提供这些信息。

下面我以电脑网页端在线支付为例,介绍整个从集成、测试到上线的具体流程。

预期效果展示

在开始之前我们先看下我们要达到的最后效果,具体如下:

  1. 前端点击支付跳转到支付宝界面
  2. 支付宝界面展示付款二维码
  3. 用户手机端支付
  4. 完成支付,支付宝回调开发者指定的url。

202309241543362827.webp

开发流程

沙箱调试

支付宝为我们准备了完善的沙箱开发环境,我们可以先在沙箱环境调试好程序,后续新建好应用并成功上线后,把程序中对应的参数替换为线上参数即可。

1、创建沙箱应用

直接进入 open.alipay.com/develop/san… 创建沙箱应用即可。

202309251325326244.webp

这里因为是测试环境,我们就选择系统默认密钥就行了,下面选择公钥模式,另外应用网关地址就是用户完成支付之后,支付宝会回调的url。在开发环境中,我们可以采用内网穿透的方式,将我们本机的端口暴露在某个公网地址上,这里推荐 natapp.cn/ ,可以免费注册使用。

2、SpringBoot代码实现

在创建好沙盒应用,获取到密钥,APPID,商家账户PID等信息之后,就可以在测试环境开发集成对应的API了。这里我以电脑端支付API为例,介绍如何进行集成。

关于电脑网站支付的详细产品介绍和API接入文档可以参考:opendocs.alipay.com/open/repo-0…opendocs.alipay.com/open/270/01…
  • 步骤1 添加alipay sdk对应的Maven依赖。
<!-- alipay -->  
<dependency>  
   <groupId>com.alipay.sdk</groupId>  
   <artifactId>alipay-sdk-java</artifactId>  
   <version>4.35.132.ALL</version>  
</dependency>
  • 步骤2 添加支付宝下单、支付成功后同步调用和异步调用的接口。
@RestController
@Api(tags = "支付模块控制器")
@RequestMapping(value = RequestConstants.Handler.PC_V1)
public class AliPayController {
    @Resource
    private AliPayService aliPayService;

    @PostMapping(RequestConstants.Payment.ALI_PAY_ORDER)
    @ApiOperation("PCWeb下订单")
    public AjaxResult placeOrderForPCWeb(@RequestBody AliPayRequest aliPayRequest) {
        return aliPayService.placeOrderForPCWeb(aliPayRequest);
    }

    @PostMapping(RequestConstants.Payment.ALI_PAY_CALLBACK_ASYNC)
    public AjaxResult asyncCallback(HttpServletRequest request) {
        return aliPayService.orderCallbackInAsync(request);
    }

    @GetMapping(RequestConstants.Payment.ALI_PAY_CALLBACK_SYNC)
    public void syncCallback(HttpServletRequest request, HttpServletResponse response) {
        aliPayService.orderCallbackInSync(request, response);
    }
}
这里需要注意,同步接口是用户完成支付后会自动跳转的地址,因此需要是Get请求。异步接口,是用户完成支付之后,支付宝会回调来通知支付结果的地址,所以是POST请求。
  • 步骤3 实现Service层代码
public interface AliPayService {
    //PCWeb 下订单
    AjaxResult placeOrderForPCWeb(AliPayRequest aliPayRequest);

    AjaxResult orderCallbackInAsync(HttpServletRequest request);

    void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response);
}

实现类:

@Service("AliPayService")
@Slf4j
public class AliPayServiceImpl extends BaseController implements AliPayService {
    @Resource
    private AliPayHelper aliPayHelper;
    @Resource
    private AlipayConfig alipayConfig;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult placeOrderForPCWeb(AliPayRequest aliPayRequest) {
        log.info("【请求开始-在线购买-交易创建】*********统一下单开始*********");

        String tradeNo = aliPayHelper.generateTradeNumber();
        log.info("订单号:"+tradeNo);
        String subject = "购买套餐1";
        Map<String, Object> map = aliPayHelper.placeOrderAndPayForPCWeb(tradeNo, 12000.50f, subject);

        if (Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))) {
            log.info("【请求开始-在线购买-交易创建】统一下单成功,开始保存订单数据");
            //保存订单信息
            // 添加你自己的业务逻辑,主要是保存订单数据
            log.info("【请求成功-在线购买-交易创建】*********统一下单结束*********");
            return success(String.valueOf(map.get("body")).replace("\\\"","\""));
        }else{
            log.info("【失败:请求失败-在线购买-交易创建】*********统一下单结束*********");
            return error(String.valueOf(map.get("subMsg")));
        }
    }

    @Override
    public AjaxResult orderCallbackInAsync(HttpServletRequest request) {
        try {
            log.info("进入异步回调方法");
            Map<String, String> map = aliPayHelper.paramsToMap(request);
            String tradeNo = map.get("out_trade_no");
            String sign = map.get("sign");
            String content = AlipaySignature.getSignCheckContentV1(map);
            boolean signVerified = aliPayHelper.CheckSignIn(sign, content);

            // check order status
            // 这里在DB中检查order的状态,如果已经支付成功,无需再次验证。
//            if(从DB中拿到order,并且判断order是否支付成功过){
//                log.info("订单:" + tradeNo + " 已经支付成功,无需再次验证。");
//                return "success";
//            }
            OrderInfo orderInfo = new OrderInfo(new BigDecimal("100.50"), 0);
            if(orderInfo.status() == 1){
                log.info("订单:" + tradeNo + " 已经支付成功,无需再次验证。");
                return success("已经支付成功!");
            }
            //验证业务数据是否一致
            if(!checkData(map, orderInfo)){
                log.error("返回业务数据验证失败,订单:" + tradeNo );
                return error("返回业务数据验证失败");
            }
            //签名验证成功
            if(signVerified){
                log.info("支付宝签名验证成功,订单:" + tradeNo);
                // 验证支付状态
                String tradeStatus = request.getParameter("trade_status");
                if(tradeStatus.equals("TRADE_SUCCESS")){
                    log.info("支付成功,订单:"+tradeNo);
                    // 更新订单状态,执行一些业务逻辑

                    return success("支付成功,订单:"+tradeNo);
                }else{
                    System.out.println("支付失败,订单:" + tradeNo);
                    return error("支付失败,订单:" + tradeNo);
                }
            }else{
                log.error("签名验证失败,订单:" + tradeNo );
                return error("签名验证失败");
            }
        } catch (IOException e) {
            log.error("IO exception happened ", e);
            throw new RuntimeException();
        }
    }

    @Override
    public void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response) {
        try {
            log.info("进入同步回调方法");
            OutputStream outputStream = response.getOutputStream();
            //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
            response.setHeader("content-type", "text/html;charset=UTF-8");
            String outputData = "支付成功,请返回网站并刷新页面。";

            /**
             * data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,
             * 如果是中文的操作系统环境,默认就是查找查GB2312的码表,
             */
            //将字符转换成字节数组,指定以UTF-8编码进行转换
            byte[] dataByteArr = outputData.getBytes(StandardCharsets.UTF_8);
            outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private boolean checkData(Map<String, String> map, OrderInfo order) {
        log.info("【请求开始-交易回调-订单确认】*********校验订单确认开始*********");
        //todo 此处应该验证订单号是否准确,并且订单状态为待支付
        if(true){
            float amount1 = Float.parseFloat(map.get("total_amount"));
            float amount2 = order.orderAmount().floatValue();
            //判断金额是否相等
            if(amount1 == amount2){
                //验证收款商户id是否一致
                if(map.get("seller_id").equals(alipayConfig.getPid())){
                    //判断appid是否一致
                    if(map.get("app_id").equals(alipayConfig.getAppid())){
                        log.info("【成功:请求开始-交易回调-订单确认】*********校验订单确认成功*********");
                        return true;                    }
                }
            }
        }
        log.info("【失败:请求开始-交易回调-订单确认】*********校验订单确认失败*********");
        return false;
    }
}
  • 步骤4 实现AliPayHelper核心类。这个类里面对支付宝的接口进行封装。
@Slf4j
@Component
public class AliPayHelper {

    @Resource
    private AlipayConfig alipayConfig;

    //返回数据格式
    private static final String FORMAT = "json";
    //编码类型
    private static final String CHART_TYPE = "utf-8";
    //签名类型
    private static final String SIGN_TYPE = "RSA2";

    /*支付销售产品码,目前支付宝只支持FAST_INSTANT_TRADE_PAY*/
    public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";

    private static AlipayClient alipayClient = null;

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    private static final Random random = new Random();

    @PostConstruct
    public void init(){
        alipayClient = new DefaultAlipayClient(
                alipayConfig.getGateway(),
                alipayConfig.getAppid(),
                alipayConfig.getPrivateKey(),
                FORMAT,
                CHART_TYPE,
                alipayConfig.getPublicKey(),
                SIGN_TYPE);
    };

    /**
     * 生成交易号
     */
    public String generateTradeNumber() {
        // 获取当前时间戳
        long timestamp = System.currentTimeMillis();
        // 将时间戳格式化为字符串
        String timestampStr = dateFormat.format(new Date(timestamp));
        int randomNumber = random.nextInt(1000000); // 0 到 999999 之间的随机数
        // 使用字符串拼接将时间戳和随机数组合成交易号
        return timestampStr + String.format("%06d", randomNumber);
    }
    /*================PC网页支付====================*/
    /**
     * 统一下单并调用支付页面接口
     * @param outTradeNo
     * @param totalAmount
     * @param subject
     * @return
     */
    public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setNotifyUrl(alipayConfig.getNotifyUrl());
        request.setReturnUrl(alipayConfig.getReturnUrl());
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", outTradeNo);
        bizContent.put("total_amount", totalAmount);
        bizContent.put("subject", subject);
        bizContent.put("product_code", PRODUCT_CODE);

        request.setBizContent(bizContent.toString());
        AlipayTradePagePayResponse response = null;
        try {
            response = alipayClient.pageExecute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        assert response != null;
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            log.info("调用成功");
            resultMap.put("body", response.getBody());
        } else {
            log.error("调用失败");
            log.error(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 交易订单查询
     * @param out_trade_no
     * @return
     */
    public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("trade_no", out_trade_no);
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        assert response != null;
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            System.out.println("调用成功");
            System.out.println(JSON.toJSONString(response));
            resultMap.put("status", response.getTradeStatus());
        } else {
            System.out.println("调用失败");
            System.out.println(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 验证签名是否正确
     * @param sign
     * @param content
     * @return
     */
    public boolean CheckSignIn(String sign, String content){
        try {
            return AlipaySignature.rsaCheck(content, sign, alipayConfig.getPublicKey(), CHART_TYPE, SIGN_TYPE);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将异步通知的参数转化为Map
     * @return
     */
    public Map<String, String> paramsToMap(HttpServletRequest request) throws UnsupportedEncodingException {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            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] + ",";
            }
            // 乱码解决,这段代码在出现乱码时使用。
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        return params;
    }

}
  • 步骤5,封装config类,用于存放所有的配置属性。
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
    private String gateway;

    private String appid;

    private String pid;

    private String privateKey;

    private String publicKey;

    private String returnUrl;

    private String notifyUrl;
}

另外需要在application.yml中,准备好上述对应的属性。

202309261631328185.webp

3、前端代码实现

由于是测试的小demo,这里就简单做一下页面,就只有一个支付的按钮。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="paymentContainer" style="width: 100%;height: 500px"></div>
    <button onclick="pay()">点击支付</button>
</body>
<script>
    function pay(){
        var xhr = new XMLHttpRequest();
        xhr.open('POST', 'http://3qr9js.natappfree.cc/pc/v1/alipay/order', true);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.onload = function () {

            if (xhr.status===200){
                let msg = JSON.parse(xhr.response).msg;
                console.log(msg)
                document.getElementById("paymentContainer").innerHTML = msg;
                document.forms[0].submit();
            }
        };
        var requestBody = JSON.stringify({  });
        xhr.send(requestBody);
    }
</script>
</html>

打开页面,当我们点击支付按钮的时候就会跳到支付宝支付页面

20230926163658859.webp

跳出此页面,然后就可以用支付宝沙箱版扫码测试了

202309261704372639.webp

创建并上线应用

完成沙盒调试没问题之后,我们需要创建对应的支付宝网页应用并上线。

登录 open.alipay.com/develop/man… 并选择创建网页应用

202309271409199948.webp

然后输入对应的信息即可

202309271410046561.webp

创建好应用之后,首先在开发设置中,设置好接口加签方式以及应用网关

202309271411557066.webp

注意:密钥选择RSA2,其他按照上面的操作指南一步步走即可,注意保管好自己的私钥公钥

之后在产品绑定页,绑定对应的API,比如我们这里是PC网页端支付,找到对应的API绑定就可以了。如果第一次绑定,可能需要填写相关的信息进行审核,按需填写即可,一般审核一天就通过了。

202309271416228298.webp

最后如果一切就绪,我们就可以把APP提交上线了,上线成功之后,我们需要把下面SpringBoot中的application.yml配置替换为线上应用的信息,然后就可以在生产环境调用支付宝的接口进行支付了。

#支付宝支付配置属性
alipay:
  gateway: https://openapi-sandbox.dl.alipaydev.com/gateway.do
  appid: ****
  pid: ****
  privateKey: ****
  publicKey: ****
  #完成支付后的同步跳转地址
  returnUrl: 3qr9js.natappfree.cc/pc/v1/alipay/callback/sync
  #完成支付后,支付宝会异步回调的地址
  notifyUrl: 3qr9js.natappfree.cc/pc/v1/alipay/callback/async
参考: https://juejin.cn/post/7244498421283881021#heading-6