SpringBoot + Vue 使用支付宝沙箱支付订单

目录

1. 前期准备

1.1 申请沙盒账号

1.2 获取必要密钥

1.3 下载沙盒环境APP       

2. 环境配置

2.1 Maven项目依赖配置

2.2 配置文件设置

3.3 外网访问

3.3.1 配置本地hosts

3.3.2 内网穿透配置(可选)

3. 部分相关代码

3.1 SQL

3.2 Java

3.3 Vue

 4. 效果图


1. 前期准备

1.1 申请沙盒账号

        登录支付宝开放平台,注册/登录开发者账号

1.2 获取必要密钥

        下载密钥工具生成密钥,获取应用公钥、应用私钥

1.3 下载沙盒环境APP       

        1. 进入沙盒应用

        2. 将上面的“应用公钥”复制到沙箱应用的自定义密钥中,此页面可以获取“AppID、支付宝网关地址”

        3. 沙箱账号就是模拟付款的卖家和买家账号,输入账号密码可在付款页面或沙箱工具下载的手机端支付宝沙箱app中进行登录,输入付款密码可完成支付流程

2. 环境配置

2.1 Maven项目依赖配置

        pom.xml

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.22.57.ALL</version>
</dependency>

2.2 配置文件设置

        application.yml

# 支付宝沙箱配置
alipay:
  app-id: …… # 你的支付宝应用APPID
  private-key: …… # 密钥生成工具-应用私钥
  public-key: …… # 沙箱应用-自定义密钥-支付宝公钥
  gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do # 测试环境使用沙箱网关地址
  return-url: http://localhost:8080/payment/return  # 支付完成后前端回调页面
  notify-url: http://your-domain.com/api/payment/notify  # 支付通知接收地址(需外网可访问)
  charset: UTF-8  # 编码
  sign-type: RSA2  # 签名类型

3.3 外网访问

ngrok http 8080
3.3.1 配置本地hosts

        由于localhost无法直接被外网访问,需要使用内网穿透工具或修改hosts文件:

127.0.0.1    yourdomain.com
3.3.2 内网穿透配置(可选)

        为了让支付宝服务器能访问localhost的回调接口,可以使用内网穿透工具:

        工具1:ngrok:下载并安装ngrok,启动并映射端口:

ngrok http 8080

        工具2:花生壳:下载并安装花生壳,注册并购买服务,配置内网映射

3. 部分相关代码

3.1 SQL

CREATE TABLE `payment_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '检查报告ID',
  `order_id` varchar(255) NOT NULL COMMENT '订单ID (同时也是商户订单号)',
  `record_id` int(11) NOT NULL COMMENT '就诊记录ID',
  `user_id` int(11) NOT NULL COMMENT '患者ID',
  `username` varchar(255) DEFAULT NULL COMMENT '患者',
  `drug_id` int(11) NOT NULL COMMENT '药品ID',
  `drug_name` varchar(255) DEFAULT NULL COMMENT '药品名称',
  `quantity` int(11) NOT NULL COMMENT '购买数量',
  `total_amount` int(11) NOT NULL COMMENT '订单总金额',
  `subject` varchar(255) DEFAULT NULL COMMENT '订单标题',
  `body` varchar(255) DEFAULT NULL COMMENT '订单描述',
  `trade_no` varchar(255) DEFAULT NULL COMMENT '支付宝交易号',
  `status` varchar(255) DEFAULT NULL COMMENT '订单状态:WAIT_BUYER_PAY (交易创建,等待买家付款);TRADE_CLOSED (未付款交易超时关闭,或支付完成后全额退款);TRADE_SUCCESS (交易支付成功);TRADE_FINISHED (交易结束,不可退款)',
  `return_url` varchar(100) DEFAULT NULL COMMENT '支付完成后的前端回调地址',
  `create_user` int(11) NOT NULL COMMENT '创建人',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='支付订单表';

3.2 Java

        Controller

@RestController
@RequestMapping("/api/payment")
public class PaymentController {

	public final static Logger logger = LoggerFactory.getLogger(PaymentController.class);

	@Value("${alipay.app-id}")
	private String appId;

	@Value("${alipay.private-key}")
	private String privateKey;

	@Value("${alipay.public-key}")
	private String aliPayPublicKey;

	@Value("${alipay.gateway-url}")
	private String gatewayUrl;

	@Value("${alipay.return-url}")
	private String returnUrl;

	@Value("${alipay.notify-url}")
	private String notifyUrl;

	@Autowired
	private IOrderService orderService;

	private AlipayClient getAlipayClient() {
		return new DefaultAlipayClient(
				gatewayUrl, appId, privateKey, "json", "UTF-8",
				aliPayPublicKey, "RSA2");
	}

	// 创建支付订单
	@PostMapping("/create")
	public Map<String, Object> createPayment(@RequestBody PaymentOrder paymentOrder) {
		Map<String, Object> result = new HashMap<>();

		try {
			// 生成订单号
			String outTradeNo = orderService.generateOrderId();
			paymentOrder.setOrderId(outTradeNo);

			// 保存订单信息到数据库
			orderService.saveOrder(paymentOrder);

			// 设置回调地址(如果前端传入了自定义returnUrl,则使用前端提供的)
			String actualReturnUrl = paymentOrder.getReturnUrl() != null ?
					paymentOrder.getReturnUrl() : returnUrl;

			// 创建支付宝支付请求
			AlipayClient alipayClient = getAlipayClient();
			AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
			AlipayTradePagePayModel model = new AlipayTradePagePayModel();

			// 设置异步通知地址
			request.setNotifyUrl(notifyUrl);
			// 设置同步返回地址
			request.setReturnUrl(actualReturnUrl);

			// 设置请求参数
			model.setOutTradeNo(outTradeNo);
			model.setProductCode("FAST_INSTANT_TRADE_PAY"); // PC场景固定值
			model.setTotalAmount(paymentOrder.getTotalAmount().toString());
			model.setSubject(paymentOrder.getSubject());
			model.setBody(paymentOrder.getBody());

			request.setBizModel(model);

			// 获取支付表单
			AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

			if (response.isSuccess()) {
				result.put("success", true);

				// 返回支付相关信息
				Map<String, Object> data = new HashMap<>();
				data.put("orderId", outTradeNo);
				data.put("paymentFormHtml", response.getBody()); // 完整的支付表单HTML

				result.put("data", data);
				result.put("message", "订单创建成功");
			} else {
				result.put("success", false);
				result.put("message", "创建支付订单失败: " + response.getMsg());
			}
		} catch (AlipayApiException e) {
			result.put("success", false);
			result.put("message", "支付接口异常: " + e.getMessage());
			logger.error("支付接口异常: ", e);
		} catch (Exception e) {
			result.put("success", false);
			result.put("message", "系统异常: " + e.getMessage());
			logger.error("系统异常: ", e);
		}

		return result;
	}

	// 查询支付状态
	@GetMapping("/status/{orderId}")
	public Map<String, Object> checkPaymentStatus(@PathVariable String orderId) {
		Map<String, Object> result = new HashMap<>();

		try {
			// 先查询订单数据库记录
			PaymentOrder orderInfo = orderService.getOrderById(orderId);

			if (orderInfo == null) {
				result.put("success", false);
				result.put("message", "订单不存在");
				return result;
			}

			// 如果订单已支付,直接返回结果
			if ("TRADE_SUCCESS".equals(orderInfo.getStatus()) ||
					"TRADE_FINISHED".equals(orderInfo.getStatus())) {
				result.put("success", true);
				Map<String, Object> data = new HashMap<>();
				data.put("status", orderInfo.getStatus());
				result.put("data", data);
				return result;
			}

			// 调用支付宝接口查询订单状态
			AlipayClient alipayClient = getAlipayClient();
			AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
			AlipayTradeQueryModel model = new AlipayTradeQueryModel();
			model.setOutTradeNo(orderId);
			request.setBizModel(model);

			AlipayTradeQueryResponse response = alipayClient.execute(request);

			if (response.isSuccess()) {
				// 更新订单状态
				String tradeStatus = response.getTradeStatus();
				orderService.updateOrderStatus(orderId, tradeStatus, response.getTradeNo());

				result.put("success", true);
				Map<String, Object> data = new HashMap<>();
				data.put("status", tradeStatus);
				result.put("data", data);
			} else {
				result.put("success", false);
				result.put("message", "查询订单状态失败: " + response.getMsg());
			}
		} catch (Exception e) {
			result.put("success", false);
			result.put("message", "系统异常: " + e.getMessage());
		}

		return result;
	}

	// 取消支付
	@PostMapping("/cancel/{orderId}")
	public Map<String, Object> cancelPayment(@PathVariable String orderId) {
		Map<String, Object> result = new HashMap<>();

		try {
			// 查询订单状态
			PaymentOrder orderInfo = orderService.getOrderById(orderId);

			if (orderInfo == null) {
				result.put("success", false);
				result.put("message", "订单不存在");
				return result;
			}

			// 如果已支付,不能取消
			if ("TRADE_SUCCESS".equals(orderInfo.getStatus()) ||
					"TRADE_FINISHED".equals(orderInfo.getStatus())) {
				result.put("success", false);
				result.put("message", "订单已支付,无法取消");
				return result;
			}

			// 调用支付宝接口关闭交易
			AlipayClient alipayClient = getAlipayClient();
			AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
			AlipayTradeCloseModel model = new AlipayTradeCloseModel();
			model.setOutTradeNo(orderId);
			request.setBizModel(model);

			alipayClient.execute(request);

			// 更新订单状态为已取消
			orderService.updateOrderStatus(orderId, "TRADE_CLOSED", null);

			result.put("success", true);
			result.put("message", "订单已取消");
		} catch (Exception e) {
			result.put("success", false);
			result.put("message", "取消订单异常: " + e.getMessage());
		}

		return result;
	}

	// 处理支付宝异步通知
	@PostMapping("/notify")
	public String handlePaymentNotify(@RequestParam Map<String, String> params) {
		try {
			// 这里需要验证支付宝的通知签名
			boolean signVerified = orderService.verifyAlipayNotify(params);

			if (signVerified) {
				// 验证订单金额等关键信息
				String outTradeNo = params.get("outTradeNo");
				String tradeStatus = params.get("tradeStatus");
				String tradeNo = params.get("tradeNo");
				String totalAmount = params.get("total_amount");

				PaymentOrder orderInfo = orderService.getOrderById(outTradeNo);
				if (orderInfo != null &&
						orderInfo.getTotalAmount().toString().equals(totalAmount)) {

					if ("TRADE_SUCCESS".equals(tradeStatus) ||
							"TRADE_FINISHED".equals(tradeStatus)) {

						// 更新订单状态
						orderService.updateOrderStatus(outTradeNo, tradeStatus, tradeNo);
					}
				}
				return "success";
			} else {
				return "fail";
			}
		} catch (Exception e) {
			return "fail";
		}
	}

	// 验证支付结果
	@PostMapping("/verify")
	public Map<String, Object> verifyPayment(@RequestBody Map<String, String> params) {
		Map<String, Object> result = new HashMap<>();

		try {
			String outTradeNo = params.get("outTradeNo");
			String tradeNo = params.get("tradeNo");
			String tradeStatus = params.get("tradeStatus");

			if (outTradeNo == null || tradeNo == null) {
				result.put("success", false);
				result.put("message", "缺少必要参数");
				return result;
			}

			// 调用支付宝查询订单接口验证
			AlipayClient alipayClient = getAlipayClient();
			AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
			AlipayTradeQueryModel model = new AlipayTradeQueryModel();
			model.setOutTradeNo(outTradeNo);
			model.setTradeNo(tradeNo);
			request.setBizModel(model);

			AlipayTradeQueryResponse response = alipayClient.execute(request);

			if (response.isSuccess()) {
				// 验证状态
				if ("TRADE_SUCCESS".equals(response.getTradeStatus()) ||
						"TRADE_FINISHED".equals(response.getTradeStatus())) {

					// 更新订单状态
					orderService.updateOrderStatus(outTradeNo, response.getTradeStatus(), tradeNo);

					result.put("success", true);
					result.put("message", "支付验证成功");
				} else {
					result.put("success", false);
					result.put("message", "订单未完成支付");
				}
			} else {
				result.put("success", false);
				result.put("message", "订单验证失败: " + response.getMsg());
			}
		} catch (Exception e) {
			result.put("success", false);
			result.put("message", "验证异常: " + e.getMessage());
		}

		return result;
	}
}

        ServiceImpl

/**
 * 订单服务实现类
 */
@Service
public class OrderServiceImpl extends ServiceImpl<PaymentOrderMapper, PaymentOrder> implements IOrderService {

    @Value("${alipay.public-key}")
    private String alipayPublicKey;

    @Value("${alipay.charset}")
    private String charset;

    @Value("${alipay.sign-type}")
    private String signType;

    @Resource
    RecorddruginfoMapper recorddruginfoMapper;

    /**
     * 生成订单ID - 格式: 年月日时分秒 + 4位随机数
     * 例如: 2023102010303012345
     */
    @Override
    public String generateOrderId() {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        String timeStr = format.format(new Date());

        // 生成4位随机数
        Random random = new Random();
        int randomNum = random.nextInt(10000);
        String randomStr = String.format("%04d", randomNum);

        return timeStr + randomStr;
    }

    /**
     * 保存订单信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrder(PaymentOrder paymentOrder) {
        // 设置初始订单状态为等待支付
        paymentOrder.setStatus("WAIT_BUYER_PAY");
        paymentOrder.setCreateTime(new Date());

        return baseMapper.insert(paymentOrder) > 0;
    }

    /**
     * 根据订单ID查询订单
     */
    @Override
    public PaymentOrder getOrderById(String orderId) {
        return baseMapper.selectOne(new LambdaQueryWrapper<PaymentOrder>().eq(PaymentOrder::getOrderId, orderId));
    }

    /**
     * 更新订单状态
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateOrderStatus(String orderId, String status, String tradeNo) {
        PaymentOrder order = baseMapper.selectOne(new LambdaQueryWrapper<PaymentOrder>().eq(PaymentOrder::getOrderId, orderId));
        if (order == null) {
            return false;
        }

        order.setStatus(status);
        if (tradeNo != null) {
            order.setTradeNo(tradeNo);
        }
        order.setUpdateTime(new Date());

        boolean flag = baseMapper.updateById(order) > 0;
        if (flag && "TRADE_SUCCESS".equals(status)) {
            Recorddruginfo recorddruginfo = recorddruginfoMapper.selectById(order.getRecordId());
            if (null != recorddruginfo) {
                recorddruginfo.setActualNum(recorddruginfo.getActualNum() + order.getQuantity());
                recorddruginfo.setDistributeStatus(0);
                recorddruginfoMapper.updateById(recorddruginfo);
            }
        }
        return flag;
    }

    /**
     * 验证支付宝异步通知签名
     */
    @Override
    public boolean verifyAlipayNotify(Map<String, String> params) {
        try {
            return AlipaySignature.rsaCheckV1(
                    params,
                    alipayPublicKey,
                    charset,
                    signType
            );
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
}

         Entity

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class PaymentOrder implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "订单ID (同时也是商户订单号)")
    private String orderId;

    @ApiModelProperty(value = "就诊记录ID")
    private Integer recordId;

    @ApiModelProperty(value = "用户ID")
    private Integer userId;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "药品ID")
    private Integer drugId;

    @ApiModelProperty(value = "药品名称")
    private String drugName;

    @ApiModelProperty(value = "购买数量")
    private Integer quantity;

    @ApiModelProperty(value = "订单总金额")
    private BigDecimal totalAmount;

    @ApiModelProperty(value = "订单标题")
    private String subject;

    @ApiModelProperty(value = "订单描述")
    private String body;

    @ApiModelProperty(value = "支付宝交易号")
    private String tradeNo;

    /**
     * 订单状态
     * WAIT_BUYER_PAY (交易创建,等待买家付款)
     * TRADE_CLOSED (未付款交易超时关闭,或支付完成后全额退款)
     * TRADE_SUCCESS (交易支付成功)
     * TRADE_FINISHED (交易结束,不可退款)
     */
    @ApiModelProperty(value = "订单状态")
    private String status;

    @ApiModelProperty(value = "支付完成后的前端回调地址")
    private String returnUrl;

    @ApiModelProperty(value = "创建时间")
    private Integer createUser;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

}

3.3 Vue

<template>
    ……
    <el-dialog title="购买" :visible.sync="dialogVisible" width="35%">
      <el-form v-if="!isPaying && !paymentVisible">
        <el-form-item label="请输入加购数量">
          <el-input v-model.number="addBuyNum" @input="calculate"></el-input>
        </el-form-item>
        <el-form-item>
          <span>共计:{{ totalPrice }} 元</span>
        </el-form-item>
      </el-form>
      <div v-if="isPaying && !paymentVisible" class="payment-loading">
        <el-progress type="circle" :percentage="paymentProgress"></el-progress>
        <p>正在创建订单,请稍后...</p>
      </div>
      <div v-if="paymentVisible" class="payment-iframe-container">
        <div v-if="paymentQrCode" class="payment-qrcode">
          <img :src="paymentQrCode" alt="支付二维码" />
          <p>请使用支付宝扫码支付</p>
        </div>
        <div v-else-if="paymentFormHtml" v-html="paymentFormHtml"></div>
        <iframe v-else-if="paymentUrl" :src="paymentUrl" frameborder="0" width="100%" height="500px"></iframe>
      </div>
      <div slot="footer" class="dialog-footer">
        <template v-if="!isPaying && !paymentVisible">
          <el-button :disabled="disabledBuyConfirmButton" round class="bg16d" @click="doSubmit">确定</el-button>
          <el-button round @click="closeBuyDialog">取 消</el-button>
        </template>
        <template v-if="paymentVisible">
          <el-button type="primary" round @click="checkPaymentStatus">检查支付状态</el-button>
          <el-button round @click="cancelPayment">取消支付</el-button>
        </template>
      </div>
    </el-dialog>
    ……
</template>

<script>
  import axios from 'axios' // 确保项目中已安装并导入axios

  export default {
    data() {
      return {
        userSearchKeyWord: '',
        pageIndex: 1,
        pageSize: 10,
        total: 0,
        tableData: [], // 分页记录
        dialogVisible: false,
        currentRow: {},
        addBuyNum: 0,
        totalPrice: 0.0,
        disabledBuyConfirmButton: true,
        isPaying: false, // 支付状态标识
        paymentProgress: 0, // 支付进度
        paymentVisible: false, // 是否显示支付界面
        paymentUrl: '', // 支付链接URL
        paymentFormHtml: '', // 支付表单HTML
        paymentQrCode: '', // 支付二维码图片
        orderId: '', // 订单ID
        progressInterval: null, // 进度条定时器
        paymentCheckInterval: null, // 支付状态检查定时器
      };
    },
    watch: {
      $route() {
        const index = window.location.href.lastIndexOf('/');
        this.url = window.location.href.substring(index + 1);
      }
    },
    created() {
      this.doSearch();
      this.checkPaymentCallback();
    },
    beforeDestroy() {
      // 清除所有定时器
      if (this.progressInterval) {
        clearInterval(this.progressInterval);
      }
      if (this.paymentCheckInterval) {
        clearInterval(this.paymentCheckInterval);
      }
    },
    methods: {
      // 检查URL中是否包含支付回调参数
      checkPaymentCallback() {
        const queryParams = new URLSearchParams(window.location.search);
        const outTradeNo = queryParams.get('out_trade_no');
        const tradeNo = queryParams.get('trade_no');
        const tradeStatus = queryParams.get('tradeStatus');

        // 如果包含这些参数,说明是从支付宝回调回来的
        if (outTradeNo && tradeNo) {
          // 验证支付状态
          this.verifyPayment(outTradeNo, tradeNo, tradeStatus);
        }
      },

      // 验证支付结果
      async verifyPayment(outTradeNo, tradeNo, tradeStatus) {
        try {
          const response = await axios.post('/api/payment/verify', {
            outTradeNo: outTradeNo,
            tradeNo: tradeNo,
            tradeStatus: tradeStatus
          });
          console.log("verifyPayment0");
          if (response.success) {
            this.$message.success('支付验证成功!');
            // 清除URL中的查询参数(避免刷新重复处理)
            window.history.replaceState({}, document.title, window.location.pathname);
            // 支付成功
            clearInterval(this.paymentCheckInterval);
            // 刷新数据列表
            this.doSearch();
          } else {
            this.$message.error('支付验证失败:' + response.message);
          }
        } catch (error) {
          console.error('支付验证异常:', error);
          this.$message.error('支付验证发生错误,请联系客服');
        }
      },

      // 查询
      async doSearch() {
        this.loading = true;
        const res = await request.page({
          pageSize: this.pageSize,
          pageIndex: this.pageIndex,
          userId: this.$store.getters.getUserInfo.id,
          userSearchKeyWord: this.userSearchKeyWord,
        });
        this.tableData = res.records;
        this.total = res.total;
        this.pageSize = res.pageSize;
        this.pageIndex = res.pageIndex;
        this.loading = false;
      },

      closeBuyDialog() {
        this.dialogVisible = false;
        this.addBuyNum = 0;
        this.totalPrice = 0.0;
        this.isPaying = false;
        this.paymentVisible = false;
        this.paymentUrl = '';
        this.paymentFormHtml = '';
        this.paymentQrCode = '';
        this.orderId = '';
        this.doSearch();

        // 清除定时器
        if (this.progressInterval) {
          clearInterval(this.progressInterval);
          this.progressInterval = null;
        }
        if (this.paymentCheckInterval) {
          clearInterval(this.paymentCheckInterval);
          this.paymentCheckInterval = null;
        }
      },

      // 搜索框清空
      clearSearchValue() {
        this.userSearchKeyWord = '';
        this.doSearch();
      },

      // 分页功能
      handleSizeChange(val) {
        this.pageSize = val;
        this.doSearch();
      },
      handleCurrentChange(val) {
        this.pageIndex = val;
        this.doSearch();
      },

      checkedRow(row) {
        this.dialogVisible = true;
        this.currentRow = row;
        this.addBuyNum = 0;
        this.totalPrice = 0.0;
        this.disabledBuyConfirmButton = true;
      },

      calculate(addBuyNum) {
        this.disabledBuyConfirmButton = true;
        let totalActualNum = Number(addBuyNum) + Number(this.currentRow.actualNum);
        if (addBuyNum <= 0 || totalActualNum > this.currentRow.adviceNum) {
          this.$message({
            type: 'error',
            message: '加购数量为空或超过限制请重新输入,操作失败!'
          });
        } else {
          this.totalPrice = (addBuyNum * this.currentRow.price).toFixed(2);
          this.disabledBuyConfirmButton = false;
        }
      },

      async doSubmit() {
        this.$confirm('请确认是否购买并支付 ' + this.totalPrice + ' 元?', '确认支付', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
                .then(() => {
                  this.startPayment();
                })
                .catch(() => {
                  this.$message({
                    type: 'info',
                    message: '已取消支付'
                  });
                });
      },

      // 开始支付流程
      startPayment() {
        this.isPaying = true;
        this.paymentProgress = 0;

        // 使用定时器模拟进度增加
        this.progressInterval = setInterval(() => {
          if (this.paymentProgress < 90) {
            this.paymentProgress += 10;
          }
        }, 300);

        // 调用后端创建订单接口
        this.createPaymentOrder();
      },

      // 创建支付订单
      async createPaymentOrder() {
        try {
          // 构建订单信息
          const orderData = {
            userId: this.$store.getters.getUserInfo.id,
            username: this.$store.getters.getUserInfo.username,
            recordId: this.currentRow.id,
            drugId: this.currentRow.drugId,
            drugName: this.currentRow.drugName,
            quantity: this.addBuyNum,
            totalAmount: this.totalPrice,
            subject: `购买药品: ${this.currentRow.drugName}`,
            body: `购买${this.addBuyNum}个${this.currentRow.drugName},单价${this.currentRow.price}元`,
            // 客户端支付完成后的回调地址
            returnUrl: window.location.origin + window.location.pathname,
            createUser: this.$store.getters.getUserInfo.id,
          };

          // 调用后端接口创建订单并获取支付信息
          const response = await axios.post('/api/payment/create', orderData);

          if (response.success) {
            // 清除进度条定时器
            clearInterval(this.progressInterval);
            this.paymentProgress = 100;

            // 设置订单ID
            this.orderId = response.data.orderId;

            // 处理支付方式 - 这是关键部分
            setTimeout(() => {
              this.isPaying = false;

              if (response.data.payUrl) {
                // 方式1: 直接跳转到支付宝支付页面
                window.location.href = response.data.payUrl;
              } else if (response.data.paymentFormHtml) {
                // 方式2: 显示支付表单并自动提交
                this.paymentVisible = true;
                this.paymentFormHtml = response.data.paymentFormHtml;

                // 关键代码: 自动提交表单
                setTimeout(() => {
                  // 创建临时div存放表单
                  const div = document.createElement('div');
                  div.innerHTML = this.paymentFormHtml;
                  document.body.appendChild(div);

                  // 获取并提交表单
                  const form = div.getElementsByTagName('form')[0];
                  if (form) {
                    form.submit();
                  }

                  // 移除临时div
                  setTimeout(() => {
                    document.body.removeChild(div);
                  }, 100);
                }, 10);
              }
            }, 500);
          } else {
            this.handlePaymentError(response.message || '创建订单失败');
          }
        } catch (error) {
          console.error('创建支付订单异常:', error);
          this.handlePaymentError('支付系统异常,请稍后再试');
        }
      },

      // 开始定时检查支付状态
      startPaymentStatusCheck() {
        this.paymentCheckInterval = setInterval(() => {
          this.checkPaymentStatus();
        }, 5000); // 每5秒检查一次
      },

      // 检查支付状态
      async checkPaymentStatus() {
        try {
          const response = await axios.get(`/api/payment/status/${this.orderId}`);

          if (response.success) {
            const status = response.status;

            if (status === 'TRADE_SUCCESS' || status === 'TRADE_FINISHED') {
              // 支付成功
              clearInterval(this.paymentCheckInterval);
              this.handlePaymentSuccess();
            } else if (status === 'TRADE_CLOSED') {
              // 交易关闭
              clearInterval(this.paymentCheckInterval);
              this.$message.warning('订单已关闭或取消');
              this.closeBuyDialog();
            }
          }
        } catch (error) {
          console.error('检查支付状态异常:', error);
        }
      },

      // 取消支付
      async cancelPayment() {
        try {
          await axios.post(`/api/payment/cancel/${this.orderId}`);
          this.$message.info('已取消支付');
          this.closeBuyDialog();
        } catch (error) {
          console.error('取消支付异常:', error);
          this.$message.error('取消支付失败,请稍后再试');
        }
      },

      // 处理支付成功
      async handlePaymentSuccess() {
        this.$message.success('支付成功!');

        // 更新药品购买信息
        try {
          const result = await request.edit({
            id: this.currentRow.id,
            actualNum: Number(this.currentRow.actualNum) + Number(this.addBuyNum),
            distributeStatus: 0, // 待配送状态
          });

          if (!result) {
            this.$message.error('更新药品购买信息失败');
          } else {
            this.$message.success('药品购买成功,等待配送');
          }
        } catch (error) {
          console.error('更新药品信息异常:', error);
          this.$message.error('更新药品信息失败');
        }

        // 关闭对话框并刷新列表
        this.closeBuyDialog();
      },

      // 处理支付错误
      handlePaymentError(message) {
        // 清除进度定时器
        if (this.progressInterval) {
          clearInterval(this.progressInterval);
          this.progressInterval = null;
        }

        this.isPaying = false;
        this.$message.error(message);
      },
    },
  };
</script>

<style lang="scss">
    ……
    .payment-loading {
      text-align: center;
      padding: 20px 0;

      p {
        margin-top: 15px;
        color: #606266;
      }
    }

    .payment-iframe-container {
      min-height: 200px;

      .payment-qrcode {
        text-align: center;
        padding: 20px;

        img {
          max-width: 200px;
          max-height: 200px;
        }

        p {
          margin-top: 10px;
          color: #606266;
        }
      }
    }
  }
</style>

 4. 效果图

        购买药品前

        购买石膏

         准备跳转支付页面

        支付宝沙箱手机app可扫码完成付款,或者页面输入买家账号信息完成付款,两种均可

        输入付款密码生成付款订单并跳转回购买页面

         购买药品后显示购买数量,切换“待配送”状态

        手机沙箱app查看付款详情

### Spring BootVue 实现支付宝沙箱支付的功能集成 #### 后端部分(Spring Boot) 后端主要负责处理支付请求、生成订单以及接收支付宝的通知回调。 ##### 创建支付接口 创建一个控制器来处理支付逻辑: ```java @RestController @RequestMapping("/alipay") public class AlipayController { private static final String APP_PRIVATE_KEY = "your_app_private_key"; private static final String ALIPAY_PUBLIC_KEY = "your_alipay_public_key"; @PostMapping("/pay") public ResponseEntity<String> pay(@RequestBody Map<String, Object> requestData) { try { // 获取订单号和其他必要参数 String orderNo = (String) requestData.get("orderNo"); String subject = (String) requestData.get("subject"); BigDecimal totalAmount = new BigDecimal(requestData.get("totalAmount").toString()); // 构建支付请求 AlipayClient alipayClient = new DefaultAlipayClient( "https://openapi.alipaydev.com/gateway.do", "your_app_id", APP_PRIVATE_KEY, "json", "UTF-8", ALIPAY_PUBLIC_KEY, "RSA2" ); AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setBizContent(String.format("{" + "\"out_trade_no\":\"%s\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + "\"total_amount\":%.2f," + "\"subject\":\"%s\"}", orderNo, totalAmount.doubleValue(), subject)); String form = alipayClient.pageExecute(request).getBody(); return ResponseEntity.ok(form); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); } } @PostMapping("/notify") public String notify(@RequestParam Map<String, String[]> parameterMap) throws Exception { String tradeStatus = Optional.ofNullable(parameterMap.get("trade_status")) .map(status -> status[0]) .orElse(""); if ("TRADE_SUCCESS".equals(tradeStatus)) { boolean isValid = Factory.Payment.Common().verifyNotify(parameterMap); // 验证通知有效性 if (isValid) { System.out.println("订单ID: " + parameterMap.get("out_trade_no")[0]); return "success"; // 告知支付宝已经收到通知 } } return "fail"; } } ``` 上述代码实现了两个核心功能:`/pay` 接口用于生成支付链接,`/notify` 接口用于接收支付宝异步通知[^1]。 --- #### 前端部分(Vue.js) 前端的主要职责是展示支付界面,并调用后端生成的支付链接。 ##### 安装依赖库 确保安装了 `axios` 来发送 HTTP 请求: ```bash npm install axios ``` ##### 页面设计 在 Vue 组件中实现支付流程: ```javascript <template> <div> <el-button type="primary" @click="initiatePayment">发起支付</el-button> </div> </template> <script> import axios from 'axios'; export default { data() { return { transactionId: null, }; }, methods: { async initiatePayment() { const response = await axios.post('http://localhost:8080/alipay/pay', { orderNo: this.generateOrderNumber(), subject: '测试商品', totalAmount: 0.01, }); const paymentForm = document.createElement('form'); paymentForm.method = 'POST'; paymentForm.action = 'https://openapi.alipaydev.com/gateway.do'; // 沙箱环境网关地址 paymentForm.innerHTML = `<input type="hidden" name="biz_content" value="${response.data}"/>`; document.body.appendChild(paymentForm); paymentForm.submit(); }, generateOrderNumber() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); }, }, }; </script> ``` 此组件提供了一个按钮,点击后会触发支付流程。它通过 POST 请求向后端传递订单信息,而后端返回支付表单数据[^4]。 --- #### 测试与调试 1. **准备沙箱账号** 登录 [支付宝开放平台](https://openhome.alipay.com/) 并创建应用,获取 App ID、公钥和私钥。 2. **配置本地服务器** 使用工具如 Ngrok 将本地服务暴露给公网,以便支付宝可以访问回调接口[^2]。 3. **模拟支付过程** 在浏览器中打开前端页面,点击“发起支付”,跳转至支付宝沙箱支付页完成付款。 --- #### 注意事项 1. 确保正确设置应用密钥对(App Private Key 和 AliPay Public Key)。 2. 回调 URL 必须可公开访问,建议使用内网穿透工具。 3. 生产环境中需严格校验签名以防止伪造请求[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值