1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-23 13:42:25 +08:00

Compare commits

..

4 Commits

Author SHA1 Message Date
febelery
df5309e9cb Merge bd5191b206 into 9e810be88a 2023-09-22 01:45:43 +00:00
ross
bd5191b206 chore: golang ci lint 2023-09-22 09:42:35 +08:00
ross
f79259e988 feat: 添加发放红包接口 2023-09-21 19:45:35 +08:00
ross
ea546fa205 feat: 添加发放红包接口 2023-09-21 19:06:51 +08:00
6 changed files with 161 additions and 71 deletions

View File

@@ -15,8 +15,6 @@ const (
checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
checkSessionURL = "https://api.weixin.qq.com/wxa/checksession?access_token=%s&openid=%s&signature=%s&sig_method=hmac_sha256"
)
// Auth 登录/用户信息
@@ -35,7 +33,7 @@ type ResCode2Session struct {
OpenID string `json:"openid"` // 用户唯一标识
SessionKey string `json:"session_key"` // 会话密钥
UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回
UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符在满足UnionID下发条件的情况下会返回
}
// RspCheckEncryptedData .
@@ -72,12 +70,12 @@ func (auth *Auth) GetPaidUnionID() {
// TODO
}
// CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近 3 天生成的加密数据
// CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedData(encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
return auth.CheckEncryptedDataContext(context2.Background(), encryptedMsgHash)
}
// CheckEncryptedDataContext .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近 3 天生成的加密数据
// CheckEncryptedDataContext .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
var response []byte
var (
@@ -87,7 +85,7 @@ func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHa
return
}
// 由于 GetPhoneNumberContext 需要传入 JSON所以 HTTPPostContext 入参改为 []byte
// 由于GetPhoneNumberContext需要传入JSON所以HTTPPostContext入参改为[]byte
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), []byte("encrypted_msg_hash="+encryptedMsgHash), nil); err != nil {
return
}
@@ -115,62 +113,38 @@ type PhoneInfo struct {
} `json:"watermark"` // 数据水印
}
// GetPhoneNumberContext 小程序通过 code 获取用户手机号
func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (result *GetPhoneNumberResponse, err error) {
var accessToken string
if accessToken, err = auth.GetAccessToken(); err != nil {
// GetPhoneNumberContext 小程序通过code获取用户手机号
func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*GetPhoneNumberResponse, error) {
var response []byte
var (
at string
err error
)
if at, err = auth.GetAccessToken(); err != nil {
return nil, err
}
bodyBytes, err := json.Marshal(map[string]interface{}{
body := map[string]interface{}{
"code": code,
})
}
bodyBytes, err := json.Marshal(body)
if err != nil {
return nil, err
}
var (
header = map[string]string{"Content-Type": "application/json;charset=utf-8"}
response []byte
)
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(getPhoneNumber, accessToken), bodyBytes, header); err != nil {
header := map[string]string{"Content-Type": "application/json;charset=utf-8"}
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(getPhoneNumber, at), bodyBytes, header); err != nil {
return nil, err
}
err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber")
return
var result GetPhoneNumberResponse
if err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber"); err != nil {
return nil, err
}
return &result, nil
}
// GetPhoneNumber 小程序通过 code 获取用户手机号
// GetPhoneNumber 小程序通过code获取用户手机号
func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
return auth.GetPhoneNumberContext(context2.Background(), code)
}
// // CheckSession 检验登录态是否过期。
// func (auth *Auth) CheckSession(sessionKey, openID string) (result *CheckSessionResponse, err error) {
// return auth.CheckSessionContext(context2.Background(), sessionKey, openID)
// }
//
// // CheckSessionContext 检验登录态是否过期。
// func (auth *Auth) CheckSessionContext(ctx context2.Context, sessionKey, openID string) (result *CheckSessionResponse, err error) {
// var accessToken string
// if accessToken, err = auth.GetAccessToken(); err != nil {
// return nil, err
// }
// var (
// response []byte
// signature string = sessionKey
// )
// if response, err = util.HTTPGetContext(ctx, fmt.Sprintf(checkSessionURL, accessToken, openID, signature)); err != nil {
// return nil, err
// }
//
// err = util.DecodeWithError(response, &result, "CheckSessionContext")
// return
// }
//
// // CheckSessionResponse 检验登录态是否过期。
// type CheckSessionResponse struct {
// util.CommonError
// }

View File

@@ -117,24 +117,6 @@ const (
// queryPublishGoods 查询批量发布道具任务状态
queryPublishGoods = "/xpay/query_publish_goods"
// queryBizBalance 查询商家账户里的可提现余额
queryBizBalance = "/xpay/query_biz_balance"
// queryTransferAccount 查询广告金充值账户
queryTransferAccount = "/xpay/query_transfer_account"
// queryAdverFunds 查询广告金发放记录
queryAdverFunds = "/xpay/query_adver_funds"
// createFundsBill 充值广告金
createFundsBill = "/xpay/create_funds_bill"
// bindTransferAccount 绑定广告金充值账户
bindTransferAccount = "/xpay/bind_transfer_accout"
// defaultUnifiedOrderURL default unified order url
defaultUnifiedOrderURL = "requestVirtualPayment"
)
const (

View File

@@ -145,8 +145,6 @@ type OrderItem struct {
WxOrderID string `json:"wx_order_id"` // 微信内部单号
ChannelOrderID string `json:"channel_order_id"` // 渠道订单号,为用户微信支付详情页面上的商户单号
WxPayOrderID string `json:"wxpay_order_id"` // 微信支付交易单号,为用户微信支付详情页面上的交易单号
SettTime int64 `json:"sett_time"` // 结算时间unix 秒级时间戳,结算时间的秒级时间戳,大于 0 表示结算成功
SettState uint `json:"sett_state"` // 结算状态 0-未开始结算 1-结算中 2-结算成功
}
// QueryOrderResponse 查询创建的订单(现金单,非代币单)响应参数

View File

@@ -479,7 +479,6 @@ func (s *VirtualPayment) requestAddress(params URLParams) (url string, err error
case queryUserBalance:
case currencyPay:
case cancelCurrencyPay:
case defaultUnifiedOrderURL:
if params.PaySign, params.Signature, err = s.PaySignature(params.Path, params.Content); err != nil {
return
}

View File

@@ -4,6 +4,7 @@ import (
"github.com/silenceper/wechat/v2/pay/config"
"github.com/silenceper/wechat/v2/pay/notify"
"github.com/silenceper/wechat/v2/pay/order"
"github.com/silenceper/wechat/v2/pay/redpacket"
"github.com/silenceper/wechat/v2/pay/refund"
"github.com/silenceper/wechat/v2/pay/transfer"
)
@@ -37,3 +38,8 @@ func (pay *Pay) GetRefund() *refund.Refund {
func (pay *Pay) GetTransfer() *transfer.Transfer {
return transfer.NewTransfer(pay.cfg)
}
// GetRedpacket 红包
func (pay *Pay) GetRedpacket() *redpacket.Redpacket {
return redpacket.NewRedpacket(pay.cfg)
}

131
pay/redpacket/redpacket.go Normal file
View File

@@ -0,0 +1,131 @@
package redpacket
import (
"encoding/xml"
"fmt"
"strconv"
"github.com/silenceper/wechat/v2/pay/config"
"github.com/silenceper/wechat/v2/util"
)
// redpacketGateway 发放红包接口
// https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
var redpacketGateway = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"
// Redpacket struct extends context
type Redpacket struct {
*config.Config
}
// NewRedpacket return an instance of Redpacket package
func NewRedpacket(cfg *config.Config) *Redpacket {
return &Redpacket{cfg}
}
// Params 调用参数
type Params struct {
MchBillno string // 商户订单号
SendName string // 商户名称
ReOpenID string
TotalAmount int
TotalNum int
Wishing string
ClientIP string
ActName string
Remark string
RootCa string // ca证书
}
// request 接口请求参数
type request struct {
NonceStr string `xml:"nonce_str"`
Sign string `xml:"sign"`
MchID string `xml:"mch_id"`
MchBillno string `xml:"mch_billno"`
Wxappid string `xml:"wxappid"`
SendName string `xml:"send_name"`
ReOpenID string `xml:"re_openid"`
TotalAmount int `xml:"total_amount"`
TotalNum int `xml:"total_num"`
Wishing string `xml:"wishing"`
ClientIP string `xml:"client_ip"`
ActName string `xml:"act_name"`
Remark string `xml:"remark"`
}
// Response 接口返回
type Response struct {
ReturnCode string `xml:"return_code"`
ReturnMsg string `xml:"return_msg"`
ResultCode string `xml:"result_code,omitempty"`
ErrCode string `xml:"err_code,omitempty"`
ErrCodeDes string `xml:"err_code_des,omitempty"`
MchBillno string `xml:"mch_billno,omitempty"`
MchID string `xml:"mch_id,omitempty"`
Wxappid string `xml:"wxappid"`
ReOpenID string `xml:"re_openid"`
TotalAmount int `xml:"total_amount"`
SendListid string `xml:"send_listid"`
}
// SendRedpacket 发放红包
func (redpacket *Redpacket) SendRedpacket(p *Params) (rsp *Response, err error) {
nonceStr := util.RandomStr(32)
param := make(map[string]string)
param["nonce_str"] = nonceStr
param["mch_id"] = redpacket.MchID
param["wxappid"] = redpacket.AppID
param["mch_billno"] = p.MchBillno
param["send_name"] = p.SendName
param["re_openid"] = p.ReOpenID
param["total_amount"] = strconv.Itoa(p.TotalAmount)
param["total_num"] = strconv.Itoa(p.TotalNum)
param["wishing"] = p.Wishing
param["client_ip"] = p.ClientIP
param["act_name"] = p.ActName
param["remark"] = p.Remark
//param["scene_id"] = "PRODUCT_2"
sign, err := util.ParamSign(param, redpacket.Key)
if err != nil {
return
}
req := request{
NonceStr: nonceStr,
Sign: sign,
MchID: redpacket.MchID,
Wxappid: redpacket.AppID,
MchBillno: p.MchBillno,
SendName: p.SendName,
ReOpenID: p.ReOpenID,
TotalAmount: p.TotalAmount,
TotalNum: p.TotalNum,
Wishing: p.Wishing,
ClientIP: p.ClientIP,
ActName: p.ActName,
Remark: p.Remark,
}
rawRet, err := util.PostXMLWithTLS(redpacketGateway, req, p.RootCa, redpacket.MchID)
if err != nil {
return
}
err = xml.Unmarshal(rawRet, &rsp)
if err != nil {
return
}
if rsp.ReturnCode == "SUCCESS" {
if rsp.ResultCode == "SUCCESS" {
err = nil
return
}
err = fmt.Errorf("send redpacket error, errcode=%s,errmsg=%s", rsp.ErrCode, rsp.ErrCodeDes)
return
}
err = fmt.Errorf("[msg : xmlUnmarshalError] [rawReturn : %s] [sign : %s]", string(rawRet), sign)
return
}