client.go 11 KB

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