228 lines
12 KiB
Go
228 lines
12 KiB
Go
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
|
||
}
|