You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
12 KiB
454 lines
12 KiB
|
2 months ago
|
package moneydealernative
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/hmac"
|
||
|
|
"crypto/md5"
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/base64"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"fmt"
|
||
|
|
"io/ioutil"
|
||
|
|
"net/http"
|
||
|
|
"server/modules/pay/base"
|
||
|
|
"server/modules/pay/values"
|
||
|
|
"server/pb"
|
||
|
|
"sort"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/google/uuid"
|
||
|
|
jsoniter "github.com/json-iterator/go"
|
||
|
|
|
||
|
|
"github.com/gogo/protobuf/proto"
|
||
|
|
"github.com/liangdas/mqant/log"
|
||
|
|
)
|
||
|
|
|
||
|
|
func NewSub(b *base.Base) {
|
||
|
|
sub := &Sub{
|
||
|
|
Base: b,
|
||
|
|
}
|
||
|
|
b.HttpType = base.HttpTypeJson
|
||
|
|
b.PostformType = base.PostformTypeQuery
|
||
|
|
b.ShouldSignUpper = true
|
||
|
|
if b.Opt == base.OPTPay {
|
||
|
|
b.Resp = new(PayResp)
|
||
|
|
b.ReqURL = getUrl() + payURL
|
||
|
|
b.SubReq = sub.SubPayReq
|
||
|
|
} else if b.Opt == base.OPTWithdraw {
|
||
|
|
b.Resp = new(WithdrawResp)
|
||
|
|
b.ReqURL = getUrl() + withdrawURL
|
||
|
|
// todo
|
||
|
|
b.SubReq = sub.SubPayReq
|
||
|
|
} 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.HttpType = base.HttpTypeGet
|
||
|
|
b.Resp = new(QueryWithdrawResp)
|
||
|
|
b.ReqURL = getUrl() + queryWithdrawURL + fmt.Sprintf("?order_no=%s", b.QueryWithdrawReq.APIOrderID)
|
||
|
|
b.SubReq = sub.SubPayReq
|
||
|
|
} else if b.Opt == base.OPTQueryPay { // 查询
|
||
|
|
b.HttpType = base.HttpTypeGet
|
||
|
|
b.Resp = new(QueryPayResp)
|
||
|
|
b.ReqURL = getUrl() + queryPayURL + fmt.Sprintf("?order_no=%s", b.QueryPayReq.APIOrderID)
|
||
|
|
b.SubReq = sub.SubPayReq
|
||
|
|
} else {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
b.Sub = sub
|
||
|
|
}
|
||
|
|
|
||
|
|
type Sub struct {
|
||
|
|
Base *base.Base
|
||
|
|
Req interface{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) SubPayReq() ([]byte, error) {
|
||
|
|
send := s.PackReq()
|
||
|
|
s.Req = send
|
||
|
|
reqData := s.Base.PackReq(send)
|
||
|
|
method := "POST"
|
||
|
|
if s.Base.HttpType == base.HttpTypeGet {
|
||
|
|
method = "GET"
|
||
|
|
}
|
||
|
|
req, err := http.NewRequest(method, s.Base.ReqURL, reqData)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("err:%v", err)
|
||
|
|
return nil, errors.New("pay fail")
|
||
|
|
}
|
||
|
|
s.Base.PackHeader(req.Header)
|
||
|
|
s.Base.Status = s.Post(req)
|
||
|
|
ret, err := s.GetResp()
|
||
|
|
if err != nil {
|
||
|
|
log.Error("err:%v", err)
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
data, _ := proto.Marshal(ret)
|
||
|
|
return data, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) Post(req *http.Request) int {
|
||
|
|
client := &http.Client{}
|
||
|
|
log.Debug("req:%+v", req)
|
||
|
|
resp, err := client.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("http post call err:%v", err)
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
||
|
|
log.Debug("response Body:%v", string(body))
|
||
|
|
err = json.Unmarshal(body, &s.Base.Resp)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("unmarshal err, %s", err.Error())
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
func marshalSortedMap(m map[string]interface{}) (string, error) {
|
||
|
|
keys := make([]string, 0, len(m))
|
||
|
|
for k := range m {
|
||
|
|
keys = append(keys, k)
|
||
|
|
}
|
||
|
|
sort.Strings(keys)
|
||
|
|
|
||
|
|
ordered := make(map[string]interface{})
|
||
|
|
for _, k := range keys {
|
||
|
|
ordered[k] = m[k]
|
||
|
|
}
|
||
|
|
|
||
|
|
value, err := json.Marshal(ordered)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
return string(value), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackHeader(header http.Header) {
|
||
|
|
params := make(map[string]string)
|
||
|
|
now := time.Now()
|
||
|
|
uuid := uuid.New().String()
|
||
|
|
switch s.Req.(type) {
|
||
|
|
case *WithdrawReq, *PayReq:
|
||
|
|
reqData, _ := jsoniter.MarshalToString(s.Req)
|
||
|
|
tmpParams := make(map[string]interface{})
|
||
|
|
err := jsoniter.UnmarshalFromString(reqData, &tmpParams)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("unmarshal err, %s", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
for k, v := range tmpParams {
|
||
|
|
var value string
|
||
|
|
if k == "payout_info" {
|
||
|
|
value, _ = jsoniter.MarshalToString(v)
|
||
|
|
tmpMap := make(map[string]interface{})
|
||
|
|
_ = jsoniter.UnmarshalFromString(value, &tmpMap)
|
||
|
|
value, _ = marshalSortedMap(tmpMap)
|
||
|
|
params[k] = value
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
switch v.(type) {
|
||
|
|
case float64:
|
||
|
|
value = fmt.Sprintf("%d", int(v.(float64)))
|
||
|
|
case string:
|
||
|
|
value = v.(string)
|
||
|
|
default:
|
||
|
|
value = fmt.Sprintf("%+v", v)
|
||
|
|
}
|
||
|
|
params[k] = value
|
||
|
|
}
|
||
|
|
case *QueryPayReq, *QueryWithdrawReq:
|
||
|
|
reqData, _ := jsoniter.MarshalToString(s.Req)
|
||
|
|
err := jsoniter.UnmarshalFromString(reqData, ¶ms)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("unmarshal err, %s", err.Error())
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
header.Set("x-merchant-id", getPayInfo().ProjectCode)
|
||
|
|
header.Set("x-api-key", getPayInfo().ApiKey)
|
||
|
|
header.Set("x-timestamp", fmt.Sprintf("%d", now.Unix()))
|
||
|
|
header.Set("x-nonce", uuid)
|
||
|
|
header.Set("x-sign-type", signType)
|
||
|
|
|
||
|
|
params["x-merchant-id"] = getPayInfo().ProjectCode
|
||
|
|
params["x-api-key"] = getPayInfo().ApiKey
|
||
|
|
params["x-timestamp"] = fmt.Sprintf("%d", now.Unix())
|
||
|
|
params["x-nonce"] = uuid
|
||
|
|
params["x-sign-type"] = signType
|
||
|
|
|
||
|
|
header.Set("x-signature", s.GetSign(params))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) GetSign(params map[string]string) string {
|
||
|
|
switch signType {
|
||
|
|
case "MD5":
|
||
|
|
return s.signMD5(params)
|
||
|
|
case "HMAC-SHA256":
|
||
|
|
return s.signSha256(params)
|
||
|
|
default:
|
||
|
|
log.Error("sign type is wrong")
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) signMD5(params map[string]string) string {
|
||
|
|
var keys []string
|
||
|
|
for k := range params {
|
||
|
|
if k == "Content-Type" || k == "signature" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
keys = append(keys, k)
|
||
|
|
}
|
||
|
|
sort.Strings(keys)
|
||
|
|
|
||
|
|
var buf strings.Builder
|
||
|
|
for i, k := range keys {
|
||
|
|
if i > 0 {
|
||
|
|
buf.WriteString("&")
|
||
|
|
}
|
||
|
|
buf.WriteString(k)
|
||
|
|
buf.WriteString("=")
|
||
|
|
buf.WriteString(params[k])
|
||
|
|
}
|
||
|
|
|
||
|
|
// 追加密钥
|
||
|
|
buf.WriteString("&key=")
|
||
|
|
buf.WriteString(getPayInfo().ApiSecret)
|
||
|
|
|
||
|
|
// MD5加密并转大写
|
||
|
|
h := md5.New()
|
||
|
|
h.Write([]byte(buf.String()))
|
||
|
|
sign := strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
|
||
|
|
log.Debug("moneydealer, sign value:%s sign:%s", buf.String(), sign)
|
||
|
|
return sign
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) signSha256(params map[string]string) string {
|
||
|
|
// 按照键名排序
|
||
|
|
var keys []string
|
||
|
|
for k := range params {
|
||
|
|
if k == "Content-Type" || k == "signature" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
keys = append(keys, k)
|
||
|
|
}
|
||
|
|
sort.Strings(keys)
|
||
|
|
|
||
|
|
// 拼接字符串
|
||
|
|
var buf strings.Builder
|
||
|
|
for i, k := range keys {
|
||
|
|
if i > 0 {
|
||
|
|
buf.WriteString("&")
|
||
|
|
}
|
||
|
|
buf.WriteString(k)
|
||
|
|
buf.WriteString("=")
|
||
|
|
buf.WriteString(params[k])
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用HMAC-SHA256计算签名
|
||
|
|
h := hmac.New(sha256.New, []byte(getPayInfo().ApiSecret))
|
||
|
|
h.Write([]byte(buf.String()))
|
||
|
|
sign := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||
|
|
log.Debug("moneydealer, sign value:%s sign:%s", buf.String(), sign)
|
||
|
|
return sign
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackReq() interface{} {
|
||
|
|
if s.Base.Opt == base.OPTPay {
|
||
|
|
return s.PackPayReq()
|
||
|
|
} else if s.Base.Opt == base.OPTWithdraw {
|
||
|
|
return s.PackWithdrawReq()
|
||
|
|
} else if s.Base.Opt == base.OPTQueryWithdraw {
|
||
|
|
return s.PackQueryWithdrawReq()
|
||
|
|
} else if s.Base.Opt == base.OPTQueryPay {
|
||
|
|
return s.PackQueryPayReq()
|
||
|
|
}
|
||
|
|
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.PayUrl == "" {
|
||
|
|
return nil, errors.New("pay fail")
|
||
|
|
}
|
||
|
|
return &pb.InnerRechargeResp{
|
||
|
|
APIOrderID: resp.Data.OrderNo,
|
||
|
|
URL: resp.Data.PayUrl,
|
||
|
|
Channel: uint32(values.MoneydealerNativePay),
|
||
|
|
}, nil
|
||
|
|
} else if s.Base.Opt == base.OPTWithdraw {
|
||
|
|
resp := s.Base.Resp.(*WithdrawResp)
|
||
|
|
if s.Base.Status == 0 && resp.Code != 0 {
|
||
|
|
return nil, fmt.Errorf("result status is %d", s.Base.Status)
|
||
|
|
}
|
||
|
|
return &pb.InnerWithdrawResp{
|
||
|
|
APIOrderID: resp.Data.OrderNo,
|
||
|
|
Channel: uint32(values.MoneydealerNativePay),
|
||
|
|
Status: uint32(s.Base.Status),
|
||
|
|
}, nil
|
||
|
|
} else if s.Base.Opt == base.OPTQueryWithdraw {
|
||
|
|
resp := s.Base.Resp.(*QueryWithdrawResp)
|
||
|
|
ret := &pb.InnerQueryWithdrawResp{Msg: resp.Message}
|
||
|
|
if s.Base.Status == 0 && resp.Code != 0 {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusUnknown
|
||
|
|
return ret, nil
|
||
|
|
}
|
||
|
|
data := resp.Data
|
||
|
|
if data.OrderNo == "" || data.MerchantOrderNo == "" {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusUnknown
|
||
|
|
return ret, nil
|
||
|
|
}
|
||
|
|
s.Base.QueryWithdrawResp.APIOrderID = data.OrderNo
|
||
|
|
s.Base.QueryWithdrawResp.OrderID = data.MerchantOrderNo
|
||
|
|
s.Base.QueryWithdrawResp.Msg = resp.Message
|
||
|
|
if s.Base.Status != 200 && s.Base.Status != 0 {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusUnknown
|
||
|
|
} else {
|
||
|
|
if data.Status == 2 {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusOrderSuccess
|
||
|
|
} else if data.Status == 3 || data.Status == 4 {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusFail
|
||
|
|
} else {
|
||
|
|
s.Base.QueryWithdrawResp.Status = base.QueryStatusUnknown
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ret, nil
|
||
|
|
} else if s.Base.Opt == base.OPTQueryPay {
|
||
|
|
resp := s.Base.Resp.(*QueryPayResp)
|
||
|
|
ret := &pb.InnerQueryWithdrawResp{Msg: resp.Message}
|
||
|
|
if s.Base.Status == 0 && resp.Code != 0 {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusUnknown
|
||
|
|
return ret, nil
|
||
|
|
}
|
||
|
|
data := resp.Data
|
||
|
|
if data.OrderNo == "" || data.MerchantOrderNo == "" {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusUnknown
|
||
|
|
return ret, nil
|
||
|
|
}
|
||
|
|
s.Base.QueryPayResp.APIOrderID = data.OrderNo
|
||
|
|
s.Base.QueryPayResp.OrderID = data.MerchantOrderNo
|
||
|
|
s.Base.QueryPayResp.Msg = resp.Message
|
||
|
|
if s.Base.Status != 200 && s.Base.Status != 0 {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusUnknown
|
||
|
|
} else {
|
||
|
|
if data.Status == 2 {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusOrderSuccess
|
||
|
|
} else if data.Status == 3 || data.Status == 4 {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusFail
|
||
|
|
} else {
|
||
|
|
s.Base.QueryPayResp.Status = base.QueryStatusUnknown
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ret, nil
|
||
|
|
}
|
||
|
|
return nil, errors.New("unknown opt")
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackPayReq() interface{} {
|
||
|
|
r := s.Base.PayReq
|
||
|
|
userDataStr, _ := json.Marshal(UserData{UserName: r.Name, UserEmail: r.Email, UserPhone: r.Phone})
|
||
|
|
send := &PayReq{
|
||
|
|
ProjectCode: getPayInfo().ProjectCode,
|
||
|
|
MerchantOrderNo: r.OrderID,
|
||
|
|
UserUid: fmt.Sprintf("%d", r.UID),
|
||
|
|
Amount: fmt.Sprintf("%d", r.Amount),
|
||
|
|
Currency: "INR",
|
||
|
|
PaymentMode: 3,
|
||
|
|
NotifyUrl: s.Base.GetPayCallbackURL(),
|
||
|
|
ReturnUrl: values.GetFrontCallback(),
|
||
|
|
ExtraData: string(userDataStr),
|
||
|
|
Phone: r.Phone,
|
||
|
|
}
|
||
|
|
return send
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackWithdrawReq() interface{} {
|
||
|
|
r := s.Base.WithdrawReq
|
||
|
|
withdrawPayInfo := WithdrawPayInfo{
|
||
|
|
AccountType: "Bank",
|
||
|
|
AccountNumber: r.CardNo,
|
||
|
|
BeneficiaryName: r.Name,
|
||
|
|
BeneficiaryPhone: r.Phone,
|
||
|
|
BankName: r.BankName,
|
||
|
|
BankBranch: r.BankBranchName,
|
||
|
|
Ifsc: r.PayCode,
|
||
|
|
Remark: "Withdrawal payment",
|
||
|
|
}
|
||
|
|
send := &WithdrawReq{
|
||
|
|
ProjectCode: getPayInfo().ProjectCode,
|
||
|
|
MerchantOrderNo: r.OrderID,
|
||
|
|
UserUid: fmt.Sprintf("%d", r.UID),
|
||
|
|
Amount: fmt.Sprintf("%d", r.Amount),
|
||
|
|
Currency: "INR",
|
||
|
|
PaymentMode: 2,
|
||
|
|
NotifyUrl: s.Base.GetWithdrawCallbackURL(),
|
||
|
|
PayoutInfo: withdrawPayInfo,
|
||
|
|
}
|
||
|
|
return send
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackQueryWithdrawReq() interface{} {
|
||
|
|
r := s.Base.QueryWithdrawReq
|
||
|
|
send := &QueryWithdrawReq{
|
||
|
|
OrderNo: r.APIOrderID,
|
||
|
|
}
|
||
|
|
return send
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) PackQueryPayReq() interface{} {
|
||
|
|
r := s.Base.QueryPayReq
|
||
|
|
send := &QueryPayReq{
|
||
|
|
OrderNo: r.APIOrderID,
|
||
|
|
}
|
||
|
|
return send
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Sub) CheckSign(str string) bool {
|
||
|
|
log.Debug("callback:%v", str)
|
||
|
|
params := make(map[string]interface{})
|
||
|
|
err := jsoniter.UnmarshalFromString(str, ¶ms)
|
||
|
|
if err != nil {
|
||
|
|
log.Error("unmarshal err, %s", err.Error())
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
paramsStr := make(map[string]string, len(params))
|
||
|
|
for k, v := range params {
|
||
|
|
switch val := v.(type) {
|
||
|
|
case int64:
|
||
|
|
paramsStr[k] = fmt.Sprintf("%d", val)
|
||
|
|
case float64:
|
||
|
|
paramsStr[k] = fmt.Sprintf("%.f", val)
|
||
|
|
default:
|
||
|
|
paramsStr[k] = fmt.Sprintf("%+v", val)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if s.GetSign(paramsStr) != params["signature"] {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if s.Base.Opt == base.OPTPayCB {
|
||
|
|
req := s.Base.CallbackReq.(*PayCallbackReq)
|
||
|
|
s.Base.CallbackResp.OrderID = req.OrderNo
|
||
|
|
s.Base.CallbackResp.APIOrderID = req.MerchantOrderNo
|
||
|
|
s.Base.CallbackResp.Success = req.Status == 2
|
||
|
|
} else if s.Base.Opt == base.OPTWithdrawCB {
|
||
|
|
req := s.Base.CallbackReq.(*WithdrawCallbackReq)
|
||
|
|
s.Base.CallbackResp.OrderID = req.OrderNo
|
||
|
|
s.Base.CallbackResp.APIOrderID = req.MerchantOrderNo
|
||
|
|
s.Base.CallbackResp.Success = req.Status == 2
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|