最近的做了个开发微信H5支付的需求,H5支付和app支付的流程基本一样,差别主要有下面两点:
Talk is cheap. Show me the code
package product
import (
"bytes"
"crypto/md5"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/golang/glog"
)
// 创建H5微信订单
//CreateH5WXOrderParam 请求参数
type CreateH5WXOrderParam struct {
UserID string `json:"userid"` //用户名
}
//CreateH5WXOrderReply 返回结果
type CreateH5WXOrderReply struct {
MwebURL string `json:"mweb_url"` //支付跳转链接
}
//WXOrderParam 微信请求参数
type WXOrderParam struct {
APPID string `xml:"appid"` //公众账号ID
MchID string `xml:"mch_id"` //商户号
NonceStr string `xml:"nonce_str"` //随机字符串
Sign string `xml:"sign"` //签名
Body string `xml:"body"` //商品描述
OutTradeNo string `xml:"out_trade_no"` //商户订单号
TotalFee string `xml:"total_fee"` //总金额
SpbillCreateIP string `xml:"spbill_create_ip"` //终端IP
NotifyUrl string `xml:"notify_url"` //通知地址
TradeType string `xml:"trade_type"` //交易类型
SceneInfo string `xml:"scene_info"` //场景信息
}
//WXOrderReply 微信请求返回结果
type WXOrderReply struct {
ReturnCode string `xml:"return_code"` //返回状态码
ReturnMsg string `xml:"return_msg"` //返回信息
APPID string `xml:"appid"` //公众账号ID
MchID string `xml:"mch_id"` //商户号
DeviceInfo string `xml:"device_info"` //设备号
NonceStr string `xml:"nonce_str"` //随机字符串
Sign string `xml:"sign"` //签名
ResultCode string `xml:"result_code"` //业务结果
ErrCode string `xml:"err_code"` //错误代码
ErrCodeDes string `xml:"err_code_des"` //错误代码描述
TradeType string `xml:"trade_type"` //交易类型
PrepayID string `xml:"prepay_id"` //预支付交易会话标识
MwebURL string `xml:"mweb_url"` //支付跳转链接
}
const (
WXPackage = "Sign=WXPay"
WXAppID = "" //公众账号ID
WXMchID = "" //商户号
WXPApiKEY = "" //商户key
WXOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"
WXTradeType = "MWEB"
WXNotifyURL = "" //支付回调链接
)
/*
* CreateH5WXOrder 创建微信H5订单
* 使用rpc框架
*/
func (p *Product) CreateH5WXOrder(r *http.Request, param *CreateH5WXOrderParam, reply *CreateH5WXOrderReply) error {
var data WXOrderParam
userid, _ := strconv.ParseInt(param.UserID, 10, 64)
data.OutTradeNo = "W" + orderIDGenerator(userid) //生成订单
data.TotalFee = "1"
data.Body = "测试购买"
data.APPID = WXAppID
data.MchID = WXMchID
data.NonceStr = getRandStr(32) //获取32随机数
data.SpbillCreateIP = getIpAddress(r) //获取IP地址
data.NotifyUrl = WXNotifyURL
data.TradeType = WXTradeType
data.SceneInfo = `{"h5_info": {"type":"Wap","wap_url": "WAP网站URL地址","wap_name": "WAP网站名"}`
data.Sign = WXmd5Sign(data) //生成md5签名
prepayid, mweb_url := submitWXOrder(data) //提交微信
if "" == prepayid {
err := errors.New(`获取prepayid失败`)
glog.Error(err)
return err
}
//返回支付跳转链接
reply.MwebURL = mweb_url
return nil
}
/*
* orderIDGenerator 生成订单id
* param userid 用户名
* reply 生成的订单
*/
func orderIDGenerator(userid int64) string {
str1 := fmt.Sprintf("%d", (userid>>4)%10000)
tag := 'A' + userid%10
str1 = string(tag) + str1
now := time.Now()
t := now.Year()%100*10000000000 + int(now.Month())*100000000 + now.Day()*1000000 + now.Hour()*10000 + now.Minute()*100 + now.Second()
rnd := mathrand.New(mathrand.NewSource(int64(t)))
t = t*1000 + rnd.Intn(10000)
str2 := strconv.Itoa(t)
return str1 + str2
}
/*
* getRandStr 获取随机字符串
* param n 位数
* reply 随机字符串
*/
func getRandStr(n int) string {
leterset := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var leteridxbits uint = 6
var mask int64 = 1<<leteridxbits - 1
rnd := rand.NewSource(time.Now().UnixNano())
res := make([]byte, 0, n)
for i, bits := 0, rnd.Int63(); i < n; i++ {
if bits == 0 {
bits = rnd.Int63()
}
idx := int(bits & mask)
if idx < len(leterset) {
res = append(res, leterset[idx])
} else {
i--
}
bits >>= leteridxbits
}
return string(res[:n])
}
/*
* getIpAddress 获取IP地址
* param r *http.Request
* reply IP地址
*/
func getIpAddress(r *http.Request) string {
realip := r.Header.Get("X-Real-Ip")
if realip != "" {
return realip
}
remoteAddr := r.RemoteAddr
if remoteAddr != "" {
idx := strings.Index(remoteAddr, ":")
return remoteAddr[:idx]
}
ips := r.Header.Get("X-Forwarded-For")
glog.Info("X-Forwarded-For", ips)
if ips != "" {
iplist := strings.Split(ips, ",")
return strings.TrimSpace(iplist[0])
}
return ""
}
/*
* WXmd5Sign 微信 md5 签名
* param data interface{}
* reply sign 生成的签名
*/
func WXmd5Sign(data interface{})(sign string) {
val := make(map[string]string)
datavalue := reflect.ValueOf(data)
glog.Info("datavalue:", datavalue)
if datavalue.Kind() != reflect.Struct {
glog.Error("NOT A STRUCT")
return ""
}
var keys []string
for i := 0; i < datavalue.NumField(); i++ {
k := datavalue.Type().Field(i)
kl := k.Tag.Get("xml")
v := fmt.Sprintf("%v", datavalue.Field(i))
if v != "" && v != "0" && kl != "sign" {
glog.Info(kl, v)
val[kl] = v
keys = append(keys, kl)
}
}
sort.Strings(keys)
var stra string
for _, v := range keys {
stra = stra + v + "=" + val[v] + "&"
}
strb := stra + "key=" + WXPApiKEY
glog.Info("signstr:", strb)
hstr := md5.Sum([]byte(strb))
sum := fmt.Sprintf("%x", hstr)
sign = strings.ToUpper(sum)
return sign
}
/*
* submitWXOrder 提交微信订单
* param data WXOrderParam
* reply prepay_id 预支付交易会话标识
* reply mweb_url 支付跳转链接
*/
func submitWXOrder(data WXOrderParam) (prepay_id string, mweb_url string) {
xdata, err := xml.Marshal(data)
if err != nil {
glog.Error(err, xdata)
}
glog.Info(string(xdata))
xmldata := strings.Replace(string(xdata), "WXOrderParam", "xml", -1)
glog.Info("request : ", xmldata)
body := bytes.NewBufferString(xmldata)
resp, err := http.Post(WXOrderURL, "content-type:text/xml; charset=utf-8", body)
if err != nil {
glog.Error(err)
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
var reply WXOrderReply
err = xml.Unmarshal(result, &reply)
if err != nil {
glog.Error(err)
return "", ""
}
glog.Info(reply)
if reply.ReturnCode == "SUCCESS" && reply.ResultCode == "SUCCESS" {
return reply.PrepayID, reply.MwebURL
}
return "", ""
}
参考:微信H5【统一下单】文档