创建项目
vue create alipay-demo
AlipayDemo.vue
<template><div class="cart-container"><h2>商品列表</h2><table class="product-table"><tr><th>商品</th><th>价格</th><th>商品描述</th><th>操作</th></tr><tr v-for="item in products" :key="item.name"><td>{{ item.name }}</td><td>{{ item.price }}元</td><td>{{ item.description }}</td><td><button @click="addToCart(item)">添加到购物车</button></td></tr><tr v-if="products.length === 0"><td colspan="4">没有任何商品</td></tr></table><div class="cart"><h2>未支付商品</h2><table class="cart-table"><tr><th>商品</th><th>数量</th><th>价格</th><th>小计</th><th>操作</th></tr><tr v-for="(item, index) in unpaidProducts" :key="index"><td>{{ item.name }}</td><td><button @click="updateQuantity(index, -1)" :disabled="item.quantity <= 1">-</button>{{ item.quantity }}<button @click="updateQuantity(index, 1)">+</button></td><td>{{ item.price }}元</td><td>{{ item.price * item.quantity }}元</td><td><button @click="removeFromCart(index)">删除</button></td></tr><tr v-if="unpaidProducts.length === 0"><td colspan="5">没有任何商品</td></tr><tr v-if="unpaidProducts.length > 0"><td colspan="4">总价</td><td>{{ calculateTotal() }}元</td></tr></table><button @click="openAlipay">支付</button></div><div class="cart"><h2>已支付商品</h2><table class="cart-table"><tr><th>商品</th><th>数量</th><th>价格</th><th>小计</th><th>总金额</th><th>支付时间</th><th>支付订单号</th><th>操作</th></tr><tr v-for="(order, index) in paidOrders" :key="index"><td><div v-for="(item, idx) in order.items" :key="idx">{{ item.name }}<br></div></td><td><div v-for="(item, idx) in order.items" :key="idx">{{ item.quantity }}</div></td><td><div v-for="(item, idx) in order.items" :key="idx">{{ item.price }}元<br></div></td><td><div v-for="(item, idx) in order.items" :key="idx">{{ item.price * item.quantity }}元<br></div></td><td>{{ order.totalAmount }}元</td><td>{{ order.paymentTime }}</td><td>{{ order.orderNumber }}</td><td><button @click="removePaidOrder(index)">删除订单</button></td></tr><tr v-if="paidOrders.length === 0"><td colspan="8">没有任何已支付商品</td></tr></table></div></div>
</template><script>import axios from 'axios';export default {name: 'AlipayDemo',data() {return {products: [{ name: '图书', price: 30, description: '经典好书' },{ name: '衣服', price: 300, description: '时尚外套' },{ name: '零食', price: 20, description: '美味零食' },{ name: '鞋子', price: 500, description: '舒适运动鞋' },],paidProducts: [],unpaidProducts: JSON.parse(localStorage.getItem('unpaidProducts')) || [],paidOrders: JSON.parse(localStorage.getItem('paidOrders')) || [],};},watch: {unpaidProducts: {handler(newVal) {localStorage.setItem('unpaidProducts', JSON.stringify(newVal));},deep: true},paidOrders: {handler(newVal) {localStorage.setItem('paidOrders', JSON.stringify(newVal));},deep: true}},methods: {addToCart(product) {let found = this.unpaidProducts.find(p => p.name === product.name);if (found) {found.quantity++;} else {this.unpaidProducts.push({ ...product, quantity: 1 });}},updateQuantity(index, value) {const item = this.unpaidProducts[index];if (value === -1 && item.quantity > 1) {item.quantity--;} else if (value === 1) {item.quantity++;}},removeFromCart(index) {this.unpaidProducts.splice(index, 1);},calculateTotal() {return this.unpaidProducts.reduce((acc, product) => acc + product.price * product.quantity, 0);},openAlipay() {if (this.unpaidProducts.length === 0) {alert('购物车为空,无法支付!');return;}const total = this.calculateTotal(); // 假设这个方法返回商品总价if (confirm(`商品总价为:${total}元。是否确认支付?`)) {// 生成订单号const orderNumber = this.generateOrderNumber(); // 假设这个方法生成订单号axios.get('/api/alipay/pagepay', {params: {total_amount: total,order_num: orderNumber // 将订单号作为参数发送给后端},headers: {'Content-Type': 'application/json'}}).then(response => {// 创建一个新的窗口并打开const newWindow = window.open('', '_blank');newWindow.document.write(response.data);// 设置一个定时器,用于在60秒后关闭窗口,以防回调失败setTimeout(() => {if (newWindow && !newWindow.closed) {newWindow.close();}}, 60000); // 60秒超时关闭页面// 每6秒查询一次支付状态,最多查询10次let queryCount = 0; // 查询次数计数器const maxQueries = 10; // 最大查询次数const intervalId = setInterval(() => {queryCount++; // 每次查询时递增计数器if (queryCount > maxQueries) {clearInterval(intervalId); // 超过最大查询次数,停止查询alert('支付已取消');return;}axios.get('/api/alipay/query', {params: {orderNumber: orderNumber // 使用订单号查询支付状态}}).then(response => {console.log(response.data);if (response.data === 'paid') {clearInterval(intervalId); // 停止查询newWindow.close(); // 关闭支付页面// 处理支付成功的逻辑const paymentTime = this.getCurrentTime(); // 假设这个方法获取当前时间const paidOrder = {items: [...this.unpaidProducts],totalAmount: total,paymentTime,orderNumber,};this.paidOrders.push(paidOrder); // 将订单添加到已支付列表this.unpaidProducts = []; // 清空未支付商品列表alert('支付成功'); // 提示支付成功}}).catch(error => {console.error('Error:', error);});}, 6000);}).catch(error => {console.error('Error:', error);// 处理错误});}
},getCurrentTime() {const now = new Date();return now.getFullYear() + '-' + (now.getMonth() + 1).toString().padStart(2, '0') + '-' + now.getDate().toString().padStart(2, '0') + ' ' + now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');},generateOrderNumber() {return Math.floor(Math.random() * 100000) + 100000;},removePaidOrder(index) {this.paidOrders.splice(index, 1);},}
}
</script><style scoped>
body {font-family: Arial, sans-serif;background-color: #f4f4f4;margin: 0;padding: 20px;
}.cart-container {max-width: 800px;margin: auto;background: #fff;padding: 20px;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}h2 {text-align: center;color: #333;
}.product-table, .cart-table {width: 100%;border-collapse: collapse;margin-bottom: 20px;
}.product-table, .cart-table, th, td {border: 1px solid #d3e2f2;
}th, td {padding: 10px;text-align: left;
}th {background-color: #e0f7fa;color: #333;
}button {background-color: #81d4fa;color: white;border: none;padding: 10px 20px;cursor: pointer;margin: 5px;border-radius: 4px;
}button:hover {background-color: #2bb8ff;
}.cart-table tr:nth-child(even) {background-color: #e3f2fd;
}.cart-table tr:hover {background-color: #cfe8fb;
}.cart-table th {background-color: #b3e0ef;color: #333;
}
</style>
配置跨域vue.config.js
module.exports = {devServer: {proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,pathRewrite: {'^/api': '' // 去掉请求路径中的/api前缀}}}}
};
后端接口
package com.example.alipayback.alipay.controller;import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.example.alipayback.alipay.config.AlipayConfig;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.*;@RestController
@RequestMapping("/alipay")
public class AlipayController {// 处理GET请求,映射到 "/alipay/hello" 路径@GetMapping("/test")public String hello() {return "Hello Alipay!";}@GetMapping("/pagepay")public String openPayment(@RequestParam(name = "total_amount", required = true) String totalAmount,@RequestParam(name = "order_num", required = true) String orderNum) {try {String out_trade_no = orderNum;String subject = "商品服务";String body = "";// 获得初始化的AlipayClientAlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl,AlipayConfig.app_id,AlipayConfig.merchant_private_key,AlipayConfig.format,AlipayConfig.charset,AlipayConfig.alipay_public_key,AlipayConfig.sign_type);// 设置请求参数AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();alipayRequest.setReturnUrl(AlipayConfig.return_url);alipayRequest.setNotifyUrl(AlipayConfig.notify_url);alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\","+ "\"total_amount\":\"" + totalAmount + "\","+ "\"subject\":\"" + subject + "\","+ "\"body\":\"" + body + "\","+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");// 请求String result = alipayClient.pageExecute(alipayRequest).getBody();// 这里可以将result输出到浏览器,或者重定向到支付宝支付页面// 例如:return "<html>...</html>"; 或者使用Response对象进行重定向return result;} catch (AlipayApiException e) {throw new RuntimeException("阿里云请求支付页面失败", e);}}@RequestMapping("/notify")public String notifyUrl(HttpServletRequest request) {try {System.out.println("异步通知接口被支付宝服务器调用了~~~~~~~~~~~~~~~~~~~~~~~~");//获取支付宝POST过来反馈信息Map<String, String> params = new HashMap<String, String>();Map<String, String[]> requestParams = request.getParameterMap();for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {String name = (String) iter.next();String[] values = (String[]) 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);}//二次验证签名的方法 //调用SDK验证签名boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, "utf-8", "RSA2");System.out.println("signVerified:" + signVerified);//——请在这里编写您的程序(以下代码仅作参考)——/* 实际验证过程建议商户务必添加以下校验:1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)4、验证app_id是否为该商户本身。*/if (signVerified) {//验证成功//商户订单号String out_trade_no = request.getParameter("out_trade_no");//支付宝交易 流水号 2024072422001489450503509425String trade_no = request.getParameter("trade_no");//交易状态String trade_status = request.getParameter("trade_status");//交易金额String total_amount = request.getParameter("total_amount");if (trade_status.equals("TRADE_FINISHED") || trade_status.equals("TRADE_SUCCESS")) {return "success";}}} catch (AlipayApiException e) {e.printStackTrace();}return "fail";}public String generateOrderId() {long timestamp = System.currentTimeMillis();int random = new Random().nextInt(1000);String out_trade_no = "ORDER" + timestamp + "-" + random;return out_trade_no;}// 查询支付状态接口@GetMapping("/query")public ResponseEntity<String> checkPaymentStatus(@RequestParam String orderNumber) {// 初始化SDK// 获得初始化的AlipayClientAlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl,AlipayConfig.app_id,AlipayConfig.merchant_private_key,AlipayConfig.format,AlipayConfig.charset,AlipayConfig.alipay_public_key,AlipayConfig.sign_type);// 构造请求参数以调用接口AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();AlipayTradeQueryModel model = new AlipayTradeQueryModel();// 设置订单支付时传入的商户订单号model.setOutTradeNo(orderNumber);// 设置查询选项List<String> queryOptions = new ArrayList<>();queryOptions.add("trade_settle_info");model.setQueryOptions(queryOptions);request.setBizModel(model);// 调用支付宝接口查询订单状态String result = null;try {AlipayTradeQueryResponse response = alipayClient.execute(request);if (response == null || !response.isSuccess()) {return ResponseEntity.status(500).body("Failed to get response from Alipay");}// 检查交易状态String tradeStatus = response.getTradeStatus();if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {return ResponseEntity.ok("paid");} else {return ResponseEntity.status(404).body("Payment is not completed");}} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(500).body("Error during payment status check");}}}
部署到容器中
docker run -d --name nginx -p 80:80 -v /root/nginx/html:/usr/share/nginx/html -v /root/nginx/nginx.conf:/etc/nginx/nginx.conf nginx
效果
在沙箱环境支付后
将未支付商品列表添加到已支付商品列表中