golang实现微信公众号发送代金券及获取代金券的功能

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
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值