[go]沙盒环境下调用支付宝扫码支付

709 阅读3分钟

参考于这篇博客,在此基础上进行了封装

配置支付宝开放平台

  1. 支付宝开放平台,使用支付宝扫码并成为开发者。然后进入沙盒进行测试

    image-20220620162142775

  2. 下载沙盒版支付宝并使用沙盒账号中的买家信息进行登陆,之后使用此账号登陆的支付宝来进行扫码

  3. 配置接口加签方式

    下载并安装密钥生成工具

    image-20220620164450091

沙盒下除了组织/公司必须和商户账号一样,其他可以随便填,之后得到这几个证书

image-20220620164602965

然后进入开发者平台上传csr证书来配置接口加签方式,(使用系统默认的密钥我总是没法测试成功)

image-20220620164725846

之后下载这些证书用于程序中校验使用

image-20220620164747696

image-20220620164830480

服务端代码

.
├── 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 stringconst (
    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
}
​

模拟测试

注意异步响应地址和回调地址必须是公网可以访问到的。

image-20220620165447966

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,然后重定向到这个二维码,然后使用沙盒支付宝进行扫码支付即可。

image-20220620170350616

image-20220620170628806

image-20220620170611688

建议测试时使用类似frp的内网穿透工具,比较方便。