├── vendor └── .DS_Store ├── .gitignore ├── glide.yaml ├── go.mod ├── api ├── randstr.go ├── apicaller.go └── request.go ├── conf └── mockserver.app.conf ├── preinit └── preinit.go ├── mockserver.go ├── models ├── setup.go └── apicode.go ├── go.sum ├── routers └── router.go ├── controllers ├── MerchantController.go └── OuterController.go ├── LICENSE └── merchant ├── README.md └── README.html /vendor/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CYBAVO/sofa_mock_server/HEAD/vendor/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/cybavo/SOFA_MOCK_SERVER 2 | import: 3 | - package: github.com/astaxie/beego 4 | version: v1.11.1 5 | subpackages: 6 | - logs 7 | - orm 8 | - plugins/cors 9 | - package: github.com/mattn/go-sqlite3 10 | version: v1.10.0 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cybavo/SOFA_MOCK_SERVER 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/astaxie/beego v1.11.1 7 | github.com/mattn/go-sqlite3 v1.10.0 8 | ) 9 | 10 | require ( 11 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect 12 | google.golang.org/appengine v1.6.7 // indirect 13 | gopkg.in/yaml.v2 v2.4.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /api/randstr.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 9 | 10 | var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) 11 | 12 | func RandomString(length int) string { 13 | b := make([]byte, length) 14 | for i := range b { 15 | b[i] = charset[seededRand.Intn(len(charset))] 16 | } 17 | return string(b) 18 | } 19 | -------------------------------------------------------------------------------- /conf/mockserver.app.conf: -------------------------------------------------------------------------------- 1 | appname = SOFA_MOCK_SERVER 2 | 3 | ########Deploy flag############## 4 | #runmode prod or dev 5 | runmode = dev 6 | # 0: debug, stage. 1: release 7 | DeployMode = "${DEPLOY_MODE||0}" 8 | 9 | 10 | 11 | #########HTTP default bind all ip################### 12 | EnableHTTP = "${SRV_HTTP_ENABLE||true}" 13 | HTTPAddr = "${SRV_HTTP_ADDR||0.0.0.0}" 14 | HTTPPort = "${SRV_HTTP_PORT||8889}" 15 | 16 | copyrequestbody = true 17 | 18 | ####################MySQL 数据库配置########################### 19 | db_adapter=sqlite3 20 | 21 | ####################sqlite3 数据库配置########################### 22 | db_database=./runtime/mocksrv.db 23 | 24 | ####################WW Server URL########################### 25 | api_server_url="" 26 | -------------------------------------------------------------------------------- /preinit/preinit.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package preinit 12 | 13 | import ( 14 | "os" 15 | 16 | "github.com/astaxie/beego" 17 | "github.com/astaxie/beego/logs" 18 | ) 19 | 20 | const ( 21 | ConfigurationFile = "conf/mockserver.app.conf" 22 | ) 23 | 24 | func init() { 25 | logs.Info("LoadAppConfig %s", ConfigurationFile) 26 | err := beego.LoadAppConfig("ini", ConfigurationFile) 27 | 28 | if err != nil { 29 | logs.Error("LoadAppConfig,An error occurred:", err) 30 | os.Exit(1) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /mockserver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package main 12 | 13 | import ( 14 | "github.com/astaxie/beego" 15 | "github.com/astaxie/beego/plugins/cors" 16 | "github.com/cybavo/SOFA_MOCK_SERVER/models" 17 | _ "github.com/cybavo/SOFA_MOCK_SERVER/preinit" 18 | _ "github.com/cybavo/SOFA_MOCK_SERVER/routers" 19 | ) 20 | 21 | func main() { 22 | 23 | models.RegisterDataBase() 24 | models.RegisterModel() 25 | enableCORS() 26 | 27 | beego.Run() 28 | } 29 | 30 | func enableCORS() { 31 | v := beego.AppConfig.DefaultBool("enable_cors", true) 32 | if v { 33 | beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ 34 | AllowAllOrigins: true, 35 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 36 | AllowHeaders: []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Content-Type", "X-LOGIN-TOKEN"}, 37 | ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin"}, 38 | })) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /models/setup.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package models 12 | 13 | import ( 14 | "os" 15 | "path/filepath" 16 | "time" 17 | 18 | "github.com/astaxie/beego" 19 | "github.com/astaxie/beego/logs" 20 | "github.com/astaxie/beego/orm" 21 | _ "github.com/mattn/go-sqlite3" 22 | ) 23 | 24 | func GetMockDatabasePrefix() string { 25 | return beego.AppConfig.DefaultString("db_prefix", "mock_") 26 | } 27 | 28 | func RegisterDataBase() { 29 | logs.Info("Init Database Configuration.") 30 | 31 | adapter := beego.AppConfig.DefaultString("db_adapter", "") 32 | 33 | if adapter == "sqlite3" { 34 | orm.DefaultTimeLoc = time.UTC 35 | database := beego.AppConfig.DefaultString("db_database", "") 36 | 37 | dbPath := filepath.Dir(database) 38 | err := os.MkdirAll(dbPath, 0750) 39 | if err != nil { 40 | logs.Error("Failed to mkdir =>", err) 41 | } 42 | err = orm.RegisterDataBase("default", "sqlite3", database) 43 | 44 | if err != nil { 45 | logs.Error("sqlite3 Register Database Fail:", err) 46 | } 47 | } else { 48 | logs.Error("DB Non support type:", adapter) 49 | os.Exit(1) 50 | } 51 | logs.Info("Complete the database init:", adapter) 52 | } 53 | 54 | func RegisterModel() { 55 | 56 | orm.RegisterModelWithPrefix(GetMockDatabasePrefix(), 57 | new(APICode), 58 | ) 59 | err := orm.RunSyncdb("default", false, true) 60 | if err != nil { 61 | logs.Warning("Failed to RunSyncdb => ", err) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /models/apicode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package models 12 | 13 | import ( 14 | "github.com/astaxie/beego/logs" 15 | "github.com/astaxie/beego/orm" 16 | ) 17 | 18 | type APICode struct { 19 | APICodeID int64 `orm:"pk;auto;unique;column(api_code_id)" json:"api_code_id"` 20 | APICode string `orm:"unique;column(api_code)" json:"api_code"` 21 | ApiSecret string `orm:"unique;column(api_secret)" json:"api_secret"` 22 | WalletID int64 `orm:"unique;column(wallet_id)" json:"wallet_id"` 23 | } 24 | 25 | func (m *APICode) TableName() string { 26 | return "apicode" 27 | } 28 | 29 | func (m *APICode) TableEngine() string { 30 | return "INNODB" 31 | } 32 | 33 | func (m *APICode) TableNameWithPrefix() string { 34 | return GetMockDatabasePrefix() + m.TableName() 35 | } 36 | 37 | func SetAPICode(apiCodeObj *APICode) (err error) { 38 | o := orm.NewOrm() 39 | 40 | existedAPICodeObj, err := GetWalletAPICode(apiCodeObj.WalletID) 41 | if err != nil { 42 | apiCodeObj.APICodeID, err = o.Insert(apiCodeObj) 43 | if err != nil { 44 | logs.Error("Failed to insert API token =>", err) 45 | return 46 | } 47 | } else { 48 | apiCodeObj.APICodeID = existedAPICodeObj.APICodeID 49 | _, err = o.Update(apiCodeObj, "api_secret", "api_code") 50 | if err != nil { 51 | logs.Warning("Failed to update API secret =>", err) 52 | return 53 | } 54 | } 55 | logs.Info("Succeeded to set API token =>", apiCodeObj) 56 | return 57 | } 58 | 59 | func GetWalletAPICode(walletID int64) (apiCodeObj *APICode, err error) { 60 | o := orm.NewOrm() 61 | 62 | apiCodeObj = &APICode{} 63 | err = o.QueryTable(apiCodeObj.TableNameWithPrefix()). 64 | Filter("wallet_id", walletID). 65 | One(apiCodeObj) 66 | 67 | if err != nil { 68 | // try read-only API code 69 | err = o.QueryTable(apiCodeObj.TableNameWithPrefix()). 70 | Filter("wallet_id", 0). 71 | One(apiCodeObj) 72 | } 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /api/apicaller.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package api 12 | 13 | import ( 14 | "bytes" 15 | "crypto/sha256" 16 | "encoding/base64" 17 | "encoding/json" 18 | "errors" 19 | "fmt" 20 | "io/ioutil" 21 | "net/http" 22 | "sort" 23 | "strings" 24 | "time" 25 | 26 | "github.com/astaxie/beego" 27 | "github.com/astaxie/beego/logs" 28 | "github.com/cybavo/SOFA_MOCK_SERVER/models" 29 | ) 30 | 31 | var baseURL = beego.AppConfig.DefaultString("api_server_url", "") 32 | 33 | func buildChecksum(params []string, secret string, time int64, r string) string { 34 | params = append(params, fmt.Sprintf("t=%d", time)) 35 | params = append(params, fmt.Sprintf("r=%s", r)) 36 | sort.Strings(params) 37 | params = append(params, fmt.Sprintf("secret=%s", secret)) 38 | return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(params, "&")))) 39 | } 40 | 41 | func MakeRequest(targetID int64, method string, api string, params []string, postBody []byte) ([]byte, error) { 42 | if targetID < 0 || method == "" || api == "" { 43 | return nil, errors.New("invalid parameters") 44 | } 45 | 46 | client := &http.Client{} 47 | r := RandomString(8) 48 | if r == "" { 49 | return nil, errors.New("can't generate random byte string") 50 | } 51 | t := time.Now().Unix() 52 | url := fmt.Sprintf("%s%s?t=%d&r=%s", baseURL, api, t, r) 53 | if len(params) > 0 { 54 | url += fmt.Sprintf("&%s", strings.Join(params, "&")) 55 | } 56 | var req *http.Request 57 | var err error 58 | if postBody == nil || len(postBody) == 0 { 59 | req, err = http.NewRequest(method, url, nil) 60 | } else { 61 | req, err = http.NewRequest(method, url, bytes.NewReader(postBody)) 62 | params = append(params, string(postBody)) 63 | } 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | apiCodeObj, err := models.GetWalletAPICode(targetID) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | req.Header.Set("X-API-CODE", apiCodeObj.APICode) 74 | req.Header.Set("X-CHECKSUM", buildChecksum(params, apiCodeObj.ApiSecret, t, r)) 75 | if postBody != nil { 76 | req.Header.Set("Content-Type", "application/json") 77 | } 78 | req.Header.Set("User-Agent", "golang") 79 | logs.Debug("Request URL:", url) 80 | logs.Debug("\tX-CHECKSUM:\t", req.Header.Get("X-CHECKSUM")) 81 | 82 | res, err := client.Do(req) 83 | if err != nil { 84 | return nil, err 85 | } 86 | defer res.Body.Close() 87 | 88 | body, err := ioutil.ReadAll(res.Body) 89 | if err != nil { 90 | return nil, err 91 | } 92 | if res.StatusCode != 200 { 93 | result := &ErrorCodeResponse{} 94 | _ = json.Unmarshal(body, result) 95 | msg := fmt.Sprintf("%s, Error: %s", res.Status, result.String()) 96 | return body, errors.New(msg) 97 | } 98 | 99 | // 100 | // verify checksum of a successful response 101 | // 102 | checksum := res.Header.Get("X-CHECKSUM") 103 | payload := string(body) + apiCodeObj.ApiSecret 104 | sha, _ := CalcSHA256([]byte(payload)) 105 | checksumVerf := base64.URLEncoding.EncodeToString(sha) 106 | if checksum != checksumVerf { 107 | return nil, errors.New("mismatched response checksum") 108 | } 109 | return body, nil 110 | } 111 | 112 | func CalcSHA256(data []byte) (calculatedHash []byte, err error) { 113 | sha := sha256.New() 114 | _, err = sha.Write(data) 115 | if err != nil { 116 | return 117 | } 118 | calculatedHash = sha.Sum(nil) 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 2 | github.com/astaxie/beego v1.11.1 h1:6DESefxW5oMcRLFRKi53/6exzup/IR6N4EzzS1n6CnQ= 3 | github.com/astaxie/beego v1.11.1/go.mod h1:i69hVzgauOPSw5qeyF4GVZhn7Od0yG5bbCGzmhbWxgQ= 4 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= 5 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= 6 | github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= 7 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= 8 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= 9 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= 10 | github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= 11 | github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= 12 | github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 13 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 14 | github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 15 | github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= 16 | github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= 17 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 18 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 19 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 20 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 21 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 22 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 23 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 24 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 25 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 26 | github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= 27 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 28 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 29 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 31 | github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= 32 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 33 | github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= 34 | github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 35 | github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= 36 | golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 37 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 40 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 41 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 44 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 45 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 46 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 50 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 51 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 52 | -------------------------------------------------------------------------------- /api/request.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package api 12 | 13 | import ( 14 | "encoding/json" 15 | "fmt" 16 | ) 17 | 18 | type CommonResponse struct { 19 | Result int64 `json:"result"` 20 | } 21 | 22 | type ErrorCodeResponse struct { 23 | ErrMsg string `json:"error,omitempty"` 24 | ErrCode int `json:"error_code,omitempty"` 25 | Message string `json:"message,omitempty"` 26 | Blacklist map[string][]string `json:"blacklist,omitempty"` 27 | } 28 | 29 | func (m *ErrorCodeResponse) String() string { 30 | if len(m.Blacklist) > 0 { 31 | blacklist, _ := json.Marshal(m.Blacklist) 32 | return fmt.Sprintf("%s (msg:%s) (code:%d)", m.ErrMsg, string(blacklist), m.ErrCode) 33 | } 34 | return fmt.Sprintf("%s (msg:%s) (code:%d)", m.ErrMsg, m.Message, m.ErrCode) 35 | } 36 | 37 | type SetAPICodeRequest struct { 38 | APICode string `json:"api_code"` 39 | ApiSecret string `json:"api_secret"` 40 | } 41 | 42 | type CallbackType int 43 | 44 | const ( 45 | DepositCallback CallbackType = 1 46 | WithdrawCallback CallbackType = 2 47 | CollectCallback CallbackType = 3 48 | AirdropCallback CallbackType = 4 49 | ) 50 | 51 | type ProcessingState int8 52 | 53 | const ( 54 | ProcessingStateUndefined ProcessingState = -1 55 | ProcessingStateInPool ProcessingState = 0 56 | ProcessingStateInChain ProcessingState = 1 57 | ProcessingStateDone ProcessingState = 2 58 | ) 59 | 60 | type CallbackState int64 61 | 62 | const ( 63 | CallbackStateHolding CallbackState = 1 // Processing batch in KMS (1) 64 | CallbackStateInPool CallbackState = 2 // KMS process done, TXID created (2) 65 | CallbackStateInChain CallbackState = 3 // TXID in chain (3) 66 | CallbackStateFailed CallbackState = 5 // Failed (5) 67 | CallbackStateCancelled CallbackState = 8 // cancelled 68 | CallbackStateDropped CallbackState = 10 // Dropped 69 | CallbackStateInChainFailed CallbackState = 11 // Transaction Failed (11) 70 | ) 71 | 72 | const ( 73 | MerchantOrderStatePending = -1 74 | MerchantOrderStateSuccess = 0 75 | MerchantOrderStateExpired = 1 76 | MerchantOrderStateInsufficient = 2 77 | MerchantOrderStateExcess = 3 78 | MerchantOrderStateCancel = 4 79 | ) 80 | 81 | type CallbackStruct struct { 82 | Type int `json:"type"` 83 | Serial int64 `json:"serial"` 84 | OrderID string `json:"order_id"` 85 | Currency string `json:"currency"` 86 | TXID string `json:"txid"` 87 | BlockHeight int64 `json:"block_height"` 88 | TIndex int `json:"tindex"` 89 | VOutIndex int `json:"vout_index"` 90 | Amount string `json:"amount"` 91 | Fees string `json:"fees"` 92 | Memo string `json:"memo"` 93 | BroadcastAt int64 `json:"broadcast_at"` 94 | ChainAt int64 `json:"chain_at"` 95 | FromAddress string `json:"from_address"` 96 | ToAddress string `json:"to_address"` 97 | WalletID int64 `json:"wallet_id"` 98 | State CallbackState `json:"state"` 99 | ConfirmBlocks int64 `json:"confirm_blocks"` 100 | ProcessingState ProcessingState `json:"processing_state"` 101 | Addon map[string]interface{} `json:"addon"` 102 | Decimals int `json:"decimal"` 103 | } 104 | 105 | type MerchantCallbackStruct struct { 106 | MerchantID int64 `json:"merchant_id"` 107 | OrderID string `json:"order_id"` 108 | Currency string `json:"currency"` 109 | TXID string `json:"txid"` 110 | RecvAmount string `json:"recv_amount"` 111 | BroadcastAt int64 `json:"broadcast_at"` 112 | BlockHeight int64 `json:"block_height"` 113 | FromAddress string `json:"from_address"` 114 | ToAddress string `json:"to_address"` 115 | State int64 `json:"state"` 116 | Addon map[string]interface{} `json:"addon"` 117 | CurrencyBIP44 int64 `json:"currency_bip44"` 118 | TokenAddress string `json:"token_address"` 119 | Fee string `json:"fee"` 120 | Decimals int `json:"decimal"` 121 | FeeDecimals int `json:"fee_decimal"` 122 | } 123 | 124 | type RequestPaymentOrderRequest struct { 125 | Currency int64 `json:"currency"` 126 | TokenAddress string `json:"token_address"` 127 | Amount string `json:"amount"` 128 | Duration int64 `json:"duration"` 129 | Description string `json:"description"` 130 | RedirectURL string `json:"redirect_url"` 131 | OrderID string `json:"order_id"` 132 | } 133 | 134 | type QueryPaymentOrderResponse struct { 135 | Address string `json:"address"` 136 | State int8 `json:"state"` 137 | TXID string `json:"tx_id"` 138 | ExpiredTime int64 `json:"expired_time"` 139 | RedirectURL string `json:"redirect_url"` 140 | } 141 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package routers 12 | 13 | import ( 14 | "github.com/astaxie/beego" 15 | "github.com/cybavo/SOFA_MOCK_SERVER/controllers" 16 | ) 17 | 18 | func init() { 19 | beego.Router("/v1/mock/wallets/:wallet_id/apitoken", &controllers.OuterController{}, "POST:SetAPIToken") 20 | beego.Router("/v1/mock/wallets/:wallet_id/addresses", &controllers.OuterController{}, "POST:CreateDepositWalletAddresses") 21 | beego.Router("/v1/mock/wallets/:wallet_id/addresses", &controllers.OuterController{}, "GET:GetDepositWalletAddresses") 22 | beego.Router("/v1/mock/wallets/:wallet_id/addresses/label", &controllers.OuterController{}, "POST:UpdateDepositWalletAddressLabel") 23 | beego.Router("/v1/mock/wallets/:wallet_id/addresses/get_labels", &controllers.OuterController{}, "POST:GetDepositWalletAddressesLabel") 24 | beego.Router("/v1/mock/wallets/:wallet_id/receiver/addresses/verify", &controllers.OuterController{}, "POST:VerifyDepositAddresses") 25 | beego.Router("/v1/mock/wallets/:wallet_id/pooladdress", &controllers.OuterController{}, "GET:GetDepositWalletPoolAddress") 26 | beego.Router("/v1/mock/wallets/:wallet_id/pooladdress/balance", &controllers.OuterController{}, "GET:GetDepositWalletPoolAddressBalance") 27 | beego.Router("/v1/mock/wallets/:wallet_id/collection/notifications/manual", &controllers.OuterController{}, "POST:ResendDepositCollectionCallbacks") 28 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions", &controllers.OuterController{}, "POST:WithdrawAssets") 29 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions/:order_id/cancel", &controllers.OuterController{}, "POST:CancelWithdrawTransactions") 30 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions/:order_id", &controllers.OuterController{}, "GET:GetWithdrawTransactionState") 31 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions/:order_id/all", &controllers.OuterController{}, "GET:GetWithdrawTransactionStateAll") 32 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions/eventlog", &controllers.OuterController{}, "GET:GetTransactionEventLog") 33 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions", &controllers.OuterController{}, "GET:GetSenderTransactionHistory") 34 | beego.Router("/v1/mock/wallets/:wallet_id/sender/balance", &controllers.OuterController{}, "GET:GetWithdrawalWalletBalance") 35 | beego.Router("/v1/mock/wallets/:wallet_id/apisecret", &controllers.OuterController{}, "GET:GetTxAPITokenStatus") 36 | beego.Router("/v1/mock/wallets/:wallet_id/apisecret/activate", &controllers.OuterController{}, "POST:ActivateAPIToken") 37 | beego.Router("/v1/mock/wallets/:wallet_id/notifications", &controllers.OuterController{}, "GET:GetNotifications") 38 | beego.Router("/v1/mock/wallets/:wallet_id/notifications/get_by_id", &controllers.OuterController{}, "POST:GetCallbackBySerial") 39 | beego.Router("/v1/mock/wallets/:wallet_id/notifications/inspect", &controllers.OuterController{}, "POST:NotificationsInspect") 40 | beego.Router("/v1/mock/wallets/:wallet_id/receiver/notifications/txid/:txid/:vout_index", &controllers.OuterController{}, "GET:GetDepositCallback") 41 | beego.Router("/v1/mock/wallets/:wallet_id/sender/notifications/order_id/:order_id", &controllers.OuterController{}, "GET:GetWithdrawalCallback") 42 | beego.Router("/v1/mock/wallets/:wallet_id/transactions", &controllers.OuterController{}, "GET:GetTransactionHistory") 43 | beego.Router("/v1/mock/wallets/:wallet_id/blocks", &controllers.OuterController{}, "GET:GetWalletBlockInfo") 44 | beego.Router("/v1/mock/wallets/:wallet_id/addresses/invalid-deposit", &controllers.OuterController{}, "GET:GetInvalidDepositAddresses") 45 | beego.Router("/v1/mock/wallets/:wallet_id/info", &controllers.OuterController{}, "GET:GetWalletInfo") 46 | beego.Router("/v1/mock/wallets/:wallet_id/addresses/verify", &controllers.OuterController{}, "POST:VerifyAddresses") 47 | beego.Router("/v1/mock/wallets/:wallet_id/autofee", &controllers.OuterController{}, "POST:GetAutoFee") 48 | beego.Router("/v1/mock/wallets/:wallet_id/autofees", &controllers.OuterController{}, "POST:GetAutoFees") 49 | beego.Router("/v1/mock/wallets/:wallet_id/receiver/balance", &controllers.OuterController{}, "GET:GetDepositWalletBalance") 50 | beego.Router("/v1/mock/wallets/:wallet_id/vault/balance", &controllers.OuterController{}, "GET:GetVaultWalletBalance") 51 | beego.Router("/v1/mock/wallets/:wallet_id/addresses/contract_txid", &controllers.OuterController{}, "GET:GetDeployedContractCollectionAddresses") 52 | beego.Router("/v1/mock/wallets/:wallet_id/sender/transactions/acl", &controllers.OuterController{}, "POST:SetWithdrawalACL") 53 | beego.Router("/v1/mock/wallets/:wallet_id/sender/notifications/manual", &controllers.OuterController{}, "POST:ResendWithdrawalCallbacks") 54 | beego.Router("/v1/mock/wallets/:wallet_id/refreshsecret", &controllers.OuterController{}, "POST:RefreshSecret") 55 | beego.Router("/v1/mock/wallets/:wallet_id/sender/whitelist", &controllers.OuterController{}, "GET:GetSenderWhitelist") 56 | beego.Router("/v1/mock/wallets/:wallet_id/sender/whitelist", &controllers.OuterController{}, "POST:AddSenderWhitelist") 57 | beego.Router("/v1/mock/wallets/:wallet_id/sender/whitelist", &controllers.OuterController{}, "DELETE:RemoveSenderWhitelist") 58 | beego.Router("/v1/mock/wallets/:wallet_id/sender/whitelist/config", &controllers.OuterController{}, "GET:QuerySenderWhitelistConfig") 59 | beego.Router("/v1/mock/wallets/:wallet_id/sender/whitelist/check", &controllers.OuterController{}, "POST:CheckSenderWhitelist") 60 | beego.Router("/v1/mock/wallets/:wallet_id/signmessage", &controllers.OuterController{}, "POST:SignMessage") 61 | beego.Router("/v1/mock/wallets/:wallet_id/signtransaction", &controllers.OuterController{}, "POST:SignTransaction") 62 | beego.Router("/v1/mock/wallets/:wallet_id/contract/read", &controllers.OuterController{}, "GET:CallContractRead") 63 | beego.Router("/v1/mock/wallets/readonly/walletlist", &controllers.OuterController{}, "GET:GetReadOnlyWalletList") 64 | beego.Router("/v1/mock/wallets/readonly/walletlist/balances", &controllers.OuterController{}, "GET:GetReadOnlyWalletListBalances") 65 | beego.Router("/v1/mock/currency/prices", &controllers.OuterController{}, "GET:GetCurrencyPrices") 66 | beego.Router("/v1/mock/wallets/:wallet_id/receiver/get-balances", &controllers.OuterController{}, "POST:GetDelegatedBalances") 67 | beego.Router("/v1/mock/currency/:currency/contract/get-multiple-tokenuri", &controllers.OuterController{}, "POST:GetContractTokenMeta") 68 | beego.Router("/v1/mock/healthcheck", &controllers.OuterController{}, "GET:HealthCheck") 69 | 70 | beego.Router("/v1/mock/wallets/callback", &controllers.OuterController{}, "POST:Callback") 71 | beego.Router("/v1/mock/wallets/withdrawal/callback", &controllers.OuterController{}, "POST:WithdrawalCallback") 72 | 73 | beego.Router("/v1/mock/merchant/:merchant_id/apitoken", &controllers.MerchantController{}, "POST:SetAPIToken") 74 | beego.Router("/v1/mock/merchant/:merchant_id/order", &controllers.MerchantController{}, "POST:RequestPaymentOrder") 75 | beego.Router("/v1/mock/merchant/:merchant_id/order", &controllers.MerchantController{}, "GET:QueryPaymentOrder") 76 | beego.Router("/v1/mock/merchant/:merchant_id/order/duration", &controllers.MerchantController{}, "POST:UpdateOrderDuration") 77 | beego.Router("/v1/mock/merchant/:merchant_id/order", &controllers.MerchantController{}, "DELETE:CancelPaymentOrder") 78 | beego.Router("/v1/mock/merchant/:merchant_id/apisecret", &controllers.MerchantController{}, "GET:GetMerchantAPITokenStatus") 79 | beego.Router("/v1/mock/merchant/:merchant_id/apisecret/activate", &controllers.MerchantController{}, "POST:ActivateMerchantAPIToken") 80 | beego.Router("/v1/mock/merchant/:merchant_id/apisecret/refreshsecret", &controllers.MerchantController{}, "POST:RefreshMerchantSecret") 81 | beego.Router("/v1/mock/merchant/:merchant_id/notifications/manual", &controllers.MerchantController{}, "POST:ResendFailedMerchantCallbacks") 82 | 83 | beego.Router("/v1/mock/merchant/callback", &controllers.MerchantController{}, "POST:Callback") 84 | } 85 | -------------------------------------------------------------------------------- /controllers/MerchantController.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package controllers 12 | 13 | import ( 14 | "encoding/base64" 15 | "encoding/json" 16 | "errors" 17 | "fmt" 18 | "net/http" 19 | "net/url" 20 | "strconv" 21 | 22 | "github.com/astaxie/beego" 23 | "github.com/astaxie/beego/logs" 24 | "github.com/cybavo/SOFA_MOCK_SERVER/api" 25 | "github.com/cybavo/SOFA_MOCK_SERVER/models" 26 | ) 27 | 28 | type MerchantController struct { 29 | beego.Controller 30 | } 31 | 32 | func (c *MerchantController) getMerchantID() int64 { 33 | merchantID, err := strconv.ParseInt(c.Ctx.Input.Param(":merchant_id"), 10, 64) 34 | if err != nil { 35 | logs.Error("Invalid merchant ID =>", err) 36 | c.AbortWithError(http.StatusBadRequest, err) 37 | } 38 | return merchantID 39 | } 40 | 41 | func (c *MerchantController) AbortWithError(status int, err error) { 42 | resp := api.ErrorCodeResponse{ 43 | ErrMsg: err.Error(), 44 | ErrCode: status, 45 | } 46 | c.Data["json"] = resp 47 | c.Abort(strconv.Itoa(status)) 48 | } 49 | 50 | // @Title Set API token 51 | // @router /merchant/:merchant_id/apitoken [post] 52 | func (c *MerchantController) SetAPIToken() { 53 | defer c.ServeJSON() 54 | 55 | merchantID := c.getMerchantID() 56 | 57 | var request api.SetAPICodeRequest 58 | err := json.Unmarshal(c.Ctx.Input.RequestBody, &request) 59 | if err != nil { 60 | c.AbortWithError(http.StatusBadRequest, err) 61 | } 62 | 63 | apiCodeParams := models.APICode{ 64 | APICode: request.APICode, 65 | ApiSecret: request.ApiSecret, 66 | WalletID: merchantID, 67 | } 68 | err = models.SetAPICode(&apiCodeParams) 69 | if err != nil { 70 | logs.Error("SetAPICode failed", err) 71 | c.AbortWithError(http.StatusInternalServerError, err) 72 | } 73 | 74 | response := &api.CommonResponse{ 75 | Result: 1, 76 | } 77 | c.Data["json"] = response 78 | } 79 | 80 | // @Title Request a payment order 81 | // @router /merchant/:merchant_id/order [post] 82 | func (c *MerchantController) RequestPaymentOrder() { 83 | defer c.ServeJSON() 84 | 85 | req := api.RequestPaymentOrderRequest{} 86 | if err := json.Unmarshal([]byte(c.Ctx.Input.RequestBody), &req); err != nil { 87 | c.AbortWithError(http.StatusInternalServerError, err) 88 | } 89 | 90 | request := c.Ctx.Input.RequestBody 91 | if len(req.RedirectURL) > 0 { 92 | req.RedirectURL = url.QueryEscape(req.RedirectURL) 93 | } else { 94 | request, _ = json.Marshal(req) 95 | } 96 | 97 | merchantID := c.getMerchantID() 98 | resp, err := api.MakeRequest(merchantID, "POST", fmt.Sprintf("/v1/merchant/%d/order", merchantID), 99 | nil, request) 100 | if err != nil { 101 | logs.Error("RequestPaymentOrder failed", err) 102 | c.AbortWithError(http.StatusInternalServerError, err) 103 | } 104 | 105 | var m map[string]interface{} 106 | json.Unmarshal(resp, &m) 107 | c.Data["json"] = m 108 | } 109 | 110 | // @Title Query certain payment order 111 | // @router /merchant/:merchant_id/order [get] 112 | func (c *MerchantController) QueryPaymentOrder() { 113 | defer c.ServeJSON() 114 | 115 | merchantID := c.getMerchantID() 116 | resp, err := api.MakeRequest(merchantID, "GET", fmt.Sprintf("/v1/merchant/%d/order", merchantID), 117 | getQueryString(c.Ctx), nil) 118 | if err != nil { 119 | logs.Error("QueryPaymentOrder failed", err) 120 | c.AbortWithError(http.StatusInternalServerError, err) 121 | } 122 | 123 | res := api.QueryPaymentOrderResponse{} 124 | if err := json.Unmarshal(resp, &res); err != nil { 125 | c.AbortWithError(http.StatusInternalServerError, err) 126 | } 127 | 128 | if len(res.RedirectURL) > 0 { 129 | res.RedirectURL, _ = url.QueryUnescape(res.RedirectURL) 130 | resp, _ = json.Marshal(res) 131 | } 132 | 133 | var m map[string]interface{} 134 | json.Unmarshal(resp, &m) 135 | c.Data["json"] = m 136 | } 137 | 138 | // @Title Update duration of certain payment order 139 | // @router /merchant/:merchant_id/order/duration [post] 140 | func (c *MerchantController) UpdateOrderDuration() { 141 | defer c.ServeJSON() 142 | 143 | merchantID := c.getMerchantID() 144 | resp, err := api.MakeRequest(merchantID, "POST", fmt.Sprintf("/v1/merchant/%d/order/duration", merchantID), 145 | nil, c.Ctx.Input.RequestBody) 146 | if err != nil { 147 | logs.Error("UpdateOrderDuration failed", err) 148 | c.AbortWithError(http.StatusInternalServerError, err) 149 | } 150 | 151 | var m map[string]interface{} 152 | json.Unmarshal(resp, &m) 153 | c.Data["json"] = m 154 | } 155 | 156 | // @Title Update duration of certain payment order 157 | // @router /merchant/:merchant_id/order [delete] 158 | func (c *MerchantController) CancelPaymentOrder() { 159 | defer c.ServeJSON() 160 | 161 | merchantID := c.getMerchantID() 162 | resp, err := api.MakeRequest(merchantID, "DELETE", fmt.Sprintf("/v1/merchant/%d/order", merchantID), 163 | getQueryString(c.Ctx), nil) 164 | if err != nil { 165 | logs.Error("CancelPaymentOrder failed", err) 166 | c.AbortWithError(http.StatusInternalServerError, err) 167 | } 168 | 169 | var m map[string]interface{} 170 | json.Unmarshal(resp, &m) 171 | c.Data["json"] = m 172 | } 173 | 174 | // @Title Get merchant API token status 175 | // @router /merchant/:merchant_id/apisecret [get] 176 | func (c *MerchantController) GetMerchantAPITokenStatus() { 177 | defer c.ServeJSON() 178 | 179 | merchantID := c.getMerchantID() 180 | resp, err := api.MakeRequest(merchantID, "GET", fmt.Sprintf("/v1/merchant/%d/apisecret", merchantID), 181 | nil, nil) 182 | if err != nil { 183 | logs.Error("GetMerchantAPITokenStatus failed", err) 184 | c.AbortWithError(http.StatusInternalServerError, err) 185 | } 186 | 187 | var m map[string]interface{} 188 | json.Unmarshal(resp, &m) 189 | c.Data["json"] = m 190 | } 191 | 192 | // @Title Activate merchant API token 193 | // @router /merchant/:merchant_id/apisecret/activate [post] 194 | func (c *MerchantController) ActivateMerchantAPIToken() { 195 | defer c.ServeJSON() 196 | 197 | merchantID := c.getMerchantID() 198 | resp, err := api.MakeRequest(merchantID, "POST", fmt.Sprintf("/v1/merchant/%d/apisecret/activate", merchantID), 199 | nil, nil) 200 | if err != nil { 201 | logs.Error("ActivateMerchantAPIToken failed", err) 202 | c.AbortWithError(http.StatusInternalServerError, err) 203 | } 204 | 205 | var m map[string]interface{} 206 | json.Unmarshal(resp, &m) 207 | c.Data["json"] = m 208 | } 209 | 210 | // @Title Refresh merchant API code and secret 211 | // @router /merchant/:merchant_id/apisecret/refreshsecret [post] 212 | func (c *MerchantController) RefreshMerchantSecret() { 213 | defer c.ServeJSON() 214 | 215 | merchantID := c.getMerchantID() 216 | resp, err := api.MakeRequest(merchantID, "POST", fmt.Sprintf("/v1/merchant/%d/apisecret/refreshsecret", merchantID), 217 | nil, c.Ctx.Input.RequestBody) 218 | if err != nil { 219 | logs.Error("RefreshMerchantSecret failed", err) 220 | c.AbortWithError(http.StatusInternalServerError, err) 221 | } 222 | 223 | var m map[string]interface{} 224 | json.Unmarshal(resp, &m) 225 | c.Data["json"] = m 226 | } 227 | 228 | // @Title Resend failed callback 229 | // @router /merchant/:merchant_id/notifications/manual [post] 230 | func (c *MerchantController) ResendFailedMerchantCallbacks() { 231 | defer c.ServeJSON() 232 | 233 | merchantID := c.getMerchantID() 234 | resp, err := api.MakeRequest(merchantID, "POST", fmt.Sprintf("/v1/merchant/%d/notifications/manual", merchantID), 235 | nil, nil) 236 | if err != nil { 237 | logs.Error("ResendFailedMerchantCallbacks failed", err) 238 | c.AbortWithError(http.StatusInternalServerError, err) 239 | } 240 | 241 | var m map[string]interface{} 242 | json.Unmarshal(resp, &m) 243 | c.Data["json"] = m 244 | } 245 | 246 | // @Title Merchant callback 247 | // @router /merchant/callback [post] 248 | func (c *MerchantController) Callback() { 249 | var cb api.MerchantCallbackStruct 250 | err := json.Unmarshal(c.Ctx.Input.RequestBody, &cb) 251 | if err != nil { 252 | c.AbortWithError(http.StatusBadRequest, err) 253 | } 254 | 255 | apiCodeObj, err := models.GetWalletAPICode(cb.MerchantID) 256 | if err != nil { 257 | c.AbortWithError(http.StatusBadRequest, err) 258 | } 259 | 260 | checksum := c.Ctx.Input.Header("X-CHECKSUM") 261 | payload := string(c.Ctx.Input.RequestBody) + apiCodeObj.ApiSecret 262 | sha, _ := api.CalcSHA256([]byte(payload)) 263 | checksumVerf := base64.URLEncoding.EncodeToString(sha) 264 | 265 | if checksum != checksumVerf { 266 | c.AbortWithError(http.StatusBadRequest, errors.New("Bad checksum")) 267 | } 268 | 269 | logs.Debug("Merchant Callback => %s", c.Ctx.Input.RequestBody) 270 | 271 | if cb.State == api.MerchantOrderStateSuccess { 272 | } else if cb.State == api.MerchantOrderStateExpired { 273 | } else if cb.State == api.MerchantOrderStateInsufficient { 274 | } else if cb.State == api.MerchantOrderStateExcess { 275 | } else if cb.State == api.MerchantOrderStateCancel { 276 | } 277 | 278 | // reply 200 OK to confirm the callback has been processed 279 | c.Ctx.WriteString("OK") 280 | } 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /merchant/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Table of contents 3 | 4 | - [Get Started](#get-started) 5 | - [API Authentication](#api-authentication) 6 | - [Callback Integration](#callback-integration) 7 | - REST API 8 | - [Create Payment Order](#create-payment-order) 9 | - [Query Payment Order Status](#query-payment-order-status) 10 | - [Cancel Payment Order](#cancel-payment-order) 11 | - [Update Payment Order Expiration Duration](#update-payment-order-expiration-duration) 12 | - [Activate Merchant API Code](#activate-merchant-api-code) 13 | - [Query Merchant API Code Status](#query-merchant-api-code-status) 14 | - [Refresh Merchant API Code](#refresh-merchant-api-code) 15 | - Testing 16 | - [Mock Server](#mock-server) 17 | - [cURL Testing Commands](#curl-testing-commands) 18 | - [Other Language Versions](#other-language-versions) 19 | - Appendix 20 | - [Callback Definition](#callback-definition) 21 | - [Currency Definition](https://github.com/CYBAVO/SOFA_MOCK_SERVER#currency-definition) 22 | 23 | 24 | # Get Started 25 | 26 | CYBAVO Merchant Service(CMS) is a comprehensive cryptocurrency payment servcie, based on the deposit wallet of CYBAVO VAULT. 27 | 28 | ### Try it now 29 | - Use [mock server](#mock-server) to test CYBAVO Merchant API right away. 30 | 31 | ### Start integration 32 | - To make a correct API call, refer to [API Authentication](#api-authentication). 33 | - To handle callback correctly, refer to [Callback Integration](#callback-integration). 34 | 35 | 36 | # API Authentication 37 | 38 | - The CYBAVO Merchant Service verifies all incoming requests. All requests must include X-API-CODE, X-CHECKSUM headers otherwise caller will get a 403 Forbidden error. 39 | 40 | ### How to acquire and refresh API code and secret 41 | - Request the merchant API code/secret from the **Merchant Details** page on the web control panel for the first time. 42 | - A paired refresh code can be used in the [refresh API](#refresh-merchant-api-code) to acquire the new inactive API code/secret of the wallet. 43 | - Before the inactive API code is activated, the currently activated API code is still valid. 44 | - Once the paired API code becomes invalid, the paired refresh code will also become invalid. 45 | 46 | ### How to make a correct request? 47 | - Put the merchant API code in the X-API-CODE header. 48 | - Use the inactivated API code in any request will activate it automatically. Once activated, the currently activated API code will immediately become invalid. 49 | - Or you can explicitly call the [activation API](#activate-merchant-api-code) to activate the API code before use 50 | - Calculate the checksum with the corresponding API secret and put the checksum in the X-CHECKSUM header. 51 | - The checksum calculation will use all the query parameters, the current timestamp, user-defined random string and the post body (if any). 52 | - Please refer to the code snippet on the github project to know how to calculate the checksum. 53 | - [Go](https://github.com/CYBAVO/SOFA_MOCK_SERVER/blob/master/api/apicaller.go#L40) 54 | - [Java](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVA/blob/master/src/main/java/com/cybavo/sofa/mock/Api.java#L71) 55 | - [Javascript](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVASCRIPT/blob/master/helper/apicaller.js#L58) 56 | - [PHP](https://github.com/CYBAVO/SOFA_MOCK_SERVER_PHP/blob/master/helper/apicaller.php#L26) 57 | 58 | 59 | 60 | # Callback Integration 61 | 62 | - Please note that the merchant must have an activated API code, otherwise no callback will be sent. 63 | - Use the [activation API](#activate-merchant-api-code) to activate an API code. 64 | 65 | - To ensure that the callbacks have processed by callback handler, the CYBAVO Merchant Service will continue to send the callbacks to the callback URL until a callback confirmation (HTTP/1.1 200 OK) is received or exceeds the number of retries (retry time interval: 1-3-5-15-45 mins). 66 | - If all attempts fail, the callback will be set to a failed state, the callback handler can call the [resend failed callbacks](#resend-failed-merchant-callbacks) API to request CYBAVO Merchant Serviceto resend such kind of callback(s) or through the web control panel. 67 | 68 | - Refer to [Callback Definition](#callback-definition), [Callback Type Definition](#callback-type-definition) for detailed definition. 69 | - Please refer to the code snippet on the github project to know how to validate the callback payload. 70 | - [Go](https://github.com/CYBAVO/SOFA_MOCK_SERVER/blob/master/controllers/MerchantController.go#L248) 71 | - [Java](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVA/blob/master/src/main/java/com/cybavo/sofa/mock/MerchantController.java#L165) 72 | - [Javascript](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVASCRIPT/blob/master/routes/merchant.js#L163) 73 | - [PHP](https://github.com/CYBAVO/SOFA_MOCK_SERVER_PHP/blob/master/index.php#L418) 74 | 75 | # REST API 76 | 77 | 78 | ### Create Payment Order 79 | 80 | Create a payment order. 81 | 82 | ##### Request 83 | 84 | **POST** /v1/merchant/`MERCHANT_ID`/order 85 | 86 | - [Sample curl command](#curl-create-payment-order) 87 | 88 | > The order\_id must be prefixed. **Find prefix from corresponding merchant detail on web control panel.** 89 | > 90 | > The prefix is `N520335069_` is the following example. 91 | 92 | ##### Request Format 93 | 94 | An example of the request: 95 | 96 | ###### API 97 | 98 | ``` 99 | /v1/merchant/520335069/order 100 | ``` 101 | 102 | ###### Post body 103 | 104 | ```json 105 | { 106 | "currency": 60, 107 | "token_address": "", 108 | "amount": "0.01", 109 | "duration": 50, 110 | "description": "ETH payment", 111 | "order_id": "N520335069_1000022", 112 | "redirect_url": "https%3A%2F%2Fmyredirect.example.com%3Fk%3Dv%26k1%3Dv1" 113 | } 114 | ``` 115 | 116 | The request includes the following parameters: 117 | 118 | ###### Post body 119 | 120 | | Field | Type | Note | Description | 121 | | :--- | :--- | :--- | :--- | 122 | | currency | int64 | required, refer to the [Currency Definition](#currency-definition) | The cryptocurrency used to pay the order | 123 | | token_address | string | optional | The token contract address of cryptocurrency used to pay the order | 124 | | amount | string | required, refer to [the definition](#doundary-definition) for the default maximum amount | The required amount of the payment order | 125 | | duration | int64 | optional, refer to [the definition](#doundary-definition) for the minimum duration | The expiration duration (in minutes) of the payment order | 126 | | description | string | optional, max `255` chars | The description of the payment order | 127 | | order_id | string | required | The user defined order ID (must be prefixed) | 128 | | redirect_url | string | optional | User defined redirect URL (must be encoded) | 129 | 130 | > The `redirect_url` must be encoded. Please refer to the code snippet on the github project to know how to encode the URL. [Go](https://github.com/CYBAVO/SOFA_MOCK_SERVER/blob/master/controllers/MerchantController.go#L92), [Java](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVA/blob/master/src/main/java/com/cybavo/sofa/mock/MerchantController.java#L72), [Javascript](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVASCRIPT/blob/master/routes/merchant.js#L45), [PHP](https://github.com/CYBAVO/SOFA_MOCK_SERVER_PHP/blob/master/index.php#L370) 131 | > 132 | > If using BNB or XLM payment order, the payment transaction must specify an accurate memo to complete the order. 133 | 134 | 135 | Boundary definition 136 | 137 | | ID | Currency Symbol | Minimum Duration | Default Duration | Default Minimum / Maximum Amount | Confirm Blocks | 138 | | :--- | :--- | :--- | :--- | :--- | :--- | 139 | | 0 | BTC | 20 | 120 | 0.00002 / 0.2 | 2 | 140 | | 2 | LTC | 20 | 120 | 0.005 / 50 | 6 | 141 | | 3 | DOGE | 20 | 120 | 0.005 / 50 | 15 | 142 | | 60 | ETH | 10 | 60 | 0.0005 / 5 | 6 | 143 | | 145 | BCH | 20 | 120 | 0.005 / 20 | 6 | 144 | | 148 | XLM | 10 | 30 | 2 / 2000 | 1 | 145 | | 195 | TRX | 10 | 30 | 10 / 10000 | 1 | 146 | | 714 | BNB | 10 | 30 | 0.003 / 30 | 1 | 147 | | 966 | MATIC | 10 | 60 | 0.005 / 50 | 50 | 148 | | 1815 | ADA | 10 | 30 | 1 / 1000 | 30 | 149 | | 99999999997 | BSC | 10 | 60 | 0.005 / 50 | 20 | 150 | 151 | > The `Maximum Amount` boundary can be adjusted in the web control panel. 152 | 153 | ##### Response Format 154 | 155 | An example of a successful response: 156 | 157 | ```json 158 | { 159 | "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE", 160 | "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC", 161 | "expired_time": 1615975467, 162 | "order_id": "N520335069_1000022" 163 | } 164 | ``` 165 | 166 | For BNB, XLM 167 | 168 | ```json 169 | { 170 | "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE", 171 | "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC", 172 | "expired_time": 1615975467, 173 | "order_id": "N520335069_1000023", 174 | "memo": "63574" 175 | } 176 | ``` 177 | 178 | The response includes the following parameters: 179 | 180 | | Field | Type | Description | 181 | | :--- | :--- | :--- | 182 | | access_token | string | The access token used to query, update or cancel the payment order | 183 | | address | string | The address to accept the payment | 184 | | expired_time | int64 | The due date of the payment order (unix time in UTC) | 185 | | order_id | string | The order ID of the payment order | 186 | | memo | string | The memo of the payment order, the payment transaction must specify an accurate memo to complete the order. | 187 | 188 | ##### Error Code 189 | 190 | | HTTP Code | Error Code | Error | Message | Description | 191 | | :--- | :--- | :--- | :--- | :--- | 192 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 193 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 194 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 195 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 196 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 197 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 198 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 199 | | 400 | 112 | Invalid parameter | - | Malformatted post body | 200 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 201 | | 400 | 20003 | Wallet address not available | - | There is no available deposit address | 202 | | 400 | 20007 | Wallet not found | - | No corresponding linked deposit wallet found (specified by the currency and token address in the request) | 203 | | 400 | 20009 | Duplicated Order ID| - | The order ID has been used | 204 | | 400 | 20010 | Wrong Order prefix | - | The order prefix is wrong | 205 | | 400 | 20011 | Wrong Order format | - | The order contains invalid characters | 206 | | 400 | 20014 | Wrong order amount | - | The decimals of the amount does not conform to currency's decimals | 207 | | 400 | 20015 | Invalid order duration, should not be less than minimum | - | The requested duration is less than the lower bound | 208 | | 400 | 20016 | Over amount limit| - | The requested amount exceeds upper bound | 209 | | 400 | 20017 | Invalid url host | - | The redirect URL is invalid | 210 | 211 | ##### [Back to top](#table-of-contents) 212 | 213 | 214 | 215 | ### Query Payment Order Status 216 | 217 | Query current status of a payment order. 218 | 219 | ##### Request 220 | 221 | `VIEW` **GET** /v1/merchant/`MERCHANT_ID`/order?token=`ACCESS_TOKEN`&order=`ORDER_ID` 222 | 223 | - [Sample curl command](#curl-query-payment-order-status) 224 | 225 | ##### Request Format 226 | 227 | An example of the request: 228 | 229 | ###### API 230 | 231 | ``` 232 | /v1/merchant/520335069/order?token=ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE&order=N520335069_1000022 233 | ``` 234 | 235 | ##### Response Format 236 | 237 | An example of a successful response: 238 | 239 | ```json 240 | { 241 | "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC", 242 | "expired_time": 1615975468, 243 | "redirect_url": "https%3A%2F%2Fmyredirect.example.com%3Fk%3Dv%26k1%3Dv1", 244 | "state": 1, 245 | "tx_id": "" 246 | } 247 | ``` 248 | 249 | The response includes the following parameters: 250 | 251 | | Field | Type | Description | 252 | | :--- | :--- | :--- | 253 | | address | string | The deposit address to accept payment | 254 | | expired_time | int64 | The due date of the payment (unix time in UTC) | 255 | | redirect_url | string | User defined redirect URL (encoded) | 256 | | state | int | Refer to [Order State Definition](#order-state-definition) | 257 | | tx_id | string | The TX ID of the payment if state is 0, 2 or 3 | 258 | 259 | > The `redirect_url` is encoded. Please refer to the code snippet on the github project to know how to decode the URL. [Go](https://github.com/CYBAVO/SOFA_MOCK_SERVER/blob/master/controllers/MerchantController.go#L129), [Java](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVA/blob/master/src/main/java/com/cybavo/sofa/mock/MerchantController.java#L97), [Javascript](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVASCRIPT/blob/master/routes/merchant.js#L66), [PHP](https://github.com/CYBAVO/SOFA_MOCK_SERVER_PHP/blob/master/index.php#L381) 260 | 261 | ##### Error Code 262 | 263 | | HTTP Code | Error Code | Error | Message | Description | 264 | | :--- | :--- | :--- | :--- | :--- | 265 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 266 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 267 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 268 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 269 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 270 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 271 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 272 | | 403 | 112 | Invalid parameter | - | The invalid ACCESS\_TOKEN/ORDER\_ID | 273 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 274 | 275 | ##### [Back to top](#table-of-contents) 276 | 277 | 278 | 279 | ### Cancel Payment Order 280 | 281 | Cancel a payment order. Only pending payment order can be cancelled. 282 | 283 | ##### Request 284 | 285 | **DELETE** /v1/merchant/`MERCHANT_ID`/order?token=`ACCESS_TOKEN`&order=`ORDER_ID` 286 | 287 | - [Sample curl command](#curl-cancel-payment-order) 288 | 289 | ##### Request Format 290 | 291 | An example of the request: 292 | 293 | ###### API 294 | 295 | ``` 296 | /v1/merchant/520335069/order?token=ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE&order=N520335069_1000022 297 | ``` 298 | 299 | ##### Response Format 300 | 301 | An example of a successful response: 302 | 303 | ```json 304 | { 305 | "result": 1 306 | } 307 | ``` 308 | 309 | The response includes the following parameters: 310 | 311 | | Field | Type | Description | 312 | | :--- | :--- | :--- | 313 | | result | int | Always be 1, means the payment order has been successfully cancelled | 314 | 315 | ##### Error Code 316 | 317 | | HTTP Code | Error Code | Error | Message | Description | 318 | | :--- | :--- | :--- | :--- | :--- | 319 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 320 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 321 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 322 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 323 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 324 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 325 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 326 | | 403 | 112 | Invalid parameter | - | The invalid ACCESS\_TOKEN/ORDER\_ID or the payment can't be cancelled | 327 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 328 | 329 | 330 | ##### [Back to top](#table-of-contents) 331 | 332 | 333 | 334 | ### Update Payment Order Expiration Duration 335 | 336 | Update a payment order expiration duration. Only pending payment order can be updated. 337 | 338 | **POST** /v1/merchant/`MERCHANT_ID`/order/duration 339 | 340 | - [Sample curl command](#curl-update-payment-order-expiration-duration) 341 | 342 | ##### Request Format 343 | 344 | An example of the request: 345 | 346 | ###### API 347 | 348 | ``` 349 | /v1/merchant/520335069/order 350 | ``` 351 | 352 | ###### Post body 353 | 354 | ```json 355 | { 356 | "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE", 357 | "order_id": "N520335069_1000022", 358 | "duration": 50 359 | } 360 | ``` 361 | 362 | The request includes the following parameters: 363 | 364 | ###### Post body 365 | 366 | | Field | Type | Note | Description | 367 | | :--- | :--- | :--- | :--- | 368 | | access_token | string | required | The access token of the payment order returned with [Create order](#create-payment-order) API | 369 | | order_id | string | required | The order ID of the payment order | 370 | | duration | int64 | required | The expiration duration (in minutes) of the payment order | 371 | 372 | > The new due date is calculated based on the submission time of the payment order. 373 | 374 | ##### Response Format 375 | 376 | An example of a successful response: 377 | 378 | ```json 379 | { 380 | "result": 1 381 | } 382 | ``` 383 | 384 | The response includes the following parameters: 385 | 386 | | Field | Type | Description | 387 | | :--- | :--- | :--- | 388 | | result | int | Always be 1, means the payment order has been successfully updated | 389 | 390 | ##### Error Code 391 | 392 | | HTTP Code | Error Code | Error | Message | Description | 393 | | :--- | :--- | :--- | :--- | :--- | 394 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 395 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 396 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 397 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 398 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 399 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 400 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 401 | | 403 | 112 | Invalid parameter | - | The invalid ACCESS\_TOKEN/ORDER\_ID or the payment can't be updated | 402 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 403 | 404 | ##### [Back to top](#table-of-contents) 405 | 406 | 407 | 408 | ### Resend Failed Merchant Callbacks 409 | 410 | The callback handler can call this API to resend pending or failed merchant callbacks. 411 | 412 | Refer to [Callback Integration](#callback-integration) for callback rules. 413 | 414 | > The resend operation could be requested on the web control panel as well. 415 | 416 | ##### Request 417 | 418 | **POST** /v1/merchant/`MERCHANT_ID`/notifications/manual 419 | 420 | - [Sample curl command](#curl-resend-failed-merchant-callbacks) 421 | 422 | ##### Request Format 423 | 424 | An example of the request: 425 | 426 | ###### API 427 | 428 | ``` 429 | /v1/merchant/520335069/notifications/manual 430 | ``` 431 | 432 | ##### Response Format 433 | 434 | An example of a successful response: 435 | 436 | ```json 437 | { 438 | "count": 0 439 | } 440 | ``` 441 | 442 | The response includes the following parameters: 443 | 444 | | Field | Type | Description | 445 | | :--- | :--- | :--- | 446 | | count | int | Count of callbacks just resent | 447 | 448 | ##### Error Code 449 | 450 | | HTTP Code | Error Code | Error | Message | Description | 451 | | :--- | :--- | :--- | :--- | :--- | 452 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 453 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 454 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 455 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 456 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 457 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 458 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 459 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 460 | 461 | ##### [Back to top](#table-of-contents) 462 | 463 | 464 | 465 | ### Activate Merchant API Code 466 | 467 | Activate the API code of a certain merchant. Once activated, the currently activated API code will immediately become invalid. 468 | 469 | ##### Request 470 | 471 | **POST** /v1/merchant/`MERCHANT_ID`/apisecret/activate 472 | 473 | - [Sample curl command](#curl-activate-merchant-api-code) 474 | 475 | ##### Request Format 476 | 477 | An example of the request: 478 | 479 | ###### API 480 | 481 | ``` 482 | /v1/merchant/520335069/apisecret/activate 483 | ``` 484 | 485 | ##### Response Format 486 | 487 | An example of a successful response: 488 | 489 | ```json 490 | { 491 | "api_code": "4PcdE9VjXfrk7WjC1", 492 | "exp": 1609646716 493 | } 494 | ``` 495 | 496 | The response includes the following parameters: 497 | 498 | | Field | Type | Description | 499 | | :--- | :--- | :--- | 500 | | api_code | string | The activated API code | 501 | | exp | int64 | The API code expiration unix time in UTC | 502 | 503 | ##### Error Code 504 | 505 | | HTTP Code | Error Code | Error | Message | Description | 506 | | :--- | :--- | :--- | :--- | :--- | 507 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 508 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 509 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 510 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 511 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 512 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 513 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 514 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 515 | 516 | ##### [Back to top](#table-of-contents) 517 | 518 | 519 | 520 | ### Query Merchant API Code Status 521 | 522 | Query the API code info of a certain merchant. Use the `inactivated` API code in any request will activate it. Once activated, the currently activated API code will immediately become invalid. 523 | 524 | ##### Request 525 | 526 | `VIEW` **GET** /v1/merchant/`MERCHANT_ID`/apisecret 527 | 528 | - [Sample curl command](#curl-query-merchant-api-code-status) 529 | 530 | ##### Request Format 531 | 532 | An example of the request: 533 | 534 | ###### API 535 | 536 | ``` 537 | /v1/merchant/520335069/apisecret 538 | ``` 539 | 540 | ##### Response Format 541 | 542 | An example of a successful response: 543 | 544 | ```json 545 | { 546 | "valid": { 547 | "api_code": "H4Q6xFZgiTZb37GN", 548 | "exp": 1583144863 549 | }, 550 | "inactivated": { 551 | "api_code": "32PmGCjNzXda4mNHX" 552 | } 553 | } 554 | ``` 555 | 556 | The response includes the following parameters: 557 | 558 | | Field | Type | Description | 559 | | :--- | :--- | :--- | 560 | | valid | object | The activated API code | 561 | | inactivated | object | Not active API code | 562 | | api_code | string | The API code for querying wallet | 563 | | exp | int64 | The API code expiration unix time in UTC | 564 | 565 | > Use an invalid API-CODE, the caller will get a 403 Forbidden error. 566 | 567 | ##### Error Code 568 | 569 | | HTTP Code | Error Code | Error | Message | Description | 570 | | :--- | :--- | :--- | :--- | :--- | 571 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 572 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 573 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 574 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 575 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 576 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 577 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 578 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 579 | 580 | ##### [Back to top](#table-of-contents) 581 | 582 | 583 | 584 | ### Refresh Merchant API Code 585 | 586 | Use paired refresh code to acquire the new inactive API code/secret of the merchant. 587 | 588 | ##### Request 589 | 590 | **POST** /v1/merchant/`MERCHANT_ID`/apisecret/refreshsecret 591 | 592 | - [Sample curl command](#curl-refresh-merchant-api-code) 593 | 594 | ##### Request Format 595 | 596 | An example of the request: 597 | 598 | ###### API 599 | 600 | ``` 601 | /v1/merchant/520335069/apisecret/refreshsecret 602 | ``` 603 | 604 | ###### Post body 605 | 606 | ```json 607 | { 608 | "refresh_code":"3EbaSPUpKzHJ9wYgYZqy6W4g43NT365bm9vtTfYhMPra" 609 | } 610 | ``` 611 | 612 | The request includes the following parameters: 613 | 614 | ###### Post body 615 | 616 | | Field | Type | Note | Description | 617 | | :--- | :--- | :--- | :--- | 618 | | refresh_code | string | required | The corresponding refresh code of the API code specified in the X-API-CODE header | 619 | 620 | ##### Response Format 621 | 622 | An example of a successful response: 623 | 624 | ```json 625 | { 626 | "api_code": "4QjbY3qES4tEh19PU", 627 | "api_secret": "3jC1qjr4mrKxfoXkxoN27Uhmbm1E", 628 | "refresh_code": "HcN17gxZ3ojrBYSXnjKsU9Pun8krP6J9Pn678k4rZ13m" 629 | } 630 | ``` 631 | 632 | The response includes the following parameters: 633 | 634 | | Field | Type | Description | 635 | | :--- | :--- | :--- | 636 | | api_code | string | The new inactive API code | 637 | | api_secret | string | The API secret | 638 | | refresh_code | string | The paired refresh code | 639 | 640 | ##### Error Code 641 | 642 | | HTTP Code | Error Code | Error | Message | Description | 643 | | :--- | :--- | :--- | :--- | :--- | 644 | | 403 | - | Forbidden. Invalid ID | - | No merchant ID found | 645 | | 403 | - | Forbidden. Header not found | - | Missing `X-API-CODE`, `X-CHECKSUM` header or query param `t` | 646 | | 403 | - | Forbidden. Invalid timestamp | - | The timestamp `t` is not in the valid time range | 647 | | 403 | - | Forbidden. Invalid checksum | - | The request is considered a replayed request | 648 | | 403 | - | Forbidden. Invalid API code | - | `X-API-CODE` header contains invalid API code | 649 | | 403 | - | Forbidden. Checksum unmatch | - | `X-CHECKSUM` header contains wrong checksum | 650 | | 403 | - | Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute) | - | Send requests too frequently | 651 | | 400 | 112 | Invalid parameter | - | Malformatted post body or the refresh code is invalid | 652 | | 403 | 113 | Permission denied | - | Merchant API not allowed | 653 | 654 | ##### [Back to top](#table-of-contents) 655 | 656 | 657 | 658 | 659 | # Mock Server 660 | 661 | ### How to compile 662 | - Put sample code to {YOUR\_GO\_PATH}/github.com/cybavo/SOFA\_MOCK\_SERVER 663 | - Execute 664 | - glide install 665 | - go build ./mockserver.go 666 | - ./mockserver 667 | 668 | ### Setup configuration 669 | 670 | > Configure CYBAVO API server URL in mockserver.app.conf 671 | 672 | ``` 673 | api_server_url="BACKEND_SERVER_URL" 674 | ``` 675 | 676 | ### Put merchant API code/secret into mock server 677 | - Get API code/secret on web control panel 678 | - API_CODE, API\_SECRET, WALLET\_ID 679 | - Put API code/secret to mock server's database 680 | 681 | ``` 682 | curl -X POST -H "Content-Type: application/json" -d '{"api_code":"API_CODE","api_secret":"API_SECRET"}' \ 683 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apitoken 684 | ``` 685 | 686 | ### Register mock server callback URL 687 | > Operate on web control panel 688 | 689 | Notification Callback URL 690 | 691 | ``` 692 | http://localhost:8889/v1/mock/merchant/callback 693 | ``` 694 | 695 | ##### [Back to top](#table-of-contents) 696 | 697 | 698 | # cURL Testing Commands 699 | 700 | 701 | ### Create Payment Order 702 | 703 | ``` 704 | curl -X POST -H "Content-Type: application/json" -d '{"currency":60,"token_address":"","amount":"0.01","duration":50,"description":"TEST Order","redirect_url":"","order_id":"N520335069_10001"}' \ 705 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order 706 | ``` 707 | 708 | - [API definition](#create-payment-order) 709 | 710 | 711 | 712 | ### Query Payment Order Status 713 | 714 | ``` 715 | curl http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order?token={ACCESS_TOKEN}&order={ORDER_ID} 716 | ``` 717 | 718 | - [API definition](#query-payment-order-status) 719 | 720 | 721 | 722 | ### Cancel Payment Order 723 | 724 | ``` 725 | curl -X DELETE http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order?token={ACCESS_TOKEN}&order={ORDER_ID} 726 | ``` 727 | 728 | - [API definition](#cancel-payment-order) 729 | 730 | 731 | 732 | ### Update Payment Order Expiration Duration 733 | 734 | ``` 735 | curl -X POST -H "Content-Type: application/json" -d '{"access_token":"IUxyiWsdIBp_FS6Tu2afaecH4F_dqpYOLj4oXxn02AA","order_id":"N520335069_10001","duration":100}' \ 736 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order/duration 737 | ``` 738 | 739 | - [API definition](#update-payment-order-expiration-duration) 740 | 741 | 742 | ### Resend Failed Merchant Callbacks 743 | 744 | ``` 745 | curl -X POST http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/notifications/manual 746 | ``` 747 | - [API definition](#resend-failed-merchant-callbacks) 748 | 749 | 750 | 751 | ### Activate Merchant API Code 752 | 753 | ``` 754 | curl -X POST http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret/activate 755 | ``` 756 | - [API definition](#activate-merchant-api-code) 757 | 758 | 759 | 760 | ### Query Merchant API Code Status 761 | 762 | ``` 763 | curl http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret 764 | ``` 765 | - [API definition](#query-merchant-api-code-status) 766 | 767 | 768 | 769 | ### Refresh Merchant API Code 770 | 771 | ``` 772 | curl -X POST -H "Content-Type: application/json" -d '{"refresh_code":"3EbaSPUpKzHJ9wYgYZqy6W4g43NT365bm9vtTfYhMPra"}' \ 773 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret/refreshsecret 774 | ``` 775 | - [API definition](#refresh-merchant-api-code) 776 | 777 | 778 | ##### [Back to top](#table-of-contents) 779 | 780 | 781 | # Other Language Versions 782 | - [Java](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVA) 783 | - [Javascript](https://github.com/CYBAVO/SOFA_MOCK_SERVER_JAVASCRIPT) 784 | - [PHP](https://github.com/CYBAVO/SOFA_MOCK_SERVER_PHP) 785 | 786 | ##### [Back to top](#table-of-contents) 787 | 788 | # Appendix 789 | 790 | 791 | ### Callback Definition 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 882 | 883 | 884 | 885 | 886 | 889 | 890 | 891 | 892 | 893 | 894 | 895 |
FieldTypeDescription
merchant_idint64The merchant ID of the callback
order_idstringThe unique order ID of the payment order
currencystringCryptocurrency of the callback
txidstringTransaction identifier
block_heightint64The block height show the transaction was packed in which block
recv_amountstringReceived amount denominated in the smallest cryptocurrency unit
feestringMining fee denominated in the smallest cryptocurrency unit
broadcast_atint64When to broadcast the transaction in UTC time
from_addressstringThe source address of the transaction
to_addressstringThe destination address of the transaction
stateint 853 | Possible states (listed in the Order State Definition table) 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 |
IDDescription
0The order has been successfully paid
1The order has expired
2The amount received is less than the amount requested
3The amount received is greater than the amount requested
4The order cancelled
864 |
decimalintThe decimal of cryptocurrency
fee_decimalintThe decimal of cryptocurrency miner fee
addonkey-value pairs 880 | The extra information of this callback 881 |
currency_bip44int64 887 | Refer to Currency Definition table 888 |
token_addressstringThe contract address of cryptocurrency
896 | 897 | ##### [Back to top](#table-of-contents) 898 | 899 | 900 | 901 | ### Order State Definition 902 | 903 | | ID | Description | 904 | | :--- | :--- | 905 | | -1 | Waiting for payment | 906 | | 0 | The order has been successfully paid | 907 | | 1 | The order has expired | 908 | | 2 | The amount received is less than the amount requested | 909 | | 3 | The amount received is greater than the amount requested | 910 | | 4 | The order cancelled | 911 | 912 | ##### [Back to top](#table-of-contents) 913 | 914 | -------------------------------------------------------------------------------- /controllers/OuterController.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2022 The CYBAVO developers 2 | // All Rights Reserved. 3 | // NOTICE: All information contained herein is, and remains 4 | // the property of CYBAVO and its suppliers, 5 | // if any. The intellectual and technical concepts contained 6 | // herein are proprietary to CYBAVO 7 | // Dissemination of this information or reproduction of this materia 8 | // is strictly forbidden unless prior written permission is obtained 9 | // from CYBAVO. 10 | 11 | package controllers 12 | 13 | import ( 14 | "encoding/base64" 15 | "encoding/json" 16 | "errors" 17 | "fmt" 18 | "net/http" 19 | "strconv" 20 | "strings" 21 | 22 | "github.com/astaxie/beego" 23 | "github.com/astaxie/beego/context" 24 | "github.com/astaxie/beego/logs" 25 | "github.com/cybavo/SOFA_MOCK_SERVER/api" 26 | "github.com/cybavo/SOFA_MOCK_SERVER/models" 27 | ) 28 | 29 | type OuterController struct { 30 | beego.Controller 31 | } 32 | 33 | func getQueryString(ctx *context.Context) []string { 34 | var qs []string 35 | tokens := strings.Split(ctx.Request.URL.RawQuery, "&") 36 | for _, token := range tokens { 37 | qs = append(qs, token) 38 | } 39 | return qs 40 | } 41 | 42 | var debugPrint = func(ctx *context.Context) { 43 | var params string 44 | qs := getQueryString(ctx) 45 | if qs != nil { 46 | params = strings.Join(qs, "&") 47 | } 48 | logs.Debug(fmt.Sprintf("Recv requst => %s, params: %s, body: %s", ctx.Input.URL(), params, ctx.Input.RequestBody)) 49 | } 50 | 51 | func init() { 52 | beego.InsertFilter("/v1/mock/*", beego.BeforeExec, debugPrint) 53 | } 54 | 55 | func (c *OuterController) getWalletID() int64 { 56 | walletID, err := strconv.ParseInt(c.Ctx.Input.Param(":wallet_id"), 10, 64) 57 | if err != nil { 58 | logs.Error("Invalid wallet ID =>", err) 59 | c.AbortWithError(http.StatusBadRequest, err) 60 | } 61 | return walletID 62 | } 63 | 64 | func (c *OuterController) getOrderID() string { 65 | orderID := c.Ctx.Input.Param(":order_id") 66 | if orderID == "" { 67 | logs.Error("Invalid order ID") 68 | c.AbortWithError(http.StatusBadRequest, errors.New("invalid order id")) 69 | } 70 | return orderID 71 | } 72 | 73 | func (c *OuterController) AbortWithError(status int, err error) { 74 | resp := api.ErrorCodeResponse{ 75 | ErrMsg: err.Error(), 76 | ErrCode: status, 77 | } 78 | c.Data["json"] = resp 79 | c.Abort(strconv.Itoa(status)) 80 | } 81 | 82 | // @Title Set API token 83 | // @router /wallets/:wallet_id/apitoken [post] 84 | func (c *OuterController) SetAPIToken() { 85 | defer c.ServeJSON() 86 | 87 | walletID := c.getWalletID() 88 | 89 | var request api.SetAPICodeRequest 90 | err := json.Unmarshal(c.Ctx.Input.RequestBody, &request) 91 | if err != nil { 92 | c.AbortWithError(http.StatusBadRequest, err) 93 | } 94 | 95 | apiCodeParams := models.APICode{ 96 | APICode: request.APICode, 97 | ApiSecret: request.ApiSecret, 98 | WalletID: walletID, 99 | } 100 | err = models.SetAPICode(&apiCodeParams) 101 | if err != nil { 102 | logs.Error("SetAPICode failed", err) 103 | c.AbortWithError(http.StatusInternalServerError, err) 104 | } 105 | 106 | response := &api.CommonResponse{ 107 | Result: 1, 108 | } 109 | c.Data["json"] = response 110 | } 111 | 112 | // @Title Create deposit wallet addresses 113 | // @router /wallets/:wallet_id/addresses [post] 114 | func (c *OuterController) CreateDepositWalletAddresses() { 115 | defer c.ServeJSON() 116 | 117 | walletID := c.getWalletID() 118 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/addresses", walletID), 119 | nil, c.Ctx.Input.RequestBody) 120 | if err != nil { 121 | logs.Error("CreateDepositWalletAddresses failed", err) 122 | c.AbortWithError(http.StatusInternalServerError, err) 123 | } 124 | 125 | var m map[string]interface{} 126 | json.Unmarshal(resp, &m) 127 | c.Data["json"] = m 128 | } 129 | 130 | // @Title Get deposit wallet addresses 131 | // @router /wallets/:wallet_id/addresses [get] 132 | func (c *OuterController) GetDepositWalletAddresses() { 133 | defer c.ServeJSON() 134 | 135 | walletID := c.getWalletID() 136 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/addresses", walletID), 137 | getQueryString(c.Ctx), nil) 138 | if err != nil { 139 | logs.Error("GetDepositWalletAddresses failed", err) 140 | c.AbortWithError(http.StatusInternalServerError, err) 141 | } 142 | 143 | var m map[string]interface{} 144 | json.Unmarshal(resp, &m) 145 | c.Data["json"] = m 146 | } 147 | 148 | // @Title Get deposit wallet pool address 149 | // @router /wallets/:wallet_id/pooladdress [get] 150 | func (c *OuterController) GetDepositWalletPoolAddress() { 151 | defer c.ServeJSON() 152 | 153 | walletID := c.getWalletID() 154 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/pooladdress", walletID), 155 | nil, nil) 156 | if err != nil { 157 | logs.Error("GetDepositWalletPoolAddress failed", err) 158 | c.AbortWithError(http.StatusInternalServerError, err) 159 | } 160 | 161 | var m map[string]interface{} 162 | json.Unmarshal(resp, &m) 163 | c.Data["json"] = m 164 | } 165 | 166 | // @Title Get balance of deposit wallet pool address 167 | // @router /wallets/:wallet_id/pooladdress/balance [get] 168 | func (c *OuterController) GetDepositWalletPoolAddressBalance() { 169 | defer c.ServeJSON() 170 | 171 | walletID := c.getWalletID() 172 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/pooladdress/balance", walletID), 173 | nil, nil) 174 | if err != nil { 175 | logs.Error("GetDepositWalletPoolAddress failed", err) 176 | c.AbortWithError(http.StatusInternalServerError, err) 177 | } 178 | 179 | var m map[string]interface{} 180 | json.Unmarshal(resp, &m) 181 | c.Data["json"] = m 182 | } 183 | 184 | // @Title Callback 185 | // @router /wallets/callback [post] 186 | func (c *OuterController) Callback() { 187 | var cb api.CallbackStruct 188 | err := json.Unmarshal(c.Ctx.Input.RequestBody, &cb) 189 | if err != nil { 190 | c.AbortWithError(http.StatusBadRequest, err) 191 | } 192 | 193 | apiCodeObj, err := models.GetWalletAPICode(cb.WalletID) 194 | if err != nil { 195 | c.AbortWithError(http.StatusBadRequest, err) 196 | } 197 | 198 | checksum := c.Ctx.Input.Header("X-CHECKSUM") 199 | payload := string(c.Ctx.Input.RequestBody) + apiCodeObj.ApiSecret 200 | sha, _ := api.CalcSHA256([]byte(payload)) 201 | checksumVerf := base64.URLEncoding.EncodeToString(sha) 202 | 203 | if checksum != checksumVerf { 204 | c.AbortWithError(http.StatusBadRequest, errors.New("Bad checksum")) 205 | } 206 | 207 | logs.Debug("Callback => %s", c.Ctx.Input.RequestBody) 208 | 209 | cbType := api.CallbackType(cb.Type) 210 | if cbType == api.DepositCallback { 211 | // 212 | // deposit unique ID 213 | // uniqueID := fmt.Sprintf("%s_%d", cb.TXID, cb.VOutIndex) 214 | // 215 | if cb.ProcessingState == api.ProcessingStateDone { 216 | // deposit succeeded, use the deposit unique ID to update your business logic 217 | } 218 | } else if cbType == api.WithdrawCallback { 219 | // 220 | // withdrawal unique ID 221 | // uniqueID := cb.OrderID 222 | // 223 | if cb.State == api.CallbackStateInChain && cb.ProcessingState == api.ProcessingStateDone { 224 | // withdrawal succeeded, use the withdrawal uniqueID to update your business logic 225 | } else if cb.State == api.CallbackStateFailed || cb.State == api.CallbackStateInChainFailed { 226 | // withdrawal failed, use the withdrawal unique ID to update your business logic 227 | } 228 | } else if cbType == api.AirdropCallback { 229 | // 230 | // airdrop unique ID 231 | // uniqueID := fmt.Sprintf("%s_%d", cb.TXID, cb.VOutIndex) 232 | // 233 | if cb.ProcessingState == api.ProcessingStateDone { 234 | // airdrop succeeded, use the airdrop unique ID to update your business logic 235 | } 236 | } 237 | 238 | // reply 200 OK to confirm the callback has been processed 239 | c.Ctx.WriteString("OK") 240 | } 241 | 242 | // @Title Withdrawal Callback 243 | // @router /wallets/withdrawal/callback [post] 244 | func (c *OuterController) WithdrawalCallback() { 245 | // How to verify: 246 | // 1. Try to find corresponding API secret by request.Requests[0].OrderID 247 | // 2. Calculate checksum then compare to X-CHECKSUM header (refer to sample code bellow) 248 | // 3. If these two checksums match and the request is valid in your system, 249 | // reply 200, OK otherwise reply 400 to decline the withdrawal 250 | 251 | // sample code to calculate checksum and verify 252 | // payload := string(c.Ctx.Input.RequestBody) + APISECRET 253 | // sha, _ := api.CalcSHA256([]byte(payload)) 254 | // checksumVerf := base64.URLEncoding.EncodeToString(sha) 255 | // checksum := c.Ctx.Input.Header("X-CHECKSUM") 256 | // if checksum != checksumVerf { 257 | // c.AbortWithError(http.StatusBadRequest, errors.New("Bad checksum")) 258 | // } 259 | 260 | logs.Debug("Withdraw Callback => %s", c.Ctx.Input.RequestBody) 261 | 262 | c.Ctx.WriteString("OK") 263 | } 264 | 265 | // @Title Resend Deposit/Collection Callback 266 | // @router /wallets/:wallet_id/collection/notifications/manual [post] 267 | func (c *OuterController) ResendDepositCollectionCallbacks() { 268 | defer c.ServeJSON() 269 | 270 | walletID := c.getWalletID() 271 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/collection/notifications/manual", walletID), 272 | nil, c.Ctx.Input.RequestBody) 273 | if err != nil { 274 | logs.Error("ResendDepositCollectionCallbacks failed", err) 275 | c.AbortWithError(http.StatusInternalServerError, err) 276 | } 277 | 278 | var m map[string]interface{} 279 | json.Unmarshal(resp, &m) 280 | c.Data["json"] = m 281 | } 282 | 283 | // @Title Withdraw transactions 284 | // @router /wallets/:wallet_id/sender/transactions [post] 285 | func (c *OuterController) WithdrawAssets() { 286 | defer c.ServeJSON() 287 | 288 | walletID := c.getWalletID() 289 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions", walletID), 290 | nil, c.Ctx.Input.RequestBody) 291 | if err != nil { 292 | logs.Error("WithdrawAssets failed", err) 293 | c.AbortWithError(http.StatusInternalServerError, err) 294 | } 295 | 296 | var m map[string]interface{} 297 | json.Unmarshal(resp, &m) 298 | c.Data["json"] = m 299 | } 300 | 301 | // @Title Cancel withdraw request that current state is init 302 | // @router /wallets/:wallet_id/sender/transactions/:order_id/cancel [post] 303 | func (c *OuterController) CancelWithdrawTransactions() { 304 | defer c.ServeJSON() 305 | 306 | walletID := c.getWalletID() 307 | orderID := c.getOrderID() 308 | resp, err := api.MakeRequest(walletID, "POST", 309 | fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions/%s/cancel", walletID, orderID), 310 | nil, nil) 311 | if err != nil { 312 | logs.Error("CancelWithdrawTransactions failed", err) 313 | c.AbortWithError(http.StatusInternalServerError, err) 314 | } 315 | 316 | var m map[string]interface{} 317 | json.Unmarshal(resp, &m) 318 | c.Data["json"] = m 319 | } 320 | 321 | // @Title Get state of withdrawal transaction 322 | // @router /wallets/:wallet_id/sender/transactions/:order_id [get] 323 | func (c *OuterController) GetWithdrawTransactionState() { 324 | defer c.ServeJSON() 325 | 326 | walletID := c.getWalletID() 327 | orderID := c.getOrderID() 328 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions/%s", walletID, orderID), 329 | nil, nil) 330 | if err != nil { 331 | logs.Error("GetWithdrawTransactionState failed", err) 332 | c.AbortWithError(http.StatusInternalServerError, err) 333 | } 334 | 335 | var m map[string]interface{} 336 | json.Unmarshal(resp, &m) 337 | c.Data["json"] = m 338 | } 339 | 340 | // @Title Get state of withdrawal transaction 341 | // @router /wallets/:wallet_id/sender/transactions/:order_id/all [get] 342 | func (c *OuterController) GetWithdrawTransactionStateAll() { 343 | defer c.ServeJSON() 344 | 345 | walletID := c.getWalletID() 346 | orderID := c.getOrderID() 347 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions/%s/all", walletID, orderID), 348 | nil, nil) 349 | if err != nil { 350 | logs.Error("GetWithdrawTransactionStateAll failed", err) 351 | c.AbortWithError(http.StatusInternalServerError, err) 352 | } 353 | 354 | var m map[string]interface{} 355 | json.Unmarshal(resp, &m) 356 | c.Data["json"] = m 357 | } 358 | 359 | // @Title Get balance of withdrawal wallet 360 | // @router /wallets/:wallet_id/sender/balance [get] 361 | func (c *OuterController) GetWithdrawalWalletBalance() { 362 | defer c.ServeJSON() 363 | 364 | walletID := c.getWalletID() 365 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/balance", walletID), 366 | nil, nil) 367 | if err != nil { 368 | logs.Error("GetWithdrawalWalletBalance failed", err) 369 | c.AbortWithError(http.StatusInternalServerError, err) 370 | } 371 | 372 | var m map[string]interface{} 373 | json.Unmarshal(resp, &m) 374 | c.Data["json"] = m 375 | } 376 | 377 | // @Title Get API token status 378 | // @router /wallets/:wallet_id/apisecret [get] 379 | func (c *OuterController) GetTxAPITokenStatus() { 380 | defer c.ServeJSON() 381 | 382 | walletID := c.getWalletID() 383 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/apisecret", walletID), 384 | nil, nil) 385 | if err != nil { 386 | logs.Error("GetTxAPITokenStatus failed", err) 387 | c.AbortWithError(http.StatusInternalServerError, err) 388 | } 389 | 390 | var m map[string]interface{} 391 | json.Unmarshal(resp, &m) 392 | c.Data["json"] = m 393 | } 394 | 395 | // @Title Activate API token 396 | // @router /wallets/:wallet_id/apisecret/activate [post] 397 | func (c *OuterController) ActivateAPIToken() { 398 | defer c.ServeJSON() 399 | 400 | walletID := c.getWalletID() 401 | var url string 402 | if walletID == 0 { 403 | url = "/v1/sofa/wallets/readonly/apisecret/activate" 404 | } else { 405 | url = fmt.Sprintf("/v1/sofa/wallets/%d/apisecret/activate", walletID) 406 | } 407 | resp, err := api.MakeRequest(walletID, "POST", url, nil, c.Ctx.Input.RequestBody) 408 | if err != nil { 409 | logs.Error("ActivateAPIToken failed", err) 410 | c.AbortWithError(http.StatusInternalServerError, err) 411 | } 412 | 413 | var m map[string]interface{} 414 | json.Unmarshal(resp, &m) 415 | c.Data["json"] = m 416 | } 417 | 418 | // @Title Query notification history 419 | // @router /wallets/:wallet_id/notifications [get] 420 | func (c *OuterController) GetNotifications() { 421 | defer c.ServeJSON() 422 | 423 | walletID := c.getWalletID() 424 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/notifications", walletID), 425 | getQueryString(c.Ctx), nil) 426 | if err != nil { 427 | logs.Error("GetNotifications failed", err) 428 | c.AbortWithError(http.StatusInternalServerError, err) 429 | } 430 | 431 | var m map[string]interface{} 432 | json.Unmarshal(resp, &m) 433 | c.Data["json"] = m 434 | } 435 | 436 | // @Title Query notification by serial 437 | // @router /wallets/:wallet_id/notifications/get_by_id [post] 438 | func (c *OuterController) GetCallbackBySerial() { 439 | defer c.ServeJSON() 440 | 441 | walletID := c.getWalletID() 442 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/notifications/get_by_id", walletID), 443 | nil, c.Ctx.Input.RequestBody) 444 | if err != nil { 445 | logs.Error("GetWalletNotificationsByID failed", err) 446 | c.AbortWithError(http.StatusInternalServerError, err) 447 | } 448 | 449 | var m map[string]interface{} 450 | json.Unmarshal(resp, &m) 451 | c.Data["json"] = m 452 | } 453 | 454 | // @Title Query deposit callback by txid and vout_index 455 | // @router /wallets/:wallet_id/receiver/notifications/txid/:txid/:vout_index [get] 456 | func (c *OuterController) GetDepositCallback() { 457 | defer c.ServeJSON() 458 | 459 | walletID := c.getWalletID() 460 | txID := c.Ctx.Input.Param(":txid") 461 | if txID == "" { 462 | logs.Error("Invalid txid") 463 | c.AbortWithError(http.StatusBadRequest, errors.New("invalid txid")) 464 | } 465 | voutIndex, err := strconv.Atoi(c.Ctx.Input.Param(":vout_index")) 466 | if err != nil { 467 | logs.Error("Invalid vout_index =>", err) 468 | c.AbortWithError(http.StatusBadRequest, err) 469 | } 470 | 471 | resp, err := api.MakeRequest(walletID, "GET", 472 | fmt.Sprintf("/v1/sofa/wallets/%d/receiver/notifications/txid/%s/%d", walletID, txID, voutIndex), 473 | nil, nil) 474 | if err != nil { 475 | logs.Error("GetDepositCallback failed", err) 476 | c.AbortWithError(http.StatusInternalServerError, err) 477 | } 478 | 479 | var m map[string]interface{} 480 | json.Unmarshal(resp, &m) 481 | c.Data["json"] = m 482 | } 483 | 484 | // @Title Query withdrawal callback by order_id 485 | // @router /wallets/:wallet_id/sender/notifications/order_id/:order_id [get] 486 | func (c *OuterController) GetWithdrawalCallback() { 487 | defer c.ServeJSON() 488 | 489 | walletID := c.getWalletID() 490 | orderID := c.getOrderID() 491 | resp, err := api.MakeRequest(walletID, "GET", 492 | fmt.Sprintf("/v1/sofa/wallets/%d/sender/notifications/order_id/%s", walletID, orderID), 493 | nil, nil) 494 | if err != nil { 495 | logs.Error("GetWithdrawalCallback failed", err) 496 | c.AbortWithError(http.StatusInternalServerError, err) 497 | } 498 | 499 | var m map[string]interface{} 500 | json.Unmarshal(resp, &m) 501 | c.Data["json"] = m 502 | } 503 | 504 | // @Title Query wallet transaction history 505 | // @router /wallets/:wallet_id/transactions [get] 506 | func (c *OuterController) GetTransactionHistory() { 507 | defer c.ServeJSON() 508 | 509 | walletID := c.getWalletID() 510 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/transactions", walletID), 511 | getQueryString(c.Ctx), nil) 512 | if err != nil { 513 | logs.Error("GetTransactionHistory failed", err) 514 | c.AbortWithError(http.StatusInternalServerError, err) 515 | } 516 | 517 | var m map[string]interface{} 518 | json.Unmarshal(resp, &m) 519 | c.Data["json"] = m 520 | } 521 | 522 | // @Title Query wallet block info 523 | // @router /wallets/:wallet_id/blocks [get] 524 | func (c *OuterController) GetWalletBlockInfo() { 525 | defer c.ServeJSON() 526 | 527 | walletID := c.getWalletID() 528 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/blocks", walletID), 529 | nil, nil) 530 | if err != nil { 531 | logs.Error("GetWalletBlockInfo failed", err) 532 | c.AbortWithError(http.StatusInternalServerError, err) 533 | } 534 | 535 | var m map[string]interface{} 536 | json.Unmarshal(resp, &m) 537 | c.Data["json"] = m 538 | } 539 | 540 | // @Title Query invalid deposit addresses 541 | // @router /wallets/:wallet_id/addresses/invalid-deposit [get] 542 | func (c *OuterController) GetInvalidDepositAddresses() { 543 | defer c.ServeJSON() 544 | 545 | walletID := c.getWalletID() 546 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/addresses/invalid-deposit", walletID), 547 | nil, nil) 548 | if err != nil { 549 | logs.Error("GetInvalidDepositAddresses failed", err) 550 | c.AbortWithError(http.StatusInternalServerError, err) 551 | } 552 | 553 | var m map[string]interface{} 554 | json.Unmarshal(resp, &m) 555 | c.Data["json"] = m 556 | } 557 | 558 | // @Title Query wallet basic info 559 | // @router /wallets/:wallet_id/info [get] 560 | func (c *OuterController) GetWalletInfo() { 561 | defer c.ServeJSON() 562 | 563 | walletID := c.getWalletID() 564 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/info", walletID), 565 | nil, nil) 566 | if err != nil { 567 | logs.Error("GetWalletInfo failed", err) 568 | c.AbortWithError(http.StatusInternalServerError, err) 569 | } 570 | 571 | var m map[string]interface{} 572 | json.Unmarshal(resp, &m) 573 | c.Data["json"] = m 574 | } 575 | 576 | // @Title Verify addresses 577 | // @router /wallets/:wallet_id/addresses/verify [post] 578 | func (c *OuterController) VerifyAddresses() { 579 | defer c.ServeJSON() 580 | 581 | walletID := c.getWalletID() 582 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/addresses/verify", walletID), 583 | nil, c.Ctx.Input.RequestBody) 584 | if err != nil { 585 | logs.Error("VerifyAddresses failed", err) 586 | c.AbortWithError(http.StatusInternalServerError, err) 587 | } 588 | 589 | var m map[string]interface{} 590 | json.Unmarshal(resp, &m) 591 | c.Data["json"] = m 592 | } 593 | 594 | // @Title Query wallet transaction avarage blockchain fee 595 | // @router /wallets/:wallet_id/autofee [post] 596 | func (c *OuterController) GetAutoFee() { 597 | defer c.ServeJSON() 598 | 599 | walletID := c.getWalletID() 600 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/autofee", walletID), 601 | nil, c.Ctx.Input.RequestBody) 602 | if err != nil { 603 | logs.Error("GetAutoFee failed", err) 604 | c.AbortWithError(http.StatusInternalServerError, err) 605 | } 606 | 607 | var m map[string]interface{} 608 | json.Unmarshal(resp, &m) 609 | c.Data["json"] = m 610 | } 611 | 612 | // @Title Get balance of deposit wallet 613 | // @router /wallets/:wallet_id/receiver/balance [get] 614 | func (c *OuterController) GetDepositWalletBalance() { 615 | defer c.ServeJSON() 616 | 617 | walletID := c.getWalletID() 618 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/receiver/balance", walletID), 619 | nil, nil) 620 | if err != nil { 621 | logs.Error("GetDepositWalletBalance failed", err) 622 | c.AbortWithError(http.StatusInternalServerError, err) 623 | } 624 | 625 | var m map[string]interface{} 626 | json.Unmarshal(resp, &m) 627 | c.Data["json"] = m 628 | } 629 | 630 | // @Title Get balance of the vault wallet 631 | // @router /wallets/:wallet_id/vault/balance [get] 632 | func (c *OuterController) GetVaultWalletBalance() { 633 | defer c.ServeJSON() 634 | 635 | walletID := c.getWalletID() 636 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/vault/balance", walletID), 637 | nil, nil) 638 | if err != nil { 639 | logs.Error("GetVaultWalletBalance failed", err) 640 | c.AbortWithError(http.StatusInternalServerError, err) 641 | } 642 | 643 | var m map[string]interface{} 644 | json.Unmarshal(resp, &m) 645 | c.Data["json"] = m 646 | } 647 | 648 | // @Title Query the deployed contract collection addresses 649 | // @router /wallets/:wallet_id/addresses/contract_txid [get] 650 | func (c *OuterController) GetDeployedContractCollectionAddresses() { 651 | defer c.ServeJSON() 652 | 653 | walletID := c.getWalletID() 654 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/addresses/contract_txid", walletID), 655 | getQueryString(c.Ctx), nil) 656 | if err != nil { 657 | logs.Error("GetDeployedContractCollectionAddresses failed", err) 658 | c.AbortWithError(http.StatusInternalServerError, err) 659 | } 660 | 661 | var m map[string]interface{} 662 | json.Unmarshal(resp, &m) 663 | c.Data["json"] = m 664 | } 665 | 666 | // @Title Set Withdrawal Request ACL 667 | // @router /wallets/:wallet_id/sender/transactions/acl [post] 668 | func (c *OuterController) SetWithdrawalACL() { 669 | defer c.ServeJSON() 670 | 671 | walletID := c.getWalletID() 672 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions/acl", walletID), 673 | nil, c.Ctx.Input.RequestBody) 674 | if err != nil { 675 | logs.Error("SetWithdrawalACL failed", err) 676 | c.AbortWithError(http.StatusInternalServerError, err) 677 | } 678 | 679 | var m map[string]interface{} 680 | json.Unmarshal(resp, &m) 681 | c.Data["json"] = m 682 | } 683 | 684 | // @Title Resend Withdrawal Callback 685 | // @router /wallets/:wallet_id/sender/notifications/manual [post] 686 | func (c *OuterController) ResendWithdrawalCallbacks() { 687 | defer c.ServeJSON() 688 | 689 | walletID := c.getWalletID() 690 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/sender/notifications/manual", walletID), 691 | nil, c.Ctx.Input.RequestBody) 692 | if err != nil { 693 | logs.Error("ResendWithdrawalCallbacks failed", err) 694 | c.AbortWithError(http.StatusInternalServerError, err) 695 | } 696 | 697 | var m map[string]interface{} 698 | json.Unmarshal(resp, &m) 699 | c.Data["json"] = m 700 | } 701 | 702 | // @Title Refresh API code and secret 703 | // @router /wallets/:wallet_id/refreshsecret [post] 704 | func (c *OuterController) RefreshSecret() { 705 | defer c.ServeJSON() 706 | 707 | walletID := c.getWalletID() 708 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/refreshsecret", walletID), 709 | nil, c.Ctx.Input.RequestBody) 710 | if err != nil { 711 | logs.Error("RefreshSecret failed", err) 712 | c.AbortWithError(http.StatusInternalServerError, err) 713 | } 714 | 715 | var m map[string]interface{} 716 | json.Unmarshal(resp, &m) 717 | c.Data["json"] = m 718 | } 719 | 720 | // @Title Query the whitelist of the withdrawal wallet 721 | // @router /wallets/:wallet_id/sender/whitelist [get] 722 | func (c *OuterController) GetSenderWhitelist() { 723 | defer c.ServeJSON() 724 | 725 | walletID := c.getWalletID() 726 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/whitelist", walletID), 727 | getQueryString(c.Ctx), nil) 728 | if err != nil { 729 | logs.Error("GetSenderWhitelist failed", err) 730 | c.AbortWithError(http.StatusInternalServerError, err) 731 | } 732 | 733 | var m map[string]interface{} 734 | json.Unmarshal(resp, &m) 735 | c.Data["json"] = m 736 | } 737 | 738 | // @Title Add the outgoing address to the withdrawal wallet's whitelist 739 | // @router /wallets/:wallet_id/sender/whitelist [post] 740 | func (c *OuterController) AddSenderWhitelist() { 741 | defer c.ServeJSON() 742 | 743 | walletID := c.getWalletID() 744 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/sender/whitelist", walletID), 745 | nil, c.Ctx.Input.RequestBody) 746 | if err != nil { 747 | logs.Error("AddSenderWhitelist failed", err) 748 | c.AbortWithError(http.StatusInternalServerError, err) 749 | } 750 | 751 | var m map[string]interface{} 752 | json.Unmarshal(resp, &m) 753 | c.Data["json"] = m 754 | } 755 | 756 | // @Title Remove the outgoing address from the withdrawal wallet's whitelist 757 | // @router /wallets/:wallet_id/sender/whitelist [delete] 758 | func (c *OuterController) RemoveSenderWhitelist() { 759 | defer c.ServeJSON() 760 | 761 | walletID := c.getWalletID() 762 | resp, err := api.MakeRequest(walletID, "DELETE", fmt.Sprintf("/v1/sofa/wallets/%d/sender/whitelist", walletID), 763 | nil, c.Ctx.Input.RequestBody) 764 | if err != nil { 765 | logs.Error("RemoveSenderWhitelist failed", err) 766 | c.AbortWithError(http.StatusInternalServerError, err) 767 | } 768 | 769 | var m map[string]interface{} 770 | json.Unmarshal(resp, &m) 771 | c.Data["json"] = m 772 | } 773 | 774 | // @Title Query the withdrawal wallet's whitelist config 775 | // @router /wallets/:wallet_id/sender/whitelist/config [get] 776 | func (c *OuterController) QuerySenderWhitelistConfig() { 777 | defer c.ServeJSON() 778 | 779 | walletID := c.getWalletID() 780 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/whitelist/config", walletID), 781 | getQueryString(c.Ctx), nil) 782 | if err != nil { 783 | logs.Error("QuerySenderWhitelistConfig failed", err) 784 | c.AbortWithError(http.StatusInternalServerError, err) 785 | } 786 | 787 | var m map[string]interface{} 788 | json.Unmarshal(resp, &m) 789 | c.Data["json"] = m 790 | } 791 | 792 | // @Title Check the outgoing address status in the withdrawal wallet's whitelist 793 | // @router /wallets/:wallet_id/sender/whitelist/check [post] 794 | func (c *OuterController) CheckSenderWhitelist() { 795 | defer c.ServeJSON() 796 | 797 | walletID := c.getWalletID() 798 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/sender/whitelist/check", walletID), 799 | nil, c.Ctx.Input.RequestBody) 800 | if err != nil { 801 | logs.Error("CheckSenderWhitelist failed", err) 802 | c.AbortWithError(http.StatusInternalServerError, err) 803 | } 804 | 805 | var m map[string]interface{} 806 | json.Unmarshal(resp, &m) 807 | c.Data["json"] = m 808 | } 809 | 810 | // @Title Update the label of the deposit address 811 | // @router /wallets/:wallet_id/addresses/label [post] 812 | func (c *OuterController) UpdateDepositWalletAddressLabel() { 813 | defer c.ServeJSON() 814 | 815 | walletID := c.getWalletID() 816 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/addresses/label", walletID), 817 | nil, c.Ctx.Input.RequestBody) 818 | if err != nil { 819 | logs.Error("UpdateDepositWalletAddressLabel failed", err) 820 | c.AbortWithError(http.StatusInternalServerError, err) 821 | } 822 | 823 | var m map[string]interface{} 824 | json.Unmarshal(resp, &m) 825 | c.Data["json"] = m 826 | } 827 | 828 | // @Title Query the deposit addresses' labels 829 | // @router /wallets/:wallet_id/addresses/get_labels [post] 830 | func (c *OuterController) GetDepositWalletAddressesLabel() { 831 | defer c.ServeJSON() 832 | 833 | walletID := c.getWalletID() 834 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/addresses/get_labels", walletID), 835 | nil, c.Ctx.Input.RequestBody) 836 | if err != nil { 837 | logs.Error("GetDepositWalletAddressesLabel failed", err) 838 | c.AbortWithError(http.StatusInternalServerError, err) 839 | } 840 | 841 | var m map[string]interface{} 842 | json.Unmarshal(resp, &m) 843 | c.Data["json"] = m 844 | } 845 | 846 | // @Title Get the wallet list that the read-only API token can access 847 | // @router /wallets/readonly/walletlist [get] 848 | func (c *OuterController) GetReadOnlyWalletList() { 849 | defer c.ServeJSON() 850 | 851 | resp, err := api.MakeRequest(0, "GET", "/v1/sofa/wallets/readonly/walletlist", 852 | nil, c.Ctx.Input.RequestBody) 853 | if err != nil { 854 | logs.Error("GetReadOnlyWalletList failed", err) 855 | c.AbortWithError(http.StatusInternalServerError, err) 856 | } 857 | 858 | var m map[string]interface{} 859 | json.Unmarshal(resp, &m) 860 | c.Data["json"] = m 861 | } 862 | 863 | // @Title Check the callback endpoints 864 | // @router /wallets/:wallet_id/notifications/inspect [post] 865 | func (c *OuterController) NotificationsInspect() { 866 | defer c.ServeJSON() 867 | 868 | walletID := c.getWalletID() 869 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/notifications/inspect", walletID), 870 | nil, c.Ctx.Input.RequestBody) 871 | if err != nil { 872 | logs.Error("NotificationsInspect failed", err) 873 | c.AbortWithError(http.StatusInternalServerError, err) 874 | } 875 | 876 | var m map[string]interface{} 877 | json.Unmarshal(resp, &m) 878 | c.Data["json"] = m 879 | } 880 | 881 | // @Title Query the withdrawal wallet's transaction history 882 | // @router /wallets/:wallet_id/sender/transactions [get] 883 | func (c *OuterController) GetSenderTransactionHistory() { 884 | defer c.ServeJSON() 885 | 886 | walletID := c.getWalletID() 887 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions", walletID), 888 | getQueryString(c.Ctx), nil) 889 | if err != nil { 890 | logs.Error("GetSenderTransactionHistory failed", err) 891 | c.AbortWithError(http.StatusInternalServerError, err) 892 | } 893 | 894 | var m map[string]interface{} 895 | json.Unmarshal(resp, &m) 896 | c.Data["json"] = m 897 | } 898 | 899 | // @Title Query wallet transaction avarage blockchain fees 900 | // @router /wallets/:wallet_id/autofees [post] 901 | func (c *OuterController) GetAutoFees() { 902 | defer c.ServeJSON() 903 | 904 | walletID := c.getWalletID() 905 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/autofees", walletID), 906 | nil, c.Ctx.Input.RequestBody) 907 | if err != nil { 908 | logs.Error("GetAutoFees failed", err) 909 | c.AbortWithError(http.StatusInternalServerError, err) 910 | } 911 | 912 | var m map[string]interface{} 913 | json.Unmarshal(resp, &m) 914 | c.Data["json"] = m 915 | } 916 | 917 | // @Title Sign Message 918 | // @router /wallets/:wallet_id/signmessage [post] 919 | func (c *OuterController) SignMessage() { 920 | defer c.ServeJSON() 921 | 922 | walletID := c.getWalletID() 923 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/signmessage", walletID), 924 | nil, c.Ctx.Input.RequestBody) 925 | if err != nil { 926 | logs.Error("SignMessage failed", err) 927 | c.AbortWithError(http.StatusInternalServerError, err) 928 | } 929 | 930 | var m map[string]interface{} 931 | json.Unmarshal(resp, &m) 932 | c.Data["json"] = m 933 | } 934 | 935 | // @Title Call contract read ABI 936 | // @router /wallets/:wallet_id/contract/read [get] 937 | func (c *OuterController) CallContractRead() { 938 | defer c.ServeJSON() 939 | 940 | walletID := c.getWalletID() 941 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/contract/read", walletID), 942 | getQueryString(c.Ctx), nil) 943 | if err != nil { 944 | logs.Error("CallContractRead failed", err) 945 | c.AbortWithError(http.StatusInternalServerError, err) 946 | } 947 | 948 | var m map[string]interface{} 949 | json.Unmarshal(resp, &m) 950 | c.Data["json"] = m 951 | } 952 | 953 | // @Title Get transaction event logs 954 | // @router /wallets/:wallet_id/sender/transactions/eventlog [get] 955 | func (c *OuterController) GetTransactionEventLog() { 956 | defer c.ServeJSON() 957 | 958 | walletID := c.getWalletID() 959 | resp, err := api.MakeRequest(walletID, "GET", fmt.Sprintf("/v1/sofa/wallets/%d/sender/transactions/eventlog", 960 | walletID), getQueryString(c.Ctx), nil) 961 | if err != nil { 962 | logs.Error("GetTransactionEventLog failed", err) 963 | c.AbortWithError(http.StatusInternalServerError, err) 964 | } 965 | 966 | var m map[string]interface{} 967 | json.Unmarshal(resp, &m) 968 | c.Data["json"] = m 969 | } 970 | 971 | // @Title Get balances of the wallet list that the read-only API token can access 972 | // @router /wallets/readonly/walletlist/balances [get] 973 | func (c *OuterController) GetReadOnlyWalletListBalances() { 974 | defer c.ServeJSON() 975 | 976 | resp, err := api.MakeRequest(0, "GET", "/v1/sofa/wallets/readonly/walletlist/balances", 977 | getQueryString(c.Ctx), nil) 978 | if err != nil { 979 | logs.Error("GetReadOnlyWalletListBalances failed", err) 980 | c.AbortWithError(http.StatusInternalServerError, err) 981 | } 982 | 983 | var m map[string]interface{} 984 | json.Unmarshal(resp, &m) 985 | c.Data["json"] = m 986 | } 987 | 988 | // @Title Verify Deposit Addresses 989 | // @router /wallets/:wallet_id/receiver/addresses/verify [post] 990 | func (c *OuterController) VerifyDepositAddresses() { 991 | defer c.ServeJSON() 992 | 993 | walletID := c.getWalletID() 994 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/receiver/addresses/verify", walletID), 995 | nil, c.Ctx.Input.RequestBody) 996 | if err != nil { 997 | logs.Error("VerifyDepositAddresses failed", err) 998 | c.AbortWithError(http.StatusInternalServerError, err) 999 | } 1000 | 1001 | var m map[string]interface{} 1002 | json.Unmarshal(resp, &m) 1003 | c.Data["json"] = m 1004 | } 1005 | 1006 | // @Title Get currency prices 1007 | // @router /currency/prices [get] 1008 | func (c *OuterController) GetCurrencyPrices() { 1009 | defer c.ServeJSON() 1010 | 1011 | resp, err := api.MakeRequest(0, "GET", "/v1/sofa/currency/prices", 1012 | getQueryString(c.Ctx), nil) 1013 | if err != nil { 1014 | logs.Error("GetCurrencyPrices failed", err) 1015 | c.AbortWithError(http.StatusInternalServerError, err) 1016 | } 1017 | 1018 | var m map[string]interface{} 1019 | json.Unmarshal(resp, &m) 1020 | c.Data["json"] = m 1021 | } 1022 | 1023 | // @Title Get Delegated Addresses Balances 1024 | // @router /wallets/:wallet_id/receiver/get-balances [post] 1025 | func (c *OuterController) GetDelegatedBalances() { 1026 | defer c.ServeJSON() 1027 | 1028 | walletID := c.getWalletID() 1029 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/receiver/get-balances", walletID), 1030 | nil, c.Ctx.Input.RequestBody) 1031 | if err != nil { 1032 | logs.Error("GetDelegatedBalances failed", err) 1033 | c.AbortWithError(http.StatusInternalServerError, err) 1034 | } 1035 | 1036 | var m map[string]interface{} 1037 | json.Unmarshal(resp, &m) 1038 | c.Data["json"] = m 1039 | } 1040 | 1041 | // @Title Query Token Meta Info 1042 | // @router /currency/:currency/contract/get-multiple-tokenuri [post] 1043 | func (c *OuterController) GetContractTokenMeta() { 1044 | defer c.ServeJSON() 1045 | 1046 | currency := c.Ctx.Input.Param(":currency") 1047 | 1048 | resp, err := api.MakeRequest(0, "POST", fmt.Sprintf("/v1/sofa/currency/%s/contract/get-multiple-tokenuri", currency), 1049 | nil, c.Ctx.Input.RequestBody) 1050 | if err != nil { 1051 | logs.Error("GetContractTokenMeta failed", err) 1052 | c.AbortWithError(http.StatusInternalServerError, err) 1053 | } 1054 | 1055 | var m map[string]interface{} 1056 | json.Unmarshal(resp, &m) 1057 | c.Data["json"] = m 1058 | } 1059 | 1060 | // @Title Sign Arweave transaction 1061 | // @router /wallets/:wallet_id/signtransaction [post] 1062 | func (c *OuterController) SignTransaction() { 1063 | defer c.ServeJSON() 1064 | 1065 | walletID := c.getWalletID() 1066 | resp, err := api.MakeRequest(walletID, "POST", fmt.Sprintf("/v1/sofa/wallets/%d/signtransaction", walletID), 1067 | nil, c.Ctx.Input.RequestBody) 1068 | if err != nil { 1069 | logs.Error("SignTransaction failed", err) 1070 | c.AbortWithError(http.StatusInternalServerError, err) 1071 | } 1072 | 1073 | var m map[string]interface{} 1074 | json.Unmarshal(resp, &m) 1075 | c.Data["json"] = m 1076 | } 1077 | 1078 | // @Title Get Service Health Status 1079 | // @router /healthcheck [get] 1080 | func (c *OuterController) HealthCheck() { 1081 | defer c.ServeJSON() 1082 | 1083 | resp, err := api.MakeRequest(0, "GET", "/v1/sofa/healthcheck", nil, nil) 1084 | if err != nil { 1085 | logs.Error("HealthCheck failed", err) 1086 | c.AbortWithError(http.StatusInternalServerError, err) 1087 | } 1088 | 1089 | var m map[string]interface{} 1090 | json.Unmarshal(resp, &m) 1091 | c.Data["json"] = m 1092 | } 1093 | -------------------------------------------------------------------------------- /merchant/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | README 9 | 10 | 11 | 325 | 326 | 327 | 328 | 329 | 330 | 331 |

332 | 333 |

Table of contents

334 | 335 | 364 | 365 |

366 | 367 |

Get Started

368 | 369 |

CYBAVO Merchant Service(CMS) is a comprehensive cryptocurrency payment servcie, based on the deposit wallet of CYBAVO VAULT.

370 | 371 |

Try it now

372 | 373 | 376 | 377 |

Start integration

378 | 379 | 383 | 384 |

385 | 386 |

API Authentication

387 | 388 | 391 | 392 |

How to acquire and refresh API code and secret

393 | 394 | 403 | 404 |

How to make a correct request?

405 | 406 | 427 | 428 |

429 | 430 |

Callback Integration

431 | 432 | 453 | 454 |

REST API

455 | 456 |

457 | 458 |

Create Payment Order

459 | 460 |

Create a payment order.

461 | 462 |
Request
463 | 464 |

POST /v1/merchant/MERCHANT_ID/order

465 | 466 | 469 | 470 |
471 |

The order_id must be prefixed. Find prefix from corresponding merchant detail on web control panel.

472 | 473 |

The prefix is N520335069_ is the following example.

474 |
475 | 476 |
Request Format
477 | 478 |

An example of the request:

479 | 480 |
API
481 | 482 |
/v1/merchant/520335069/order
483 | 484 |
Post body
485 | 486 |
{
 487 |   "currency": 60,
 488 |   "token_address": "",
 489 |   "amount": "0.01",
 490 |   "duration": 50,
 491 |   "description": "ETH payment",
 492 |   "order_id": "N520335069_1000022",
 493 |   "redirect_url": "https%3A%2F%2Fmyredirect.example.com%3Fk%3Dv%26k1%3Dv1"
 494 | }
495 | 496 |

The request includes the following parameters:

497 | 498 |
Post body
499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 |
FieldTypeNoteDescription
currencyint64required, refer to the Currency DefinitionThe cryptocurrency used to pay the order
token_addressstringoptionalThe token contract address of cryptocurrency used to pay the order
amountstringrequired, refer to the definition for the default maximum amountThe required amount of the payment order
durationint64optional, refer to the definition for the minimum durationThe expiration duration (in minutes) of the payment order
descriptionstringoptional, max 255 charsThe description of the payment order
order_idstringrequiredThe user defined order ID (must be prefixed)
redirect_urlstringoptionalUser defined redirect URL (must be encoded)
555 | 556 |
557 |

The redirect_url must be encoded. Please refer to the code snippet on the github project to know how to encode the URL. Go, Java, Javascript, PHP

558 | 559 |

If using BNB or XLM payment order, the payment transaction must specify an accurate memo to complete the order.

560 |
561 | 562 |

563 | Boundary definition

564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 |
IDCurrency SymbolMinimum DurationDefault DurationDefault Minimum / Maximum AmountConfirm Blocks
0BTC201200.00002 / 0.22
2LTC201200.005 / 506
3DOGE201200.005 / 5015
60ETH10600.0005 / 56
145BCH201200.005 / 206
148XLM10302 / 20001
195TRX103010 / 100001
714BNB10300.003 / 301
966MATIC10600.005 / 5050
1815ADA10301 / 100030
99999999997BSC10600.005 / 5020
668 | 669 |
670 |

The Maximum Amount boundary can be adjusted in the web control panel.

671 |
672 | 673 |
Response Format
674 | 675 |

An example of a successful response:

676 | 677 |
{
 678 |   "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE",
 679 |   "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC",
 680 |   "expired_time": 1615975467,
 681 |   "order_id": "N520335069_1000022"
 682 | }
683 | 684 |

For BNB, XLM

685 | 686 |
{
 687 |   "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE",
 688 |   "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC",
 689 |   "expired_time": 1615975467,
 690 |   "order_id": "N520335069_1000023",
 691 |   "memo": "63574"
 692 | }
693 | 694 |

The response includes the following parameters:

695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 |
FieldTypeDescription
access_tokenstringThe access token used to query, update or cancel the payment order
addressstringThe address to accept the payment
expired_timeint64The due date of the payment order (unix time in UTC)
order_idstringThe order ID of the payment order
memostringThe memo of the payment order, the payment transaction must specify an accurate memo to complete the order.
733 | 734 |
Error Code
735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
400112Invalid parameter-Malformatted post body
403113Permission denied-Merchant API not allowed
40020003Wallet address not available-There is no available deposit address
40020007Wallet not found-No corresponding linked deposit wallet found (specified by the currency and token address in the request)
40020009Duplicated Order ID-The order ID has been used
40020010Wrong Order prefix-The order prefix is wrong
40020011Wrong Order format-The order contains invalid characters
40020014Wrong order amount-The decimals of the amount does not conform to currency's decimals
40020015Invalid order duration, should not be less than minimum-The requested duration is less than the lower bound
40020016Over amount limit-The requested amount exceeds upper bound
40020017Invalid url host-The redirect URL is invalid
876 | 877 |
Back to top
878 | 879 |

880 | 881 |

Query Payment Order Status

882 | 883 |

Query current status of a payment order.

884 | 885 |
Request
886 | 887 |

VIEW GET /v1/merchant/MERCHANT_ID/order?token=ACCESS_TOKEN&order=ORDER_ID

888 | 889 | 892 | 893 |
Request Format
894 | 895 |

An example of the request:

896 | 897 |
API
898 | 899 |
/v1/merchant/520335069/order?token=ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE&order=N520335069_1000022
900 | 901 |
Response Format
902 | 903 |

An example of a successful response:

904 | 905 |
{
 906 |   "address": "0xed965D0A23eC4583f55Fb5d4109C0fE069B396fC",
 907 |   "expired_time": 1615975468,
 908 |   "redirect_url": "https%3A%2F%2Fmyredirect.example.com%3Fk%3Dv%26k1%3Dv1",
 909 |   "state": 1,
 910 |   "tx_id": ""
 911 | }
912 | 913 |

The response includes the following parameters:

914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 |
FieldTypeDescription
addressstringThe deposit address to accept payment
expired_timeint64The due date of the payment (unix time in UTC)
redirect_urlstringUser defined redirect URL (encoded)
stateintRefer to Order State Definition
tx_idstringThe TX ID of the payment if state is 0, 2 or 3
952 | 953 |
954 |

The redirect_url is encoded. Please refer to the code snippet on the github project to know how to decode the URL. Go, Java, Javascript, PHP

955 |
956 | 957 |
Error Code
958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403112Invalid parameter-The invalid ACCESS_TOKEN/ORDER_ID
403113Permission denied-Merchant API not allowed
1036 | 1037 |
Back to top
1038 | 1039 |

1040 | 1041 |

Cancel Payment Order

1042 | 1043 |

Cancel a payment order. Only pending payment order can be cancelled.

1044 | 1045 |
Request
1046 | 1047 |

DELETE /v1/merchant/MERCHANT_ID/order?token=ACCESS_TOKEN&order=ORDER_ID

1048 | 1049 | 1052 | 1053 |
Request Format
1054 | 1055 |

An example of the request:

1056 | 1057 |
API
1058 | 1059 |
/v1/merchant/520335069/order?token=ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE&order=N520335069_1000022
1060 | 1061 |
Response Format
1062 | 1063 |

An example of a successful response:

1064 | 1065 |
{
1066 |   "result": 1
1067 | }
1068 | 1069 |

The response includes the following parameters:

1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 |
FieldTypeDescription
resultintAlways be 1, means the payment order has been successfully cancelled
1088 | 1089 |
Error Code
1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403112Invalid parameter-The invalid ACCESS_TOKEN/ORDER_ID or the payment can't be cancelled
403113Permission denied-Merchant API not allowed
1168 | 1169 |
Back to top
1170 | 1171 |

1172 | 1173 |

Update Payment Order Expiration Duration

1174 | 1175 |

Update a payment order expiration duration. Only pending payment order can be updated.

1176 | 1177 |

POST /v1/merchant/MERCHANT_ID/order/duration

1178 | 1179 | 1182 | 1183 |
Request Format
1184 | 1185 |

An example of the request:

1186 | 1187 |
API
1188 | 1189 |
/v1/merchant/520335069/order
1190 | 1191 |
Post body
1192 | 1193 |
{
1194 |   "access_token": "ybJWKM_CT1yXxzLO2z1Y5fg1EzHuMyRA14ubzR8i-RE",
1195 |   "order_id": "N520335069_1000022",
1196 |   "duration": 50
1197 | }
1198 | 1199 |

The request includes the following parameters:

1200 | 1201 |
Post body
1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 |
FieldTypeNoteDescription
access_tokenstringrequiredThe access token of the payment order returned with Create order API
order_idstringrequiredThe order ID of the payment order
durationint64requiredThe expiration duration (in minutes) of the payment order
1234 | 1235 |
1236 |

The new due date is calculated based on the submission time of the payment order.

1237 |
1238 | 1239 |
Response Format
1240 | 1241 |

An example of a successful response:

1242 | 1243 |
{
1244 |   "result": 1
1245 | }
1246 | 1247 |

The response includes the following parameters:

1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 |
FieldTypeDescription
resultintAlways be 1, means the payment order has been successfully updated
1266 | 1267 |
Error Code
1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403112Invalid parameter-The invalid ACCESS_TOKEN/ORDER_ID or the payment can't be updated
403113Permission denied-Merchant API not allowed
1346 | 1347 |
Back to top
1348 | 1349 |

1350 | 1351 |

Resend Failed Merchant Callbacks

1352 | 1353 |

The callback handler can call this API to resend pending or failed merchant callbacks.

1354 | 1355 |

Refer to Callback Integration for callback rules.

1356 | 1357 |
1358 |

The resend operation could be requested on the web control panel as well.

1359 |
1360 | 1361 |
Request
1362 | 1363 |

POST /v1/merchant/MERCHANT_ID/notifications/manual

1364 | 1365 | 1368 | 1369 |
Request Format
1370 | 1371 |

An example of the request:

1372 | 1373 |
API
1374 | 1375 |
/v1/merchant/520335069/notifications/manual
1376 | 1377 |
Response Format
1378 | 1379 |

An example of a successful response:

1380 | 1381 |
{
1382 |   "count": 0
1383 | }
1384 | 1385 |

The response includes the following parameters:

1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 |
FieldTypeDescription
countintCount of callbacks just resent
1404 | 1405 |
Error Code
1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1414 | 1415 | 1416 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1423 | 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 | 1433 | 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | 1465 | 1466 | 1467 | 1468 | 1469 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403113Permission denied-Merchant API not allowed
1477 | 1478 |
Back to top
1479 | 1480 |

1481 | 1482 |

Activate Merchant API Code

1483 | 1484 |

Activate the API code of a certain merchant. Once activated, the currently activated API code will immediately become invalid.

1485 | 1486 |
Request
1487 | 1488 |

POST /v1/merchant/MERCHANT_ID/apisecret/activate

1489 | 1490 | 1493 | 1494 |
Request Format
1495 | 1496 |

An example of the request:

1497 | 1498 |
API
1499 | 1500 |
/v1/merchant/520335069/apisecret/activate
1501 | 1502 |
Response Format
1503 | 1504 |

An example of a successful response:

1505 | 1506 |
{
1507 |   "api_code": "4PcdE9VjXfrk7WjC1",
1508 |   "exp": 1609646716
1509 | }
1510 | 1511 |

The response includes the following parameters:

1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | 1532 | 1533 | 1534 |
FieldTypeDescription
api_codestringThe activated API code
expint64The API code expiration unix time in UTC
1535 | 1536 |
Error Code
1537 | 1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | 1568 | 1569 | 1570 | 1571 | 1572 | 1573 | 1574 | 1575 | 1576 | 1577 | 1578 | 1579 | 1580 | 1581 | 1582 | 1583 | 1584 | 1585 | 1586 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | 1593 | 1594 | 1595 | 1596 | 1597 | 1598 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | 1605 | 1606 | 1607 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403113Permission denied-Merchant API not allowed
1608 | 1609 |
Back to top
1610 | 1611 |

1612 | 1613 |

Query Merchant API Code Status

1614 | 1615 |

Query the API code info of a certain merchant. Use the inactivated API code in any request will activate it. Once activated, the currently activated API code will immediately become invalid.

1616 | 1617 |
Request
1618 | 1619 |

VIEW GET /v1/merchant/MERCHANT_ID/apisecret

1620 | 1621 | 1624 | 1625 |
Request Format
1626 | 1627 |

An example of the request:

1628 | 1629 |
API
1630 | 1631 |
/v1/merchant/520335069/apisecret
1632 | 1633 |
Response Format
1634 | 1635 |

An example of a successful response:

1636 | 1637 |
{
1638 |   "valid": {
1639 |     "api_code": "H4Q6xFZgiTZb37GN",
1640 |     "exp": 1583144863
1641 |   },
1642 |   "inactivated": {
1643 |     "api_code": "32PmGCjNzXda4mNHX"
1644 |   }
1645 | }
1646 | 1647 |

The response includes the following parameters:

1648 | 1649 | 1650 | 1651 | 1652 | 1653 | 1654 | 1655 | 1656 | 1657 | 1658 | 1659 | 1660 | 1661 | 1662 | 1663 | 1664 | 1665 | 1666 | 1667 | 1668 | 1669 | 1670 | 1671 | 1672 | 1673 | 1674 | 1675 | 1676 | 1677 | 1678 | 1679 | 1680 |
FieldTypeDescription
validobjectThe activated API code
inactivatedobjectNot active API code
api_codestringThe API code for querying wallet
expint64The API code expiration unix time in UTC
1681 | 1682 |
1683 |

Use an invalid API-CODE, the caller will get a 403 Forbidden error.

1684 |
1685 | 1686 |
Error Code
1687 | 1688 | 1689 | 1690 | 1691 | 1692 | 1693 | 1694 | 1695 | 1696 | 1697 | 1698 | 1699 | 1700 | 1701 | 1702 | 1703 | 1704 | 1705 | 1706 | 1707 | 1708 | 1709 | 1710 | 1711 | 1712 | 1713 | 1714 | 1715 | 1716 | 1717 | 1718 | 1719 | 1720 | 1721 | 1722 | 1723 | 1724 | 1725 | 1726 | 1727 | 1728 | 1729 | 1730 | 1731 | 1732 | 1733 | 1734 | 1735 | 1736 | 1737 | 1738 | 1739 | 1740 | 1741 | 1742 | 1743 | 1744 | 1745 | 1746 | 1747 | 1748 | 1749 | 1750 | 1751 | 1752 | 1753 | 1754 | 1755 | 1756 | 1757 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
403113Permission denied-Merchant API not allowed
1758 | 1759 |
Back to top
1760 | 1761 |

1762 | 1763 |

Refresh Merchant API Code

1764 | 1765 |

Use paired refresh code to acquire the new inactive API code/secret of the merchant.

1766 | 1767 |
Request
1768 | 1769 |

POST /v1/merchant/MERCHANT_ID/apisecret/refreshsecret

1770 | 1771 | 1774 | 1775 |
Request Format
1776 | 1777 |

An example of the request:

1778 | 1779 |
API
1780 | 1781 |
/v1/merchant/520335069/apisecret/refreshsecret
1782 | 1783 |
Post body
1784 | 1785 |
{
1786 |   "refresh_code":"3EbaSPUpKzHJ9wYgYZqy6W4g43NT365bm9vtTfYhMPra"
1787 | }
1788 | 1789 |

The request includes the following parameters:

1790 | 1791 |
Post body
1792 | 1793 | 1794 | 1795 | 1796 | 1797 | 1798 | 1799 | 1800 | 1801 | 1802 | 1803 | 1804 | 1805 | 1806 | 1807 | 1808 | 1809 | 1810 | 1811 |
FieldTypeNoteDescription
refresh_codestringrequiredThe corresponding refresh code of the API code specified in the X-API-CODE header
1812 | 1813 |
Response Format
1814 | 1815 |

An example of a successful response:

1816 | 1817 |
{
1818 |   "api_code": "4QjbY3qES4tEh19PU",
1819 |   "api_secret": "3jC1qjr4mrKxfoXkxoN27Uhmbm1E",
1820 |   "refresh_code": "HcN17gxZ3ojrBYSXnjKsU9Pun8krP6J9Pn678k4rZ13m"
1821 | }
1822 | 1823 |

The response includes the following parameters:

1824 | 1825 | 1826 | 1827 | 1828 | 1829 | 1830 | 1831 | 1832 | 1833 | 1834 | 1835 | 1836 | 1837 | 1838 | 1839 | 1840 | 1841 | 1842 | 1843 | 1844 | 1845 | 1846 | 1847 | 1848 | 1849 | 1850 | 1851 |
FieldTypeDescription
api_codestringThe new inactive API code
api_secretstringThe API secret
refresh_codestringThe paired refresh code
1852 | 1853 |
Error Code
1854 | 1855 | 1856 | 1857 | 1858 | 1859 | 1860 | 1861 | 1862 | 1863 | 1864 | 1865 | 1866 | 1867 | 1868 | 1869 | 1870 | 1871 | 1872 | 1873 | 1874 | 1875 | 1876 | 1877 | 1878 | 1879 | 1880 | 1881 | 1882 | 1883 | 1884 | 1885 | 1886 | 1887 | 1888 | 1889 | 1890 | 1891 | 1892 | 1893 | 1894 | 1895 | 1896 | 1897 | 1898 | 1899 | 1900 | 1901 | 1902 | 1903 | 1904 | 1905 | 1906 | 1907 | 1908 | 1909 | 1910 | 1911 | 1912 | 1913 | 1914 | 1915 | 1916 | 1917 | 1918 | 1919 | 1920 | 1921 | 1922 | 1923 | 1924 | 1925 | 1926 | 1927 | 1928 | 1929 | 1930 | 1931 |
HTTP CodeError CodeErrorMessageDescription
403-Forbidden. Invalid ID-No merchant ID found
403-Forbidden. Header not found-Missing X-API-CODE, X-CHECKSUM header or query param t
403-Forbidden. Invalid timestamp-The timestamp t is not in the valid time range
403-Forbidden. Invalid checksum-The request is considered a replayed request
403-Forbidden. Invalid API code-X-API-CODE header contains invalid API code
403-Forbidden. Checksum unmatch-X-CHECKSUM header contains wrong checksum
403-Forbidden. Call too frequently ({THROTTLING_COUNT} calls/minute)-Send requests too frequently
400112Invalid parameter-Malformatted post body or the refresh code is invalid
403113Permission denied-Merchant API not allowed
1932 | 1933 |
Back to top
1934 | 1935 |

1936 | 1937 |

Mock Server

1938 | 1939 |

How to compile

1940 | 1941 | 1951 | 1952 |

Setup configuration

1953 | 1954 |
1955 |

Configure CYBAVO API server URL in mockserver.app.conf

1956 |
1957 | 1958 |
api_server_url="BACKEND_SERVER_URL"
1959 | 1960 |

Put merchant API code/secret into mock server

1961 | 1962 | 1970 | 1971 |
curl -X POST -H "Content-Type: application/json" -d '{"api_code":"API_CODE","api_secret":"API_SECRET"}' \
1972 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apitoken
1973 | 1974 |

Register mock server callback URL

1975 | 1976 |
1977 |

Operate on web control panel

1978 |
1979 | 1980 |

Notification Callback URL

1981 | 1982 |
http://localhost:8889/v1/mock/merchant/callback
1983 | 1984 |
Back to top
1985 | 1986 |

1987 | 1988 |

cURL Testing Commands

1989 | 1990 |

1991 | 1992 |

Create Payment Order

1993 | 1994 |
curl -X POST -H "Content-Type: application/json" -d '{"currency":60,"token_address":"","amount":"0.01","duration":50,"description":"TEST Order","redirect_url":"","order_id":"N520335069_10001"}' \
1995 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order
1996 | 1997 | 2000 | 2001 |

2002 | 2003 |

Query Payment Order Status

2004 | 2005 |
curl http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order?token={ACCESS_TOKEN}&order={ORDER_ID}
2006 | 2007 | 2010 | 2011 |

2012 | 2013 |

Cancel Payment Order

2014 | 2015 |
curl -X DELETE http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order?token={ACCESS_TOKEN}&order={ORDER_ID}
2016 | 2017 | 2020 | 2021 |

2022 | 2023 |

Update Payment Order Expiration Duration

2024 | 2025 |
curl -X POST -H "Content-Type: application/json" -d '{"access_token":"IUxyiWsdIBp_FS6Tu2afaecH4F_dqpYOLj4oXxn02AA","order_id":"N520335069_10001","duration":100}' \
2026 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/order/duration
2027 | 2028 | 2031 | 2032 |

2033 | 2034 |

Resend Failed Merchant Callbacks

2035 | 2036 |
curl -X POST http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/notifications/manual
2037 | 2038 | 2041 | 2042 |

2043 | 2044 |

Activate Merchant API Code

2045 | 2046 |
curl -X POST http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret/activate
2047 | 2048 | 2051 | 2052 |

2053 | 2054 |

Query Merchant API Code Status

2055 | 2056 |
curl http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret
2057 | 2058 | 2061 | 2062 |

2063 | 2064 |

Refresh Merchant API Code

2065 | 2066 |
curl -X POST -H "Content-Type: application/json" -d '{"refresh_code":"3EbaSPUpKzHJ9wYgYZqy6W4g43NT365bm9vtTfYhMPra"}' \
2067 | http://localhost:8889/v1/mock/merchant/{MERCHANT_ID}/apisecret/refreshsecret
2068 | 2069 | 2072 | 2073 |
Back to top
2074 | 2075 |

2076 | 2077 |

Other Language Versions

2078 | 2079 | 2084 | 2085 |
Back to top
2086 | 2087 |

Appendix

2088 | 2089 |

2090 | 2091 |

Callback Definition

2092 | 2093 | 2094 | 2095 | 2096 | 2097 | 2098 | 2099 | 2100 | 2101 | 2102 | 2103 | 2104 | 2105 | 2106 | 2107 | 2108 | 2109 | 2110 | 2111 | 2112 | 2113 | 2114 | 2115 | 2116 | 2117 | 2118 | 2119 | 2120 | 2121 | 2122 | 2123 | 2124 | 2125 | 2126 | 2127 | 2128 | 2129 | 2130 | 2131 | 2132 | 2133 | 2134 | 2135 | 2136 | 2137 | 2138 | 2139 | 2140 | 2141 | 2142 | 2143 | 2144 | 2145 | 2146 | 2147 | 2148 | 2149 | 2150 | 2151 | 2152 | 2165 | 2166 | 2167 | 2168 | 2169 | 2170 | 2171 | 2172 | 2173 | 2174 | 2175 | 2176 | 2177 | 2178 | 2179 | 2182 | 2183 | 2184 | 2185 | 2186 | 2189 | 2190 | 2191 | 2192 | 2193 | 2194 | 2195 |
FieldTypeDescription
merchant_idint64The merchant ID of the callback
order_idstringThe unique order ID of the payment order
currencystringCryptocurrency of the callback
txidstringTransaction identifier
block_heightint64The block height show the transaction was packed in which block
recv_amountstringReceived amount denominated in the smallest cryptocurrency unit
feestringMining fee denominated in the smallest cryptocurrency unit
broadcast_atint64When to broadcast the transaction in UTC time
from_addressstringThe source address of the transaction
to_addressstringThe destination address of the transaction
stateint 2153 | Possible states (listed in the Order State Definition table) 2154 | 2155 | 2156 | 2157 | 2158 | 2159 | 2160 | 2161 | 2162 | 2163 |
IDDescription
0The order has been successfully paid
1The order has expired
2The amount received is less than the amount requested
3The amount received is greater than the amount requested
4The order cancelled
2164 |
decimalintThe decimal of cryptocurrency
fee_decimalintThe decimal of cryptocurrency miner fee
addonkey-value pairs 2180 | The extra information of this callback 2181 |
currency_bip44int64 2187 | Refer to Currency Definition table 2188 |
token_addressstringThe contract address of cryptocurrency
2196 | 2197 |
Back to top
2198 | 2199 |

2200 | 2201 |

Order State Definition

2202 | 2203 | 2204 | 2205 | 2206 | 2207 | 2208 | 2209 | 2210 | 2211 | 2212 | 2213 | 2214 | 2215 | 2216 | 2217 | 2218 | 2219 | 2220 | 2221 | 2222 | 2223 | 2224 | 2225 | 2226 | 2227 | 2228 | 2229 | 2230 | 2231 | 2232 | 2233 | 2234 | 2235 | 2236 | 2237 |
IDDescription
-1Waiting for payment
0The order has been successfully paid
1The order has expired
2The amount received is less than the amount requested
3The amount received is greater than the amount requested
4The order cancelled
2238 | 2239 |
Back to top
2240 | 2241 | 2242 | 2243 | 2244 | 2245 | 2246 | 2247 | --------------------------------------------------------------------------------