微信小程序接入支付宝沙箱支付(http请求)

适用:微信小程序开发阶段,勾选了不校验域名的情况下,简易版沙箱支付

1.支付宝沙箱

沙箱平台:https://open.alipay.com/develop/sandbox/app

文档:https://opendocs.alipay.com/common/02kkv7

(这里的步骤可自行查看别的博文进行配置,不再赘述)

2.SpringBoot配置

yml配置:

复制代码
alipay:
  sandbox:
    url: 网关
    app_id: 小程序id
    app_private_id: 支付宝开放平台密钥工具生成的私钥
    format: json
    charset: UTF-8
    alipay_public_key: 支付宝公钥 
    sign_type: RSA2
    notify_url: 支付宝回调地址

参数说明:

url参数及notify_url参数以及app_id对应的是图片上的APPID参数

支付宝公钥:

点击查看里面有支付宝公钥,复制填到对应的参数下即可

私钥:

注意:这个沙箱平台上面的应用公钥要跟这个密钥工具生成的应用私钥要对应起来!!!

sdk:

XML 复制代码
         <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.16.2.ALL</version>
        </dependency>

对应配置的实体类:

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "alipay.sandbox")
public class AlipayProperties {
    private String url;
    private String app_id;
    private String app_private_id;
    private String app_public_key;
    private String format;
    private String charset;
    private String alipay_public_key;
    private String sign_type;
    private String return_url;
    private String notify_url;
}

支付接口相关:

java 复制代码
 /**
     * 支付宝支付请求接口
     */
    @ResponseBody
    @RequestMapping("/pay/alipay")
    public Result<AlipayVO> alipay(HttpSession session,
                                   @RequestParam("money") float money,
                                   @RequestParam("itemId") String itemId,
                                   @RequestParam("userId") String userId) {
      
        if (money <= 0) {
            return Result.fail(400, "error: 支付金额必须大于0");
        }
        if (itemId == null || itemId.trim().isEmpty()) {
            return Result.fail(400, "error: 商品ID不能为空");
        }
        if (userId == null || userId.trim().isEmpty()) {
            return Result.fail(400, "error: 用户ID不能为空");
        }

       
        String sessionKey = "PAY_ITEM_ID_" + userId; // 唯一标识用户的商品ID
        session.setAttribute(sessionKey, itemId);

      
        String time = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); 
        String randomStr = String.valueOf(RandomUtils.randomLong());  
        String orderNum = "ORDER_" + userId + "_" + time + "_" + randomStr;

        
        BigDecimal totalAmount = new BigDecimal(String.valueOf(money)).setScale(2, BigDecimal.ROUND_HALF_UP);

       
        try {
            String subject = "商品" + itemId + "支付订单"; 
            //todo .........创建订单逻辑.......

            // 发送沙箱支付请求
            String url = sendRequestToAlipay(orderNum, totalAmount, subject);
            AlipayVO alipayVO = new AlipayVO(randomStr, url);
            return Result.success(alipayVO);
        } catch (AlipayApiException e) {
            e.printStackTrace();
            return Result.fail(500, "error: 支付请求失败," + e.getMessage());
        }
    }

    /**
     * 支付宝请求核心方法
     */
    private String sendRequestToAlipay(String outTradeNo, BigDecimal totalAmount, String subject) throws AlipayApiException {
        // 初始化支付宝客户端
        AlipayClient alipayClient = new DefaultAlipayClient(
                alipayProperties.getUrl(),
                alipayProperties.getApp_id(),
                alipayProperties.getApp_private_id(),
                alipayProperties.getFormat(),
                alipayProperties.getCharset(),
                alipayProperties.getApp_public_key(),
                alipayProperties.getSign_type()
        );

        // 设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(alipayProperties.getReturn_url()); // 同步回调
        alipayRequest.setNotifyUrl(alipayProperties.getNotify_url()); // 异步回调


        String bizContent = "{\"out_trade_no\":\"" + outTradeNo + "\","
                + "\"total_amount\":\"" + totalAmount + "\"," // 使用BigDecimal的字符串值
                + "\"subject\":\"" + subject + "\","
                + "\"body\":\"用户" + outTradeNo.split("_")[1] + "购买商品支付\"," // 补充订单描述
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}";

        alipayRequest.setBizContent(bizContent);

        // 执行请求并返回结果
        return alipayClient.pageExecute(alipayRequest).getBody();
    }

    /**
     * 支付宝异步回调接口
     */
    @PostMapping("/alipay/notify") // 必须指定POST,支付宝仅用POST回调
    public String alipayNotify(HttpServletRequest request) {
        log.warn("-------------【支付宝异步回调】开始----------");

        Map<String, String> params = new HashMap<>(16);
        Map<String, String[]> requestParams = request.getParameterMap();

        // 参数校验
        if (requestParams != null && !requestParams.isEmpty()) {
            for (String name : requestParams.keySet()) {
                String[] values = requestParams.get(name);
                if (values == null || values.length == 0) {
                    params.put(name, "");
                    continue;
                }
            
                StringBuilder valueStr = new StringBuilder();
                for (int i = 0; i < values.length; i++) {
                    valueStr.append(values[i]);
                    if (i != values.length - 1) {
                        valueStr.append(",");
                    }
                }
               
                String finalValue = valueStr.toString().trim();
                params.put(name, finalValue);
                log.info("回调参数 {} = {}", name, finalValue);
            }
        } else {
            log.error("支付宝回调参数为空!");
            return "failure"; 
        }

        try {
            // 3. 验签
            String alipayPublicKey = alipayProperties.getAlipay_public_key();
            String charset = alipayProperties.getCharset();
            String signType = alipayProperties.getSign_type();

            // 校验配置参数是否为空
            if (alipayPublicKey == null || alipayPublicKey.trim().isEmpty()) {
                log.error("支付宝公钥配置为空,验签失败!");
                return "failure";
            }
            if (charset == null || charset.trim().isEmpty()) {
                charset = "UTF-8"; 
            }
            if (signType == null || signType.trim().isEmpty()) {
                signType = "RSA2"; 
            }

            // 执行验签
            boolean signVerified = AlipaySignature.rsaCheckV1(
                    params,
                    alipayPublicKey,
                    charset,
                    signType
            );
            log.info("支付宝回调签名验证结果:{}", signVerified);

            // 4. 验签成功处理业务
            if (signVerified) {
                // 提取核心参数并校验非空
                String tradeStatus = params.get("trade_status");
                String outTradeNo = params.get("out_trade_no");
                String totalAmount = params.get("total_amount");
                String tradeNo = params.get("trade_no");
                String orderId = outTradeNo.split("_")[3];
                log.info("回调核心参数 - 商户订单号:{},支付宝交易号:{},交易状态:{},金额:{}",
                        outTradeNo, tradeNo, tradeStatus, totalAmount);

                // 参数非空校验
                if (outTradeNo == null || outTradeNo.trim().isEmpty()) {
                    log.error("商户订单号为空,处理失败!");
                    return "failure";
                }
                if (tradeStatus == null || tradeStatus.trim().isEmpty()) {
                    log.error("交易状态为空,处理失败!");
                    return "failure";
                }

                // 5. 处理不同交易状态
                if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
                    //todo ..........支付成功逻辑..........
                    return "success"; 
                } else if ("TRADE_CLOSED".equals(tradeStatus)) {
                    //todo ..........交易关闭逻辑......
                    return "success"; 
                } else {
                    log.warn("订单 {} 交易状态异常:{}", orderId, tradeStatus);
                    return "success"; 
                }
            } else {
                // 验签失败
                log.error("支付宝回调签名验证失败!参数:{}", params);
                return "failure";
            }
        } catch (AlipayApiException e) {
            // 验签相关异常
            log.error("支付宝回调验签异常", e);
            return "failure";
        } catch (Exception e) {
            log.error("支付宝回调处理异常", e);
            return "success";
        }
    }

    /**
     * 前端调用查询订单状态
     * @param orderNum
     * @param userId
     * @param itemId
     * @return
     */
    @GetMapping("/pay/checkStatus")
    public Result<PayStatusVO> checkStatus(
            @RequestParam("orderNum") String orderNum,
            @RequestParam(value = "userId", required = false) String userId,
            @RequestParam(value = "itemId", required = false) String itemId) {
         //todo 提供方法给前端查询当前的订单状态是否支付完成
       
    }

3.微信小程序端代码

确认支付按钮入口绑定方法(js方法):

javascript 复制代码
    wx.request({
      url: `${server}/item/pay/alipay`,
      method: 'GET',
      header: { 
        'Authorization': token,
        'content-type': 'application/json' // 补充默认请求头
      },
      data: {
        money: totalMoney,
        itemId: goods.itemId,
        userId: userInfo.userId
      },
      success: (res) => {
        wx.hideLoading(); 
        const result = res.data.data;
        console.log("获取的结果为:{}", result)
        if (res.data.code == 200) {
          // 保存到本地
          wx.setStorageSync('alipayForm', result.url);
          // 跳转到专门的支付页面(页面内放web-view组件)
          wx.navigateTo({
            url: `/pages/alipay/alipay?orderNum=${}&itemId=${}&orderMoney=${}`
          });
        } else {
          // 后端返回错误提示
          wx.showToast({
            title: res.data.data || '支付请求失败',
            icon: 'none',
            duration: 3000
          });
        }
      },
      fail: (err) => {
        wx.hideLoading(); 
        wx.showToast({
          title: '网络异常,请重试',
          icon: 'none',
          duration: 3000
        });
        console.error('支付宝支付请求失败:', err);
      }
    });

支付页面wxml:

javascript 复制代码
<view class="container">
  <!-- web-view 加载支付宝支付页面,并监听加载错误事件 -->
  <web-view 
    src="{{payUrl}}" 
    binderror="onWebViewError"
  ></web-view>
</view>

支付页面js(轮询查询支付结果):

javascript 复制代码
Page({
    data: {
      payUrl: '',
      orderNum: '',          // 订单号
      itemId: '',
      isPaymentDone: false,  // 标记支付流程是否已结束(成功/失败)
      server: wx.getStorageSync("server"),
      token: wx.getStorageSync("token"),
      userInfo: wx.getStorageSync("userInfo")
    },
    payTimer: null,          // 定时器实例
    maxRetry: 60,            // 最大轮询次数 
    retryCount: 0,           // 当前轮询次数
  
    onLoad(options) {
      const orderNum = options.orderNum || '';
      const itemId = options.itemId || '';
      this.setData({ orderNum, itemId });
  
      if (!orderNum) {
        wx.showToast({ title: '订单号不能为空', icon: 'none' });
        setTimeout(() => wx.navigateBack(), 2000);
        return;
      }
  
     
      const alipayForm = wx.getStorageSync('alipayForm');
      if (!alipayForm) {
        wx.showToast({ title: '支付参数异常', icon: 'none' });
        setTimeout(() => wx.navigateBack(), 2000);
        return;
      }
      console.log("alipayForm:", alipayForm);
  
     
      this.setData({ payUrl: `data:text/html;charset=utf-8,${encodeURIComponent(alipayForm)}` });
  
   
      this.startPolling();
  
       
      wx.removeStorageSync('alipayForm');
    },
  
     
    startPolling() {
      if (this.payTimer) clearInterval(this.payTimer);
      this.retryCount = 0; // 重置计数
      this.payTimer = setInterval(() => {
        this.checkPayStatus();
      }, 5000); // 5秒轮询一次
    },
  
    // 停止轮询
    stopPolling() {
      if (this.payTimer) {
        clearInterval(this.payTimer);
        this.payTimer = null;
      }
    },
  
    // 查询支付状态
    checkPayStatus() {
      const { server, token, userInfo, orderNum, itemId } = this.data;
  
    
      
      if (this.retryCount >= this.maxRetry) {
        this.stopPolling();
        wx.showToast({ title: '支付确认超时,请稍后查看订单', icon: 'none' });
        return;
      }
      this.retryCount++;
  
      wx.request({
        url: `${server}/pay/checkStatus`,
        method: 'GET',
        header: { 
          'Authorization': token,
          'content-type': 'application/json'
        },
        data: {
          orderNum: orderNum,
          userId: userInfo?.userId || '',
          itemId: itemId || ''
        },
        success: (res) => {
          console.log('订单状态查询结果:', res.data);
  
         
          const data = res.data?.data;
          if (!data) return;
  
        
          if (data.success === true) {
            console.log('支付成功,准备跳转');
            this.stopPolling();
            this.setData({ isPaymentDone: true });
            wx.showToast({ title: '支付成功', icon: 'success' });
  
           
            setTimeout(() => {
          
              wx.switchTab({
                url: '/pages/index/index',
                success: () => {
                  console.log('跳转成功');
                },
                fail: (err) => {
                  console.error('跳转失败:', err);
                  wx.showToast({ title: '跳转失败,请手动返回', icon: 'none' });
                }
              });
            }, 1500);
          } else {
           
            console.log('支付尚未完成,继续轮询...');
          }
        },
        fail: (err) => {
          console.error('支付状态查询请求失败:', err);
        
          wx.showToast({ title: '网络异常,正在重试...', icon: 'none' });
        }
      });
    },
  
  
    onWebViewError(e) {
      console.error('web-view 加载失败:', e.detail);
      wx.showToast({
        title: '支付已完成,请稍后手动返回查看',
        icon: 'none',
        duration: 3000
      });
    },
  
    onUnload() {
      this.stopPolling();
    },
  
    onHide() {
      this.stopPolling();
    },
  
 
    onShow() {
      if (this.data.orderNum && !this.payTimer && !this.data.isPaymentDone) {
        this.startPolling();
      }
    }
  });

注意:轮询很消耗资源,如果能使用return_url的还是使用这个吧,这个可能需要配置小程序域名改成支持https协议的(不太清楚)

最终的效果:

相关推荐
云起SAAS2 小时前
日历黄历八字排盘紫微斗数奇门遁甲姓名分析号码吉凶命理抖音快手微信小程序看广告流量主开源
微信小程序·小程序·ai编程·看广告变现轻·日历黄历八字排盘紫微斗数
小小王app小程序开发6 小时前
一番赏潮玩抽赏小程序开发全解析(2026技术版)
小程序
heyCHEEMS8 小时前
用 分段渲染 解决小程序长列表卡顿问题
前端·微信小程序
CRMEB系统商城8 小时前
CRMEB标准版系统(PHP)v6.0公测版发布,商城主题市场上线~
java·开发语言·小程序·php
Dragon Wu9 小时前
Taro 小程序开发注意事项(不定期记录更新)
前端·javascript·小程序·typescript·taro
qq_124987075312 小时前
基于springboot的驾校预约管理小程序(源码+论文+部署+安装)
spring boot·微信小程序·小程序·毕业设计·计算机毕业设计·毕业设计源码
StarChainTech14 小时前
告别“催款”焦虑:如何打造一款高可用、多场景的智能代扣系统
大数据·人工智能·微信小程序·小程序·软件需求
小小王app小程序开发14 小时前
剧本杀狼人杀小程序开发全解析(2026技术版)
小程序
qq_12498707531 天前
基于SpringBoot微信小程序的智能在线预约挂号系统(源码+论文+部署+安装)
spring boot·后端·微信小程序·毕业设计·计算机毕设·毕业设计源码