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.
458 lines
14 KiB
458 lines
14 KiB
package dao |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"crypto/md5" |
|
"encoding/hex" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"net/url" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"go-common/app/service/main/vip/model" |
|
"go-common/library/ecode" |
|
"go-common/library/log" |
|
|
|
"github.com/pkg/errors" |
|
) |
|
|
|
const ( |
|
//pay url |
|
_addPayOrder = "/api/add.pay.order" |
|
_paySDK = "/api/pay.sdk" |
|
_quickPayToken = "/api/quick.pay.token" |
|
_quickPayDo = "/api/quick.pay.do" |
|
_payQrcode = "/api/pay.qrcode" |
|
_payCashier = "/api/pay.cashier" |
|
_payIapAccess = "/api/add.iap.access" |
|
_payBanks = "/api/pay.banks" |
|
_payWallet = "/api/v2/user/account" |
|
_payRescission = "/payplatform/pay/rescission" |
|
_payClose = "/payplatform/pay/cancel" |
|
_createPayQrCode = "/payplatform/qrcode/createPayQrCode" |
|
|
|
_payRemark = "购买大会员服务%d个月" |
|
_merchantID = 17 |
|
_productMonthID = 60 |
|
_productQuarterID = 61 |
|
_productYearID = 62 |
|
_quarterMonths = 3 |
|
_yearMonths = 12 |
|
_iapQuantity = "1" |
|
_minRead = 1024 * 64 |
|
|
|
_retry = 3 |
|
_defversion = "1.0" |
|
_defsigntype = "MD5" |
|
) |
|
|
|
//BasicResp pay response. |
|
type BasicResp struct { |
|
Code int `json:"code"` |
|
Message string `json:"messge"` |
|
Data *interface{} `json:"data"` |
|
} |
|
|
|
// merchantProduct get Product id by months |
|
func (d *Dao) productID(months int16) (id int8) { |
|
id = _productMonthID |
|
if months >= _quarterMonths && months < _yearMonths { |
|
id = _productQuarterID |
|
} else if months >= _yearMonths { |
|
id = _productYearID |
|
} |
|
return |
|
} |
|
|
|
//PayWallet . |
|
func (d *Dao) PayWallet(c context.Context, mid int64, ip string, data *model.PayAccountResp) (err error) { |
|
val := url.Values{} |
|
val.Add("mid", fmt.Sprintf("%d", mid)) |
|
return d.dopay(c, _payWallet, ip, val, data, d.client.Post) |
|
} |
|
|
|
//PayBanks . |
|
func (d *Dao) PayBanks(c context.Context, ip string, data []*model.PayBankResp) (err error) { |
|
val := url.Values{} |
|
return d.dopay(c, _payBanks, ip, val, data, d.client.Get) |
|
} |
|
|
|
//AddPayOrder add pay order. |
|
func (d *Dao) AddPayOrder(c context.Context, ip string, o *model.PayOrder, data *model.AddPayOrderResp) (err error) { |
|
val := url.Values{} |
|
val.Add("mid", fmt.Sprintf("%d", o.Mid)) |
|
val.Add("to_mid", fmt.Sprintf("%d", o.ToMid)) |
|
val.Add("money", fmt.Sprintf("%f", o.Money)) |
|
val.Add("remark", fmt.Sprintf(_payRemark, o.BuyMonths)) |
|
val.Add("subject", fmt.Sprintf(_payRemark, o.BuyMonths)) |
|
val.Add("out_trade_no", o.OrderNo) |
|
val.Add("notify_url", d.c.Property.NotifyURL) |
|
val.Add("merchant_id", fmt.Sprintf("%d", _merchantID)) |
|
val.Add("merchant_product_id", fmt.Sprintf("%d", d.productID(o.BuyMonths))) |
|
var platform string |
|
switch o.Platform { |
|
case model.DeviceIOS: |
|
platform = "1" |
|
case model.DeviceANDROID: |
|
platform = "2" |
|
default: |
|
platform = "3" |
|
} |
|
val.Add("platform_type", platform) |
|
return d.dopay(c, _addPayOrder, ip, val, data, d.client.Post) |
|
} |
|
|
|
// PaySDK moblie pay sdk. |
|
func (d *Dao) PaySDK(c context.Context, ip string, o *model.PayOrder, data *model.APIPayOrderResp, payCode string) (err error) { |
|
val := url.Values{} |
|
val.Add("money", fmt.Sprintf("%f", o.Money)) |
|
val.Add("pay_order_no", o.OrderNo) |
|
val.Add("pay_type", payCode) |
|
return d.dopay(c, _paySDK, ip, val, data, d.client.Post) |
|
} |
|
|
|
// PayQrcode pay qrcode. |
|
func (d *Dao) PayQrcode(c context.Context, ip string, o *model.PayOrder, data *model.APIPayOrderResp, payCode string) (err error) { |
|
val := url.Values{} |
|
val.Add("money", fmt.Sprintf("%f", o.Money)) |
|
val.Add("pay_order_no", o.OrderNo) |
|
val.Add("pay_type", payCode) |
|
|
|
return d.dopay(c, _payQrcode, ip, val, data, d.client.Post) |
|
} |
|
|
|
// QuickPayToken quick pay token. |
|
func (d *Dao) QuickPayToken(c context.Context, ip string, accessKey string, cookie []*http.Cookie, data *model.QucikPayResp) (err error) { |
|
params := make(map[string]string) |
|
params["access_key"] = accessKey |
|
|
|
//return d.dopay(c, _quickPayToken, ip, val, cookie, data, d.client.Post) |
|
return d.doPaySend(c, d.c.Property.PayURL, _quickPayToken, ip, cookie, nil, http.MethodPost, params, data, d.PaySign) |
|
} |
|
|
|
// DoQuickPay do quick pay. |
|
func (d *Dao) DoQuickPay(c context.Context, ip string, token string, thirdTradeNo string, data *model.PayRetResp) (err error) { |
|
val := url.Values{} |
|
val.Add("token", token) |
|
val.Add("pay_order_no", thirdTradeNo) |
|
return d.dopay(c, _quickPayDo, ip, val, data, d.client.Post) |
|
} |
|
|
|
// PayCashier pay cashier. |
|
func (d *Dao) PayCashier(c context.Context, ip string, o *model.PayOrder, data *model.APIPayOrderResp, payCode string, bankCode string) (err error) { |
|
val := url.Values{} |
|
val.Add("pay_order_no", o.ThirdTradeNo) |
|
val.Add("money", fmt.Sprintf("%f", o.Money)) |
|
val.Add("pay_type", payCode) |
|
val.Add("bank_code", bankCode) |
|
return d.dopay(c, _payCashier, ip, val, data, d.client.Post) |
|
} |
|
|
|
// PayIapAccess pay iap access. |
|
func (d *Dao) PayIapAccess(c context.Context, ip string, o *model.PayOrder, data *model.APIPayOrderResp, productID string) (err error) { |
|
val := url.Values{} |
|
val.Add("mid", fmt.Sprintf("%d", o.Mid)) |
|
val.Add("product_id", productID) |
|
val.Add("quantity", _iapQuantity) |
|
val.Add("money", fmt.Sprintf("%f", o.Money)) |
|
val.Add("remark", fmt.Sprintf(_payRemark, o.BuyMonths)) |
|
val.Add("pay_order_no", o.ThirdTradeNo) |
|
val.Add("merchant_id", fmt.Sprintf("%d", _merchantID)) |
|
val.Add("merchant_product_id", fmt.Sprintf("%d", d.productID(o.BuyMonths))) |
|
return d.dopay(c, _payIapAccess, ip, val, data, d.client.Post) |
|
} |
|
|
|
//PayClose pay close. |
|
func (d *Dao) PayClose(c context.Context, orderNO string, ip string) (data *model.APIPayCancelResp, err error) { |
|
params := make(map[string]string) |
|
params["customerId"] = strconv.FormatInt(d.c.PayConf.CustomerID, 10) |
|
params["orderId"] = orderNO |
|
params["timestamp"] = fmt.Sprintf("%d", time.Now().Unix()*1000) |
|
params["traceId"] = model.UUID4() |
|
params["version"] = _defversion |
|
params["signType"] = _defsigntype |
|
sign := d.PaySign(params, d.c.PayConf.Token) |
|
params["sign"] = sign |
|
resp := new( |
|
struct { |
|
Code int64 `json:"errno"` |
|
Data *model.APIPayCancelResp `json:"data"` |
|
}) |
|
header := make(map[string]string) |
|
header["Content-Type"] = "application/json" |
|
marshal, _ := json.Marshal(params) |
|
if err = d.doSend(c, d.payCloseURL, "127.0.0.1", header, marshal, resp); err != nil { |
|
err = errors.Wrapf(err, "Call pay service(%s)", d.payCloseURL+"?"+string(marshal)) |
|
return |
|
} |
|
if resp.Code != 0 { |
|
err = fmt.Errorf("Call pay service(%s) error, response code is not 0, resp:%v", d.payCloseURL+"?"+string(marshal), resp) |
|
return |
|
} |
|
data = resp.Data |
|
log.Info("Call pay service(%s) successful, resp(%v)", d.payCloseURL+"?"+string(marshal), data) |
|
return |
|
} |
|
|
|
//PayQrCode pay qr code. |
|
func (d *Dao) PayQrCode(c context.Context, mid int64, orderID string, req map[string]interface{}) (data *model.PayQrCode, err error) { |
|
payParam, _ := json.Marshal(req) |
|
params := make(map[string]string) |
|
params["customerId"] = strconv.FormatInt(d.c.PayConf.CustomerID, 10) |
|
params["uid"] = fmt.Sprintf("%d", mid) |
|
params["orderId"] = orderID |
|
params["payParam"] = string(payParam) |
|
params["timestamp"] = fmt.Sprintf("%d", time.Now().Unix()*1000) |
|
params["traceId"] = model.UUID4() |
|
params["version"] = d.c.PayConf.Version |
|
params["signType"] = d.c.PayConf.SignType |
|
sign := d.PaySign(params, d.c.PayConf.Token) |
|
params["sign"] = sign |
|
resp := new( |
|
struct { |
|
Code int64 `json:"errno"` |
|
Data *model.PayQrCode `json:"data"` |
|
}) |
|
url := d.c.Property.PayCoURL + _createPayQrCode |
|
header := make(map[string]string) |
|
header["Content-Type"] = "application/json" |
|
marshal, _ := json.Marshal(params) |
|
if err = d.doSend(c, url, "127.0.0.1", header, marshal, resp); err != nil { |
|
err = errors.Wrapf(err, "Call pay service(%s)", url+"?"+string(marshal)) |
|
return |
|
} |
|
if resp.Code != 0 { |
|
err = fmt.Errorf("Call pay service(%s) error, response code is not 0, resp:%v", url+"?"+string(marshal), resp) |
|
return |
|
} |
|
data = resp.Data |
|
log.Info("Call pay service(%s) successful, resp(%v)", url+"?"+string(marshal), data) |
|
return |
|
} |
|
|
|
func (d *Dao) doSend(c context.Context, url, IP string, header map[string]string, marshal []byte, data interface{}) (err error) { |
|
var ( |
|
req *http.Request |
|
client = new(http.Client) |
|
resp *http.Response |
|
bs []byte |
|
) |
|
if req, err = http.NewRequest(http.MethodPost, url, strings.NewReader(string(marshal))); err != nil { |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
for k, v := range header { |
|
req.Header.Add(k, v) |
|
} |
|
if resp, err = client.Do(req); err != nil { |
|
err = errors.Wrapf(err, "Call pay service(%s)", url+"?"+string(marshal)) |
|
return |
|
} |
|
defer resp.Body.Close() |
|
|
|
defer func() { |
|
log.Info("call url:%v params:(%v) result:(%+v) header:(%+v)", url, string(marshal), data, header) |
|
}() |
|
if resp.StatusCode >= http.StatusBadRequest { |
|
err = errors.Errorf("incorrect http status:%d host:%s, url:%s", resp.StatusCode, req.URL.Host, req.URL.String()) |
|
return |
|
} |
|
if bs, err = readAll(resp.Body, _minRead); err != nil { |
|
err = errors.Wrapf(err, "host:%s, url:%s", req.URL.Host, req.URL.String()) |
|
return |
|
} |
|
if err = json.Unmarshal(bs, data); err != nil { |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
return |
|
} |
|
|
|
func (d *Dao) dopay(c context.Context, |
|
urlPath string, ip string, params url.Values, data interface{}, |
|
fn func(c context.Context, uri string, ip string, params url.Values, r interface{}) error, |
|
) (err error) { |
|
var ( |
|
resp = &BasicResp{} |
|
urlAddr string |
|
) |
|
urlAddr = d.c.Property.PayURL + urlPath |
|
err = fn(c, urlAddr, ip, params, resp) |
|
if err != nil { |
|
err = errors.Wrapf(err, "Call pay service(%s)", urlAddr+"?"+params.Encode()) |
|
return |
|
} |
|
if resp.Code != 0 { |
|
err = fmt.Errorf("Call pay service(%s) error, response code is not 0, resp:%v,%v", urlAddr+"?"+params.Encode(), resp, data) |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
data = resp.Data |
|
log.Info("Call pay service(%s) successful, resp: %v", urlAddr+"?"+params.Encode(), resp) |
|
return |
|
} |
|
|
|
// PayRecission call pay refund api. |
|
func (d *Dao) PayRecission(c context.Context, params map[string]interface{}, clietIP string) (err error) { |
|
rel := new(struct { |
|
Code int64 `json:"errno"` |
|
Data int64 `json:"msg"` |
|
}) |
|
paramsObj := make(map[string]string) |
|
for k, v := range params { |
|
paramsObj[k] = fmt.Sprintf("%v", v) |
|
} |
|
header := make(map[string]string) |
|
header["Content-Type"] = "application/json" |
|
|
|
defer func() { |
|
log.Info("url:%v params:%+v return:%+v error(%+v)", d.c.Property.PayURL+_payRescission, paramsObj, rel, err) |
|
}() |
|
|
|
success := false |
|
for i := 0; i < _retry; i++ { |
|
|
|
if err = d.doPaySend(c, d.c.Property.PayURL, _payRescission, clietIP, nil, header, http.MethodPost, paramsObj, rel, d.PaySignNotDel); err != nil { |
|
log.Error(" dopaysend(url:%v,params:%+v) return(%+v) ", d.c.Property.PayURL+_payRescission, paramsObj, rel) |
|
continue |
|
} |
|
if rel.Code == int64(ecode.OK.Code()) { |
|
success = true |
|
break |
|
} |
|
} |
|
if !success { |
|
err = ecode.VipRescissionErr |
|
} |
|
return |
|
} |
|
|
|
// PaySign pay sign. |
|
func (d *Dao) PaySign(params map[string]string, token string) (sign string) { |
|
delete(params, "payChannelId") |
|
delete(params, "payChannel") |
|
delete(params, "accessKey") |
|
delete(params, "sdkVersion") |
|
delete(params, "openId") |
|
delete(params, "sign") |
|
delete(params, "device") |
|
|
|
tmp := d.sortParamsKey(params) |
|
|
|
var b bytes.Buffer |
|
b.WriteString(tmp) |
|
b.WriteString(fmt.Sprintf("&token=%s", token)) |
|
log.Info("pay sign params:(%s) \n", b.String()) |
|
mh := md5.Sum(b.Bytes()) |
|
// query |
|
sign = hex.EncodeToString(mh[:]) |
|
log.Info("pay sign (%v)", sign) |
|
return |
|
} |
|
|
|
//PaySignNotDel pay sign not del. |
|
func (d *Dao) PaySignNotDel(params map[string]string, token string) (sign string) { |
|
|
|
tmp := d.sortParamsKey(params) |
|
|
|
var b bytes.Buffer |
|
b.WriteString(tmp) |
|
b.WriteString(fmt.Sprintf("&token=%s", token)) |
|
log.Info("pay sign params:(%s) \n", b.String()) |
|
mh := md5.Sum(b.Bytes()) |
|
// query |
|
sign = hex.EncodeToString(mh[:]) |
|
log.Info("pay sign (%v)", sign) |
|
return |
|
} |
|
|
|
func (d *Dao) sortParamsKey(v map[string]string) string { |
|
if v == nil { |
|
return "" |
|
} |
|
|
|
var buf bytes.Buffer |
|
keys := make([]string, 0, len(v)) |
|
for k := range v { |
|
keys = append(keys, k) |
|
} |
|
sort.Strings(keys) |
|
for _, k := range keys { |
|
vs := v[k] |
|
prefix := k + "=" |
|
if buf.Len() > 0 { |
|
buf.WriteByte('&') |
|
} |
|
buf.WriteString(prefix) |
|
buf.WriteString(vs) |
|
} |
|
return buf.String() |
|
} |
|
|
|
func (d *Dao) doPaySend(c context.Context, basePath, path, IP string, cookie []*http.Cookie, header map[string]string, method string, params map[string]string, data interface{}, signFn func(params map[string]string, token string) string) (err error) { |
|
var ( |
|
req *http.Request |
|
client = new(http.Client) |
|
resp *http.Response |
|
bs []byte |
|
) |
|
url := basePath + path |
|
sign := signFn(params, d.c.PayConf.Token) |
|
params["sign"] = sign |
|
marshal, _ := json.Marshal(params) |
|
if req, err = http.NewRequest(method, url, strings.NewReader(string(marshal))); err != nil { |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
for _, v := range cookie { |
|
req.AddCookie(v) |
|
} |
|
for k, v := range header { |
|
req.Header.Add(k, v) |
|
} |
|
if resp, err = client.Do(req); err != nil { |
|
log.Error("call url:%v params:(%+v)", basePath+path, params) |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
defer resp.Body.Close() |
|
if resp.StatusCode >= http.StatusBadRequest { |
|
err = errors.Errorf("incorrect http status:%d host:%s, url:%s", resp.StatusCode, req.URL.Host, req.URL.String()) |
|
return |
|
} |
|
if bs, err = readAll(resp.Body, _minRead); err != nil { |
|
err = errors.Wrapf(err, "host:%s, url:%s", req.URL.Host, req.URL.String()) |
|
return |
|
} |
|
if err = json.Unmarshal(bs, data); err != nil { |
|
err = errors.WithStack(err) |
|
return |
|
} |
|
log.Info("call url:%v params:%+v result:%+v", url, params, data) |
|
return |
|
} |
|
|
|
func readAll(r io.Reader, capacity int64) (b []byte, err error) { |
|
buf := bytes.NewBuffer(make([]byte, 0, capacity)) |
|
// If the buffer overflows, we will get bytes.ErrTooLarge. |
|
// Return that as an error. Any other panic remains. |
|
defer func() { |
|
e := recover() |
|
if e == nil { |
|
return |
|
} |
|
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { |
|
err = panicErr |
|
} else { |
|
panic(e) |
|
} |
|
}() |
|
_, err = buf.ReadFrom(r) |
|
return buf.Bytes(), err |
|
}
|
|
|