Просмотр исходного кода

微信支付写好请求,已完成,后续还需要修改细节

Jerry 7 лет назад
Родитель
Сommit
34b5a352ff
5 измененных файлов с 196 добавлено и 111 удалено
  1. 14 6
      constant.go
  2. 2 4
      util.go
  3. 126 80
      wechat_params.go
  4. 38 5
      wechat_pay.go
  5. 16 16
      wechat_pay_test.go

+ 14 - 6
constant.go

@@ -1,10 +1,18 @@
 package go_pay
 
 const (
-	WechatPayUrl         = "https://api.mch.weixin.qq.com/pay/unifiedorder"
-	WechatPayType_Mini   = "JSAPI"
-	WechatPayType_JsApi  = "JSAPI"
-	WechatPayType_App    = "APP"
-	WechatPayType_H5     = "MWEB"
-	WechatPayType_Native = "NATIVE"
+	//URL
+	WX_PayUrl        = "https://api.mch.weixin.qq.com/pay/unifiedorder"
+	WX_PayUrl_SanBox = "https://api.mch.weixin.qq.com/pay/unifiedorder"
+
+	//支付类型
+	WX_PayType_Mini   = "JSAPI"
+	WX_PayType_JsApi  = "JSAPI"
+	WX_PayType_App    = "APP"
+	WX_PayType_H5     = "MWEB"
+	WX_PayType_Native = "NATIVE"
+
+	//签名方式
+	WX_SignType_MD5         = "MD5"
+	WX_SignType_HMAC_SHA256 = "HMAC-SHA256"
 )

+ 2 - 4
util.go

@@ -5,10 +5,8 @@ import (
 	"time"
 )
 
-/*
-	获取随机字符串
-	length:字符串长度
-*/
+//获取随机字符串
+//    length:字符串长度
 func GetRandomString(length int) string {
 	str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
 	bytes := []byte(str)

+ 126 - 80
wechat_params.go

@@ -2,8 +2,11 @@ package go_pay
 
 import (
 	"bytes"
+	"crypto/hmac"
 	"crypto/md5"
+	"crypto/sha256"
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"sort"
 	"strconv"
@@ -31,18 +34,32 @@ func (w WXReq) Remove(key string) {
 	delete(w, key)
 }
 
-/*
-	NonceStr: 随机字符串,长度要求在32位以内(如不写,go-pay将为你随机生成)
-	Body: 商品简单描述
-	OutTradeNo: 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
-	TotalFee: 订单总金额,单位为分
-	SpbillCreateIp: 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
-	NotifyUrl: 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数
-	TradeType: 交易类型:(JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式)
-*/
-type WechatParamsNecessary struct {
-	Appid          string `xml:"appid"`
-	MchId          string `xml:"mch_id"`
+// 必选.
+//    Appid: 微信支付分配的公众账号ID(企业号corpid即为此appId).
+//    MchId: 微信支付分配的商户号.
+//    NonceStr: 随机字符串,长度要求在32位以内(如不写,go-pay将为你随机生成).
+//    Body: 商品简单描述.
+//    OutTradeNo: 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一.
+//    TotalFee: 订单总金额,单位为分.
+//    SpbillCreateIp: 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP.
+//    NotifyUrl: 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数.
+//    TradeType: 交易类型:(JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式).
+//
+// 非必选.
+//    DeviceInfo: 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB".
+//    SignType: 签名类型,默认为MD5,支持HMAC-SHA256和MD5.
+//    Detail: 商品详细描述,对于使用单品优惠的商户,字段必须按照规范上传.
+//    Attach: 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用.
+//    FeeType: 符合ISO 4217标准的三位字母代码,默认人民币:CNY.
+//    TimeStart: 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010.
+//    TimeExpire: 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id.
+//    GoodsTag: 订单优惠标记,使用代金券或立减优惠功能时需要的参数.
+//    ProductId: trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义.
+//    LimitPay: 上传此参数 no_credit 可限制用户不能使用信用卡支付.
+//    Openid: 用户标识: trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识.
+//    Receipt: Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效.
+//    SceneInfo: 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }}.
+type WechatParams struct {
 	NonceStr       string `xml:"nonce_str"`
 	Body           string `xml:"body"`
 	OutTradeNo     string `xml:"out_trade_no"`
@@ -50,41 +67,20 @@ type WechatParamsNecessary struct {
 	SpbillCreateIp string `xml:"spbill_create_ip"`
 	NotifyUrl      string `xml:"notify_url"`
 	TradeType      string `xml:"trade_type"`
-}
 
-/*
-	NecessaryParams: 必传参数
-	DeviceInfo: 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
-	SignType: 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
-	Detail: 商品详细描述,对于使用单品优惠的商户,字段必须按照规范上传
-	Attach: 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
-	FeeType: 符合ISO 4217标准的三位字母代码,默认人民币:CNY
-	TimeStart: 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010
-	TimeExpire: 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id
-	GoodsTag: 订单优惠标记,使用代金券或立减优惠功能时需要的参数
-	ProductId: trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义
-	LimitPay: 上传此参数 no_credit 可限制用户不能使用信用卡支付
-	Openid: 用户标识: trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识
-	Receipt: Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效
-	SceneInfo: 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }}
-	StoreInfo: SceneInfo 的字段信息
-*/
-type WechatParams struct {
-	NecessaryParams WechatParamsNecessary
-	DeviceInfo      string    `xml:"device_info"`
-	SignType        string    `xml:"sign_type"`
-	Detail          string    `xml:"detail"`
-	Attach          string    `xml:"attach"`
-	FeeType         string    `xml:"fee_type"`
-	TimeStart       string    `xml:"time_start"`
-	TimeExpire      string    `xml:"time_expire"`
-	GoodsTag        string    `xml:"goods_tag"`
-	ProductId       string    `xml:"product_id"`
-	LimitPay        string    `xml:"limit_pay"`
-	Openid          string    `xml:"openid"`
-	Receipt         string    `xml:"receipt"`
-	SceneInfo       string    `xml:"scene_info"`
-	StoreInfo       StoreInfo `xml:"-"`
+	DeviceInfo string     `xml:"device_info"`
+	SignType   string     `xml:"sign_type"`
+	Detail     string     `xml:"detail"`
+	Attach     string     `xml:"attach"`
+	FeeType    string     `xml:"fee_type"`
+	TimeStart  string     `xml:"time_start"`
+	TimeExpire string     `xml:"time_expire"`
+	GoodsTag   string     `xml:"goods_tag"`
+	ProductId  string     `xml:"product_id"`
+	LimitPay   string     `xml:"limit_pay"`
+	Openid     string     `xml:"openid"`
+	Receipt    string     `xml:"receipt"`
+	SceneInfo  *StoreInfo `xml:"scene_info"`
 }
 
 //StoreInfo: SceneInfo 的字段信息
@@ -95,38 +91,89 @@ type StoreInfo struct {
 	Address  string `json:"address"`   // 门店详细地址
 }
 
-//获取Sign签名
-func getSign(secretKey string, params *WechatParams) string {
-	paramMap := make(map[string]string, 0)
-	paramMap["appid"] = params.NecessaryParams.Appid
-	paramMap["mch_id"] = params.NecessaryParams.MchId
-	paramMap["nonce_str"] = params.NecessaryParams.NonceStr
-	paramMap["body"] = params.NecessaryParams.Body
-	paramMap["out_trade_no"] = params.NecessaryParams.OutTradeNo
-	paramMap["total_fee"] = strconv.Itoa(params.NecessaryParams.TotalFee)
-	paramMap["spbill_create_ip"] = params.NecessaryParams.SpbillCreateIp
-	paramMap["notify_url"] = params.NecessaryParams.NotifyUrl
-	paramMap["trade_type"] = params.NecessaryParams.TradeType
+//获取请求支付的参数
+func (w *WechatParams) getRequestParams(appid, mchId string, params *WechatParams) WXReq {
+	reqs := make(WXReq)
+	reqs.Set("appid", appid)
+	reqs.Set("mch_id", mchId)
+	reqs.Set("nonce_str", params.NonceStr)
+	reqs.Set("body", params.Body)
+	reqs.Set("out_trade_no", params.OutTradeNo)
+	reqs.Set("total_fee", strconv.Itoa(params.TotalFee))
+	reqs.Set("spbill_create_ip", params.SpbillCreateIp)
+	reqs.Set("notify_url", params.NotifyUrl)
+	reqs.Set("trade_type", params.TradeType)
 
+	if params.DeviceInfo != "" {
+		reqs.Set("device_info", params.DeviceInfo)
+	}
+	if params.SignType != "" {
+		reqs.Set("sign_type", params.SignType)
+	} else {
+		reqs.Set("sign_type", "MD5")
+	}
+	if params.Detail != "" {
+		reqs.Set("detail", params.Detail)
+	}
+	if params.Attach != "" {
+		reqs.Set("attach", params.Attach)
+	}
+	if params.FeeType != "" {
+		reqs.Set("fee_type", params.FeeType)
+	}
+	if params.TimeStart != "" {
+		reqs.Set("time_start", params.TimeStart)
+	}
+	if params.TimeExpire != "" {
+		reqs.Set("time_expire", params.TimeExpire)
+	}
+	if params.GoodsTag != "" {
+		reqs.Set("goods_tag", params.GoodsTag)
+	}
+	if params.ProductId != "" {
+		reqs.Set("product_id", params.ProductId)
+	}
+	if params.LimitPay != "" {
+		reqs.Set("limit_pay", params.LimitPay)
+	}
 	if params.Openid != "" {
-		paramMap["openid"] = params.Openid
+		reqs.Set("openid", params.Openid)
 	}
-	if params.DeviceInfo != "" {
-		paramMap["openid"] = params.DeviceInfo
-	}
-	signStr := getSignString(secretKey, paramMap)
-	fmt.Println("signStr:", signStr)
-	hash := md5.New()
-	hash.Write([]byte(signStr))
-	md5Sign := hash.Sum(nil)
-	sign := strings.ToUpper(hex.EncodeToString(md5Sign))
-	return sign
+	if params.Receipt != "" {
+		reqs.Set("receipt", params.Receipt)
+	}
+	if params.SceneInfo != nil {
+		marshal, _ := json.Marshal(params.SceneInfo)
+		reqs.Set("scene_info", string(marshal))
+	}
+	return reqs
 }
 
-//获取排好序的Key
-func getSignString(secretKey string, paramMap map[string]string) string {
+//获取Sign签名和请求支付的参数
+func getSignAndRequestParam(appid, mchId string, secretKey string, param *WechatParams) (sign string, reqs WXReq) {
+
+	reqs = param.getRequestParams(appid, mchId, param)
+
+	signStr := getSignString(secretKey, reqs)
+	//fmt.Println("signStr:", signStr)
+	var hashSign []byte
+	if param.SignType == WX_SignType_MD5 {
+		hash := md5.New()
+		hash.Write([]byte(signStr))
+		hashSign = hash.Sum(nil)
+	} else {
+		hash := hmac.New(sha256.New, []byte(secretKey))
+		hash.Write([]byte(signStr))
+		hashSign = hash.Sum(nil)
+	}
+	sign = strings.ToUpper(hex.EncodeToString(hashSign))
+	return
+}
+
+//获取根据Key排序后的请求参数字符串
+func getSignString(secretKey string, wxReq WXReq) string {
 	keyList := make([]string, 0)
-	for k := range paramMap {
+	for k := range wxReq {
 		keyList = append(keyList, k)
 	}
 	sort.Strings(keyList)
@@ -134,20 +181,19 @@ func getSignString(secretKey string, paramMap map[string]string) string {
 	for _, k := range keyList {
 		buffer.WriteString(k)
 		buffer.WriteString("=")
-		buffer.WriteString(paramMap[k])
+		buffer.WriteString(wxReq[k])
 		buffer.WriteString("&")
 	}
 	buffer.WriteString("key=")
 	buffer.WriteString(secretKey)
 	return buffer.String()
-
 }
 
-func generateXml(m map[string]string) string {
-	xml := "<xml>"
-	for k, v := range m {
-		xml += fmt.Sprintf("<%s>%s</%s>", k, v, k)
+func (w WXReq) generateXml() (reqXML string) {
+	reqXML = "<xml>"
+	for k, v := range w {
+		reqXML += fmt.Sprintf("<%s>%s</%s>", k, v, k)
 	}
-	xml += "</xml>"
-	return xml
+	reqXML += "</xml>"
+	return
 }

+ 38 - 5
wechat_pay.go

@@ -1,20 +1,53 @@
 package go_pay
 
+import (
+	"fmt"
+	"github.com/parnurzeal/gorequest"
+)
+
 type WechatPayClient struct {
-	Params *WechatParams
-	Sign   string
+	Appid    string
+	MchId    string
+	Params   *WechatParams
+	ReqParam WXReq
+	isDebug  bool
 }
 
-func NewWechatPayClient() *WechatPayClient {
+//    Appid: 微信支付分配的公众账号ID(企业号corpid即为此appId)
+//    MchId: 微信支付分配的商户号
+func NewWechatPayClient(appid, mchId string, isDebug bool) *WechatPayClient {
 	client := new(WechatPayClient)
+	client.Appid = appid
+	client.MchId = mchId
+	client.isDebug = isDebug
 	return client
 }
 
+//设置参数
 func (this *WechatPayClient) SetParams(param *WechatParams) {
 	this.Params = param
 }
 
-func (this *WechatPayClient) SetSign(secretKey string) error {
-	this.Sign = getSign(secretKey, this.Params)
+func (this *WechatPayClient) GetSignAndSetReqParam(secretKey string) string {
+	sign, reqs := getSignAndRequestParam(this.Appid, this.MchId, secretKey, this.Params)
+	this.ReqParam = reqs
+	this.ReqParam.Set("sign", sign)
+	return sign
+}
+
+//APP支付
+func (this *WechatPayClient) GoWechatPay() error {
+	//fmt.Println("reqs:", this.ReqParam)
+	reqXML := this.ReqParam.generateXml()
+	agent := gorequest.New()
+	agent.Post(WX_PayUrl)
+	agent.Type("xml")
+	agent.SendString(reqXML)
+	response, bytes, _ := agent.EndBytes()
+	defer response.Body.Close()
+
+	//fmt.Println("response:", response.Body)
+	fmt.Println("bytes:", string(bytes))
+	//fmt.Println("errors:", errors)
 	return nil
 }

+ 16 - 16
wechat_pay_test.go

@@ -1,27 +1,27 @@
 package go_pay
 
 import (
-	"fmt"
 	"testing"
 )
 
-func TestGetSign(t *testing.T) {
-	necessary := new(WechatParamsNecessary)
-	necessary.Appid = "zbcdefg"
-	necessary.MchId = "1234455"
-	necessary.NonceStr = "dyUNIkNS29hvDUC1CmoF0alSdfCQGg9I"
-	necessary.Body = "测试充值"
-	necessary.OutTradeNo = "GYsadfjksdhgflkhfgnlsdkf"
-	necessary.TotalFee = 15
-	necessary.SpbillCreateIp = "127.0.0.1"
-	necessary.NotifyUrl = "http://www.igoogle.ink"
-	necessary.TradeType = "APP"
-
+func TestWXPay(t *testing.T) {
+	client := NewWechatPayClient("fdgdfg", "23466", false)
 	params := new(WechatParams)
+	params.NonceStr = "dyUNIkNS29hvDUC1CmoF0alSdfCQGg9I"
+	params.Body = "测试充值"
+	params.OutTradeNo = "GYsadfjksdhgflkhfgnlsdkf"
+	params.TotalFee = 15
+	params.SpbillCreateIp = "127.0.0.1"
+	params.NotifyUrl = "http://www.igoogle.ink"
+	params.TradeType = WX_PayType_Mini
 	params.DeviceInfo = "WEB"
-	params.NecessaryParams = *necessary
+	params.SignType = WX_SignType_HMAC_SHA256
+	params.Openid = "o0Df70H2Q0fY8JXh1aFPIRyOBgu8"
+
+	client.SetParams(params)
 
-	s := getSign("asdfdsagsfdg", params)
+	client.GetSignAndSetReqParam("bfvnbhnmt5435")
+	//fmt.Println("sign:", sign)
 
-	fmt.Println("sign:", s)
+	client.GoWechatPay()
 }