123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- // 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 // 交易币种
- NotifyURL string // 回调通知地址
- }
- // 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 string `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
- }
- // V3Ok 请求返回是否成功(Version 3.0)
- func (res *ReqRespose) V3Ok() (ok bool, err error) {
- if res.RespCode == "00" {
- 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) // 交易币种
- }
- if len(p.BackURL) > 10 { // http[s]://
- 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
- }
|