结合gin框架在沙箱环境下实现支付宝电脑网站支付和当面支付

配置支付宝开放平台

支付宝开放平台

点击链接,扫码进入后,点击沙箱:

点击沙箱应用,可以看到APPID,接口加签方式选择系统默认密钥就行,启用公钥模式。然后点击查看。

由于后端使用go,我们选择非JAVA语言,这里我们只需要应用公钥和支付宝公钥。

接下来我们在项目根目录新建配置文件config.yaml

yaml 复制代码
AppId: 你的AppId
PrivateKey: 你的应用私钥
AliPublicKey: 你的支付宝公钥
NotifyURL: http://182.254.140.235:8080/notify
ReturnURL: http://182.254.140.235:8080/callback

分别将你的AppIdPrivateKeyAliPublicKey复制进去。其中NotifyURL是的异步通知地址,这个地址需要是公网地址,这样支付宝才可以访问然后给你发支付后的通知,ReturnURL是回调地址,用户在网站上支付成功后会跳转进入的界面,这个地址不要求公网地址,如果是在本机测试,写http://127.0.0.1:8080/callback即可。

编写代码

在项目根目录新建main.go文件 :

go 复制代码
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/skip2/go-qrcode"
	"github.com/smartwalle/alipay/v3"
	"github.com/spf13/viper"
)

var (
	cfg    Config
	client *alipay.Client
)

// Config 定义配置结构体
type Config struct {
	AppId        string `mapstructure:"AppId"`
	PrivateKey   string `mapstructure:"PrivateKey"`
	AliPublicKey string `mapstructure:"AliPublicKey"`
	NotifyURL    string `mapstructure:"NotifyURL"`
	ReturnURL    string `mapstructure:"ReturnURL"`
}

// 初始化函数,用于读取配置文件和初始化支付宝客户端
func init() {
	v := viper.New()
	v.SetConfigFile("config.yaml")
	if err := v.ReadInConfig(); err != nil {
		panic(err)
	}
	if err := v.Unmarshal(&cfg); err != nil {
		panic(err)
	}
	client, _ = alipay.New(cfg.AppId, cfg.PrivateKey, false)
	if err := client.LoadAliPayPublicKey(cfg.AliPublicKey); err != nil {
		panic(err)
	}
}

func main() {
	r := gin.Default()
	r.GET("/sitePay", SitePay)
	r.GET("/facePay", FacePay)
	r.POST("/notify", Notify)
	r.GET("/callback", Callback)
	srv := &http.Server{
		Addr:    fmt.Sprintf(":%d", 8080),
		Handler: r,
	}
	go func() {
		log.Println("服务器启动成功!")
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			panic(err)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("服务器正在关闭...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		panic(err)
	}
	log.Println("服务器已关闭.")
}

// SitePay 电脑网站支付
func SitePay(c *gin.Context) {
	pay := alipay.TradePagePay{ // 电脑网站支付
		Trade: alipay.Trade{
			Subject:     "测试订单balabala",                       // 订单主题
			OutTradeNo:  fmt.Sprintf("%d", time.Now().Unix()), // 商户订单号,必须唯一
			TotalAmount: "100.00",                             // 订单金额
			ProductCode: "FAST_INSTANT_TRADE_PAY",             // 电脑网站支付,产品码为固定值
			NotifyURL:   cfg.NotifyURL,                        // 异步通知地址
			ReturnURL:   cfg.ReturnURL,                        // 回调地址
		},
	}
	url, err := client.TradePagePay(pay) // 生成支付链接
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  err.Error(),
		})
	}
	c.Redirect(http.StatusTemporaryRedirect, url.String()) // 重定向到支付宝支付页面
}

// FacePay 扫码支付
func FacePay(c *gin.Context) {
	pay := alipay.TradePreCreate{ // 扫码支付
		Trade: alipay.Trade{
			Subject:     "测试订单balabala",                       // 订单主题
			OutTradeNo:  fmt.Sprintf("%d", time.Now().Unix()), // 商户订单号,必须唯一
			TotalAmount: "100.00",                             // 订单金额
			ProductCode: "FACE_TO_FACE_PAYMENT",               // 当面付,产品码为固定值
			NotifyURL:   cfg.NotifyURL,                        // 异步通知地址
		},
	}
	resp, err := client.TradePreCreate(c, pay) // 生成支付二维码
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  err.Error(),
		})
	}
	qrCode, err := qrcode.New(resp.QRCode, qrcode.Medium)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  err.Error(),
		})
	}

	// 设置二维码的大小
	qrCode.DisableBorder = true
	png, err := qrCode.PNG(256) // 生成256x256的PNG图片
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate QR code image"})
		return
	}

	// 返回PNG图像
	c.Data(http.StatusOK, "image/png", png)
}

// Callback Callback函数,处理支付结果
func Callback(c *gin.Context) {
	_ = c.Request.ParseForm()
	err := client.VerifySign(c.Request.Form)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"code": 500,
			"msg":  err.Error(),
		})
		return
	}
	_, err = c.Writer.Write([]byte("<h1>成功支付的界面,可由商家自由定制</h1>"))
	if err != nil {
		panic(err)
	}
}

// Notify Notify函数,处理支付宝的异步通知
func Notify(c *gin.Context) {
	_ = c.Request.ParseForm()
	// 业务逻辑处理,比如更新订单状态,由开发者自由定制
	fmt.Println(c.Request.Form.Get("trade_no"))
	fmt.Println(c.Request.Form.Get("out_trade_no"))
	fmt.Println(c.Request.Form.Get("total_amount"))
	fmt.Println(c.Request.Form.Get("subject"))
	fmt.Println(c.Request.Form.Get("trade_status"))
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"msg":  "ok",
	})
}

运行代码前记得go mod tidy一下。

测试

我选择在公网服务器上测试,这样便于接收支付宝发来的异步通知。没有公网服务器可以使用内网穿透工具。

运行程序:go run main.go

电脑网站支付

输入网址http://182.254.140.235:8080/sitePay会自动跳转到支付宝支付页面:

接下来在沙箱账号中获取买家账号、登录密码、支付密码。

输入买家账号和密码(扫码支付不可用,用沙箱工具扫码也不行)。

输入支付密码后,如果支付成功,就会自动跳转到之前设置的回调地址。

在终端也可以看到我们的异步通知路由和回调路由都成功的被调用,电脑网址支付测试完成。

当面扫码支付

在沙箱工具处下载支付宝沙箱版:

在手机上打开安装好的app用之前的买家账号和登录密码登录!!!

接着在浏览器中访问:http://182.254.140.235:8080/facePay,会自动显示支付二维码:

在手机上使用沙箱版支付宝扫一扫,支付成功后页面不会调转,当面支付是这样的,所以在代码中也没有设置回调地址。

在终端处可以看到异步通知路由被访问了,当面支付功能测试成功。

相关推荐
颜酱18 分钟前
# 02 | 搭骨架:用 LangGraph 编排 12 步工作流(思路)
前端·人工智能·后端
颜酱19 分钟前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康2 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端