go-zero之App支付寶支付

charliecen發表於2021-12-17

支付寶支付

例項化客戶端

func NewAliAppPayClient(appId, apiPrivateKey, notifyUrl, appPublicKeyCertPath, aliPayRootCertPath, aliPayCertPublicKeyPath string) *alipay.Client {
    // 初始化支付寶客戶端
    //    appId:應用ID
    //    privateKey:應用私鑰,支援PKCS1和PKCS8
    //    isProd:是否是正式環境
    client, err := alipay.NewClient(appId, apiPrivateKey, true)
    if err != nil {
        logx.Error(err)
        return nil
    }
    // 開啟Debug開關,輸出日誌,預設關閉
    //client.DebugSwitch = gopay.DebugOn

    // 設定支付寶請求 公共引數
    //    注意:具體設定哪些引數,根據不同的方法而不同,此處列舉出所有設定引數
    client.SetLocation(alipay.LocationShanghai). // 設定時區,不設定或出錯均為預設伺服器時間
                            SetCharset(alipay.UTF8).  // 設定字元編碼,不設定預設 utf-8
                            SetSignType(alipay.RSA2). // 設定簽名型別,不設定預設 RSA2
        //SetReturnUrl("https://www.fmm.ink").        // 設定返回URL
        SetNotifyUrl(notifyUrl) // 設定非同步通知URL
        //SetAppAuthToken()                           // 設定第三方應用授權

    // 自動同步驗籤(只支援證照模式)
    // 傳入 alipayCertPublicKey_RSA2.crt 內容
    appPublicKeyCertContent, err := ioutil.ReadFile(appPublicKeyCertPath)
    if err != nil {
        logx.Error(err)
        return nil
    }
    client.AutoVerifySign(appPublicKeyCertContent)

    // 公鑰證照模式,需要傳入證照,以下兩種方式二選一
    // 證照路徑
    err = client.SetCertSnByPath(appPublicKeyCertPath, aliPayRootCertPath, aliPayCertPublicKeyPath)
    if err != nil {
        logx.Error()
        return nil
    }
    // 證照內容
    //err := client.SetCertSnByContent("appCertPublicKey bytes", "alipayRootCert bytes", "alipayCertPublicKey_RSA2 bytes")
    return client
}

生成支付資訊

支付寶申請介面加密方式,並生成證照。請求引數需要至少4個。

//  APP 支付寶支付
func (l *AliAppPayLogic) AliAppPay(in *order.AliPrePayRequest) (*order.Response, error) {
    data := make(map[string]string, 0)

    notifyUrl := l.svcCtx.Config.AliAppPay.NotifyUrl
    appId := l.svcCtx.Config.AliAppPay.AppId
    apiPrivateKey := l.svcCtx.Config.AliAppPay.ApiPrivateKey
    appPublicKeyCertPath := tool.GetFilePath(l.svcCtx.Config.AliAppPay.AppPublicKeyCertPath)
    aliPayRootCertPath := tool.GetFilePath(l.svcCtx.Config.AliAppPay.AliPayRootCertPath)
    aliPayCertPublicKeyPath := tool.GetFilePath(l.svcCtx.Config.AliAppPay.AliPayCertPublicKeyPath)
    //expireTime := l.svcCtx.Config.AliAppPay.ExpireTime

    // 初始化
    client := tool.NewAliAppPayClient(appId, apiPrivateKey, notifyUrl, appPublicKeyCertPath, aliPayRootCertPath, aliPayCertPublicKeyPath)
    if client == nil {
        return &order.Response{
            Code: 201,
            Msg:  "初始化支付寶客戶端失敗",
            Data: data,
        }, nil
    }
    // 查詢訂單
    orderInfo, err := model.NewOrder().FindOneByOrderId(in.OrderId)
    if err != nil {
        return &order.Response{
            Code: 201,
            Msg:  "訂單不存在",
            Data: data,
        }, nil
    }
    // 支付寶需要元為單位,所以轉成浮點型數值
    money := float64(orderInfo.Amount) / float64(100)
    amount := fmt.Sprintf("%.2f", money)

    bm := make(gopay.BodyMap)
    bm.Set("subject", orderInfo.Desc).
        Set("out_trade_no", in.OrderId).
        Set("total_amount", amount).
        Set("product_code", config.AppProductCode)

    //logx.Info("支付寶支付金額:", amount)
    aliPayResp, err := client.TradeAppPay(l.ctx, bm)
    if err != nil {
        logx.Error(err)
        return &order.Response{
            Code: 201,
            Msg:  err.Error(),
            Data: data,
        }, nil
    }
    data["info"] = aliPayResp
    return &order.Response{
        Code: 200,
        Msg:  "success",
        Data: data,
    }, nil
}

api返回資訊

生成的資訊是一段字串,包含appid, 引數內容,證照序列號,資料型別,請求介面,通知地址,簽名,簽名型別,時間戳,版本等

// APP 支付
func (l *AppPayLogic) AppPay(req types.GenerateOrderReq) (*types.OrderResponse, error) {
    data := make(map[string]interface{}, 0)
    userId, _ := l.ctx.Value("userId").(json.Number).Int64()
    // 建立訂單
    // 商品詳情
    goodsInfo, err := l.svcCtx.Lookclient.GoodsDetail(l.ctx, &lookclient.FindGoodsByIdRequest{Id: req.GoodsId})
    if err != nil || goodsInfo.Code != 200 {
        return &types.OrderResponse{
            Code: 201,
            Msg:  goodsInfo.Msg,
            Data: data,
        }, nil
    }
    // 商品表價格字串轉int64
    //price, _ := strconv.ParseFloat(goodsInfo.Data.Price, 10)
    //goodsPrice := int64(price * 100)
    goodsPrice := int64(goodsInfo.Data.Price * 100)
    // 生成訂單詳情
    goodsInfoData, _ := json.Marshal(goodsInfo.Data)
    // rpc 生成訂單
    orderInfo, err := l.svcCtx.OrderClient.GenerateOrder(l.ctx, &orderclient.GenerateOrderRequest{
        UserId:  userId,
        GoodsId: goodsInfo.Data.Id,
        PayType: req.PayType,
        Amount:  goodsPrice,
        Desc:    goodsInfo.Data.Desc,
        Data:    string(goodsInfoData),
    })
    if err != nil || orderInfo.Code != 200 {
        return &types.OrderResponse{
            Code: 201,
            Msg:  orderInfo.Msg,
            Data: data,
        }, nil
    }
    // 判斷支付方式
    if req.PayType == config.WxPay {
        // 生成預支付
        wxPrePayResp, err := l.svcCtx.OrderClient.WxPrePay(l.ctx, &orderclient.WxPrePayRequest{
            OrderId: orderInfo.Data["order_id"],
            Desc:    goodsInfo.Data.Desc,
            Total:   goodsPrice,
        })

        if err != nil || wxPrePayResp.Code != 200 {
            return &types.OrderResponse{
                Code: 201,
                Msg:  wxPrePayResp.Msg,
                Data: data,
            }, nil
        }

        // 生成支付資訊
        wxPayResp, err := l.svcCtx.OrderClient.WxPay(l.ctx, &orderclient.WxPayRequest{
            PrePayId: wxPrePayResp.Data["prepay_id"],
        })
        if err != nil || wxPayResp.Code != 200 {
            return &types.OrderResponse{
                Code: 201,
                Msg:  wxPayResp.Msg,
                Data: data,
            }, nil
        }
        data["info"] = wxPayResp.Data
    } else if req.PayType == config.AliPay {
        // 支付寶支付
        aliPayResp, err := l.svcCtx.OrderClient.AliAppPay(l.ctx, &orderclient.AliPrePayRequest{
            OrderId: orderInfo.Data["order_id"],
            Desc:    goodsInfo.Data.Desc,
            Total:   goodsPrice,
        })
        if err != nil || aliPayResp.Code != 200 {
            return &types.OrderResponse{
                Code: 201,
                Msg:  aliPayResp.Msg,
                Data: data,
            }, nil
        }
        data["info"] = aliPayResp.Data
    }
    return &types.OrderResponse{
        Code: 200,
        Msg:  "success",
        Data: data,
    }, nil
}

測試下

客戶端拿到返回資訊後,請求支付寶來完成支付

curl --location --request POST 'http://localhost/order/app_pay' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--form 'goods_id=2' \
--form 'pay_type=2'
{
    "code": 200,
    "msg": "success",
    "data": {
        "info": {
            "info": "alipay_root_cert_sn=6998_02941eef..."
        }
    }
}

回撥通知

支付成功後,會通知服務端提供的地址,服務端接收引數,解析,驗籤等操作,並修改訂單狀態等後續操作。

//  rpc 程式碼
//  APP 支付寶通知
func (l *AliAppPayNotifyLogic) AliAppPayNotify(in *order.AliAppPayNotifyRequest) (*order.Response, error) {
    var data = make(map[string]string, 0)

    // 解析請求引數
    var bodyMap gopay.BodyMap
    _ = json.Unmarshal([]byte(in.Body), &bodyMap)

    // 獲取證照公鑰路徑
    aliPayCertPublicKeyPath := tool.GetFilePath(l.svcCtx.Config.AliAppPay.AliPayCertPublicKeyPath)
    aliPayCertPublicKeyContent, err := ioutil.ReadFile(aliPayCertPublicKeyPath)
    if err != nil {
        logx.Error("支付寶回撥讀取公鑰證照檔案錯誤:", err)
        return &order.Response{
            Code: 201,
            Msg:  "支付寶回撥讀取公鑰證照檔案錯誤",
            Data: data,
        }, nil
    }
    ok, err := alipay.VerifySignWithCert(aliPayCertPublicKeyContent, bodyMap)
    if err != nil || !ok {
        logx.Error("支付寶回撥驗證錯誤:", err)
        return &order.Response{
            Code: 201,
            Msg:  "支付寶回撥驗證錯誤",
            Data: data,
        }, nil
    }
    //notifyReq.GetString("TradeStatus")
    logx.Info("支付寶返回通知資料: ", bodyMap)
    if bodyMap.GetString("trade_status") != "TRADE_SUCCESS" {
        logx.Error("支付寶回撥失敗:", err)
        return &order.Response{
            Code: 201,
            Msg:  "支付寶回撥失敗",
            Data: data,
        }, nil
    }
    data["order_id"] = bodyMap.GetString("out_trade_no")
    return &order.Response{
        Code: 200,
        Msg:  "success",
        Data: data,
    }, nil
}


// api 程式碼
// APP 支付寶回撥
func (l *AliPayNotifyLogic) AliPayNotify(req *http.Request, resp http.ResponseWriter) {
    // 解析非同步通知的引數
    //    req:*http.Request
    notifyReq, err := alipay.ParseNotifyToBodyMap(req)
    if err != nil {
        logx.Error("支付寶回撥解析req錯誤:", err)
        return
    }
    notifyBody := notifyReq.JsonBody()

    // rpc 獲取通知結果
    result, err := l.svcCtx.OrderClient.AliAppPayNotify(l.ctx, &orderclient.AliAppPayNotifyRequest{
        Body: notifyBody,
    })
    if err != nil || result.Code != 200 {
        logx.Error("支付寶回撥錯誤:", err)
        return
    }
    // 查詢訂單
    orderInfo, _ := l.svcCtx.OrderClient.FindOneByOrderId(l.ctx, &orderclient.FindOrderRequest{
        OrderId: result.Data["order_id"],
    })

    // 訂單已支付
    if orderInfo.Data.Status == config.Paid {
        logx.Info("支付寶回撥查詢訂單已支付成功")
        return
    } else {
        // 非同步更新訂單狀態
        common := NewCommonHandleLogic(l.ctx, l.svcCtx)
        go common.UpdateAppOrder(result.Data["order_id"], orderInfo.Data, config.Paid)
    }
    // 確認收到通知訊息
    resp.WriteHeader(http.StatusOK)
    _, _ = resp.Write([]byte("success"))
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章