package tonglianpayapi import ( "crypto" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "fmt" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" "net/http" "sort" "strings" ) const ( //RSA私钥(非JAVA适用) RSAPrivateKeyNotJava = `MIIEpAIBAAKCAQEAx9qhvqwyZ5hUKoxErxjgOgpjipbmJr20tGCtd/7UXCksVE9S/JWF6+LuT9jKAQpDDU4Dniu5mpwp6vfz2S7D51bO+S2mmSyiV6//NHpWZJwP9Lsb2fbU0tJUNiXGx3P79sA7ZDi5hkho59CX+tOr/gVDKXTb4Rbyj50+N+Rxt3i/Kd4Sf8AMRodP4SxcOjS4pbmwLFcD5H35czvXSRBQkHAptJ6keiYJLmi2ylsLWnBgf0BZYp04i5EVxiUT389ji7WNOUpNedb3wjOio877VatNm4XIZx0BFZVF8ZFEzEJkVXRAxRlR71nl1uKpNPVqfPQZ8pY3RrUmCxOwnJl5vQIDAQABAoIBAB3U4jjabkmtYL7bIjN6xJmVTGd2/9K/lXYpSitzs9Iv6SiKkKoYTZ24yXbMttZx6DFXuE2HXFSaQ92JdnIwO1jQSePC7y/FDFSHdlIogrXQ38bZmR4vbHQtphlRCTtjcjRSXGso1nSXYWVc6xqrNuybb3uEMIAIU1uhjpR8Ooc2sMfmpNdqCS5SKngIsRj5FQZzBPUoBiAlKiYck/UJzUgdVyxz5hpaFqB2V6VciD3E/PSa6OtSTPVO7a1KUD1rmvADZTmAYSqYwRBqZxkb42MNDtOW8YqRFC/rTlSQOo76XvT0AGSY2ZvFC89LrIZc9hF76k8OfCi8XjrwrMKgbpcCgYEA6/WiwthWy41Ch9R9xCubkPYfulNjq9CKbYyWnmmARnnhM5iRYJidqyfw1IbSVVm9Qy3o3VBnYocOTRJUTmOmpQgWqI4t/HjCZiFZeKF30F9ytl1urCbkXYWA2lygf2ZUYiCt3/CNC0bct4nZa42sGu3TXYZKcpVqlZT6tWywu8sCgYEA2NP4Zz5zJGzskhVdsEJbL7ttv/yX7X/g6AW3IhcI108bWfoA+i6YjbDsC8aftgIHPheaW/6n+oirDwoYdgrWa2/eCIKBo5uchtDmUb8qIXog1RbryKqOevLDkGRlJ8NAOIZfGwjT3pfhbtM+wvH5PCFBywoMlxz2sWZ2x4Vzf5cCgYEAza8Jeh9rSXSRkiXAm8gHi42AisM2FwdqI6RxhxUgJE8J6BgOYc2nYxMl85yyrIPVX0Idww3bkR95b+WSZ+Kl2SX72mJV48bAbpaTj3vxHUqWjDFVz+r3Fi7R64biwStKU195McRWroXO0I7xX7fXVoIJxXTSYJ+ukUWUZaGhTZECgYEAuHv6IVYYP8jRrCWztjFvRniRk8VGOxQP9zpNrBqvMgqjufWl+TfGIuCCpi5UW1b0dJc+hcFxiQ/Zg41SbLUh5P2ki9cGmH7hOi/pl2owXZV88/FxoiXD3sZJMMTK8H8HWFC0ANuM8RqG+3WPM+0P42JkiW2+cqB5IU2OCIr6T3cCgYBSLkui8zbMJB0TfE+KGxdm3e+sX26ZE2ZqQmTfJoRcxGgkWC7oY8HdIwws1uFeO38C5SMkkT8YlJghAjIIsfIUuCWQc5tBwvcb5f78/iJRUVgHXl3dH+9HvJKh/I4NeTIX6NTlaOrkUjgnkLLWbU7+b+3SCsVnvKH7uEm5cH0Ejg==` //RSA公钥 RSAPublicKey = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9qhvqwyZ5hUKoxErxjgOgpjipbmJr20tGCtd/7UXCksVE9S/JWF6+LuT9jKAQpDDU4Dniu5mpwp6vfz2S7D51bO+S2mmSyiV6//NHpWZJwP9Lsb2fbU0tJUNiXGx3P79sA7ZDi5hkho59CX+tOr/gVDKXTb4Rbyj50+N+Rxt3i/Kd4Sf8AMRodP4SxcOjS4pbmwLFcD5H35czvXSRBQkHAptJ6keiYJLmi2ylsLWnBgf0BZYp04i5EVxiUT389ji7WNOUpNedb3wjOio877VatNm4XIZx0BFZVF8ZFEzEJkVXRAxRlR71nl1uKpNPVqfPQZ8pY3RrUmCxOwnJl5vQIDAQAB` //RSA私钥(JAVA适用) RSAPrivateKeyJava = `MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDH2qG+rDJnmFQqjESvGOA6CmOKluYmvbS0YK13/tRcKSxUT1L8lYXr4u5P2MoBCkMNTgOeK7manCnq9/PZLsPnVs75LaaZLKJXr/80elZknA/0uxvZ9tTS0lQ2JcbHc/v2wDtkOLmGSGjn0Jf606v+BUMpdNvhFvKPnT435HG3eL8p3hJ/wAxGh0/hLFw6NLilubAsVwPkfflzO9dJEFCQcCm0nqR6JgkuaLbKWwtacGB/QFlinTiLkRXGJRPfz2OLtY05Sk151vfCM6KjzvtVq02bhchnHQEVlUXxkUTMQmRVdEDFGVHvWeXW4qk09Wp89BnyljdGtSYLE7CcmXm9AgMBAAECggEAHdTiONpuSa1gvtsiM3rEmZVMZ3b/0r+VdilKK3Oz0i/pKIqQqhhNnbjJdsy21nHoMVe4TYdcVJpD3Yl2cjA7WNBJ48LvL8UMVId2UiiCtdDfxtmZHi9sdC2mGVEJO2NyNFJcayjWdJdhZVzrGqs27Jtve4QwgAhTW6GOlHw6hzawx+ak12oJLlIqeAixGPkVBnME9SgGICUqJhyT9QnNSB1XLHPmGloWoHZXpVyIPcT89Jro61JM9U7trUpQPWua8ANlOYBhKpjBEGpnGRvjYw0O05bxipEUL+tOVJA6jvpe9PQAZJjZm8ULz0ushlz2EXvqTw58KLxeOvCswqBulwKBgQDr9aLC2FbLjUKH1H3EK5uQ9h+6U2Or0IptjJaeaYBGeeEzmJFgmJ2rJ/DUhtJVWb1DLejdUGdihw5NElROY6alCBaoji38eMJmIVl4oXfQX3K2XW6sJuRdhYDaXKB/ZlRiIK3f8I0LRty3idlrjawa7dNdhkpylWqVlPq1bLC7ywKBgQDY0/hnPnMkbOySFV2wQlsvu22 //Jftf+DoBbciFwjXTxtZ+gD6LpiNsOwLxp+2Agc+F5pb/qf6iKsPChh2CtZrb94IgoGjm5yG0OZRvyoheiDVFuvIqo568sOQZGUnw0A4hl8bCNPel+Fu0z7C8fk8IUHLCgyXHPaxZnbHhXN/lwKBgQDNrwl6H2tJdJGSJcCbyAeLjYCKwzYXB2ojpHGHFSAkTwnoGA5hzadjEyXznLKsg9VfQh3DDduRH3lv5ZJn4qXZJfvaYlXjxsBulpOPe/EdSpaMMVXP6vcWLtHrhuLBK0pTX3kxxFauhc7QjvFft9dWggnFdNJgn66RRZRloaFNkQKBgQC4e/ohVhg/yNGsJbO2MW9GeJGTxUY7FA/3Ok2sGq8yCqO59aX5N8Yi4IKmLlRbVvR0lz6FwXGJD9mDjVJstSHk/aSL1waYfuE6L+mXajBdlXzz8XGiJcPexkkwxMrwfwdYULQA24zxGob7dY8z7Q/jYmSJbb5yoHkhTY4IivpPdwKBgFIuS6LzNswkHRN8T4obF2bd76xfbpkTZmpCZN8mhFzEaCRYLuhjwd0jDCzW4V47fwLlIySRPxiUmCECMgix8hS4JZBzm0HC9xvl/vz+IlFRWAdeXd0f70e8kqH8jg15Mhfo1OVo6uRSOCeQstZtTv5v7dIKxWe8ofu4SblwfQSO` //SM2公钥 SM2PublicKey = `MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE7yCR6yp3jPOgBTqWqE9D3ugudcsi4epjq3EJ6mq3Y0nrR489Pld7V5yk+QuH2SUZMFmu46/adlnN/+cd496lkg==` //SM2私钥 SM2PrivateKey = `MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg1Rj + skv1B3dGwe2wGy9OF6N4vqxAGM44MR6e2kWTxhegCgYIKoEcz1UBgi2hRANCAATvIJHrKneM86AFOpaoT0Pe6C51yyLh6mOrcQnqardjSetHjz0 + V3tXnKT5C4fZJRkwWa7jr9p2Wc3/5x3j3qWS` // 测试私钥 cusRsaPrivateTestKey = `MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJgHMGYsspghvP+yCbjLG43CkZuQ3YJyDcmEKxvmgblITfmiTPx2b9Y2iwDT9gnLGExTDm1BL2A8VzMobjaHfiCmTbDctu680MLmpDDkVXmJOqdlXh0tcLjhN4+iDA2KkRqiHxsDpiaKT6MMBuecXQbJtPlVc1XjVhoUlzUgPCrvAgMBAAECgYAV9saYTGbfsdLOF5kYo0dve1JxaO7dFMCcgkV+z2ujKtNmeHtU54DlhZXJiytQY5Dhc10cjb6xfFDrftuFcfKCaLiy6h5ETR8jyv5He6KH/+X6qkcGTkJBYG1XvyyFO3PxoszQAs0mrLCqq0UItlCDn0G72MR9/NuvdYabGHSzEQJBAMXB1/DUvBTHHH4LiKDiaREruBb3QtP72JQS1ATVXA2v6xJzGPMWMBGQDvRfPvuCPVmbHENX+lRxMLp39OvIn6kCQQDEzYpPcuHW/7h3TYHYc+T0O6z1VKQT2Mxv92Lj35g1XqV4Oi9xrTj2DtMeV1lMx6n/3icobkCQtuvTI+AcqfTXAkB6bCz9NwUUK8sUsJktV9xJN/JnrTxetOr3h8xfDaJGCuCQdFY+rj6lsLPBTnFUC+Vk4mQVwJIE0mmjFf22NWW5AkAmsVaRGkAmui41Xoq52MdZ8WWm8lY0BLrlBJlvveU6EPqtcZskWW9KiU2euIO5IcRdpvrB6zNMgHpLD9GfMRcPAkBUWOV/dH13v8V2Y/Fzuag/y5k3/oXi/WQnIxdYbltad2xjmofJ7DbB7MJqiZZD8jlr8PCZPwRNzc5ntDStc959` // 回调地址 OnlinePayCallbackUrl = "https://callback.jxc4.com/tongLian/onLinePay" // 接口地址 OnlinePayUrl = `https://vsp.allinpay.com/apiweb/unitorder/scanqrpay` // 扫码支付 OnLineTransactionQuery = `https://vsp.allinpay.com/apiweb/tranx/query` // 交易查询,查询订单的支付状态 OnLineRefund = `https://vsp.allinpay.com/apiweb/tranx/refund` // 同一退款接口 OnLineAddTerm = `https://vsp.allinpay.com/cusapi/merchantapi/addterm` // 添加设备终端 OnLineQuerYTerm = `https://vsp.allinpay.com/cusapi/merchantapi/qryterm` // 查询设备终端 ) const ( EncryptionRsa = "RSA" // 加密方式 ScannerOperrationStart = "00" // 扫码枪本次操作类型-新增 ScannerOperrationLogOff = "02" // 扫码枪本次操作类型-注销 ScannerOperrationUpdate = "01" // 扫码枪本次操作类型-修改 ) var ( TransactionStatus = map[string]string{ "0000": "交易成功", "1001": "交易不存在", "2008": "交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易", "2000": "交易处理中,请查询交易,如果是实时交易(例如刷卡支付,交易撤销,退货),建议每隔一段时间(10秒)查询交易", "3888": "流水号重复", "3099": "渠道商户错误", "3014": "交易金额小于应收手续费", "3031": "校验实名信息失败", "3088": "交易未支付", "3089": "撤销异常,如已影响资金24小时内会做差错退款处理", "3050": "交易已被撤销", "3999": "1", "3889": "1", "3045": "1", } ) // CreateOnlineBankOrder 网银创建订单(扫码枪,扫二维码支付) func (a *API) CreateOnlineBankOrder(param *OnlinePayParam) (statusCode, trxid, errRemake string, err error) { onlineReq := utils.Struct2Map(param, "", false) result, err := a.AccessAPI2(OnlinePayUrl, onlineReq) if err != nil { return "", "", "", err } // 请求接口成功后通联系统返回JSON字符串数据,把除sign之外的其他所有非空字段组成键值对,进行同样的排序和组装处理,并用通联的公钥进行验签 // 暂时不考虑验签 if result["retcode"].(string) != "SUCCESS" { return "", "", "", fmt.Errorf(result["retmsg"].(string)) } payInfo := &OnlinePayRetMsg{} if err = utils.Map2StructByJson(result, payInfo, true); err != nil { return "", "", "", err } return payInfo.Trxstatus, payInfo.Trxid, payInfo.Errmsg, nil } // QueryPayTransaction 订单交易状态查询 func (a *API) QueryPayTransaction(param *PayTransactionStatusQuery) (statusCode, errRemake string, err error) { onlineReq := utils.Struct2Map(param, "", false) result, err := a.AccessAPI2(OnLineTransactionQuery, onlineReq) if err != nil { return "", "", err } if result["retcode"].(string) != "SUCCESS" { return "", "", fmt.Errorf(result["retmsg"].(string)) } payInfo := &PayTransactionStatusResult{} if err = utils.Map2StructByJson(result, payInfo, true); err != nil { return "", "", err } return payInfo.Trxstatus, payInfo.Errmsg, nil } // OrderRefund 退单 func (a *API) OrderRefund(param *OnLineOrderRefundParam) (statusCode, errRemake string, err error) { onlineReq := utils.Struct2Map(param, "", false) result, err := a.AccessAPI2(OnLineRefund, onlineReq) if err != nil { return "", "", err } if result["retcode"].(string) != "SUCCESS" { return "", "", fmt.Errorf(result["retmsg"].(string)) } payInfo := &OnLineOrderRefundResult{} if err = utils.Map2StructByJson(result, payInfo, true); err != nil { return "", "", err } return payInfo.Trxstatus, payInfo.Errmsg, nil } // TLAddTerm 采集门店终端设备信息 func (a *API) TLAddTerm(param *AddTermReq) error { onlineReq := utils.Struct2Map(param, "", false) result, err := a.AccessAPI2(OnLineAddTerm, onlineReq) if err != nil { return err } if result["retcode"].(string) != "SUCCESS" { return fmt.Errorf(result["retmsg"].(string)) } return nil } // TLQueryTerm 采集门店终端设备信息查询 func (a *API) TLQueryTerm(param *AddTermQuery) (string, error) { onlineReq := utils.Struct2Map(param, "", false) result, err := a.AccessAPI2(OnLineQuerYTerm, onlineReq) if err != nil { return "", err } if result["retcode"].(string) != "SUCCESS" { return "", fmt.Errorf(result["retmsg"].(string)) } return fmt.Sprintf("微信报备:%s,阿里报备:%s", result["wxmsg"].(string), result["almsg"].(string)), nil } func (a *API) AccessAPI2(action string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { params := make(map[string]interface{}) params["appid"] = a.appID params["cusid"] = a.cusID if action != OnLineAddTerm { params["randomstr"] = utils.GetUUID() } params["version"] = "12" params = utils.MergeMaps(params, bizParams) sign, err := a.signParamRSA(params, RSAPrivateKeyNotJava) if err != nil { return nil, err } params["sign"] = sign err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { request, _ := http.NewRequest(http.MethodPost, action, strings.NewReader(utils.Map2URLValues(params).Encode())) request.Header.Set("charset", "UTF-8") request.Header.Set("Content-Type", "application/x-www-form-urlencoded") return request }, a.config, func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { if jsonResult1 == nil { return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") } if err == nil { returnCode := utils.Interface2String(jsonResult1["retcode"]) if returnCode != OnlinePayStatusSuccess { errLevel = platformapi.ErrLevelGeneralFail err = utils.NewErrorCode(utils.Interface2String(jsonResult1["retcode"])+utils.Interface2String(jsonResult1["retmsg"]), returnCode) } retVal = jsonResult1 } return errLevel, err }) return retVal, err } func (a *API) signParamRSA(params map[string]interface{}, RSAPrivate string) (sig string, err error) { decodeString, err := base64.StdEncoding.DecodeString(RSAPrivate) if err != nil { return "", err } private, err := x509.ParsePKCS1PrivateKey(decodeString) //之前看java demo中使用的是pkcs8 if err != nil { return "", err } var valueList []string for k, v := range params { if k != sigKey { if str := fmt.Sprint(v); str != "" { valueList = append(valueList, fmt.Sprintf("%s=%s", k, str)) } } } sort.Sort(sort.StringSlice(valueList)) context := strings.Join(valueList, "&") h := crypto.Hash.New(crypto.SHA1) //进行SHA1的散列 h.Write([]byte(context)) hashed := h.Sum(nil) // 进行rsa加密签名 signedData, err := rsa.SignPKCS1v15(rand.Reader, private, crypto.SHA1, hashed) if err != nil { return "", err } signData := base64.StdEncoding.EncodeToString(signedData) return signData, nil }