支付宝官方暂时没有go语言的SDK
github.com/smartwalle/alipay
几个测试类直接就ok
应用创建:
创建应用 | 网页&移动应用
支付宝文档中心
https://opendocs.alipay.com/open/200/105310
API:
alipay.trade.create(统一收单交易创建接口) | API
支付宝文档中心
https://opendocs.alipay.com/apis
沙箱环境:
直接上代码:
package main
import (
"PayTest2/cert"
"fmt"
"github.com/gin-gonic/gin"
"github.com/smartwalle/alipay/v3"
"github.com/smartwalle/xid"
"log"
"net/http"
"time"
)
var aliClient *alipay.Client
const (
kServerPort = "8080"
kServerDomain = "http://xx.xx.xx.xx" + ":" + kServerPort
)
func main() {
var err error
if aliClient, err = alipay.New(cert.AppId, cert.PrivateKey, false); err != nil {
log.Println("初始化支付宝失败", err)
return
}
// 使用支付宝证书
if err = aliClient.LoadAppPublicCertFromFile("cert/appCertPublicKey_2021000118628779.crt"); err != nil {
log.Println("加载证书发生错误", err)
return
}
if err = aliClient.LoadAliPayRootCertFromFile("cert/alipayRootCert.crt"); err != nil {
log.Println("加载证书发生错误", err)
return
}
if err = aliClient.LoadAliPayPublicCertFromFile("cert/alipayCertPublicKey_RSA2.crt"); err != nil {
log.Println("加载证书发生错误", err)
return
}
var s = gin.Default()
s.GET("/alipay", pay)
s.GET("/callback", callback)
s.POST("/notify", notify)
s.Run(":" + kServerPort)
}
func pay(c *gin.Context) {
var tradeNo = fmt.Sprintf("%d", xid.Next())
var p = alipay.TradePagePay{}
p.NotifyURL = kServerDomain + "/notify"
p.ReturnURL = kServerDomain + "/callback"
p.Subject = "支付宝测试:" + tradeNo
p.OutTradeNo = time.Now().Format("20060102")+"_"+"name"
p.TotalAmount = "0.1"
p.ProductCode = "FAST_INSTANT_TRADE_PAY"
fmt.Println("TradePagePay\n")
fmt.Println(p)
url, _ := aliClient.TradePagePay(p)
c.Redirect(http.StatusTemporaryRedirect, url.String())
}
func callback(c *gin.Context) {
c.Request.ParseForm()
ok, err := aliClient.VerifySign(c.Request.Form)
if err != nil {
log.Println("回调验证签名发生错误", err)
return
}
if ok == false {
log.Println("回调验证签名未通过")
return
}
fmt.Println("------c.Request.Form: -------\n", c.Request.Form)
var outTradeNo = c.Request.Form.Get("out_trade_no")
var p = alipay.TradeQuery{}
p.OutTradeNo = outTradeNo
rsp, err := aliClient.TradeQuery(p)
if err != nil {
c.String(http.StatusBadRequest, "验证订单 %s 信息发生错误: %s", outTradeNo, err.Error())
return
}
if rsp.IsSuccess() == false {
c.String(http.StatusBadRequest, "验证订单 %s 信息发生错误: %s-%s", outTradeNo, rsp.Content.Msg, rsp.Content.SubMsg)
return
}
c.String(http.StatusOK, "订单 %s 支付成功", outTradeNo)
}
func notify(c *gin.Context) {
fmt.Println("------notify------")
c.Request.ParseForm()
fmt.Println("c.Request.Form: ",c.Request.Form)
ok, err := aliClient.VerifySign(c.Request.Form)
if err != nil {
log.Println("异步通知验证签名发生错误", err)
return
}
if ok == false {
log.Println("异步通知验证签名未通过")
return
}
log.Println("异步通知验证签名通过")
var outTradeNo = c.Request.Form.Get("out_trade_no")
var p = alipay.TradeQuery{}
p.OutTradeNo = outTradeNo
rsp, err := aliClient.TradeQuery(p)
if err != nil {
log.Printf("异步通知验证订单 %s 信息发生错误: %s \n", outTradeNo, err.Error())
return
}
if rsp.IsSuccess() == false {
log.Printf("异步通知验证订单 %s 信息发生错误: %s-%s \n", outTradeNo, rsp.Content.Msg, rsp.Content.SubMsg)
return
}
log.Printf("订单 %s 支付成功 \n", outTradeNo)
}
服务器流程:
1.接收到客户端请求,请求支付宝服务器
会将包含app_id,sign_type等等在内的配置数据都放到一个map中,转为一个字符串切片进行排序,对字符串进行RSA算法,再拿着服务器端的私钥进行签名。
func (this *Client) TradePagePay(param TradePagePay) (result *url.URL, err error) {
p, err := this.URLValues(param)
if err != nil {
return nil, err
}
result, err = url.Parse(this.apiDomain + "?" + p.Encode())
if err != nil {
return nil, err
}
return result, err
}
func (this *Client) URLValues(param Param) (value url.Values, err error) {
var p = url.Values{}
p.Add("app_id", this.appId)
p.Add("method", param.APIName())
p.Add("format", kFormat)
p.Add("charset", kCharset)
p.Add("sign_type", kSignTypeRSA2)
p.Add("timestamp", time.Now().In(this.location).Format(kTimeFormat))
p.Add("version", kVersion)
if this.appCertSN != "" {
p.Add("app_cert_sn", this.appCertSN)
}
if this.rootCertSN != "" {
p.Add("alipay_root_cert_sn", this.rootCertSN)
}
bytes, err := json.Marshal(param)
if err != nil {
return nil, err
}
p.Add("biz_content", string(bytes))
var ps = param.Params()
if ps != nil {
for key, value := range ps {
if key == kAppAuthToken && value == "" {
continue
}
p.Add(key, value)
}
}
sign, err := signWithPKCS1v15(p, this.appPrivateKey, crypto.SHA256)
if err != nil {
return nil, err
}
p.Add("sign", sign)
return p, nil
}
func signWithPKCS1v15(param url.Values, privateKey *rsa.PrivateKey, hash crypto.Hash) (s string, err error) {
if param == nil {
param = make(url.Values, 0)
}
var pList = make([]string, 0, 0)
for key := range param {
var value = strings.TrimSpace(param.Get(key))
if len(value) > 0 {
pList = append(pList, key+"="+value)
}
}
// 排序
sort.Strings(pList)
var src = strings.Join(pList, "&")
// 加密私钥加签
sig, err := crypto4go.RSASignWithKey([]byte(src), privateKey, hash)
if err != nil {
return "", err
}
s = base64.StdEncoding.EncodeToString(sig)
return s, nil
}
2、支付宝同步返回一个支付url,跳转支付,支付成功后,支付宝向前端返回支付成功结果,并且向服务器发送一个支付通知,return_url
3、nofity_url,支付成功后支付宝会异步发送消息给该地址。发送多次直到收到。
流程:从支付宝的http请求中获取form表单,除去sign、sign_type、alipay_cert_sn等字段,拼成字符串,进行排序,decode和公钥验签
// 获取表单
c.Request.ParseForm()
ok, err := aliClient.VerifySign(c.Request.Form)
func (this *Client) VerifySign(data url.Values) (ok bool, err error) {
var certSN = data.Get(kCertSNNodeName)
publicKey, err := this.getAliPayPublicKey(certSN)
if err != nil {
return false, err
}
return verifySign(data, publicKey)
}
func verifySign(data url.Values, key *rsa.PublicKey) (ok bool, err error) {
sign := data.Get(kSignNodeName)
// 除去部分字段
var keys = make([]string, 0, 0)
for k := range data {
if k == kSignNodeName || k == kSignTypeNodeName || k == kCertSNNodeName {
continue
}
keys = append(keys, k)
}
// 排序并拼接字符串
sort.Strings(keys)
var buf strings.Builder
for _, k := range keys {
vs := data[k]
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(v)
}
}
s := buf.String()
return verifyData([]byte(s), sign, key)
}
func verifyData(data []byte, sign string, key *rsa.PublicKey) (ok bool, err error) {
// decode
signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return false, err
}
// 公钥验签
if err = crypto4go.RSAVerifyWithKey(data, signBytes, key, crypto.SHA256); err != nil
{
return false, err
}
return true, nil
}
表单示例:
c.Request.Form:
map[
app_id:[2021000118628779]
auth_app_id:[2021000118628779]
buyer_id:[2088622957059425]
buyer_pay_amount:[0.10]
charset:[utf-8]
fund_bill_list:[[{"amount":"0.10","fundChannel":"ALIPAYACCOUNT"}]]
gmt_create:[2021-10-15 10:28:48] gmt_payment:[2021-10-15 10:28:55]
invoice_amount:[0.10]
notify_id:[2021101500222102856059421000717167]
notify_time:[2021-10-15 10:28:57]
notify_type:[trade_status_sync]
out_trade_no:[20211015102842_wangfei]
point_amount:[0.00]
receipt_amount:[0.10]
seller_id:[2088621956589955]
sign:[A9yHB9Epb+SNvNvEzFeEq/EPUW+J50DnAhw6j3b/nDXzB3zcpe8xTeXhyIz0IefJrHMPTJazvGEnPcr6vkuEVzDi0MY3mLDSW1gljXnOycWhnoSAQkPqfaT7NwjusGCSrgRK1ZUU+QkvwrEqI8m42MEBOonqJwUPx+s/70yTR0BO9L+de50b3dRMJ/zDVn9s2iUsElY9xxdzTlRZmjZb5PRVjEdQQyMzihLvjHwsd6jZu8BmuSpxsUSwQ1Atcs4a4vSzGV2YkzmAxWfdpEEc+engEQXkN4D3GSqyNeXyxNd2n7WxUIvNoHYL8H24Rci89iTQ/nDEaLOG+B7jAAZh/A==]
sign_type:[RSA2]
subject:[支付宝测试:3509557196494995456]
total_amount:[0.10]
trade_no:[2021101522001459421000179848]
trade_status:[TRADE_SUCCESS]
version:[1.0]
]