xml:"mch_id"
//VXWAP支付 POST 模型
type VXXML struct {
XMLName xml.Name `xml:"xml"`
Service string `xml:"service"` //接口类型,pay.weixin.wappay
Version string `xml:"version,omitempty"` //版本, 2.0
Charset string `xml:"charset,omitempty"` //字符集 UTF-8
SignType string `xml:"sign_type"` //签名方式 RSA_1_256
MchId string `xml:"mch_id"` //商户号 待填
OutTradeNo string `xml:"out_trade_no"` //用户订单号
Body string `xml:"body"` //商品描述 待填
Attach string `xml:"attach,omitempty"` //附加信息,选填
TotalFee string `xml:"total_fee"` //总金额
MchCreateIp string `xml:"mch_create_ip"` // 终端ip
NotifyUrl string `xml:"notify_url"` //通知接口
CallBackUrl string `xml:"callback_url"` //回调界面
GoodsTag string `xml:"goods_tag,omitempty"` //商品标记 选填
DevInfo string `xml:"device_info"` //应用类型 苹果-iOS_SDK,安卓-AND_SDK,其他iOS_WAP
MchAppName string `xml:"mch_app_name"` //应用名
MchAppId string `xml:"mch_app_id"` //应用标识
NonceStr string `xml:"nonce_str"` //随机字符串 32位以内
Sign string `xml:"sign"` //签名结果
}
//将该(任意)字符串结构体转换成a=x&b=y&c=z 格式,参数名按照asc递增排序,""值不排
func ToParam(vx interface{}) string {
var result string
var tagName_FieldValue = make(map[string]string)
var SortedArr = make([]string, 0)
var tagValueTemp string
var valueStrTemp string
vType := reflect.TypeOf(vx)
vValue := reflect.ValueOf(vx)
for i := 0; i < vType.NumField(); i++ {
tagValueTemp = filtTag(vType.Field(i).Tag.Get("xml"))
//设置过滤
if tagValueTemp == "" || tagValueTemp == "xml" || tagValueTemp == "sign" {
continue
}
valueStrTemp = vValue.Field(i).String()
if valueStrTemp == "" {
continue
}
tagName_FieldValue[tagValueTemp] = valueStrTemp
SortedArr = append(SortedArr, tagValueTemp)
}
sort.Strings(SortedArr)
for i, v := range SortedArr {
if i == 0 {
result = result + v + "=" + tagName_FieldValue[v]
continue
}
result = result + "&" + v + "=" + tagName_FieldValue[v]
}
return result
}
//获取标签第一个值
func filtTag(tag string) string {
if strings.Contains(tag, ",") {
return strings.Split(tag, ",")[0]
} else {
return tag
}
}
上述代码的使用条件:
xml参数的格式都是string
请求参数的排列不包括sign
参数排列过滤掉xmlName即首行以及sign以及空
2. 签名的生成
wap支付已经不支持md5,统一使用rsa_1_256
rsa/interface.go
package rsa
import "crypto"
type Cipher interface {
Encrypt(plaintext []byte) ([]byte, error)
Decrypt(ciphertext []byte) ([]byte, error)
Sign(src []byte, hash crypto.Hash) ([]byte, error)
Verify(src []byte, sign []byte, hash crypto.Hash,publicKey string) error
}
rsa/Imple.go
package rsa
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/pem"
"errors"
)
type Type int64
const (
PKCS1 Type = iota
PKCS8
)
type pkcsClient struct {
privateKey *rsa.PrivateKey
publicKey *rsa.PublicKey
}
func (this *pkcsClient) Encrypt(plaintext []byte) ([]byte, error) {
return rsa.EncryptPKCS1v15(rand.Reader, this.publicKey, plaintext)
}
func (this *pkcsClient) Decrypt(ciphertext []byte) ([]byte, error) {
return rsa.DecryptPKCS1v15(rand.Reader, this.privateKey, ciphertext)
}
func (this *pkcsClient) Sign(src []byte, hash crypto.Hash) ([]byte, error) {
h := hash.New()
h.Write(src)
hashed := h.Sum(nil)
return rsa.SignPKCS1v15(rand.Reader, this.privateKey, hash, hashed)
}
func (this *pkcsClient) Verify(src []byte, sign []byte, hash crypto.Hash,publickKey string) error {
h := hash.New()
h.Write(src)
hashed := h.Sum(nil)
if publickKey ==""{
return rsa.VerifyPKCS1v15(this.publicKey, hash, hashed, sign)
}else{
blockPub, _ := pem.Decode([]byte(publickKey))
if blockPub == nil {
return errors.New("public key error")
}
pubKey, err := genPubKey(blockPub.Bytes)
if err != nil {
return err
}
return rsa.VerifyPKCS1v15(pubKey, hash, hashed, sign)
}
}
rsa/client.go
package rsa
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
//默认客户端,pkcs8私钥格式,pem编码
func NewDefault(privateKey, publicKey string) (Cipher, error) {
blockPri, _ := pem.Decode([]byte(privateKey))
if blockPri == nil {
return nil, errors.New("private key error")
}
blockPub, _ := pem.Decode([]byte(publicKey))
if blockPub == nil {
return nil, errors.New("public key error")
}
return New(blockPri.Bytes, blockPub.Bytes,PKCS8)
}
func New(privateKey, publicKey []byte, privateKeyType Type) (Cipher, error) {
priKey, err := genPriKey(privateKey, privateKeyType)
if err != nil {
return nil, err
}
pubKey, err := genPubKey(publicKey)
if err != nil {
return nil, err
}
return &pkcsClient{privateKey: priKey, publicKey: pubKey}, nil
}
func genPubKey(publicKey []byte) (*rsa.PublicKey, error) {
pub, err := x509.ParsePKIXPublicKey(publicKey)
if err != nil {
return nil, err
}
return pub.(*rsa.PublicKey), nil
}
func genPriKey(privateKey []byte, privateKeyType Type) (*rsa.PrivateKey, error) {
var priKey *rsa.PrivateKey
var err error
switch privateKeyType {
case PKCS1:
{
priKey, err = x509.ParsePKCS1PrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
}
case PKCS8:
{
prkI, err := x509.ParsePKCS8PrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}
priKey = prkI.(*rsa.PrivateKey)
}
default:
{
return nil, errors.New("unsupport private key type")
}
}
return priKey, nil
}
加密rsa包可以独立使用,使用者不用关注内部逻辑,下面是使用方式
beanFactory/beans.go
package beanFactory
import (
"github.com/fwhezfwhez/BeanFactory" //工厂
"Pay/Common/rsa" //rsa加密工具
"Pay/Common/utils"
"fmt"
"Pay/Common/consts"
)
//全局单例的工厂实例
var Factory *BeanFactory.BeanFactory
//工厂bean初始化
func init() {
Factory = BeanFactory.GetFactory()
//rsa设置好私钥和公钥,己方的rsa 私钥公钥
rsaClient, err := rsa.NewDefault(string(consts.Private), string(consts.Public))
if err != nil {
fmt.Println(utils.Location() + err.Error())
return
}
Factory.SetBean("rsaClient", rsaClient)
}
...
//signData是上述结构体执行ToParam()方法后得到的query参数,比如:
//body=测试支付&mch_create_ip=127.0.0.1&mch_id=175510359638&nonce_str=1409196838notify_url=http://227.0.0.1:9001/javak/sds?123&23=3&out_trade_no=141903606228&service=pay.weixin.wappay&total_fee=1
sign, err := rsaEncrypt([]byte(signData))
...
//对数据进行签名
func rsaEncrypt(data []byte) (string, error) {
rsClient := beanFactory.Factory.UnsafeGet("rsaClient").(rsa.Cipher)
signBytes,err:=rsClient.Sign(data, crypto.SHA256)
if err!=nil {
orderControlFATALPrinter.Println("location:%s,err:%s",utils.Location(),err.Error())
return "",err
}
rs:=base64.StdEncoding.EncodeToString(signBytes) //这句话一定要加
return rs,nil
}
请注意,使用rsClient.Sign()获得的signBytes一定还要经过一轮base64编码,不然通不过微信的检测
rs:=base64.StdEncoding.EncodeToString(signBytes)
3.将结构体进行xml格式转化以后post给微信方即可
...
vxXml = VXXML{
//赋值
//记得带上sign值
}
buf, err := xml.Marshal(vxXml)
//handle err
//http post
//handle response
...
示例:
<xml>
<service>pay.weixin.wappay</service>
<version>2.0</version>
<charset>UTF-8</charset>
<sign_type>RSA_1_256</sign_type>
<mch_id>xxxxxxxxxxx</mch_id><!-商户后台对应的mch_id-->
<out_trade_no>D201805231527042043733482</out_trade_no>
<body>测试数据</body>
<total_fee>6600</total_fee>
<mch_create_ip>199.95.193.25</mch_create_ip>
<notify_url>https://xxx.xxx.com/notifyUrl</notify_url>
<callback_url>https://xxx.xxx.com/</callback_url>
<device_info>iOS_SDK</device_info>
<mch_app_name>xxxxx</mch_app_name>
<mch_app_id>xxxxxxx</mch_app_id>
<nonce_str>Eq4PMmHW05JVhINmy4lcTtXSuJpfN8wv</nonce_str>
<sign>ROa16mZncQp7q/DbYKa1kN5OkAaqxk31hzQT+w75opoANGouPjygUAwJfVObTnshwsW0/ZF5+nOBRp3v373uXKcQRVLynxtfsbXM44WveN4215cHJvCsRHyHMrPHqdbE+EPo1guCXYabtcZ+J+Z0N0wbWgY4yRCTzrC0VN9jyIwODbpSQsCoo4PIcbXECkJl6TNU4Tv308WDTqgdIRqfJrsOeB1H2IaHIeXoJ9YpaKudE6gE/eopHnKdGqE3snh86qd+YabZCiAhk6wkwzW0jm2Qkg/MXcaxNqdV527d5hudIuFx1E8DU5FOhwgr33+bBGKvl/+Pf9SkemBd2gvdRg==</sign>
</xml>
4.处理响应支付结果notifyurl,该响应可以和公众号支付复用
//异步支付结果通知结果
//model包里
type NotifyResult struct {
XMLName xml.Name `xml:"xml"`
Version string `xml:"version"`
Charset string `xml:"charset"`
SignType string `xml:"sign_type"`
Status string `xml:"status"`
Message string `xml:"message"`
ResultCode string `xml:"result_code"`
MchId string `xml:"mch_id"`
DeviceInfo string `xml:"device_info"`
NonceStr string `xml:"nonce_str"`
ErrCode string `xml:"err_code"`
ErrMsg string `xml:"err_msg"`
Sign string `xml:"sign"`
OpenId string `xml:"openid"`
TradeType string `xml:"trade_type"`
IsSubscribe string `xml:"is_subscribe"`
PayResult string `xml:"pay_result"`
PayInfo string `xml:"pay_info"`
TransactionId string `xml:"transaction_id"`
OutTransactionId string `xml:"out_transaction_id"`
SubIsSubscribe string `xml:"sub_is_subscribe"`
SubAppId string `xml:"sub_appid"`
SubOpenId string `xml:"sub_openid"`
OutTradeNo string `xml:"out_trade_no"`
TotalFee string `xml:"total_fee"`
CashFee string `xml:"cash_fee"`
CouponFee string `xml:"coupon_fee"`
FeeType string `xml:"fee_type"`
Attach string `xml:"attach"`
BankType string `xml:"bank_type"`
BankBillNo string `xml:"bank_billno"`
TimeEnd string `xml:"time_end"`
}
//Control包里
func Notify(c *gin.Context) {
//TODO 清空测试记录
log.Println("支付信息成功进入notify,%s")
controlPrinter.Println("缓存的orderId_oid列表是::%v", consts.OrderId_Oid)
controlPrinter.Println("缓存的orderId_Money列表是::%v", consts.OrderId_Money)
controlPrinter.Println("缓存的orderId_ProductId列表是::%v", consts.OrderId_ProductId)
rs := model.NotifyResult{}
c.Bind(&rs)
//该资源用于判断是否合法
src := model.ToParam(rs)
controlPrinter.Println("location:%s,rsParam:%s", utils.Location(), src)
controlPrinter.Println("location:%s,rs:%s", utils.Location(), rs.String())
_, er := OrderDao.SaveNotifyResult(rs)
if er != nil {
controlFATALPrinter.Println("%s,存放notifyresult失败:%s", utils.Location(), er.Error())
}
if rs.Status != "0" {
controlFATALPrinter.Println("location:%s,notify状态state非0失败", utils.Location())
c.String(200, "fail")
return
}
orderIdrs := rs.OutTradeNo
var oid string
var ok bool
if _, ok = consts.OrderId_Money[orderIdrs]; !ok {
controlPrinter.Println(utils.Location() + ",该oid不存在于orderid_oid表里,已处理完")
//不存在,即已经处理完了
c.String(200, "%s", "success")
return
} else {
//更新ditribution order状态
if doDecodeSign(rs.Sign, src) {
//签名验证成功
if localMoney, ok2 := consts.OrderId_Money[orderIdrs]; !ok2 {
controlFATALPrinter.Println("Location:%s,order_money表没有该orderId的金额记录", utils.Location())
} else {
if rs.PayResult != "0" {
controlPrinter.Println("[%s],支付状态为非零,用户取消了,或者支付失败", utils.Location())
//支付失败,用户不想支付了
c.String(200, "%s", "success")
removeCache(orderIdrs)
return
}
controlPrinter.Println("金额缓存列表:%v,[%s]", consts.OrderId_Money, utils.Location())
if localMoney == rs.TotalFee {
controlFATALPrinter.Println("金额匹配成功,[%s]", utils.Location())
//金额与订单匹配
//同步数据
//检查缓存里是否存在对应的oid,判断是否需要同步第三方服务
if oid, ok = consts.OrderId_Oid[orderIdrs]; !ok {
controlFATALPrinter.Println(utils.GenerateMsg("缓存里找不到orderiD,该条处理已经处理完了"))
} else if oid == "" {
//即本地支付成功可存,业务方订单出了问题
controlFATALPrinter.Println(utils.GenerateMsg("缓存里找不到oid,该条处理未被业务方记录,仅在本地记录了"))
//添加未被远程记录的订单入库
orderService.UpdateSyn(orderIdrs)
} else {
//对远程业务进行同步
switch consts.OrderId_ProductId[orderIdrs]{
case"1001":
ok1, errMsg := OrderService.CallDistributionText(oid)
if !ok1 {
controlFATALPrinter.Println(utils.GenerateErrMsg(errMsg + "调用短信服务失败"))
}
case"1002":{}
}
}
//同步本地
ok2 := orderService.UpdatePayStatus(orderIdrs)
if ok2 {
controlFATALPrinter.Println("成功同步业务pay和本地paystatus,%s", utils.Location())
c.String(200, "%s", "success")
//移除缓存
removeCache(orderIdrs)
log.Println("缓存移除成功orderid:%s,oid:%s,location:%s", orderIdrs, oid, utils.Location())
return
}
}
}
} else {
c.String(200, "%s", "fail")
controlFATALPrinter.Println("签名不正确,数据可能丢失或者篡改,%s", utils.Location())
}
}
}
//移除缓存
func removeCache(orderId string){
consts.RemoveOrderId_Money(orderId)
consts.RemoveOrderId_Oid(orderId)
consts.RemoveOrderId_ProductId(orderId)
}
//签名验证
func doDecodeSign(sign string, src string) bool {
controlPrinter.Println("进入验证sign,%s", utils.Location())
rsClient := beanFactory.Factory.UnsafeGet("rsaClient").(rsa.Cipher)
signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
controlFATALPrinter.Println("转换签名出错,%s", utils.Location())
return false
}
if err != nil {
controlFATALPrinter.Println("location:%s,err:%s", utils.Location(), err.Error())
return false
}
errV := rsClient.Verify([]byte(src), signBytes, crypto.SHA256, consts.VXPublic)
if errV != nil {
controlFATALPrinter.Println("验证sign失败%s,location:%s", errV.Error(), utils.Location())
return false
}
controlPrinter.Println("验证签名成功,%s", utils.Location())
return true
}
公众号支付待更