Skip to content

Commit

Permalink
Feat 添加发送红包接口
Browse files Browse the repository at this point in the history
Feat 添加发送红包接口
  • Loading branch information
aimuz committed Jul 23, 2018
1 parent afd3452 commit 856ba2a
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 14 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*_test.go
*_test.go
test
test/*
1 change: 1 addition & 0 deletions common/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ const (
ErrAppIDEmpty = "appid empty"
ErrRefreshTokenEmpty = "refresh token is empty"
ErrOpenIDEmpty = "openid is empty"
ErrCertCertEmpty = "cert path is empty "
)
21 changes: 11 additions & 10 deletions pay/pay.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import (
type (
// WePay 微信支付配置类
WePay struct {
AppID string // 微信应用APPId或小程序APPId
MchID string // 商户号
PayKey string // 支付密钥
NotifyURL string // 回调地址
TradeType string // 小程序写"JSAPI",客户端写"APP"
Body string // 商品描述 必填
AppID string // 微信应用APPId或小程序APPId
MchID string // 商户号
PayKey string // 支付密钥
NotifyURL string // 回调地址
TradeType string // 小程序写"JSAPI",客户端写"APP"
Body string // 商品描述 必填
CertFile string // 微信支付平台证书
keyFile string // 微信支付平台证书秘钥
RootCaFile string // 微信支付平台根证书
}

// AppRet 返回的基本内容
Expand Down Expand Up @@ -67,7 +70,7 @@ func (m *WePay) AppPay(totalFee int) (results *AppPayRet, outTradeNo string, err
OutTradeNo: outTradeNo,
TotalFee: totalFee,
Body: m.Body,
NonceStr: utils.RandomNumString(16, 32),
NonceStr: utils.RandomString(32),
},
}
t, err := utils.Struct2Map(appUnifiedOrder)
Expand Down Expand Up @@ -98,7 +101,6 @@ func (m *WePay) AppPay(totalFee int) (results *AppPayRet, outTradeNo string, err
}

r, err := utils.Struct2Map(results)

if err != nil {
return results, outTradeNo, err
}
Expand Down Expand Up @@ -126,7 +128,7 @@ func (m *WePay) WaxPay(totalFee int, openID string) (results *WaxPayRet, outTrad
OutTradeNo: outTradeNo,
TotalFee: totalFee,
Body: m.Body,
NonceStr: utils.RandomNumString(16, 32),
NonceStr: utils.RandomString(32),
},
OpenID: openID,
}
Expand All @@ -143,7 +145,6 @@ func (m *WePay) WaxPay(totalFee int, openID string) (results *WaxPayRet, outTrad
wxaUnifiedOrder.Sign = strings.ToUpper(sign)

unifiedOrderResp, err := NewUnifiedOrder(wxaUnifiedOrder)

if err != nil {
return results, outTradeNo, err
}
Expand Down
141 changes: 140 additions & 1 deletion pay/redpack.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,148 @@
package pay

import (
"encoding/xml"
"github.com/aimuz/wechat-sdk/utils"
"github.com/aimuz/wechat-sdk/common"
"errors"
"fmt"
)

/*
redpack 红包
*/

func (WePay) SendPack() {
type (
RedPack interface {
Send(payKey, certFile, keyFile, rootCaFile string) (*RedPackResp, error)
}

SendRedPackReq struct {
XMLName xml.Name `xml:"xml"`
NonceStr string `xml:"nonce_str,omitempty" json:"nonce_str"` // NonceStr 随机字符串
Sign string `xml:"sign,omitempty" json:"sign"` // Sign 签名
MchBillNo string `xml:"mch_billno,omitempty" json:"mch_billno"` // MchBillNo 商户订单号
MchID string `xml:"mch_id,omitempty" json:"mch_id"` // MchID 商户号
WxAppID string `xml:"wxappid,omitempty" json:"wxappid"` // WxAppID 公众账号APPID
SendName string `xml:"send_name,omitempty" json:"send_name"` // SendName 商户名称,发送者名称
ReOpenID string `xml:"re_openid,omitempty" json:"re_openid"` // ReOpenID 用户OpenID
TotalAmount int64 `xml:"total_amount,omitempty" json:"total_amount"` // TotalAmount 发送金额大小
TotalNum int `xml:"total_num,omitempty" json:"total_num"` // TotalNum 发送数量
Wishing string `xml:"wishing,omitempty" json:"wishing"` // Wishing 红包祝福语
ClientIP string `xml:"client_ip,omitempty" json:"client_ip"` // ClientIP 服务器ID
ActName string `xml:"act_name,omitempty" json:"act_name"` // ActName 活动名称
Remark string `xml:"remark,omitempty" json:"remark"` // Remark 备注
SceneID string `xml:"scene_id,omitempty" json:"scene_id"` // SceneID 场景ID
RiskInfo string `xml:"risk_info,omitempty" json:"risk_info"` // RiskInfo 活动信息
ConsumeMchID string `xml:"consume_mch_id,omitempty" json:"consume_mch_id"` // ConsumeMchID 资金授权商户号
}

RedPackResp struct {
ReturnMsg string `xml:"return_msg"`
MchID string `xml:"mch_id"`
WxAppID string `xml:"wxappid"`
ReOpenid string `xml:"re_openid"`
TotalAmount string `xml:"total_amount"`
ReturnCode string `xml:"return_code"`
ResultCode string `xml:"result_code"`
ErrCode string `xml:"err_code"`
ErrCodeDes string `xml:"err_code_des"`
MchBillNo string `xml:"mch_billno"`

// 以下发送裂变红包的时候才会用到
SendTime string `xml:"send_time"`
SendListID string `xml:"send_listid"`
}
)

// SendRedPack 简单调用方法
func (m *WePay) SendRedPack(totalAmount int64, openID, sendName, wishing, actName, remark string) (string, *RedPackResp, error) {
mchBillNo := utils.GetBillNo(m.MchID, 28)
req := &SendRedPackReq{
NonceStr: utils.RandomString(32),
MchBillNo: mchBillNo,
MchID: m.MchID,
WxAppID: m.AppID,
SendName: sendName,
ReOpenID: openID,
TotalAmount: totalAmount,
TotalNum: 1,
Wishing: wishing,
ClientIP: "123.123.123.123",
ActName: actName,
Remark: remark,
}
return m.sendRedPack(req)
}

//func (m *WePay) SendRedPackByMap( map[string]interface{}) (string, *RedPackResp, error) {
//
//}

// SendRedPackByStruct 自定义发送红包参数
func (m *WePay) SendRedPackByStruct(req *SendRedPackReq) (string, *RedPackResp, error) {
return m.sendRedPack(req)
}

func (m *WePay) sendRedPack(req *SendRedPackReq) (string, *RedPackResp, error) {
resp, err := req.Send(m.PayKey, m.CertFile, m.keyFile, m.RootCaFile)
if err != nil {
return req.MchBillNo, resp, err
}

err = resp.CheckErr()
if err != nil {
return req.MchBillNo, resp, err
}

return req.MchBillNo, resp, nil
}

// SendRedPack 发送普通红包
func (m *SendRedPackReq) Send(payKey string, certFile, keyFile, rootCaFile string) (*RedPackResp, error) {

tMap, err := utils.Struct2Map(m)
if err != nil {
return nil, err
}

fmt.Println(tMap)

m.Sign, err = utils.GenWeChatPaySign(tMap, payKey)
if err != nil {
return nil, err
}
fmt.Println(m.Sign)

data, err := xml.Marshal(m)
if err != nil {
return nil, err
}

request, err := utils.NewCertRequest(certFile, keyFile, rootCaFile)
if err != nil {
return nil, err
}

body, err := request.NewRequest("POST", common.SendRedPackURL, data)
if err != nil {
return nil, err
}

resp := new(RedPackResp)
err = xml.Unmarshal(body, resp)
if err != nil {
return nil, err
}

return resp, nil
}

// CheckErr 检查微信是否返回错误信息
func (m *RedPackResp) CheckErr() error {
if m.ResultCode != "SUCCESS" || m.ReturnCode != "SUCCESS" {
return errors.New(fmt.Sprintf("[%s,%s]%s", m.ResultCode, m.ErrCode, m.ErrCodeDes))
}

return nil
}
131 changes: 129 additions & 2 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import (
"strconv"
"strings"
"time"
"crypto/tls"
"crypto/x509"
"errors"
"github.com/aimuz/wechat-sdk/common"
)

// NewRequest 请求包装
Expand All @@ -38,10 +42,84 @@ func NewRequest(method, url string, data []byte) (body []byte, err error) {
}

body, err = ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return body, err
}

return body, err
}

type Request struct {
Client *http.Client
}

// NewCertRequest 双向安全证书请求
func NewCertRequest(certFile, keyFile, rootCaFile string) (*Request, error) {

if certFile == "" || keyFile == "" || rootCaFile == "" {
return nil, errors.New(common.ErrCertCertEmpty)
}

cert, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}

key, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}

rootCa, err := ioutil.ReadFile(rootCaFile)
if err != nil {
return nil, err
}

tlsCert, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}

certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(rootCa)
if !ok {
return nil, errors.New("failed to parse root certificate")
}

conf := &tls.Config{
Certificates: []tls.Certificate{tlsCert},
RootCAs: certPool,
}
trans := &http.Transport{
TLSClientConfig: conf,
}
client := &http.Client{
Transport: trans,
}

return &Request{Client: client}, nil
}
func (m *Request) NewRequest(method, url string, data []byte) (body []byte, err error) {
if method == "GET" {
url = fmt.Sprint(url, "?", string(data))
data = nil
}

req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
if err != nil {
return body, err
}
resp, err := m.Client.Do(req)
if err != nil {
return body, err
}

body, err = ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return body, err
}

return body, err
}
Expand All @@ -65,7 +143,6 @@ func Struct2Map(r interface{}) (s map[string]string, err error) {
result[k] = v2
break
case int8, uint8, int, uint, int32, uint32, int64, uint64:
fmt.Println("k2=", v2)
result[k] = fmt.Sprint(v2)
break
case float32, float64:
Expand Down Expand Up @@ -114,19 +191,69 @@ func GetTradeNO(prefix string) string {
return prefix + strTime + RandomNumString(100000, 999999)
}

// GetBillNo 生成订单号,指定位数
func GetBillNo(prefix string, length int) string {
now := time.Now()
strTime := fmt.Sprintf("%04d%02d%02d%02d%02d%02d", now.Year(), now.Month(), now.Day(), now.Hour(),
now.Minute(),
now.Second())

str := fmt.Sprint(prefix, strTime)
if a := length - len(str); a > 0 {
str = str + RandomLenNum(a)
}
fmt.Println(str)
return str[:length]
}

// RandomNum 随机数
func RandomNum(min int64, max int64) int64 {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
num := min + r.Int63n(max-min+1)
return num
}

// RandomNumString 随机字符串
// RandomNumString 随机数字字符串
func RandomNumString(min int64, max int64) string {
num := RandomNum(min, max)
return strconv.FormatInt(num, 10)
}

// RandomLenNum 指定随机数字字符串
func RandomLenNum(length int) string {
str := ""
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < length; i++ {
str += strconv.Itoa(r.Intn(10))
}
return str
}

const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

// RandomString 随机字符串
func RandomString(length int) string {
b := make([]byte, length)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i, cache, remain := length-1, r.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = r.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}

// PKCS7Padding Aes 加密 PKCS7填充
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
Expand Down

0 comments on commit 856ba2a

Please sign in to comment.