ps:在读代码前一定要先阅读微信公众号发红包官方文档:
https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_3&index=4
说明:批次号是在微信商户平台配置号系统自动生成的
package server
import (
"time"
"math/rand"
"strconv"
"strings"
"github.com/alecthomas/log4go"
"fmt"
"encoding/xml"
"net/http"
"io/ioutil"
"encoding/hex"
"bytes"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"sort"
"reflect"
)
var (
_tlsConfig *tls.Config
sendCouponUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/send_coupon"
queryCouponInfo = "https://api.mch.weixin.qq.com/mmpaymkttransfers/querycouponsinfo"
CashCouponRequestStr = "CashCouponRequest"
formatDate = "20060102"
weixinPayKey="19200bbb0b4c09247ec02edce69f6ddd" //微信密钥
weixinMchId = "10000098" //微信商户号
xmlStr = "xml"
strWeixinAppId = "wx8888888888888888" //公众账号ID
strWeixinClientCertPemPath = "/home/apiclient_cert.pem" //客户端证书存放绝对路径
strWeixinClientKeyPemPath = "/home/apiclient_key.pem" //客户端私匙存放绝对路径
strWeixinRootCaPath = "/home/rootca.pem" //服务端证书存放绝对路径
)
type WeixinSendCoupon struct {
}
//公众号代金券请求实体
type CashCouponRequest struct {
CouponStockId string `xml:"coupon_stock_id"` //代金券批次号id
OpenidCount int `xml:"openid_count"` //openid记录数(目前支持num=1)
PartnerTradeNo string `xml:"partner_trade_no"` //商户此次发放凭据号(格式:商户id+日期+流水号)
Openid string `xml:"openid"` //Openid信息,用户在appid下的openid。
Appid string `xml:"appid"` //微信为发券方商户分配的公众账号ID
MchId string `xml:"mch_id"` //微信为发券方商户分配的商户号
NonceStr string `xml:"nonce_str"` //随机字符串,不长于32位
Sign string `xml:"sign"` //必填,签名
//OpUserId string `xml:"op_user_id"` //操作员
//DeviceInfo string `xml:"device_info"` //设备号
//Version string `xml:"version"` //版本号
//Type string `xml:"type"` //协议类型
}
//发送优惠券
func (*WeixinSendCoupon)SendCoupon(cashCoupon *CashCouponRequest) (*http.Response, error){
nonce_str,_ := getUUID() //随机字符串
//订单号,随机生成
r := rand.New(rand.NewSource(time.Now().UnixNano()))
tradeNo :=weixinMchId + time.Now().Format(formatDate) + strconv.FormatInt(time.Now().Unix(), 10)[4:]+strconv.Itoa(r.Intn(8999) + 1000)
cashCoupon.OpenidCount = 1
cashCoupon.Appid = strWeixinAppId
cashCoupon.MchId = weixinMchId
cashCoupon.NonceStr = nonce_str
cashCoupon.PartnerTradeNo = tradeNo
//生成签名
sign := strings.ToUpper(signature(*cashCoupon))
cashCoupon.Sign=sign
data, err := xml.MarshalIndent(cashCoupon, "", " ")
if err!=nil {
log4go.Error(err)
return nil,err
}
sendData:=strings.Replace(string(data), CashCouponRequestStr , xmlStr ,-1)
fmt.Println(sendData)
//POST数据
return securePost(sendCouponUrl, []byte(sendData))
}
//查询用户代金券信息请求的实体
type CashCouponQuery struct {
Appid string `xml:"appid"` //公众号账号id
CouponId string `xml:"coupon_id"` //代金券id
Mchid string `xml:"mch_id"` //商户号
NonceStr string `xml:"nonce_str"` //随机字符串,不长于32位
Openid string `xml:"openid"` //用户openid
StockId string `xml:"stock_id"` //批次号
Sign string `xml:"sign"` //必填,签名
//OpUserId string `xml:"op_user_id"` //操作员
//DeviceInfo string `xml:"device_info"` //设备号
//Version string `xml:"version"` //版本号
//Type string `xml:"type"` //协议类型
}
//查询代金券返回的实体
type CashCouponQueryResponse struct {
ReturnCode string `xml:"return_code"` //返回状态码,SUCCESS/FAIL
ReturnMsg string `xml:"return_msg"` //返回信息
Appid string `xml:"appid"` //微信为发券方商户分配的公众账号ID
MchId string `xml:"mch_id"` //微信为发券方商户分配的商户号
SubMchId string `xml:"sub_mch_id"` //子商户号
DeviceInfo string `xml:"device_info"` //设备号
NonceStr string `xml:"nonce_str"` //随机字符串,不长于32位
Sign string `xml:"sign"` //必填,签名
ResultCode string `xml:"result_code"` //业务结果
ErrCode string `xml:"err_code"` //错误代码描述
ErrCodeDes string `xml:"err_code_des"` //错误代码描述
CouponStockId string `xml:"coupon_stock_id"` //代金券批次id
CouponId string `xml:"coupon_id"` //代金券id
CouponValue string `xml:"coupon_value"` //代金券面额,单位为分
CouponMininum string `xml:"coupon_mininum"` //代金券使用门槛
CouponName string `xml:"coupon_name"` //代金券名称
//官方文档为int类型
CouponState string `xml:"coupon_state"` //代金券状态:SENDED-可用,USED-已实扣,EXPIRED-已过期
CouponDesc string `xml:"coupon_desc"` //代金券描述
CouponUseValue uint `xml:"coupon_use_value"` //代金券优惠金额
CouponRemainValue uint `xml:"coupon_remain_value"`
BeginTime string `xml:"begin_time"` //生效开始时间,十位时间戳
EndTime string `xml:"end_time"` //生效结束时间
SendTime string `xml:"send_time"` //发放时间
ConsumerMchId string `xml:"consumer_mch_id"` //消耗方商户id
SendSource string `xml:"send_source"` //发放来源
IsPartialUse string `xml:"is_partial_use"` //是否允许部分使用,1表示支持
}
//查询某个代金券的信息
func (*WeixinSendCoupon)SelectCoupon(couponQuery *CashCouponQuery) *CashCouponQueryResponse {
nonce_str,_ := getUUID() //随机字符串
couponQuery.NonceStr=nonce_str
sign := strings.ToUpper(signature(*couponQuery))
couponQuery.Sign=sign
data, err := xml.MarshalIndent(couponQuery, "", " ")
if err!=nil {
log4go.Error(err)
return nil
}
sendData:=strings.Replace(string(data), "CashCouponQuery" , xmlStr ,-1)
fmt.Println(sendData)
//POST数据
resp,err1:= securePost(queryCouponInfo, []byte(sendData))
if err1!=nil{
log4go.Error(err)
return nil
}
resp1:=CashCouponQueryResponse{}
defer resp.Body.Close()
if body,err2:=ioutil.ReadAll(resp.Body);err==nil {
xml.Unmarshal(body, &resp1)
return &resp1
}else{
log4go.Error(err2)
return nil
}
}
//加载微信发红包需要的证书
func getTLSConfig() (*tls.Config, error) {
if _tlsConfig != nil {
return _tlsConfig, nil
}
// load cert
cert, err := tls.LoadX509KeyPair(strWeixinClientCertPemPath, strWeixinClientKeyPemPath)
if err != nil {
fmt.Println("load wechat keys fail", err)
return nil, err
}
// load root ca
caData, err := ioutil.ReadFile(strWeixinRootCaPath)
if err != nil {
fmt.Println("read wechat ca fail", err)
return nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(caData)
_tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: pool,
}
return _tlsConfig, nil
}
//http发送请求
func securePost(url string, xmlContent []byte) (*http.Response, error) {
tlsConfig, err := getTLSConfig()
if err != nil {
return nil, err
}
tr := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: tr}
return client.Post(
url,
"text/xml",
bytes.NewBuffer(xmlContent))
}
//获取结构体字段及值的拼接值
func getFieldString(sendParamEntity interface{}) string {
m:=reflect.TypeOf(sendParamEntity)
v:=reflect.ValueOf(sendParamEntity)
var tagName string
numField:=m.NumField()
w:=make([]string,numField)
numFieldCount:=0
for i := 0; i < numField; i++ {
fieldName:=m.Field(i).Name
tags := strings.Split(string(m.Field(i).Tag), "\"")
if len(tags) > 1 {
tagName = tags[1]
} else {
tagName = m.Field(i).Name
}
fieldValue:=v.FieldByName(fieldName).Interface()
if fieldValue!="" {
s:=fmt.Sprintf("%s=%v",tagName,fieldValue)
w[numFieldCount]=s
numFieldCount++
}
}
if numFieldCount==0{
return ""
}
w = w[:numFieldCount]
sort.Strings(w)
return strings.Join(w,"&")
}
//签名算法
func signature(sendParamEntity interface{}) string {
str:=getFieldString(sendParamEntity)
if str!=""{
str=fmt.Sprintf("%s&%s=%s",str,"key",weixinPayKey)
fmt.Println(str)
md5Ctx2 := md5.New()
md5Ctx2.Write([]byte(str))
str = hex.EncodeToString(md5Ctx2.Sum(nil))
return str
}else{
return ""
}
}
//获取uuid
func getUUID() (string, error) {
uuid := make([]byte, 16)
n, err := rand.Read(uuid)
if n != len(uuid) || err != nil {
return "", err
}
uuid[8] = 0x80 // variant bits see page 5
uuid[4] = 0x40 // version 4 Pseudo Random, see page 7
return hex.EncodeToString(uuid), nil
}
3582

被折叠的 条评论
为什么被折叠?



