ls 5 years ago
parent
commit
0445cf3f08
9 changed files with 604 additions and 244 deletions
  1. 12 0
      hash.go
  2. 59 0
      unionpay/cert.go
  3. 76 0
      unionpay/charging.go
  4. 271 0
      unionpay/client.go
  5. 6 0
      unionpay/const.go
  6. 55 186
      unionpay/entrust.go
  7. 14 56
      unionpay/rsa.go
  8. 2 2
      unionpay/url.go
  9. 109 0
      unionpay/util.go

+ 12 - 0
hash.go

@@ -4,9 +4,21 @@ import (
 	"crypto/md5"
 	"crypto/md5"
 	"crypto/sha1"
 	"crypto/sha1"
 	"crypto/sha256"
 	"crypto/sha256"
+	"crypto/sha512"
 	"encoding/hex"
 	"encoding/hex"
 )
 )
 
 
+// SHA512 hash
+func SHA512(data []byte) ([]byte, error) {
+	hash := sha512.New()
+	_, err := hash.Write(data)
+	if err != nil {
+		return nil, err
+	}
+
+	return hash.Sum(nil), nil
+}
+
 // SHA256 hash string
 // SHA256 hash string
 func SHA256(data string) string {
 func SHA256(data string) string {
 	hash := sha256.New()
 	hash := sha256.New()

+ 59 - 0
unionpay/cert.go

@@ -0,0 +1,59 @@
+package unionpay
+
+import (
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"io/ioutil"
+)
+
+// getPublicCert 获取加密证书信息
+func getPublicCert(filename string) (cert *x509.Certificate, data []byte, err error) {
+	data, err = ioutil.ReadFile(filename)
+	if err != nil {
+		return
+	}
+
+	block, _ := pem.Decode(data)
+	if block == nil {
+		err = errors.New(`pem Decode error`)
+		return
+	}
+
+	cert, err = x509.ParseCertificate(block.Bytes)
+	return
+}
+
+// getPrivateCert 获取签名证书信息
+func getPrivateCert(filename, password string) (key *rsa.PrivateKey, cert *x509.Certificate, err error) {
+	var (
+		bs      []byte
+		private interface{}
+	)
+	bs, err = ioutil.ReadFile(filename)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	//*
+	block, _ := pem.Decode(bs)
+	if block == nil {
+		err = errors.New(`PrivateKey pem Decode error`)
+		return
+	}
+	cert = &x509.Certificate{}
+	private, err = x509.ParsePKCS8PrivateKey(block.Bytes)
+	// */
+
+	/*
+		private, cert, err = pkcs12.Decode(bs, password)
+		if err != nil {
+			return
+		}
+
+		// */
+	key = private.(*rsa.PrivateKey)
+	return
+}

+ 76 - 0
unionpay/charging.go

@@ -1,3 +1,79 @@
 // 收款、代收
 // 收款、代收
 
 
 package unionpay
 package unionpay
+
+// EntrustCharge 代扣费用
+func EntrustCharge(mi *MerchantInfo, oi OrderInfo, ci CustomerInfo) (res ReqRespose, err error) {
+	notifyURL := "https://park.chuangxin1.com/api/payment/notify/zhuhai"
+	/*
+		var (
+			accNo     []byte
+			encrypted []byte
+			signature []byte
+			values    = &url.Values{}
+			args      = make(map[string]string)
+		)
+
+
+		args["version"] = "5.1.0"                 // 版本号
+		args["encoding"] = "utf-8"                // 编码方式
+		args["signMethod"] = "01"                 // 签名方法
+		args["txnType"] = "11"                    // 交易类型
+		args["txnSubType"] = "02"                 // 交易子类
+		args["bizType"] = "000501"                // 业务类型
+		args["accessType"] = "0"                  // 接入类型
+		args["channelType"] = "07"                // 渠道类型
+		args["currencyCode"] = "156"              // 交易币种, 境内商户勿改
+		args["backUrl"] = notifyURL               // 后台通知地址
+		args["encryptCertId"] = mi.SerialNumber() // 验签证书序列号
+		args["certId"] = mi.SerialNumber()        // 证书序列号
+		args["merId"] = mi.MerID                  // 商户代码
+		args["orderId"] = oi.OrderID              // 商户订单号,8-32位数字字母,不能含“-”或“_”
+		args["txnTime"] = oi.TxnTime              // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
+		args["txnAmt"] = "12"                     // 交易金额
+
+		accNo, err = mi.Encrypt([]byte(ci.CardNo))
+		if err != nil {
+			return
+		}
+		args["accNo"] = base64Encode(accNo)
+
+		encrypted, err = mi.EncryptCustomerInfo(ci)
+		if err != nil {
+			return
+		}
+		args["customerInfo"] = ci.String(encrypted)
+
+		signature, err = mi.Sign([]byte(createLinkString(args, true, false)))
+		if err != nil {
+			return
+		}
+		args["signature"] = base64Encode(signature) // 签名
+
+		for k, v := range args {
+			values.Add(k, v)
+		}
+		res, err = post(BaseHost+ReqBackTrans, []byte(values.Encode()))
+		if err != nil {
+			return
+		}
+		// */
+	var body string
+	args := newParams(oi, ci)
+	args.TxnType = "11"    // 交易类型, 取值:11
+	args.TxnSubType = "02" // 交易子类, 取值:02
+
+	args.BizType = "000501" // 业务类型 代收产品
+
+	args.BackURL = notifyURL
+	body, err = args.ToBody(mi, ci)
+	if err != nil {
+		return
+	}
+	res, err = post(BaseHost+ReqBackTrans, []byte(body))
+	if err != nil {
+		return
+	}
+	_, err = res.Ok()
+	return
+}

+ 271 - 0
unionpay/client.go

@@ -0,0 +1,271 @@
+// sdk v5.1.0
+// https://open.unionpay.com/tjweb/api/list
+
+package unionpay
+
+import (
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/x509"
+	"encoding/hex"
+	"errors"
+	"net/url"
+	"strconv"
+)
+
+// MerchantInfo 商家信息
+type MerchantInfo struct {
+	MerID string // 商户代码
+
+	Password   string            // 签名证书密码
+	Cert       *x509.Certificate // 加密证书
+	PublicKey  *rsa.PublicKey    // 加密 Key
+	PrivateKey *rsa.PrivateKey   // 签名 Key
+}
+
+// OrderInfo 订单信息
+type OrderInfo struct {
+	TxnType    string // 交易类型
+	TxnSubType string // 交易子类
+	OrderID    string // 商户订单号,8-32位数字字母,不能含“-”或“_”
+	// TxnTime 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
+	TxnTime      string
+	TxnAmt       int // 交易金额, 单位: 分
+	CurrencyCode int // 交易币种
+}
+
+// CustomerInfo 客户信息
+type CustomerInfo struct {
+	CardNo       string `form:"cardNo" json:"cardNo" xml:"cardNo"`             // 卡号
+	Phone        string `form:"phone" json:"phone" xml:"phone"`                // 手机号
+	CertifTp     string `form:"certifTp" json:"certifTp" xml:"certifTp"`       // 证件类型, 01 - 身份证
+	CertifID     string `form:"certifId" json:"certifId" xml:"certifId"`       // 证件号,15位身份证不校验尾号,18位会校验尾号
+	CustomerName string `form:"customerNm" json:"customerNm" xml:"customerNm"` // 姓名
+	Cvn2         string `form:"cvn2" json:"cvn2" xml:"cvn2"`                   // 信用卡安全码
+	Expired      string `form:"expired" json:"expired" xml:"expired"`          // 有效期,YYMM格式,持卡人卡面印的是MMYY的,请注意代码设置倒一下
+}
+
+// ReqParams 请求参数
+type ReqParams struct {
+	Version       string `form:"version" json:"version" xml:"version"`                   // 版本号
+	Encoding      string `form:"encoding" json:"encoding" xml:"encoding"`                // 编码方式
+	SignMethod    string `form:"signMethod" json:"signMethod" xml:"signMethod"`          // 签名方法
+	TxnType       string `form:"txnType" json:"txnType" xml:"txnType"`                   // 交易类型
+	TxnSubType    string `form:"txnSubType" json:"txnSubType" xml:"txnSubType"`          // 交易子类
+	BizType       string `form:"bizType" json:"bizType" xml:"bizType"`                   // 业务类型
+	AccessType    string `form:"accessType" json:"accessType" xml:"accessType"`          // 接入类型
+	ChannelType   string `form:"channelType" json:"channelType" xml:"channelType"`       // 渠道类型
+	CurrencyCode  int    `form:"currencyCode" json:"currencyCode" xml:"currencyCode"`    // 交易币种
+	CertID        string `form:"certId" json:"certId" xml:"certId"`                      // 签名证书序列号
+	EncryptCertID string `form:"encryptCertId" json:"encryptCertId" xml:"encryptCertId"` // 验签证书序列号
+	MerID         string `form:"merId" json:"merId" xml:"merId"`                         // 商户代码
+	OrderID       string `form:"orderId" json:"orderId" xml:"orderId"`                   // 商户订单号
+	TxnTime       string `form:"txnTime" json:"txnTime" xml:"txnTime"`                   // 订单发送时间
+	TxnAmt        int    `form:"txnAmt" json:"txnAmt" xml:"txnAmt"`                      // 交易金额,单位分
+	AccNo         string `form:"accNo" json:"accNo" xml:"accNo"`                         // 卡号,敏感信息加密
+	Signature     string `form:"signature" json:"signature" xml:"signature"`             // 签名
+	BackURL       string `form:"backUrl" json:"backUrl" xml:"backUrl"`                   // 后台通知地址
+	// 请求方保留域,
+	// 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。
+	// 出现部分特殊字符时可能影响解析,请按下面建议的方式填写:
+	// 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。
+	//    'reqReserved' =>'透传信息1|透传信息2|透传信息3',
+	// 2. 内容可能出现&={}[]"'符号时:
+	// 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示);
+	// 2) 如果对账文件没有显示要求,可做一下base64(如下)。
+	//    注意控制数据长度,实际传输的数据长度不能超过1024位。
+	//    查询、通知等接口解析时使用base64_decode解base64后再对数据做后续解析。
+	//    'reqReserved' => base64_encode('任意格式的信息都可以'),
+	ReqReserved string `form:"reqReserved" json:"reqReserved" xml:"reqReserved"` // 透传字段
+}
+
+// ReqRespose 请求返回数据
+type ReqRespose struct {
+	Encoding       string `form:"encoding" json:"encoding" xml:"encoding"`
+	Version        string `form:"version" json:"version" xml:"version"`
+	RespCode       int    `form:"respCode" json:"respCode" xml:"respCode"` // 响应代码, 0 成功, > 0 错误
+	RespMsg        string `form:"respMsg" json:"respMsg" xml:"respMsg"`
+	TxnType        string `form:"txnType" json:"txnType" xml:"txnType"`
+	ChannelType    string `form:"channelType" json:"channelType" xml:"channelType"`
+	CurrencyCode   string `form:"currencyCode" json:"currencyCode" xml:"currencyCode"`
+	TxnSubType     string `form:"txnSubType" json:"txnSubType" xml:"txnSubType"`
+	CustomerInfo   string `form:"customerInfo" json:"customerInfo" xml:"customerInfo"`
+	TxnAmt         string `form:"txnAmt" json:"txnAmt" xml:"txnAmt"`
+	SignPubKeyCert string `form:"signPubKeyCert" json:"signPubKeyCert" xml:"signPubKeyCert"`
+	SignMethod     string `form:"signMethod" json:"signMethod" xml:"signMethod"`
+	AccNo          string `form:"accNo" json:"accNo" xml:"accNo"`
+	BackURL        string `form:"backUrl" json:"backUrl" xml:"backUrl"`
+	BizType        string `form:"bizType" json:"bizType" xml:"bizType"`
+	Signature      string `form:"signature" json:"signature" xml:"signature"`
+	OrderID        string `form:"orderId" json:"orderId" xml:"orderId"`
+	TxnTime        string `form:"txnTime" json:"txnTime" xml:"txnTime"`
+	AccessType     int    `form:"accessType" json:"accessType" xml:"accessType"`
+
+	//MerID          string `form:"merId" json:"merId" xml:"merId"`
+	//CertID         string `form:"certId" json:"certId" xml:"certId"`
+	//EncryptCertID  string `form:"encryptCertId" json:"encryptCertId" xml:"encryptCertId"`
+}
+
+// NewMerchantInfo 初始化商家信息
+func NewMerchantInfo(merID, certPath, keyPath, password string) (mi *MerchantInfo, err error) {
+	mi = &MerchantInfo{}
+	mi.MerID = merID
+	mi.Password = password
+
+	mi.PrivateKey, _, err = getPrivateCert(keyPath, password)
+	if err != nil {
+		return
+	}
+
+	mi.Cert, _, err = getPublicCert(certPath)
+	if err != nil {
+		return
+	}
+	mi.PublicKey = mi.Cert.PublicKey.(*rsa.PublicKey)
+
+	return
+}
+
+func newParams(oi OrderInfo, ci CustomerInfo) *ReqParams {
+	args := &ReqParams{}
+
+	args.Version = "5.1.0"
+	args.Encoding = "utf-8"
+	args.SignMethod = "01" // 签名方法, 01 RSA签名
+
+	args.TxnType = oi.TxnType
+	args.TxnSubType = oi.TxnSubType
+
+	args.AccessType = "0"   // 接入类型, 0:普通商户直连接入 1:收单机构接入 2:平台类商户接入
+	args.ChannelType = "07" // 渠道类型 07-PC
+
+	args.OrderID = oi.OrderID
+	args.TxnTime = oi.TxnTime
+
+	// 付款
+	if oi.TxnAmt > 0 {
+		args.TxnAmt = oi.TxnAmt
+		args.CurrencyCode = oi.CurrencyCode
+	}
+
+	return args
+}
+
+// SerialNumber 证书序列号
+func (mi *MerchantInfo) SerialNumber() string {
+	return mi.Cert.SerialNumber.String()
+}
+
+// Encrypt 加密
+func (mi *MerchantInfo) Encrypt(src []byte) ([]byte, error) {
+	return encrypt(mi.PublicKey, src)
+}
+
+// Decrypt 解密
+func (mi *MerchantInfo) Decrypt(src []byte) ([]byte, error) {
+	return decrypt(mi.PrivateKey, src)
+}
+
+// Sign 签名
+func (mi *MerchantInfo) Sign(src []byte) ([]byte, error) {
+	hash := sha256.Sum256(src)
+	return sign(mi.PrivateKey, []byte(hex.EncodeToString(hash[:])))
+}
+
+// Verify 签名验证
+func (mi *MerchantInfo) Verify(src, sig []byte) error {
+	return verify(mi.PublicKey, src, sig)
+}
+
+// EncryptCustomerInfo 持卡人身份信息,敏感信息加密
+func (mi *MerchantInfo) EncryptCustomerInfo(ci CustomerInfo) ([]byte, error) {
+	encryptedInfo := `phone=` + ci.Phone
+	if len(ci.Cvn2) > 0 {
+		encryptedInfo += `&cvn2=` + ci.Cvn2
+	}
+	if len(ci.Expired) > 0 {
+		encryptedInfo += `&expired=` + ci.Expired
+	}
+	encrypted, err := mi.Encrypt([]byte(encryptedInfo))
+	if err != nil {
+		return nil, err
+	}
+
+	return encrypted, nil
+}
+
+func (ci *CustomerInfo) String(encrypted []byte) string {
+	kvs := `encryptedInfo=` + base64Encode(encrypted)
+	kvs += `&certifTp=` + ci.CertifTp
+	kvs += `&certifId=` + ci.CertifID
+	kvs += `&customerNm=` + ci.CustomerName // `&cvn2=&=expired`
+
+	return base64Encode([]byte(`{` + kvs + `}`))
+}
+
+// Ok 请求返回是否成功
+func (res *ReqRespose) Ok() (ok bool, err error) {
+	if res.RespCode == 0 {
+		ok = true
+		return
+	}
+	err = errors.New(res.RespMsg)
+	return
+}
+
+// ToBody 请求参数转换为 HTTP POST 格式数据
+func (p *ReqParams) ToBody(mi *MerchantInfo, ci CustomerInfo) (body string, err error) {
+	var (
+		accNo     []byte
+		encrypted []byte
+		signature []byte
+		values    = &url.Values{}
+		args      = make(map[string]string)
+	)
+
+	args["version"] = p.Version               // 版本号
+	args["encoding"] = p.Encoding             // 编码方式
+	args["signMethod"] = p.SignMethod         // 签名方法
+	args["txnType"] = p.TxnType               // 交易类型
+	args["txnSubType"] = p.TxnSubType         // 交易子类
+	args["bizType"] = p.BizType               // 业务类型
+	args["accessType"] = p.AccessType         // 接入类型
+	args["channelType"] = p.ChannelType       // 渠道类型
+	args["encryptCertId"] = mi.SerialNumber() // 验签证书序列号
+	args["certId"] = mi.SerialNumber()        // 证书序列号
+	args["merId"] = mi.MerID                  // 商户代码
+	args["orderId"] = p.OrderID               // 商户订单号,8-32位数字字母,不能含“-”或“_”
+	args["txnTime"] = p.TxnTime               // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
+
+	// 收款
+	if p.TxnAmt > 0 {
+		args["txnAmt"] = strconv.Itoa(p.TxnAmt)             // 交易金额
+		args["currencyCode"] = strconv.Itoa(p.CurrencyCode) // 交易币种
+		args["backUrl"] = p.BackURL                         // 后台通知地址
+	}
+
+	accNo, err = mi.Encrypt([]byte(ci.CardNo))
+	if err != nil {
+		return
+	}
+	args["accNo"] = base64Encode(accNo)
+
+	encrypted, err = mi.EncryptCustomerInfo(ci)
+	if err != nil {
+		return
+	}
+	args["customerInfo"] = ci.String(encrypted)
+
+	signature, err = mi.Sign([]byte(createLinkString(args, true, false)))
+	if err != nil {
+		return
+	}
+	args["signature"] = base64Encode(signature) // 签名
+
+	for k, v := range args {
+		values.Add(k, v)
+	}
+	body = values.Encode()
+
+	return
+}

+ 6 - 0
unionpay/const.go

@@ -0,0 +1,6 @@
+package unionpay
+
+const (
+	// CurrencyCodeCNY 人民币代码
+	CurrencyCodeCNY = 156
+)

+ 55 - 186
unionpay/entrust.go

@@ -3,209 +3,78 @@
 package unionpay
 package unionpay
 
 
 import (
 import (
-	"bytes"
-	"crypto/rsa"
-	"crypto/sha256"
-	"crypto/x509"
-	"encoding/base64"
-	"encoding/hex"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"net/url"
-	"sort"
-
-	"git.chuangxin1.com/cx/util/binding"
-
-	"git.chuangxin1.com/cx/util"
 )
 )
 
 
-// MerchantInfo 商家信息
-type MerchantInfo struct {
-	MerID   string // 商户代码
-	OrderID string // 商户订单号,8-32位数字字母,不能含“-”或“_”
-	TxnTime string // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
-}
-
-// CustomerInfo 客户信息
-type CustomerInfo struct {
-	CardNo       string `form:"cardNo" json:"cardNo" xml:"cardNo"`             // 卡号
-	Phone        string `form:"phone" json:"phone" xml:"phone"`                // 手机号
-	CertifTp     string `form:"certifTp" json:"certifTp" xml:"certifTp"`       // 证件类型, 01 - 身份证
-	CertifID     string `form:"certifId" json:"certifId" xml:"certifId"`       // 证件号,15位身份证不校验尾号,18位会校验尾号
-	CustomerName string `form:"customerNm" json:"customerNm" xml:"customerNm"` // 姓名
-	Cvn2         string `form:"cvn2" json:"cvn2" xml:"cvn2"`                   // 信用卡安全码
-	Expired      string `form:"expired" json:"expired" xml:"expired"`          // 有效期,YYMM格式,持卡人卡面印的是MMYY的,请注意代码设置倒一下
-}
-
-// ReqRes 请求返回
-type ReqRes struct {
-	TxnType     string `form:"txnType" json:"txnType" xml:"txnType"`
-	RespCode    string `form:"respCode" json:"respCode" xml:"respCode"`
-	ChannelType string `form:"channelType" json:"channelType" xml:"channelType"`
-	MerID       string `form:"merId" json:"merId" xml:"merId"`
-
-	TxnSubType     string `form:"txnSubType" json:"txnSubType" xml:"txnSubType"`
-	CustomerInfo   string `form:"customerInfo" json:"customerInfo" xml:"customerInfo"`
-	Version        string `form:"version" json:"version" xml:"version"`
-	SignPubKeyCert string `form:"signPubKeyCert" json:"signPubKeyCert" xml:"signPubKeyCert"`
-
-	SignMethod string `form:"signMethod" json:"signMethod" xml:"signMethod"`
-	AccNo      string `form:"accNo" json:"accNo" xml:"accNo"`
-	CertID     string `form:"certId" json:"certId" xml:"certId"`
-	Encoding   string `form:"encoding" json:"encoding" xml:"encoding"`
-
-	RespMsg       string `form:"respMsg" json:"respMsg" xml:"respMsg"`
-	BizType       string `form:"bizType" json:"bizType" xml:"bizType"`
-	EncryptCertID string `form:"encryptCertId" json:"encryptCertId" xml:"encryptCertId"`
-	Signature     string `form:"signature" json:"signature" xml:"signature"`
-
-	OrderID    string `form:"orderId" json:"orderId" xml:"orderId"`
-	TxnTime    string `form:"txnTime" json:"txnTime" xml:"txnTime"`
-	AccessType int    `form:"accessType" json:"accessType" xml:"accessType"`
-}
-
-// Sign 签名
-//   encryptCert 公钥证书路径
-//   signCert 签名证书路径
-func Sign(encryptCert, signCert string, mi MerchantInfo, ci CustomerInfo) (err error) {
-	var (
-		cert     = &x509.Certificate{}
-		priveKey = &rsa.PrivateKey{}
-		accNo    []byte
-	)
-	cert, _, err = GetCertificate(encryptCert)
-	if err != nil {
-		return
-	}
-
-	priveKey, err = GetPrivateKey(signCert)
-	if err != nil {
-		return
-	}
-	args := url.Values{}
-
-	args.Add("version", "5.1.0")                          // 版本号
-	args.Add("encoding", "utf-8")                         // 编码方式
-	args.Add("signMethod", "01")                          // 签名方法
-	args.Add("txnType", "72")                             // 交易类型
-	args.Add("txnSubType", "11")                          // 交易子类
-	args.Add("bizType", "000501")                         // 业务类型
-	args.Add("accessType", "0")                           // 接入类型
-	args.Add("channelType", "07")                         // 渠道类型
-	args.Add("certId", cert.SerialNumber.String())        // 证书序列号
-	args.Add("encryptCertId", cert.SerialNumber.String()) // 验签证书序列号
-
-	args.Add("merId", mi.MerID)     // 商户代码
-	args.Add("orderId", mi.OrderID) // 商户订单号,8-32位数字字母,不能含“-”或“_”
-	args.Add("txnTime", mi.TxnTime) // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
-
-	publickey := cert.PublicKey.(*rsa.PublicKey)
-	accNo, err = RSAEncrypt(publickey, []byte(ci.CardNo))
-	if err != nil {
-		return
-	}
-	args.Add("accNo", base64.StdEncoding.EncodeToString(accNo)) // 卡号,敏感信息加密方式
+// EntrustEstablish 建立委托
+func EntrustEstablish(mi *MerchantInfo, oi OrderInfo, ci CustomerInfo) (res ReqRespose, err error) {
+	/*
+			var (
+				accNo     []byte
+				encrypted []byte
+				signature []byte
+				values    = &url.Values{}
+				args      = make(map[string]string)
+			)
+
+			args["version"] = "5.1.0"                 // 版本号
+			args["encoding"] = "utf-8"                // 编码方式
+			args["signMethod"] = "01"                 // 签名方法
+			args["txnType"] = "72"                    // 交易类型
+			args["txnSubType"] = "11"                 // 交易子类
+			args["bizType"] = "000501"                // 业务类型
+			args["accessType"] = "0"                  // 接入类型
+			args["channelType"] = "07"                // 渠道类型
+			args["encryptCertId"] = mi.SerialNumber() // 验签证书序列号
+			args["certId"] = mi.SerialNumber()        // 证书序列号
+			args["merId"] = mi.MerID                  // 商户代码
+			args["orderId"] = oi.OrderID              // 商户订单号,8-32位数字字母,不能含“-”或“_”
+			args["txnTime"] = oi.TxnTime              // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
+
+			accNo, err = mi.Encrypt([]byte(ci.CardNo))
+			if err != nil {
+				return
+			}
+			args["accNo"] = base64Encode(accNo)
 
 
-	fmt.Println("CardNo", ci.CardNo)
+			encrypted, err = mi.EncryptCustomerInfo(ci)
+			if err != nil {
+				return
+			}
+			args["customerInfo"] = ci.String(encrypted)
 
 
-	var cardNo []byte
-	cardNo, err = RSADecrypt(priveKey, accNo)
-	fmt.Println("CardNo", ci.CardNo)
-	fmt.Println("De CardNo", string(cardNo))
+			signature, err = mi.Sign([]byte(createLinkString(args, true, false)))
+			if err != nil {
+				return
+			}
+			args["signature"] = base64Encode(signature) // 签名
 
 
-	// 持卡人身份信息,敏感信息加密方式
-	var phone []byte
-	phone, err = RSAEncrypt(publickey, []byte(ci.Phone))
-	if err != nil {
-		return
-	}
-	encryptedInfo := `phone=` + base64.StdEncoding.EncodeToString(phone)
-	if len(ci.Cvn2) > 0 {
-		var cvn2 []byte
-		cvn2, err = RSAEncrypt(publickey, []byte(ci.Cvn2))
-		if err != nil {
-			return
-		}
-		encryptedInfo += `cvn2=` + base64.StdEncoding.EncodeToString(cvn2)
-	}
-	if len(ci.Expired) > 0 {
-		var expired []byte
-		expired, err = RSAEncrypt(publickey, []byte(ci.Cvn2))
+			for k, v := range args {
+				values.Add(k, v)
+			}
+			res, err = post(BaseHost+ReqBackTrans, []byte(values.Encode()))
 		if err != nil {
 		if err != nil {
 			return
 			return
 		}
 		}
-		encryptedInfo += `expired=` + base64.StdEncoding.EncodeToString(expired)
-	}
-
-	kvs := `encryptedInfo=` + encryptedInfo
-	kvs += `&certifTp=` + ci.CertifTp
-	kvs += `&certifId=` + ci.CertifID
-	kvs += `&customerNm=` + ci.CustomerName // `&cvn2=&=expired`
-	info := `{` + kvs + `}`
-	args.Add("customerInfo", base64.StdEncoding.EncodeToString([]byte(info))) // 持卡人身份信息,敏感信息加密方式
-
-	keys := make([]string, 0)
-
-	for k := range args {
-		keys = append(keys, k)
-	}
-
-	sort.Strings(keys)
-
-	var s string
-	for _, k := range keys {
-		for _, value := range args[k] {
-			if value != "" {
-				s = s + "&" + k + "=" + value
-			}
-		}
-	}
+			// */
 
 
-	fmt.Println(s[1:])
-	hash := sha256.New()
-	hash.Write([]byte(s[1:]))
-	fmt.Println("sha1", hex.EncodeToString(hash.Sum(nil)))
+	var body string
+	args := newParams(oi, ci)
+	args.BizType = "000501" // 业务类型 代收产品
 
 
-	var signature []byte
-	signature, err = RSASign256(priveKey, hash.Sum(nil))
+	args.TxnType = "72"    // 交易类型 取值:72
+	args.TxnSubType = "11" // 交易子类, 11 建立委托关系
+	body, err = args.ToBody(mi, ci)
 	if err != nil {
 	if err != nil {
 		return
 		return
 	}
 	}
-	s = base64.StdEncoding.EncodeToString(signature)
-	fmt.Println("signature", s)
-	args.Add("signature", s) // 签名
-	fmt.Println(args.Encode())
-
-	headers := make(map[string]string)
-
-	headers["Accept"] = "text/html; charset=UTF-8"
-	headers["Accept-Encoding"] = "gzip, deflate, br"
-	headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
-	var msg util.Message
-	msg, err = util.Post(BaseURL+ReqBackTrans, "", "", headers, bytes.NewReader([]byte(args.Encode())))
-	if err != nil {
-		return
-	}
-	fmt.Println("*************************************")
-	fmt.Println(BaseURL + ReqBackTrans)
-	fmt.Println(msg.StatusCode)
-	fmt.Println(msg.Header)
-	fmt.Println(string(msg.Body))
-	fmt.Println("*************************************")
-	var (
-		res    ReqRes
-		values url.Values
-	)
-	values, err = url.ParseQuery(string(msg.Body))
+	res, err = post(BaseHost+ReqBackTrans, []byte(body))
 	if err != nil {
 	if err != nil {
-		fmt.Println(err)
 		return
 		return
 	}
 	}
-	err = binding.MapForm(&res, values)
-	fmt.Println(err)
-	if err == nil {
-		bs, _ := json.Marshal(&res)
-		fmt.Println(string(bs))
-	}
+	_, err = res.Ok()
+	bs, _ := json.Marshal(res)
+	fmt.Println(string(bs))
 	return
 	return
 }
 }

+ 14 - 56
unionpay/rsa.go

@@ -4,75 +4,33 @@ import (
 	"crypto"
 	"crypto"
 	"crypto/rand"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/rsa"
-	"crypto/x509"
-	"encoding/pem"
-	"errors"
-	"fmt"
-	"io/ioutil"
+	"crypto/sha256"
 )
 )
 
 
-// RSAEncrypt RSA 加密
-func RSAEncrypt(publicKey *rsa.PublicKey, src []byte) (data []byte, err error) {
+// encrypt RSA 加密
+func encrypt(publicKey *rsa.PublicKey, src []byte) (data []byte, err error) {
 	data, err = rsa.EncryptPKCS1v15(rand.Reader, publicKey, src)
 	data, err = rsa.EncryptPKCS1v15(rand.Reader, publicKey, src)
 	return
 	return
 }
 }
 
 
-// RSADecrypt RSA 解密
-func RSADecrypt(priveKey *rsa.PrivateKey, ciphertext []byte) (data []byte, err error) {
+// decrypt RSA 解密
+func decrypt(priveKey *rsa.PrivateKey, ciphertext []byte) (data []byte, err error) {
 	data, err = rsa.DecryptPKCS1v15(rand.Reader, priveKey, ciphertext)
 	data, err = rsa.DecryptPKCS1v15(rand.Reader, priveKey, ciphertext)
 
 
 	return
 	return
 }
 }
 
 
-// RSASign256 RSA sha256 签名
-func RSASign256(priveKey *rsa.PrivateKey, src []byte) (data []byte, err error) {
-	h := crypto.Hash.New(crypto.SHA256)
-	_, err = h.Write(src)
-	if err != nil {
-		return
-	}
-	hashed := h.Sum(nil)
-	data, err = rsa.SignPKCS1v15(rand.Reader, priveKey, crypto.SHA256, hashed)
+// sign RSA 签名
+func sign(priveKey *rsa.PrivateKey, src []byte) (data []byte, err error) {
+	hashed := sha256.Sum256(src)
+	//hashed = sha256.Sum256(hashed[:])
+	data, err = rsa.SignPKCS1v15(rand.Reader, priveKey, crypto.SHA256, hashed[:])
 	return
 	return
 }
 }
 
 
-// GetCertificate 获取证书信息
-func GetCertificate(filename string) (cert *x509.Certificate, data []byte, err error) {
-	data, err = ioutil.ReadFile(filename)
-	if err != nil {
-		return
-	}
-
-	block, _ := pem.Decode(data)
-	if block == nil {
-		err = errors.New(`pem Decode error`)
-		return
-	}
-
-	cert, err = x509.ParseCertificate(block.Bytes)
-	return
-}
-
-// GetPrivateKey 获取私钥信息
-func GetPrivateKey(filename string) (key *rsa.PrivateKey, err error) {
-	var (
-		bs      []byte
-		private interface{}
-	)
-	bs, err = ioutil.ReadFile(filename)
-	if err != nil {
-		fmt.Println(err)
-		return
-	}
-	block, _ := pem.Decode(bs)
-	if block == nil {
-		err = errors.New(`pem Decode error`)
-		return
-	}
-	private, err = x509.ParsePKCS8PrivateKey(block.Bytes)
-	if err != nil {
-		return
-	}
-	key = private.(*rsa.PrivateKey)
+// verify RSA 签名验证
+func verify(publicKey *rsa.PublicKey, src, sig []byte) (err error) {
+	hashed := sha256.Sum256(src)
+	err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sig)
 	return
 	return
 }
 }

+ 2 - 2
unionpay/url.go

@@ -1,8 +1,8 @@
 package unionpay
 package unionpay
 
 
 const (
 const (
-	// BaseURL 主机地址
-	BaseURL = `https://gateway.95516.com`
+	// BaseHost 主机地址
+	BaseHost = `https://gateway.95516.com`
 
 
 	// ReqBackTrans 建立委托:后台交易,只有同步应答
 	// ReqBackTrans 建立委托:后台交易,只有同步应答
 	ReqBackTrans = `/gateway/api/backTransReq.do`
 	ReqBackTrans = `/gateway/api/backTransReq.do`

+ 109 - 0
unionpay/util.go

@@ -0,0 +1,109 @@
+package unionpay
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/hex"
+	"fmt"
+	"net/http"
+	"net/url"
+	"sort"
+	"strings"
+	"time"
+
+	"git.chuangxin1.com/cx/util"
+	"git.chuangxin1.com/cx/util/binding"
+)
+
+const (
+	// TimeFmtNumeric format of time to numeric
+	TimeFmtNumeric = `20060102150405`
+)
+
+var (
+	base64Encode = base64.StdEncoding.EncodeToString
+	base64Decode = base64.StdEncoding.DecodeString
+	hexDump      = hex.EncodeToString
+)
+
+// TimeToNumericStr 时间转换为数字格式字符串
+func TimeToNumericStr(t time.Time) string {
+	return t.Format(TimeFmtNumeric)
+}
+
+// createLinkString unionpay sdk func
+func createLinkString(params map[string]string, isSort, isEncode bool) string {
+	var (
+		keys []string
+		ss   []string
+	)
+	if isSort {
+		for key := range params {
+			keys = append(keys, key)
+		}
+
+		sort.Strings(keys)
+		for _, v := range keys {
+			value := v + "="
+			if isEncode {
+				value += url.QueryEscape(params[v])
+			} else {
+				value += params[v]
+			}
+			ss = append(ss, value)
+		}
+	} else {
+		for k, v := range params {
+			value := k + "="
+			if isEncode {
+				value += url.QueryEscape(v)
+			} else {
+				value += v
+			}
+			ss = append(ss, value)
+		}
+	}
+	return strings.Join(ss, "&")
+}
+
+// POST 提交请求
+func post(uri string, body []byte) (res ReqRespose, err error) {
+	var msg util.Message
+	headers := make(map[string]string)
+
+	headers["Accept"] = "text/html; charset=UTF-8"
+	headers["Accept-Encoding"] = "gzip, deflate, br"
+	headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
+	msg, err = util.Post(uri, "", "", headers, bytes.NewReader(body))
+	if err != nil {
+		return
+	}
+
+	if msg.StatusCode != http.StatusOK {
+		err = fmt.Errorf("Request status code %d", msg.StatusCode)
+		return
+	}
+	/*
+		fmt.Println("*************************************")
+		fmt.Println(uri)
+		fmt.Println(msg.StatusCode)
+		fmt.Println(msg.Header)
+		fmt.Println(string(msg.Body))
+		fmt.Println("*************************************")
+		// */
+	var values url.Values
+	values, err = url.ParseQuery(string(msg.Body))
+	if err != nil {
+		return
+	}
+	err = binding.MapForm(&res, values)
+	/*
+		if err != nil {
+			return
+		}
+			bs, _ := json.Marshal(&res)
+			fmt.Println(string(bs))
+			fmt.Println("respCode", res.RespCode, "respMsg", res.RespMsg)
+			// */
+	return
+}