配置支付宝开放平台

支付宝开放平台

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

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

image-20240617095759799

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

image-20240617100250709

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

1
2
3
4
5
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文件 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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

image-20240617101843713

电脑网站支付

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

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

image-20240617102633081

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

image-20240617102726548

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

20240617103226_rec_

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

image-20240617103521016

当面扫码支付

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

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

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

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

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

image-20240617104056992