From 10176cc2b0567c9a6727ec668c476f15e498a267 Mon Sep 17 00:00:00 2001 From: suhanov_di Date: Mon, 20 Jul 2026 23:17:48 +0300 Subject: [PATCH 1/2] add POST /customers/push-tokens/batch --- client.go | 57 ++++++++++++++++++++++++++++++++++++++++++ client_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ response.go | 6 +++++ types.go | 39 +++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) diff --git a/client.go b/client.go index 8b6e376..ab726f0 100644 --- a/client.go +++ b/client.go @@ -934,6 +934,63 @@ func (c *Client) CustomersUpload(customers []Customer, site ...string) (Customer return resp, status, nil } +// CustomersPushTokensBatch creates or edits customer mobile application push tokens in batch. +// +// For more information see https://docs.retailcrm.ru/Developers/API/APIMethods#post--api-v5-customers-push-tokens-batch +// +// Example: +// +// var client = retailcrm.New("https://demo.url", "09jIJ") +// +// data, status, err := client.CustomersPushTokensBatch([]retailcrm.PushTokenInput{ +// { +// Customer: retailcrm.PushTokenCustomer{ +// ExternalID: "customer-1", +// Site: "main", +// }, +// FID: "firebase-installation-id", +// Type: "firebase-project", +// DeviceType: "android", +// Categories: []string{"news"}, +// }, +// }) +// +// if err != nil { +// if apiErr, ok := retailcrm.AsAPIError(err); ok { +// log.Fatalf("http status: %d, %s", status, apiErr.String()) +// } +// +// log.Fatalf("http status: %d, error: %s", status, err) +// } +// +// for _, pushToken := range data.PushTokens { +// log.Printf("%v\n", pushToken) +// } +func (c *Client) CustomersPushTokensBatch(pushTokens []PushTokenInput) (CustomersPushTokensBatchResponse, int, error) { + var resp CustomersPushTokensBatchResponse + + pushTokensJSON, err := marshalToString(&pushTokens) + if err != nil { + return resp, 0, err + } + + p := url.Values{ + "pushTokens": {pushTokensJSON}, + } + + data, status, err := c.PostRequest("/customers/push-tokens/batch", p) + if err != nil { + return resp, status, err + } + + err = json.Unmarshal(data, &resp) + if err != nil { + return resp, status, err + } + + return resp, status, nil +} + // Customer returns information about customer // // For more information see http://www.simla.com/docs/Developers/API/APIVersions/APIv5#get--api-v5-customers-externalId diff --git a/client_test.go b/client_test.go index 88b3176..54fe93d 100644 --- a/client_test.go +++ b/client_test.go @@ -801,6 +801,74 @@ func TestClient_CustomersUpload_Fail(t *testing.T) { assert.Equal(t, "Something went wrong", err.(APIError).Errors()["managerId"]) //nolint:errorlint } +func TestClient_CustomersPushTokensBatch(t *testing.T) { + defer gock.Off() + + pushTokens := []PushTokenInput{{ + Customer: PushTokenCustomer{ExternalID: "customer-1", Site: "main"}, + FID: "firebase-installation-id", + Type: "firebase-project", + DeviceType: "android", + Categories: []string{"news"}, + }} + pushTokensJSON, err := json.Marshal(pushTokens) + require.NoError(t, err) + + p := url.Values{"pushTokens": {string(pushTokensJSON)}} + + gock.New(crmURL). + Post(prefix + "/customers/push-tokens/batch"). + BodyString(p.Encode()). + Reply(http.StatusOK). + JSON(`{ + "success": true, + "pushTokens": [{ + "id": 123, + "customer": {"externalId": "customer-1"}, + "fid": "firebase-installation-id", + "type": "firebase-project", + "deviceType": "android", + "categories": [{"id": 1, "name": "News", "code": "news", "active": true}] + }] + }`) + + resp, status, err := client().CustomersPushTokensBatch(pushTokens) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, status) + assert.True(t, resp.Success) + require.Len(t, resp.PushTokens, 1) + assert.Equal(t, 123, resp.PushTokens[0].ID) + assert.Equal(t, "firebase-installation-id", resp.PushTokens[0].FID) + require.Len(t, resp.PushTokens[0].Categories, 1) + assert.Equal(t, "news", resp.PushTokens[0].Categories[0].Code) +} + +func TestClient_CustomersPushTokensBatchFail(t *testing.T) { + defer gock.Off() + + pushTokens := []PushTokenInput{{ + Customer: PushTokenCustomer{ID: 123}, + FID: "firebase-installation-id", + Type: "firebase-project", + DeviceType: "ios", + }} + pushTokensJSON, err := json.Marshal(pushTokens) + require.NoError(t, err) + + p := url.Values{"pushTokens": {string(pushTokensJSON)}} + + gock.New(crmURL). + Post(prefix + "/customers/push-tokens/batch"). + BodyString(p.Encode()). + Reply(http.StatusBadRequest). + JSON(`{"success":false,"errorMsg":"Invalid push token"}`) + + resp, status, err := client().CustomersPushTokensBatch(pushTokens) + require.Error(t, err) + assert.Equal(t, http.StatusBadRequest, status) + assert.False(t, resp.Success) +} + func TestClient_CustomersCombine(t *testing.T) { c := client() diff --git a/response.go b/response.go index 0158610..5984b89 100644 --- a/response.go +++ b/response.go @@ -77,6 +77,12 @@ type CustomersResponse struct { Customers []Customer `json:"customers,omitempty"` } +// CustomersPushTokensBatchResponse type. +type CustomersPushTokensBatchResponse struct { + Success bool `json:"success"` + PushTokens []PushToken `json:"pushTokens,omitempty"` +} + // CorporateCustomersResponse type. type CorporateCustomersResponse struct { Success bool `json:"success"` diff --git a/types.go b/types.go index 6a25905..bf07c2f 100644 --- a/types.go +++ b/types.go @@ -212,6 +212,45 @@ type Customer struct { MainCompany *IdentifiersPair `json:"mainCompany,omitempty"` } +// PushTokenCustomer identifies the customer who owns a mobile application push token. +type PushTokenCustomer struct { + ID int `json:"id,omitempty"` + ExternalID string `json:"externalId,omitempty"` + BrowserID string `json:"browserId,omitempty"` + Site string `json:"site,omitempty"` +} + +// PushTokenInput contains mobile application push token data for batch creation or editing. +type PushTokenInput struct { + Customer PushTokenCustomer `json:"customer"` + Token string `json:"token,omitempty"` // Deprecated: use FID. + FID string `json:"fid,omitempty"` + Type string `json:"type"` + DeviceType string `json:"deviceType"` + Categories []string `json:"categories,omitempty"` +} + +// PushCategory describes a mobile push notification category. +type PushCategory struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Code string `json:"code,omitempty"` + Active bool `json:"active,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` +} + +// PushToken describes a customer's mobile application push token. +type PushToken struct { + ID int `json:"id,omitempty"` + Customer PushTokenCustomer `json:"customer,omitempty"` + Token string `json:"token,omitempty"` // Deprecated: use FID. + FID string `json:"fid,omitempty"` + Type string `json:"type,omitempty"` + DeviceType string `json:"deviceType,omitempty"` + Categories []PushCategory `json:"categories,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` +} + // MGCustomer type. type MGCustomer struct { ID int `json:"id,omitempty"` From 248d94b5742f84a3ae42da2cb46ef8503de53382 Mon Sep 17 00:00:00 2001 From: suhanov_di Date: Tue, 21 Jul 2026 09:16:14 +0300 Subject: [PATCH 2/2] lint --- client.go | 4 +++- client_test.go | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index ab726f0..876f80d 100644 --- a/client.go +++ b/client.go @@ -18,6 +18,8 @@ import ( "github.com/google/go-querystring/query" ) +const pushTokensParameter = "pushTokens" + // New initialize client. func New(url string, key string) *Client { return &Client{ @@ -975,7 +977,7 @@ func (c *Client) CustomersPushTokensBatch(pushTokens []PushTokenInput) (Customer } p := url.Values{ - "pushTokens": {pushTokensJSON}, + pushTokensParameter: {pushTokensJSON}, } data, status, err := c.PostRequest("/customers/push-tokens/batch", p) diff --git a/client_test.go b/client_test.go index 54fe93d..60fdad7 100644 --- a/client_test.go +++ b/client_test.go @@ -814,7 +814,7 @@ func TestClient_CustomersPushTokensBatch(t *testing.T) { pushTokensJSON, err := json.Marshal(pushTokens) require.NoError(t, err) - p := url.Values{"pushTokens": {string(pushTokensJSON)}} + p := url.Values{pushTokensParameter: {string(pushTokensJSON)}} gock.New(crmURL). Post(prefix + "/customers/push-tokens/batch"). @@ -855,7 +855,7 @@ func TestClient_CustomersPushTokensBatchFail(t *testing.T) { pushTokensJSON, err := json.Marshal(pushTokens) require.NoError(t, err) - p := url.Values{"pushTokens": {string(pushTokensJSON)}} + p := url.Values{pushTokensParameter: {string(pushTokensJSON)}} gock.New(crmURL). Post(prefix + "/customers/push-tokens/batch").