From 19b4c3bc97c45d2f684b64a22f41f5ec5033a858 Mon Sep 17 00:00:00 2001 From: mofangmin Date: Wed, 21 Aug 2024 15:38:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eeanipay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/recharge.go | 7 ++ modules/pay/allpay/all.go | 3 + modules/pay/base/base.go | 1 + modules/pay/eanipay/base.go | 184 +++++++++++++++++++++++++++++++ modules/pay/eanipay/values.go | 196 ++++++++++++++++++++++++++++++++++ modules/pay/values/values.go | 1 + 6 files changed, 392 insertions(+) create mode 100644 modules/pay/eanipay/base.go create mode 100644 modules/pay/eanipay/values.go diff --git a/common/recharge.go b/common/recharge.go index b65dcee..b22f866 100644 --- a/common/recharge.go +++ b/common/recharge.go @@ -150,6 +150,13 @@ const ( WithdrawOrderTypeAll ) +// 支付类型 +const ( + WithdrawTypeWallet = 3 + WithdrawTypeUPI = 4 + WithdrawTypeBank = 7 +) + type WithdrawOrder struct { ID uint `gorm:"primarykey"` UID int `gorm:"column:uid;not null;type:int(11)"` diff --git a/modules/pay/allpay/all.go b/modules/pay/allpay/all.go index ff0c91b..36993e8 100644 --- a/modules/pay/allpay/all.go +++ b/modules/pay/allpay/all.go @@ -3,6 +3,7 @@ package allpay import ( "reflect" "server/modules/pay/base" + "server/modules/pay/eanipay" "server/modules/pay/gopay" "server/modules/pay/grepay" "server/modules/pay/luckyinpay" @@ -59,6 +60,7 @@ type AllPay struct { Moonpay2 func(b *base.Base) PayPlus func(b *base.Base) LuckyinPay func(b *base.Base) + Eanipay func(b *base.Base) } var All = &AllPay{} @@ -70,6 +72,7 @@ func init() { All.MLPay = mlpay.NewSub All.PayPlus = payplus.NewSub All.LuckyinPay = luckyinpay.NewSub + All.Eanipay = eanipay.NewSub } func NewSub(b *base.Base, index int) { diff --git a/modules/pay/base/base.go b/modules/pay/base/base.go index 5196bec..00a90fc 100644 --- a/modules/pay/base/base.go +++ b/modules/pay/base/base.go @@ -62,6 +62,7 @@ type Base struct { SignPassStr []string // 不参与签名的字段 KeyName string // 有时候有些渠道签名字段不一样 C *gin.Context + ReqData interface{} } type CallbackResp struct { diff --git a/modules/pay/eanipay/base.go b/modules/pay/eanipay/base.go new file mode 100644 index 0000000..0707847 --- /dev/null +++ b/modules/pay/eanipay/base.go @@ -0,0 +1,184 @@ +package eanipay + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "server/common" + "server/modules/pay/base" + "server/modules/pay/values" + "server/pb" + "server/util" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/liangdas/mqant/log" +) + +func NewSub(b *base.Base) { + sub := &Sub{ + Base: b, + } + b.HttpType = base.HttpTypeJson + b.ShouldSignUpper = true + if b.Opt == base.OPTPay { + b.Resp = new(PayResp) + b.ReqURL = baseURL + payURL + } else if b.Opt == base.OPTWithdraw { + b.Resp = new(WithdrawResp) + b.ReqURL = baseURL + withdrawURL + } else if b.Opt == base.OPTPayCB { + b.CallbackReq = new(PayCallbackReq) + b.CallbackResp.Msg = "success" + } else if b.Opt == base.OPTWithdrawCB { + b.CallbackReq = new(WithdrawCallbackReq) + b.CallbackResp.Msg = "success" + } else if b.Opt == base.OPTQueryWithdraw { // 查询 + b.Resp = new(QueryWithdrawResp) + b.ReqURL = baseURL + queryWithdrawURL + } else if b.Opt == base.OPTQueryPay { // 查询 + b.Resp = new(QueryPayResp) + b.ReqURL = baseURL + queryPayURL + } else { + return + } + b.Sub = sub +} + +type Sub struct { + Base *base.Base +} + +func (s *Sub) PackHeader(header http.Header) { + header.Set("Accept", "application/json") + url := baseURL + if s.Base.Opt == base.OPTPay { + url += payURL + } else if s.Base.Opt == base.OPTWithdraw { + url += withdrawURL + } else if s.Base.Opt == base.OPTQueryWithdraw { + url += queryWithdrawURL + } else if s.Base.Opt == base.OPTQueryPay { + url += queryPayURL + } + randomStr := util.GetSimpleRandomString(10) + bodyStr, _ := json.Marshal(s.Base.ReqData) + now := time.Now().UnixMilli() + sign := Sign(string(bodyStr), fmt.Sprintf("%d", now), randomStr, url) + header.Set("Authorization", fmt.Sprintf(`V2_SHA256 appId=%s,sign=%s,timestamp=%d,nonce=%s`, appID, sign, now, randomStr)) +} + +func (s *Sub) PackReq() interface{} { + if s.Base.Opt == base.OPTPay { + return s.PackPayReq() + } else if s.Base.Opt == base.OPTWithdraw { + return s.PackWithdrawReq() + } + return nil +} + +func (s *Sub) GetResp() (proto.Message, error) { + log.Debug("resp:%v", s.Base.Resp) + if s.Base.Opt == base.OPTPay { + resp := s.Base.Resp.(*PayResp) + if resp.Data.Action.URL == "" { + return nil, errors.New("pay fail") + } + return &pb.InnerRechargeResp{APIOrderID: resp.Data.PaymentNo, URL: resp.Data.Action.URL, Channel: uint32(values.EaniPay)}, nil + } else if s.Base.Opt == base.OPTWithdraw { + resp := s.Base.Resp.(*WithdrawResp) + if s.Base.Status == 0 && resp.Code != "OK" { + return nil, errors.New("withdraw fail") + } + return &pb.InnerWithdrawResp{APIOrderID: resp.Data.PayoutNo, Channel: uint32(values.EaniPay)}, nil + } + return nil, errors.New("unknown opt") +} + +func (s *Sub) PackPayReq() interface{} { + r := s.Base.PayReq + send := &PayReq{ + MerchantTradeNo: r.OrderID, + Amount: fmt.Sprintf("%d", r.Amount), + Currency: "INR", + Description: "TeenPatti", + Payer: Payer{UserID: fmt.Sprintf("%d", r.UID), Name: r.Name, Email: r.Email, Phone: r.Phone}, + PayMethod: struct { + Type string `json:"type"` // UPI + }{Type: "UPI"}, + TradeEnv: struct { + IP string `json:"ip"` + }{IP: r.IP}, + NotifyUrl: values.GetPayCallback(values.EaniPay), + ReturnUrl: values.GetPayCallback(values.EaniPay), + } + s.Base.ReqData = send + return send +} + +func (s *Sub) PackWithdrawReq() interface{} { + r := s.Base.WithdrawReq + send := &WithdrawReq{ + MerchantTradeNo: r.OrderID, + Amount: fmt.Sprintf("%d", r.Amount), + Currency: "INR", + Description: "TeenPatti", + NotifyUrl: values.GetWithdrawCallback(values.EaniPay), + } + if r.PayType == common.WithdrawTypeBank { + send.PayoutMethod.Type = "BANK_ACCOUNT" + send.PayoutMethod.Mode = "IMPS" + send.PayoutMethod.BankCode = r.PayCode + send.PayoutMethod.BankName = r.BankName + send.PayoutMethod.AccountNumber = r.CardNo + send.PayoutMethod.PayeeName = r.Name + send.PayoutMethod.PayeePhone = r.Phone + send.PayoutMethod.PayeeEmail = r.Email + send.PayoutMethod.PayeeAddress = r.Address + } else { + return nil + } + s.Base.ReqData = send + return send +} + +func (s *Sub) CheckSign(str string) bool { + log.Debug("callback:%v", s.Base.CallbackReq) + signStr := s.Base.C.GetHeader("Authorization") + signData := parseData(signStr) + if s.Base.Opt == base.OPTPayCB { + req := s.Base.CallbackReq.(*PayCallbackReq) + s.Base.CallbackResp.OrderID = req.Data.MerchantTradeNo + if req.Data.Status == "PENDING" || req.Data.Status == "PROCESSING" { + return false + } + s.Base.CallbackResp.APIOrderID = req.Data.PaymentNo + s.Base.CallbackResp.Success = req.Data.Status == "PAID" + return Sign(str, signData.Timestamp, signData.Nonce, values.GetPayCallback(values.EaniPay)) == signData.Sign + } else if s.Base.Opt == base.OPTWithdrawCB { + req := s.Base.CallbackReq.(*WithdrawCallbackReq) + s.Base.CallbackResp.OrderID = req.Data.MerchantTradeNo + if req.Data.Status == "PENDING" || req.Data.Status == "PROCESSING" { + return false + } + s.Base.CallbackResp.Success = req.Data.Status == "PAID" + return Sign(str, signData.Timestamp, signData.Nonce, values.GetWithdrawCallback(values.EaniPay)) == signData.Sign + } + return false +} + +func Sign(bodyStr, t, ran, url string) string { + signStr := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + appID, + appSecret, + "POST", + url, + t, + ran, + bodyStr, + ) + sign := util.CalculateSHA256(signStr) + log.Debug("signStr:%s,sign:%s", signStr, sign) + return sign +} diff --git a/modules/pay/eanipay/values.go b/modules/pay/eanipay/values.go new file mode 100644 index 0000000..5360a63 --- /dev/null +++ b/modules/pay/eanipay/values.go @@ -0,0 +1,196 @@ +package eanipay + +import "strings" + +const ( + baseURL = "https://gateway.eanishop.com" + // baseURL = "https://gateway.eanishop.com" + payURL = "/pg/v2/payment/create" + queryPayURL = "/pg/v2/payment/query" + withdrawURL = "/pg/v2/payout/create" + queryWithdrawURL = "/pg/v2/payout/query" + appID = "c553240e3e3842ad98eb8ae23c3745b5" + appSecret = "8e77714f16d2487f9846cc6c3432ead5" +) + +type PayReq struct { + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Description string `json:"description"` + Payer Payer `json:"payer"` + PayMethod struct { + Type string `json:"type"` // UPI + } `json:"payMethod"` + TradeEnv struct { + IP string `json:"ip"` + } `json:"tradeEnv"` // 如果具体环境信息比较复杂,可以直接使用json.RawMessage + MerchantAttach string `json:"merchantAttach,omitempty"` // omitempty表示如果字段为空,则在JSON中不显示 + NotifyUrl string `json:"notifyUrl"` + ReturnUrl string `json:"returnUrl"` +} + +type Payer struct { + UserID string `json:"userId" validate:"required,max=64"` + Name string `json:"name" validate:"required,max=80"` + Email string `json:"email" validate:"required,email,max=80"` + Phone string `json:"phone" validate:"required,max=20"` +} + +type PayResp struct { + Code string `json:"code"` + ErrorMessage string `json:"errorMessage"` + Data PaymentData `json:"data"` +} + +type PaymentData struct { + PaymentNo string `json:"paymentNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Action Action `json:"action"` +} + +type Action struct { + PayMethodType string `json:"payMethodType"` + Type string `json:"type"` + URL string `json:"url"` + Method string `json:"method"` +} + +type PayCallbackReq struct { + Event string `json:"event"` + Code string `json:"code"` + ErrorMessage string `json:"errorMessage"` + Data struct { + PaymentNo string `json:"paymentNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + RefundStatus string `json:"refundStatus"` + Status string `json:"status"` // PENDING 待⽀付 PROCESSING ⽀付中 PAID ⽀付成功 FAILURE ⽀付失败 CANCELLED 已取消 + MerchantAttach string `json:"merchantAttach"` + CreatedTime string `json:"createdTime"` + PaidTime string `json:"paidTime"` + } `json:"data"` +} + +type QueryPayReq struct { + MerchantTradeNo string `json:"merchantTradeNo"` +} + +type QueryPayResp struct { + Code string `json:"code"` + ErrorMessage string `json:"errorMessage"` + Data struct { + PaymentNo string `json:"paymentNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + RefundStatus string `json:"refundStatus"` + Status string `json:"status"` // PENDING 待⽀付 PROCESSING ⽀付中 PAID ⽀付成功 FAILURE ⽀付失败 CANCELLED 已取消 + MerchantAttach string `json:"merchantAttach"` + CreatedTime string `json:"createdTime"` + PaidTime string `json:"paidTime"` + } `json:"data"` +} + +type WithdrawReq struct { + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Description string `json:"description"` + PayoutMethod struct { + Type string `json:"type"` // BANK_ACCOUNT + Mode string `json:"mode"` // IMPS + BankCode string `json:"bankCode"` + BankName string `json:"bankName"` + AccountNumber string `json:"accountNumber"` + PayeeName string `json:"payeeName"` + PayeePhone string `json:"payeePhone"` + PayeeEmail string `json:"payeeEmail"` + PayeeAddress string `json:"payeeAddress"` + // VPA string `json:"vpa"` + } `json:"payoutMethod"` + MerchantAttach string `json:"merchantAttach,omitempty"` + NotifyUrl string `json:"notifyUrl"` +} + +type WithdrawResp struct { + Code string `json:"code"` // OK + ErrorMessage string `json:"errorMessage"` + Data struct { + PayoutNo string `json:"payoutNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + } `json:"data"` +} + +type WithdrawCallbackReq struct { + Event string `json:"event"` + Code string `json:"code"` + ErrorMessage string `json:"errorMessage"` + Data struct { + PayoutNo string `json:"payoutNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + TotalFee string `json:"totalFee"` + Status string `json:"status"` // PENDING 待处理 PROCESSING 处理中 PAID 代付成功 FAILURE 代付失败 REVERSED 代付退票 CANCELLED 代付取消 + MerchantAttach string `json:"merchantAttach"` + CreatedTime string `json:"createdTime"` + PaidTime string `json:"paidTime"` + } `json:"data"` +} + +type QueryWithdrawReq struct { + MerchantTradeNo string `json:"merchantTradeNo"` +} + +type QueryWithdrawResp struct { + Code string `json:"code"` + ErrorMessage string `json:"errorMessage"` + Data struct { + PayoutNo string `json:"payoutNo"` + MerchantTradeNo string `json:"merchantTradeNo"` + Amount string `json:"amount"` + Currency string `json:"currency"` + TotalFee string `json:"totalFee"` + Status string `json:"status"` // PENDING 待处理 PROCESSING 处理中 PAID 代付成功 FAILURE 代付失败 REVERSED 代付退票 CANCELLED 代付取消 + MerchantAttach string `json:"merchantAttach"` + CreatedTime string `json:"createdTime"` + PaidTime string `json:"paidTime"` + } `json:"data"` +} + +type SignData struct { + AppId string + Sign string + Timestamp string + Nonce string +} + +func parseData(input string) SignData { + input = strings.TrimPrefix(input, "V2_SHA256 ") + pairs := strings.Split(input, ",") + data := SignData{} + for _, pair := range pairs { + keyValue := strings.Split(pair, "=") + if len(keyValue) == 2 { + key := keyValue[0] + value := keyValue[1] + switch key { + case "appId": + data.AppId = value + case "sign": + data.Sign = value + case "timestamp": + data.Timestamp = value + case "nonce": + data.Nonce = value + } + } + } + return data +} diff --git a/modules/pay/values/values.go b/modules/pay/values/values.go index ec2c4f6..3679d59 100644 --- a/modules/pay/values/values.go +++ b/modules/pay/values/values.go @@ -69,6 +69,7 @@ const ( MoonPay2 PayPlus LuckyinPay + EaniPay PayAll )