go 支付宝支付

  • Post author:
  • Post category:其他


支付宝官方暂时没有go语言的SDK


github.com/smartwalle/alipay

几个测试类直接就ok

应用创建:



创建应用 | 网页&移动应用


支付宝文档中心



https://opendocs.alipay.com/open/200/105310


API:




alipay.trade.create(统一收单交易创建接口) | API


支付宝文档中心



https://opendocs.alipay.com/apis



沙箱环境:




登录 – 支付宝


欢迎登录支付宝,支付宝-全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验以及转账收款/水电煤缴费/信用卡还款等生活服务应用;为广大为从事电子商务的网站提供支付产品/支付服务的在线订购和技术支持等服务,帮助商家快速接入支付工具,高效、安全、快捷地开展电子商务。



https://openhome.alipay.com/platform/appDaily.htm?tab=info



密钥配置是必须要看的,支付宝官网很详细

直接上代码:

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]
]



版权声明:本文为hengchi_hengchi原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。