参考于这篇博客,在此基础上进行了封装
配置支付宝开放平台
沙盒下除了组织/公司必须和商户账号一样,其他可以随便填,之后得到这几个证书
然后进入开发者平台上传csr证书来配置接口加签方式,(使用系统默认的密钥我总是没法测试成功)
之后下载这些证书用于程序中校验使用
服务端代码
.
├── cert
│ ├── alipayCertPublicKey_RSA2.crt
│ ├── alipayRootCert.crt
│ └── appCertPublicKey.crt
├── main.go
└── pay
└── pay.go
pay.go
package pay
import (
"errors"
"fmt"
"net/url"
"strconv"
"github.com/smartwalle/alipay/v3"
)
type AliPayClient struct {
client *alipay.Client
notifyURL string
returnURL string
}
// Config 初始化配置文件
type Config struct {
KAppID string // 应用ID
KPrivateKey string // 应用私钥
IsProduction bool // 是否是正式环境
AppPublicCertPath string // app公钥证书路径
AliPayRootCertPath string // alipay根证书路径
AliPayPublicCertPath string // alipay公钥证书路径
NotifyURL string // 异步通知地址
ReturnURL string // 支付后回调链接地址
}
// Init 客户端初始化
func Init(config Config) *AliPayClient {
var err error
var aliClient *alipay.Client
doThat := func(f func() error) {
if err = f(); err != nil {
panic(err)
}
}
doThat(func() error {
aliClient, err = alipay.New(config.KAppID, config.KPrivateKey, config.IsProduction)
return err
})
doThat(func() error { return aliClient.LoadAppPublicCertFromFile(config.AppPublicCertPath) })
doThat(func() error { return aliClient.LoadAliPayRootCertFromFile(config.AliPayRootCertPath) })
doThat(func() error { return aliClient.LoadAliPayPublicCertFromFile(config.AliPayPublicCertPath) })
return &AliPayClient{client: aliClient, notifyURL: config.NotifyURL, returnURL: config.ReturnURL}
}
type Order struct {
ID string // 订单ID
Subject string // 订单标题
TotalAmount float32 // 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
Code ProductCode // 销售产品码,与支付宝签约的产品码名称
}
type ProductCode string
const (
AppPay ProductCode = "QUICK_MSECURITY_PAY" // app支付
PhoneWebPay ProductCode = "QUICK_WAP_WAY" // 手机网站支付
LaptopWebPay ProductCode = "FAST_INSTANT_TRADE_PAY" // 电脑网站支付
)
var (
ErrOrderAmountOver = errors.New("订单金额超限")
ErrVerifySign = errors.New("异步通知验证签名未通过")
)
// Pay 订单支付请求,返回支付界面链接及可能出现的错误
func (client *AliPayClient) Pay(order Order) (payUrl string, err error) {
if order.TotalAmount < 0.01 || order.TotalAmount > 100000000 {
return "", ErrOrderAmountOver
}
var p = alipay.TradePagePay{}
p.NotifyURL = client.notifyURL
p.ReturnURL = client.returnURL
p.Subject = order.Subject
p.OutTradeNo = order.ID
p.TotalAmount = strconv.FormatFloat(float64(order.TotalAmount), 'f', 2, 32)
p.ProductCode = string(order.Code)
pay, err := client.client.TradePagePay(p)
if err != nil {
return "", err
}
return pay.String(), nil
}
// VerifyForm 校验form表单并返回对应订单ID(注意: callback为get,notify为post)
func (client *AliPayClient) VerifyForm(form url.Values) (orderID string, err error) {
ok, err := client.client.VerifySign(form)
if err != nil {
return "", err
}
if !ok {
return "", ErrVerifySign
}
orderID = form.Get("out_trade_no")
var p = alipay.TradeQuery{}
p.OutTradeNo = orderID
rsp, err := client.client.TradeQuery(p)
if err != nil {
return "", fmt.Errorf("异步通知验证订单 %s 信息发生错误: %s", orderID, err.Error())
}
if rsp.IsSuccess() == false {
return "", fmt.Errorf("异步通知验证订单 %s 信息发生错误: %s-%s", orderID, rsp.Content.Msg, rsp.Content.SubMsg)
}
return orderID, nil
}
模拟测试
注意异步响应地址和回调地址必须是公网可以访问到的。
package main
import (
"log"
"net/http"
"strconv"
"github.com/0RAJA/TestMod/alipay/pay"
"github.com/gin-gonic/gin"
"github.com/smartwalle/xid"
)
func init() {
log.SetFlags(log.Lshortfile | log.Ltime)
}
const (
kAppID = "2021000121601691"
kPrivateKey = "XXXX"
kServerDomain = "http://XXXX:7999"
AppPublicCertPath = "cert/appCertPublicKey.crt" // app公钥证书路径
AliPayRootCertPath = "cert/alipayRootCert.crt" // alipay根证书路径
AliPayPublicCertPath = "cert/alipayCertPublicKey_RSA2.crt" // alipay公钥证书路径
NotifyURL = kServerDomain + "/notify"
ReturnURL = kServerDomain + "/callback"
IsProduction = false
)
var AliPayClient *pay.AliPayClient
func main() {
AliPayClient = pay.Init(pay.Config{
KAppID: kAppID,
KPrivateKey: kPrivateKey,
IsProduction: IsProduction,
AppPublicCertPath: AppPublicCertPath,
AliPayRootCertPath: AliPayRootCertPath,
AliPayPublicCertPath: AliPayPublicCertPath,
NotifyURL: NotifyURL,
ReturnURL: ReturnURL,
})
var s = gin.Default()
s.GET("/alipay", payUrl)
s.GET("/callback", callback)
s.POST("/notify", notify)
s.Run(":8080")
}
//重定向到支付宝二维码
func payUrl(c *gin.Context) {
orderID := strconv.FormatInt(xid.Next(), 10)
url, err := AliPayClient.Pay(pay.Order{
ID: orderID,
Subject: "ttms购票:" + orderID,
TotalAmount: 30,
Code: pay.LaptopWebPay,
})
if err != nil {
log.Println(err)
c.JSON(http.StatusOK, "系统错误")
return
}
c.Redirect(http.StatusTemporaryRedirect, url)
}
//支付后页面的重定向界面
func callback(c *gin.Context) {
_ = c.Request.ParseForm() // 解析form
orderID, err := AliPayClient.VerifyForm(c.Request.Form)
if err != nil {
log.Println(err)
c.JSON(http.StatusOK, "校验失败")
return
}
c.JSON(http.StatusOK, "支付成功:"+orderID)
}
//支付成功后支付宝异步通知
func notify(c *gin.Context) {
_ = c.Request.ParseForm() // 解析form
orderID, err := AliPayClient.VerifyForm(c.Request.Form)
if err != nil {
log.Println(err)
return
}
log.Println("支付成功:" + orderID)
// 做自己的事
}
实际测试
ip地址+/alipay
frp