bilibili-backup/app/service/main/vip/dao/pay.go
2019-04-22 02:59:20 +00:00

459 lines
14 KiB
Go

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
}