Files
baseapi/platformapi/tonglianpayapi/tonglian_online_pay.go
邹宗楠 c248d8e1d1 1
2025-04-29 14:42:24 +08:00

254 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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` // 添加设备终端
)
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
}
type AddTermReq struct {
Orgid string `json:"orgid"` // 集团/代理商商户号 是 共享集团号/代理商参数时必填
Cusid string `json:"cusid"` // 商户号实际交易的商户号 否
Appid string `json:"appid"` // 应用ID平台分配的APPID 否
Version string `json:"version"` // 版本号接口版本号 可
Termno string `json:"termno"` // 8位数字,商户下唯一 否
Devicetype string `json:"devicetype"` // 设备类型 否
Termsn string `json:"termsn"` // 终端序列号 终端类型device_type填写为 02、03、04、05、06、08、09 或 10时必须填写终端序列号。
Operation string `json:"operation"` // 本次操作标识取值范围00新增01修改02注销注销时仅需上送termno字段
Termstate string `json:"termstate"` // 取值范围00启用01注销终端注销时非必填
Termaddress string `json:"termaddress"` // 终端地址
Signtype string `json:"signtype"`
Sign string `json:"sign"`
}
// TLQueryTerm 采集门店终端设备信息查询
func (a *API) TLQueryTerm(param *AddTermQuery) error {
onlineReq := utils.Struct2Map(param, "", false)
result, err := a.AccessAPI2("https://vsp.allinpay.com/cusapi/merchantapi/qryterm", onlineReq)
if err != nil {
return err
}
if result["retcode"].(string) != "SUCCESS" {
return fmt.Errorf(result["retmsg"].(string))
}
return nil
}
type AddTermQuery struct {
Orgid string `json:"orgid"` // 集团/代理商商户号 是 共享集团号/代理商参数时必填
Cusid string `json:"cusid"` // 商户号实际交易的商户号 否
Appid string `json:"appid"` // 应用ID平台分配的APPID 否
Version string `json:"version"` // 版本号接口版本号 可
Termno string `json:"termno"` // 8位数字,商户下唯一 否
Signtype string `json:"signtype"`
Sign string `json:"sign"`
Querytype string `json:"querytype"`
}
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
}