client.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // sdk v5.1.0
  2. // https://open.unionpay.com/tjweb/api/list
  3. package unionpay
  4. import (
  5. "crypto/rsa"
  6. "crypto/sha256"
  7. "crypto/x509"
  8. "encoding/hex"
  9. "errors"
  10. "net/url"
  11. "strconv"
  12. )
  13. // MerchantInfo 商家信息
  14. type MerchantInfo struct {
  15. MerID string // 商户代码
  16. Password string // 签名证书密码
  17. Cert *x509.Certificate // 加密证书
  18. PublicKey *rsa.PublicKey // 加密 Key
  19. PrivateKey *rsa.PrivateKey // 签名 Key
  20. }
  21. // OrderInfo 订单信息
  22. type OrderInfo struct {
  23. TxnType string // 交易类型
  24. TxnSubType string // 交易子类
  25. OrderID string // 商户订单号,8-32位数字字母,不能含“-”或“_”
  26. // TxnTime 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
  27. TxnTime string
  28. TxnAmt int // 交易金额, 单位: 分
  29. CurrencyCode int // 交易币种
  30. NotifyURL string // 回调通知地址
  31. }
  32. // CustomerInfo 客户信息
  33. type CustomerInfo struct {
  34. CardNo string `form:"cardNo" json:"cardNo" xml:"cardNo"` // 卡号
  35. Phone string `form:"phone" json:"phone" xml:"phone"` // 手机号
  36. CertifTp string `form:"certifTp" json:"certifTp" xml:"certifTp"` // 证件类型, 01 - 身份证
  37. CertifID string `form:"certifId" json:"certifId" xml:"certifId"` // 证件号,15位身份证不校验尾号,18位会校验尾号
  38. CustomerName string `form:"customerNm" json:"customerNm" xml:"customerNm"` // 姓名
  39. Cvn2 string `form:"cvn2" json:"cvn2" xml:"cvn2"` // 信用卡安全码
  40. Expired string `form:"expired" json:"expired" xml:"expired"` // 有效期,YYMM格式,持卡人卡面印的是MMYY的,请注意代码设置倒一下
  41. }
  42. // ReqParams 请求参数
  43. type ReqParams struct {
  44. Version string `form:"version" json:"version" xml:"version"` // 版本号
  45. Encoding string `form:"encoding" json:"encoding" xml:"encoding"` // 编码方式
  46. SignMethod string `form:"signMethod" json:"signMethod" xml:"signMethod"` // 签名方法
  47. TxnType string `form:"txnType" json:"txnType" xml:"txnType"` // 交易类型
  48. TxnSubType string `form:"txnSubType" json:"txnSubType" xml:"txnSubType"` // 交易子类
  49. BizType string `form:"bizType" json:"bizType" xml:"bizType"` // 业务类型
  50. AccessType string `form:"accessType" json:"accessType" xml:"accessType"` // 接入类型
  51. ChannelType string `form:"channelType" json:"channelType" xml:"channelType"` // 渠道类型
  52. CurrencyCode int `form:"currencyCode" json:"currencyCode" xml:"currencyCode"` // 交易币种
  53. CertID string `form:"certId" json:"certId" xml:"certId"` // 签名证书序列号
  54. EncryptCertID string `form:"encryptCertId" json:"encryptCertId" xml:"encryptCertId"` // 验签证书序列号
  55. MerID string `form:"merId" json:"merId" xml:"merId"` // 商户代码
  56. OrderID string `form:"orderId" json:"orderId" xml:"orderId"` // 商户订单号
  57. TxnTime string `form:"txnTime" json:"txnTime" xml:"txnTime"` // 订单发送时间
  58. TxnAmt int `form:"txnAmt" json:"txnAmt" xml:"txnAmt"` // 交易金额,单位分
  59. AccNo string `form:"accNo" json:"accNo" xml:"accNo"` // 卡号,敏感信息加密
  60. Signature string `form:"signature" json:"signature" xml:"signature"` // 签名
  61. BackURL string `form:"backUrl" json:"backUrl" xml:"backUrl"` // 后台通知地址
  62. // 请求方保留域,
  63. // 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。
  64. // 出现部分特殊字符时可能影响解析,请按下面建议的方式填写:
  65. // 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。
  66. // 'reqReserved' =>'透传信息1|透传信息2|透传信息3',
  67. // 2. 内容可能出现&={}[]"'符号时:
  68. // 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示);
  69. // 2) 如果对账文件没有显示要求,可做一下base64(如下)。
  70. // 注意控制数据长度,实际传输的数据长度不能超过1024位。
  71. // 查询、通知等接口解析时使用base64_decode解base64后再对数据做后续解析。
  72. // 'reqReserved' => base64_encode('任意格式的信息都可以'),
  73. ReqReserved string `form:"reqReserved" json:"reqReserved" xml:"reqReserved"` // 透传字段
  74. }
  75. // ReqRespose 请求返回数据
  76. type ReqRespose struct {
  77. Encoding string `form:"encoding" json:"encoding" xml:"encoding"`
  78. Version string `form:"version" json:"version" xml:"version"`
  79. RespCode string `form:"respCode" json:"respCode" xml:"respCode"` // 响应代码, 0 成功, > 0 错误
  80. RespMsg string `form:"respMsg" json:"respMsg" xml:"respMsg"`
  81. TxnType string `form:"txnType" json:"txnType" xml:"txnType"`
  82. ChannelType string `form:"channelType" json:"channelType" xml:"channelType"`
  83. CurrencyCode string `form:"currencyCode" json:"currencyCode" xml:"currencyCode"`
  84. TxnSubType string `form:"txnSubType" json:"txnSubType" xml:"txnSubType"`
  85. CustomerInfo string `form:"customerInfo" json:"customerInfo" xml:"customerInfo"`
  86. TxnAmt string `form:"txnAmt" json:"txnAmt" xml:"txnAmt"`
  87. SignPubKeyCert string `form:"signPubKeyCert" json:"signPubKeyCert" xml:"signPubKeyCert"`
  88. SignMethod string `form:"signMethod" json:"signMethod" xml:"signMethod"`
  89. AccNo string `form:"accNo" json:"accNo" xml:"accNo"`
  90. BackURL string `form:"backUrl" json:"backUrl" xml:"backUrl"`
  91. BizType string `form:"bizType" json:"bizType" xml:"bizType"`
  92. Signature string `form:"signature" json:"signature" xml:"signature"`
  93. OrderID string `form:"orderId" json:"orderId" xml:"orderId"`
  94. TxnTime string `form:"txnTime" json:"txnTime" xml:"txnTime"`
  95. AccessType int `form:"accessType" json:"accessType" xml:"accessType"`
  96. //MerID string `form:"merId" json:"merId" xml:"merId"`
  97. //CertID string `form:"certId" json:"certId" xml:"certId"`
  98. //EncryptCertID string `form:"encryptCertId" json:"encryptCertId" xml:"encryptCertId"`
  99. }
  100. // NewMerchantInfo 初始化商家信息
  101. func NewMerchantInfo(merID, certPath, keyPath, password string) (mi *MerchantInfo, err error) {
  102. mi = &MerchantInfo{}
  103. mi.MerID = merID
  104. mi.Password = password
  105. mi.PrivateKey, _, err = getPrivateCert(keyPath, password)
  106. if err != nil {
  107. return
  108. }
  109. mi.Cert, _, err = getPublicCert(certPath)
  110. if err != nil {
  111. return
  112. }
  113. mi.PublicKey = mi.Cert.PublicKey.(*rsa.PublicKey)
  114. return
  115. }
  116. func newParams(oi OrderInfo, ci CustomerInfo) *ReqParams {
  117. args := &ReqParams{}
  118. args.Version = "5.1.0"
  119. args.Encoding = "utf-8"
  120. args.SignMethod = "01" // 签名方法, 01 RSA签名
  121. args.TxnType = oi.TxnType
  122. args.TxnSubType = oi.TxnSubType
  123. args.AccessType = "0" // 接入类型, 0:普通商户直连接入 1:收单机构接入 2:平台类商户接入
  124. args.ChannelType = "07" // 渠道类型 07-PC
  125. args.OrderID = oi.OrderID
  126. args.TxnTime = oi.TxnTime
  127. // 付款
  128. if oi.TxnAmt > 0 {
  129. args.TxnAmt = oi.TxnAmt
  130. args.CurrencyCode = oi.CurrencyCode
  131. }
  132. return args
  133. }
  134. // SerialNumber 证书序列号
  135. func (mi *MerchantInfo) SerialNumber() string {
  136. return mi.Cert.SerialNumber.String()
  137. }
  138. // Encrypt 加密
  139. func (mi *MerchantInfo) Encrypt(src []byte) ([]byte, error) {
  140. return encrypt(mi.PublicKey, src)
  141. }
  142. // Decrypt 解密
  143. func (mi *MerchantInfo) Decrypt(src []byte) ([]byte, error) {
  144. return decrypt(mi.PrivateKey, src)
  145. }
  146. // Sign 签名
  147. func (mi *MerchantInfo) Sign(src []byte) ([]byte, error) {
  148. hash := sha256.Sum256(src)
  149. return sign(mi.PrivateKey, []byte(hex.EncodeToString(hash[:])))
  150. }
  151. // Verify 签名验证
  152. func (mi *MerchantInfo) Verify(src, sig []byte) error {
  153. return verify(mi.PublicKey, src, sig)
  154. }
  155. // EncryptCustomerInfo 持卡人身份信息,敏感信息加密
  156. func (mi *MerchantInfo) EncryptCustomerInfo(ci CustomerInfo) ([]byte, error) {
  157. encryptedInfo := `phone=` + ci.Phone
  158. if len(ci.Cvn2) > 0 {
  159. encryptedInfo += `&cvn2=` + ci.Cvn2
  160. }
  161. if len(ci.Expired) > 0 {
  162. encryptedInfo += `&expired=` + ci.Expired
  163. }
  164. encrypted, err := mi.Encrypt([]byte(encryptedInfo))
  165. if err != nil {
  166. return nil, err
  167. }
  168. return encrypted, nil
  169. }
  170. func (ci *CustomerInfo) String(encrypted []byte) string {
  171. kvs := `encryptedInfo=` + base64Encode(encrypted)
  172. kvs += `&certifTp=` + ci.CertifTp
  173. kvs += `&certifId=` + ci.CertifID
  174. kvs += `&customerNm=` + ci.CustomerName // `&cvn2=&=expired`
  175. return base64Encode([]byte(`{` + kvs + `}`))
  176. }
  177. // Ok 请求返回是否成功
  178. func (res *ReqRespose) Ok() (ok bool, err error) {
  179. if res.RespCode == "0" {
  180. ok = true
  181. return
  182. }
  183. err = errors.New(res.RespMsg)
  184. return
  185. }
  186. // V3Ok 请求返回是否成功(Version 3.0)
  187. func (res *ReqRespose) V3Ok() (ok bool, err error) {
  188. if res.RespCode == "00" {
  189. ok = true
  190. return
  191. }
  192. err = errors.New(res.RespMsg)
  193. return
  194. }
  195. // ToBody 请求参数转换为 HTTP POST 格式数据
  196. func (p *ReqParams) ToBody(mi *MerchantInfo, ci CustomerInfo) (body string, err error) {
  197. var (
  198. accNo []byte
  199. encrypted []byte
  200. signature []byte
  201. values = &url.Values{}
  202. args = make(map[string]string)
  203. )
  204. args["version"] = p.Version // 版本号
  205. args["encoding"] = p.Encoding // 编码方式
  206. args["signMethod"] = p.SignMethod // 签名方法
  207. args["txnType"] = p.TxnType // 交易类型
  208. args["txnSubType"] = p.TxnSubType // 交易子类
  209. args["bizType"] = p.BizType // 业务类型
  210. args["accessType"] = p.AccessType // 接入类型
  211. args["channelType"] = p.ChannelType // 渠道类型
  212. args["encryptCertId"] = mi.SerialNumber() // 验签证书序列号
  213. args["certId"] = mi.SerialNumber() // 证书序列号
  214. args["merId"] = mi.MerID // 商户代码
  215. args["orderId"] = p.OrderID // 商户订单号,8-32位数字字母,不能含“-”或“_”
  216. args["txnTime"] = p.TxnTime // 订单发送时间,格式为YYYYMMDDhhmmss,取北京时间
  217. // 收款
  218. if p.TxnAmt > 0 {
  219. args["txnAmt"] = strconv.Itoa(p.TxnAmt) // 交易金额
  220. args["currencyCode"] = strconv.Itoa(p.CurrencyCode) // 交易币种
  221. }
  222. if len(p.BackURL) > 10 { // http[s]://
  223. args["backUrl"] = p.BackURL // 后台通知地址
  224. }
  225. accNo, err = mi.Encrypt([]byte(ci.CardNo))
  226. if err != nil {
  227. return
  228. }
  229. args["accNo"] = base64Encode(accNo)
  230. encrypted, err = mi.EncryptCustomerInfo(ci)
  231. if err != nil {
  232. return
  233. }
  234. args["customerInfo"] = ci.String(encrypted)
  235. signature, err = mi.Sign([]byte(createLinkString(args, true, false)))
  236. if err != nil {
  237. return
  238. }
  239. args["signature"] = base64Encode(signature) // 签名
  240. for k, v := range args {
  241. values.Add(k, v)
  242. }
  243. body = values.Encode()
  244. return
  245. }