parent
6bee0e79ba
commit
19b4c3bc97
6 changed files with 392 additions and 0 deletions
@ -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 |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
Loading…
Reference in new issue