Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83fd8cf825 | ||
|
|
ebbc21b618 | ||
|
|
f53b573d62 | ||
|
|
eb0ce6feb6 | ||
|
|
9a41bb1196 | ||
|
|
e76b9f3961 | ||
|
|
add4202b3e | ||
|
|
7beee39eb2 | ||
|
|
baeeef9b46 | ||
|
|
336c49baa4 | ||
|
|
f11ccb4714 | ||
|
|
1958dfb94e | ||
|
|
2ca20d9b12 | ||
|
|
eea0f99066 |
38
README.md
38
README.md
@@ -11,6 +11,7 @@ Read full [documentation](https://docs.ozon.ru/api/seller/en/#tag/Introduction)
|
|||||||
You can check [list of supported endpoints](ENDPOINTS.md)
|
You can check [list of supported endpoints](ENDPOINTS.md)
|
||||||
|
|
||||||
## How to start
|
## How to start
|
||||||
|
### API
|
||||||
Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en)
|
Get Client-Id and Api-Key in your seller profile [here](https://seller.ozon.ru/app/settings/api-keys?locale=en)
|
||||||
|
|
||||||
Just add dependency to your project and you're ready to go.
|
Just add dependency to your project and you're ready to go.
|
||||||
@@ -49,6 +50,43 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
Ozon can send push-notifications to your REST server. There is an implementation of REST server that handles notifications in this library.
|
||||||
|
|
||||||
|
[Official documentation](https://docs.ozon.ru/api/seller/en/#tag/push_intro)
|
||||||
|
|
||||||
|
How to use:
|
||||||
|
```Golang
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/diphantxm/ozon-api-client/ozon/notifications"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create server
|
||||||
|
port := 5000
|
||||||
|
server := notifications.NewNotificationServer(port)
|
||||||
|
|
||||||
|
// Register handlers passing message type and handler itself
|
||||||
|
server.Register(notifications.ChatClosedType, func(req interface{}) error {
|
||||||
|
notification := req.(*notifications.ChatClosed)
|
||||||
|
|
||||||
|
// Do something with the notification here...
|
||||||
|
log.Printf("chat %s has been closed\n", notification.ChatId)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run server
|
||||||
|
if err := server.Run(); err != nil {
|
||||||
|
log.Printf("error while running notification server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
If you need some endpoints ASAP, create an issue and list all the endpoints. I will add them to library soon.
|
If you need some endpoints ASAP, create an issue and list all the endpoints. I will add them to library soon.
|
||||||
|
|
||||||
|
|||||||
@@ -479,3 +479,29 @@ const (
|
|||||||
GetFBOReturnsReturnStatusAcceptedFromCustomer GetFBOReturnsReturnStatus = "Принят от покупателя"
|
GetFBOReturnsReturnStatusAcceptedFromCustomer GetFBOReturnsReturnStatus = "Принят от покупателя"
|
||||||
GetFBOReturnsReturnStatusReceivedAtOzon GetFBOReturnsReturnStatus = "Получен в Ozon"
|
GetFBOReturnsReturnStatusReceivedAtOzon GetFBOReturnsReturnStatus = "Получен в Ozon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DigitalActType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// acceptance certificate
|
||||||
|
DigitalActTypeOfAcceptance DigitalActType = "act_of_acceptance"
|
||||||
|
|
||||||
|
// discrepancy certificate
|
||||||
|
DigitalActTypeOfMismatch DigitalActType = "act_of_mismatch"
|
||||||
|
|
||||||
|
// surplus certificate
|
||||||
|
DigitalActTypeOfExcess DigitalActType = "act_of_excess"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PriceStrategy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// enable
|
||||||
|
PriceStrategyEnabled PriceStrategy = "ENABLED"
|
||||||
|
|
||||||
|
// disable
|
||||||
|
PriceStrategyDisabled PriceStrategy = "DISABLED"
|
||||||
|
|
||||||
|
// don't change anything. Default value
|
||||||
|
PriceStrategyUnknown PriceStrategy = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|||||||
128
ozon/fbs.go
128
ozon/fbs.go
@@ -340,7 +340,7 @@ type PostingProduct struct {
|
|||||||
|
|
||||||
type FBSCustomer struct {
|
type FBSCustomer struct {
|
||||||
// Delivery address details
|
// Delivery address details
|
||||||
Address FBSCustomerAddress `json:"customer"`
|
Address FBSCustomerAddress `json:"address"`
|
||||||
|
|
||||||
// Customer e-mail
|
// Customer e-mail
|
||||||
CustomerEmail string `json:"customer_email"`
|
CustomerEmail string `json:"customer_email"`
|
||||||
@@ -1594,7 +1594,13 @@ type PrintLabelingResponse struct {
|
|||||||
core.CommonResponse
|
core.CommonResponse
|
||||||
|
|
||||||
// Order content
|
// Order content
|
||||||
Content string `json:"content"`
|
Content string `json:"file_content"`
|
||||||
|
|
||||||
|
// File name
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
|
||||||
|
// File type
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a PDF file with a labeling for the specified shipments. You can pass a maximum of 20 identifiers in one request.
|
// Generates a PDF file with a labeling for the specified shipments. You can pass a maximum of 20 identifiers in one request.
|
||||||
@@ -2139,20 +2145,20 @@ type GetDigitalActParams struct {
|
|||||||
// - act_of_acceptance — acceptance certificate,
|
// - act_of_acceptance — acceptance certificate,
|
||||||
// - act_of_mismatch — discrepancy certificate,
|
// - act_of_mismatch — discrepancy certificate,
|
||||||
// - act_of_excess — surplus certificate
|
// - act_of_excess — surplus certificate
|
||||||
DocType string `json:"doc_type"`
|
DocType DigitalActType `json:"doc_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetDigitalActResponse struct {
|
type GetDigitalActResponse struct {
|
||||||
core.CommonResponse
|
core.CommonResponse
|
||||||
|
|
||||||
// File content in binary format
|
// File content in binary format
|
||||||
Content string `json:"content"`
|
Content string `json:"file_content"`
|
||||||
|
|
||||||
// File name
|
// File name
|
||||||
Name string `json:"name"`
|
Name string `json:"file_name"`
|
||||||
|
|
||||||
// File type
|
// File type
|
||||||
Type string `json:"type"`
|
Type string `json:"content_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the type of a certificate in the doc_type parameter: `act_of_acceptance`, `act_of_mismatch`, `act_of_excess`
|
// Specify the type of a certificate in the doc_type parameter: `act_of_acceptance`, `act_of_mismatch`, `act_of_excess`
|
||||||
@@ -2161,7 +2167,9 @@ func (c FBS) GetDigitalAct(params *GetDigitalActParams) (*GetDigitalActResponse,
|
|||||||
|
|
||||||
resp := &GetDigitalActResponse{}
|
resp := &GetDigitalActResponse{}
|
||||||
|
|
||||||
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
|
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2194,7 +2202,9 @@ func (c FBS) PackageUnitLabel(params *PackageUnitLabelsParams) (*PackageUnitLabe
|
|||||||
|
|
||||||
resp := &PackageUnitLabelsResponse{}
|
resp := &PackageUnitLabelsResponse{}
|
||||||
|
|
||||||
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
|
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -2645,3 +2655,105 @@ func (c FBS) ETGBCustomsDeclarations(params *ETGBCustomsDeclarationsParams) (*ET
|
|||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BarcodeFromProductShipmentParams struct {
|
||||||
|
// Freight identifier
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BarcodeFromProductShipmentResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Link to barcode image
|
||||||
|
Content string `json:"file_content"`
|
||||||
|
|
||||||
|
// File name
|
||||||
|
Name string `json:"file_name"`
|
||||||
|
|
||||||
|
// File type
|
||||||
|
Type string `json:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method for getting a barcode to show at a pick-up point or sorting center during the shipment
|
||||||
|
func (c FBS) BarcodeFromProductShipment(params *BarcodeFromProductShipmentParams) (*BarcodeFromProductShipmentResponse, error) {
|
||||||
|
url := "/v2/posting/fbs/act/get-barcode"
|
||||||
|
|
||||||
|
resp := &BarcodeFromProductShipmentResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
|
||||||
|
"Content-Type": "image/png",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BarcodeValueFromProductShipmentParams struct {
|
||||||
|
// Freight identifier
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BarcodeValueFromProductShipmentResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// Barcode in text format
|
||||||
|
Result string `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use this method to get the barcode from the /v2/posting/fbs/act/get-barcode response in text format.
|
||||||
|
func (c FBS) BarcodeValueFromProductShipment(params *BarcodeValueFromProductShipmentParams) (*BarcodeValueFromProductShipmentResponse, error) {
|
||||||
|
url := "/v2/posting/fbs/act/get-barcode/text"
|
||||||
|
|
||||||
|
resp := &BarcodeValueFromProductShipmentResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(http.MethodPost, url, params, resp, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetActPDFParams struct {
|
||||||
|
// Document generation task number (freight identifier) received from
|
||||||
|
// the POST `/v2/posting/fbs/act/create` method
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetActPDFResponse struct {
|
||||||
|
core.CommonResponse
|
||||||
|
|
||||||
|
// File content in binary format
|
||||||
|
FileContent string `json:"file_content"`
|
||||||
|
|
||||||
|
// File name
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
|
||||||
|
// File type
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the generated transfer documents in PDF format: an acceptance and transfer certificate and a waybill.
|
||||||
|
//
|
||||||
|
// If you are not connected to electronic document circulation (EDC), the method returns an acceptance and transfer certificate and a waybill.
|
||||||
|
//
|
||||||
|
// If you are connected to EDC, the method returns a waybill only.
|
||||||
|
func (c FBS) GetActPDF(params *GetActPDFParams) (*GetActPDFResponse, error) {
|
||||||
|
url := "/v2/posting/fbs/act/get-pdf"
|
||||||
|
|
||||||
|
resp := &GetActPDFResponse{}
|
||||||
|
|
||||||
|
response, err := c.client.Request(http.MethodPost, url, params, resp, map[string]string{
|
||||||
|
"Content-Type": "application/pdf",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.CopyCommonResponse(&resp.CommonResponse)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|||||||
187
ozon/fbs_test.go
187
ozon/fbs_test.go
@@ -1132,9 +1132,8 @@ func TestGetLabeling(t *testing.T) {
|
|||||||
},
|
},
|
||||||
`{
|
`{
|
||||||
"result": {
|
"result": {
|
||||||
"error": "24",
|
"status": "completed",
|
||||||
"file_url": "some-url",
|
"file_url": "https://cdn1.ozone.ru/s3/sc-temporary/e6/0c/e60cdfd7aed78c2b44d134504fbd591d.pdf"
|
||||||
"status": "completed"
|
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
@@ -1186,7 +1185,9 @@ func TestPrintLabeling(t *testing.T) {
|
|||||||
PostingNumber: []string{"48173252-0034-4"},
|
PostingNumber: []string{"48173252-0034-4"},
|
||||||
},
|
},
|
||||||
`{
|
`{
|
||||||
"content": "https://cdn1.ozone.ru/s3/ozon-disk-api/c4a11c8b748033daf6cdd44aca7ed4c492e55d6f4810f13feae4792afa7934191647255705"
|
"content_type": "application/pdf",
|
||||||
|
"file_name": "ticket-170660-2023-07-13T13:17:06Z.pdf",
|
||||||
|
"file_content": "%PDF-1.7\n%âãÏÓ\n53 0 obj\n<</MarkInfo<</Marked true/Type/MarkInfo>>/Pages 9 0 R/StructTreeRoot 10 0 R/Type/Catalog>>\nendobj\n8 0 obj\n<</Filter/FlateDecode/Length 2888>>\nstream\nxå[[ݶ\u0011~?¿BÏ\u0005Bs\u001c^\u0000Àwí5ú\u0010 m\u0016Èsà¦)\n;hÒ\u0014èÏïG\u0014)<{äµ] ]?¬¬oIÎ}¤F±óϤñï\u001bÕü×X´OÏï?^~¹$<ø¨È9q\u0013Y\u0012åñì§_¼|ÿégü\t+\u0012\u001bxª}Æxҿ¿¼_º¼xg¦þ5OkuÌ3ýíògüûå\"Ni\u0016C\u0001°\u000fA9g'r¢\"\u0013YóĪ\u0018NÑ{\u001dÕóZ¬\\Ô\""
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
// Test No Client-Id or Api-Key
|
// Test No Client-Id or Api-Key
|
||||||
@@ -1212,6 +1213,12 @@ func TestPrintLabeling(t *testing.T) {
|
|||||||
if resp.StatusCode != test.statusCode {
|
if resp.StatusCode != test.statusCode {
|
||||||
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
if resp.Content == "" {
|
||||||
|
t.Error("content cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1888,9 +1895,9 @@ func TestGetDigitalAct(t *testing.T) {
|
|||||||
DocType: "act_of_acceptance",
|
DocType: "act_of_acceptance",
|
||||||
},
|
},
|
||||||
`{
|
`{
|
||||||
"content": "string",
|
"content_type": "application/pdf",
|
||||||
"name": "string",
|
"file_name": "20816409_act_of_mismatch.pdf",
|
||||||
"type": "string"
|
"file_content": "%PDF-1.4\n%ÓôÌá\n1 0 obj\n<<\n/Creator(Chromium)\n/Producer(PDFsharp 1.50.5147 \\([www.pdfsharp.com|http://www.pdfsharp.com/]\\) \\(Original: Skia/PDF m103\\))\n/CreationDate(D:20230625092529+00'00')\n/ModDate(D:20230625092529+00'00')\n>>\nendobj\n2 0 obj\n<<\n/Type/Page\n/Resources\n<<\n/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]\n/ExtGState\n<<\n/G3 3 0 R\n/G8 8 0 R\n>>\n/XObject\n<<\n/X6 6 0 R\n/X7 7 0 R\n>>\n/Font\n<<\n/F4 4 0 R\n/F5 5 0 R\n>>\n>>\n/MediaBox[0 0 594.96 841.92]\n/Contents 9 0 R\n/StructParents 0\n/Parent 13 0 R\n/Group\n<<\n/CS/DeviceRGB\n/S/Transparency\n>>\n>>\nendobj\n3 0 obj\n<<\n/ca 1\n/BM/Normal\n>>\nendobj\n4 0 obj\n<<\n/Type/Font\n/Subtype/Type0\n/BaseFont/AAAAAA+LiberationSans\n/Encoding/Identity-H\n/DescendantFonts[160 0 R]\n/ToUnicode 161 0 R\n>>\nendobj\n5 0 obj\n<<\n/Type/Font\n/Subtype/Type0\n/BaseFont/BAAAAA+LiberationSans-Bold\n/Encoding/Identity-H\n/DescendantFonts[164 0"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
// Test No Client-Id or Api-Key
|
// Test No Client-Id or Api-Key
|
||||||
@@ -2526,3 +2533,169 @@ func TestETGBCustomsDeclarations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBarcodeFromProductShipment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *BarcodeFromProductShipmentParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&BarcodeFromProductShipmentParams{
|
||||||
|
Id: 295662811,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"content_type": "image/png",
|
||||||
|
"file_name": "20913984_barcode.png",
|
||||||
|
"file_content": "PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0003\u0010\u0000\u0000\u0000\u0010\u0000\u0000\u0000\u0000íZ\u000e'\u0000\u0000\u0002pIDATxìÕÁJ\u00031\u0014@Q+þÿ/×E\u0017\u000e¼\u0010u¡-ç¬$£Éˌp?î÷·§t» }ýü¸Ãcåz¹2wOWû\\Ϛ뫧×Ùö;ì|rÇýßîç¼úî{§¬N?í7oìv¸®µ¹Ãùû¹¾ÿÏ9ÿî?a¸ºéê7O&߿É9çÉ\u000eÏáý¯\u0007\u0000à\u0012\b\u0000@\u0000\u0004\u0002$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000@\u0000\u0004\u0002$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000@\u0000\u0004\u0002$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000@\u0000\u0004\u0002$\u0010\u0000$\u0000 \t\u0004\u0000I \u0000H\u0002\u0001@\u0012\b\u0000@\u0000\u0004\u0002$\u0010\u0000"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&BarcodeFromProductShipmentParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
resp, err := c.FBS().BarcodeFromProductShipment(test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
if resp.Content == "" {
|
||||||
|
t.Errorf("content cannot be empty")
|
||||||
|
}
|
||||||
|
if resp.Type == "" {
|
||||||
|
t.Error("type cannot be empty")
|
||||||
|
}
|
||||||
|
if resp.Name == "" {
|
||||||
|
t.Error("name cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBarcodeValueFromProductShipment(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *BarcodeValueFromProductShipmentParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&BarcodeValueFromProductShipmentParams{
|
||||||
|
Id: 295662811,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"result": "%303%24276481394"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&BarcodeValueFromProductShipmentParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
resp, err := c.FBS().BarcodeValueFromProductShipment(test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
if resp.Result == "" {
|
||||||
|
t.Errorf("result cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetActPDF(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
statusCode int
|
||||||
|
headers map[string]string
|
||||||
|
params *GetActPDFParams
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// Test Ok
|
||||||
|
{
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"},
|
||||||
|
&GetActPDFParams{
|
||||||
|
Id: 22435521842000,
|
||||||
|
},
|
||||||
|
`{
|
||||||
|
"content_type": "application/pdf",
|
||||||
|
"file_name": "20928233.pdf",
|
||||||
|
"file_content": "binarystring"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// Test No Client-Id or Api-Key
|
||||||
|
{
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
map[string]string{},
|
||||||
|
&GetActPDFParams{},
|
||||||
|
`{
|
||||||
|
"code": 16,
|
||||||
|
"message": "Client-Id and Api-Key headers are required"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers))
|
||||||
|
|
||||||
|
resp, err := c.FBS().GetActPDF(test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != test.statusCode {
|
||||||
|
t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
if resp.FileContent == "" {
|
||||||
|
t.Errorf("result cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
19
ozon/notifications/enums.go
Normal file
19
ozon/notifications/enums.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package notifications
|
||||||
|
|
||||||
|
type MessageType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PingType MessageType = "TYPE_PING"
|
||||||
|
NewPostingType MessageType = "TYPE_NEW_POSTING"
|
||||||
|
PostingCancelledType MessageType = "TYPE_POSTING_CANCELLED"
|
||||||
|
StateChangedType MessageType = "TYPE_STATE_CHANGED"
|
||||||
|
CutoffDateChangedType MessageType = "TYPE_CUTOFF_DATE_CHANGED"
|
||||||
|
DeliveryDateChangedType MessageType = "TYPE_DELIVERY_DATE_CHANGED"
|
||||||
|
CreateOrUpdateType MessageType = "TYPE_CREATE_OR_UPDATE_ITEM"
|
||||||
|
PriceIndexChangedType MessageType = "TYPE_PRICE_INDEX_CHANGED"
|
||||||
|
StocksChangedType MessageType = "TYPE_STOCKS_CHANGED"
|
||||||
|
NewMessageType MessageType = "TYPE_NEW_MESSAGE"
|
||||||
|
UpdateMessageType MessageType = "TYPE_UPDATE_MESSAGE"
|
||||||
|
MessageReadType MessageType = "TYPE_MESSAGE_READ"
|
||||||
|
ChatClosedType MessageType = "TYPE_CHAT_CLOSED"
|
||||||
|
)
|
||||||
165
ozon/notifications/server.go
Normal file
165
ozon/notifications/server.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package notifications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler func(req interface{}) error
|
||||||
|
|
||||||
|
type NotificationServer struct {
|
||||||
|
port int
|
||||||
|
handlers map[MessageType]Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotificationServer(port int) *NotificationServer {
|
||||||
|
return &NotificationServer{
|
||||||
|
port: port,
|
||||||
|
handlers: map[MessageType]Handler{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) Run() error {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", ns.handler)
|
||||||
|
server := http.Server{
|
||||||
|
Addr: fmt.Sprintf("0.0.0.0:%d", ns.port),
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) handler(rw http.ResponseWriter, httpReq *http.Request) {
|
||||||
|
mt := &Common{}
|
||||||
|
content, err := ioutil.ReadAll(httpReq.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
ns.error(rw, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(content, mt); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
ns.error(rw, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mt.MessageType == PingType {
|
||||||
|
resp := pingResponse{
|
||||||
|
Version: "1.0",
|
||||||
|
Name: "Ozon Seller API",
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
respJson, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
ns.error(rw, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write(respJson)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := ns.unmarshal(mt.MessageType, content)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
ns.result(rw, false)
|
||||||
|
//ns.error(rw, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h, ok := ns.handlers[mt.MessageType]
|
||||||
|
if !ok {
|
||||||
|
ns.result(rw, true)
|
||||||
|
log.Printf("handler for %s is not registered", mt.MessageType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h(req); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
ns.result(rw, false)
|
||||||
|
//ns.error(rw, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.result(rw, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) Register(mt MessageType, handler func(req interface{}) error) {
|
||||||
|
ns.handlers[mt] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) unmarshal(messageType MessageType, content []byte) (interface{}, error) {
|
||||||
|
switch messageType {
|
||||||
|
case NewPostingType:
|
||||||
|
v := &NewPosting{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case PostingCancelledType:
|
||||||
|
v := &PostingCancelled{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case StateChangedType:
|
||||||
|
v := &StateChanged{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case CutoffDateChangedType:
|
||||||
|
v := &CutoffDateChanged{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case DeliveryDateChangedType:
|
||||||
|
v := &DeliveryDateChanged{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case CreateOrUpdateType:
|
||||||
|
v := &CreateOrUpdateItem{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case PriceIndexChangedType:
|
||||||
|
v := &PriceIndexChanged{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case StocksChangedType:
|
||||||
|
v := &StocksChanged{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case NewMessageType:
|
||||||
|
v := &NewMessage{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case UpdateMessageType:
|
||||||
|
v := &UpdateMessage{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case MessageReadType:
|
||||||
|
v := &MessageRead{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
case ChatClosedType:
|
||||||
|
v := &ChatClosed{}
|
||||||
|
err := json.Unmarshal(content, v)
|
||||||
|
return v, err
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported type: %s", messageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) error(rw http.ResponseWriter, statusCode int, err error) {
|
||||||
|
errResp := errorResponse{
|
||||||
|
Data: errorData{
|
||||||
|
Code: fmt.Sprintf("%d", statusCode),
|
||||||
|
Message: err.Error(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
errJson, _ := json.Marshal(errResp)
|
||||||
|
rw.WriteHeader(statusCode)
|
||||||
|
rw.Write(errJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NotificationServer) result(rw http.ResponseWriter, res bool) {
|
||||||
|
rw.WriteHeader(http.StatusOK)
|
||||||
|
rw.Write([]byte(fmt.Sprintf(`{"result": %t}`, res)))
|
||||||
|
}
|
||||||
133
ozon/notifications/server_test.go
Normal file
133
ozon/notifications/server_test.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package notifications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNotificationServer(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
request string
|
||||||
|
response string
|
||||||
|
}{
|
||||||
|
// PING
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"message_type": "TYPE_PING",
|
||||||
|
"time": "2019-08-24T14:15:22Z"
|
||||||
|
}`,
|
||||||
|
`{
|
||||||
|
"version": "1.0",
|
||||||
|
"name": "Ozon Seller API"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// REGISTERED HANDLER
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"message_type": "TYPE_CHAT_CLOSED",
|
||||||
|
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
|
||||||
|
"chat_type": "Buyer_Seller",
|
||||||
|
"user": {
|
||||||
|
"id": "115568",
|
||||||
|
"type": "Сustomer"
|
||||||
|
},
|
||||||
|
"seller_id": "7"
|
||||||
|
}`,
|
||||||
|
`{
|
||||||
|
"result": true
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
// UNREGISTERED HANDLER
|
||||||
|
{
|
||||||
|
`{
|
||||||
|
"message_type": "TYPE_MESSAGE_READ",
|
||||||
|
"chat_id": "b646d975-0c9c-4872-9f41-8b1e57181063",
|
||||||
|
"chat_type": "Buyer_Seller",
|
||||||
|
"message_id": "3000000000817031942",
|
||||||
|
"created_at": "2022-07-18T20:58:04.528Z",
|
||||||
|
"user": {
|
||||||
|
"id": "115568",
|
||||||
|
"type": "Сustomer"
|
||||||
|
},
|
||||||
|
"last_read_message_id": "3000000000817031942",
|
||||||
|
"seller_id": "7"
|
||||||
|
}`,
|
||||||
|
`{
|
||||||
|
"result": true
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
port := getFreePort()
|
||||||
|
|
||||||
|
client := http.Client{}
|
||||||
|
server := NewNotificationServer(port)
|
||||||
|
server.Register(ChatClosedType, func(req interface{}) error {
|
||||||
|
_, ok := req.(*ChatClosed)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("req is not of ChatClosed type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
if err := server.Run(); err != nil {
|
||||||
|
t.Fatalf("notification server is down: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO: get rid of it
|
||||||
|
// Needed to make sure server is running
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
httpResp, err := client.Post(fmt.Sprintf("http://0.0.0.0:%d/", port), "application/json", strings.NewReader(testCase.request))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gotJson, err := ioutil.ReadAll(httpResp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{}
|
||||||
|
got := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(gotJson, &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(testCase.response), &expected)
|
||||||
|
|
||||||
|
if err := compare(expected, got); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(expected map[string]interface{}, got map[string]interface{}) error {
|
||||||
|
for k, v := range expected {
|
||||||
|
if gotValue, ok := got[k]; !ok {
|
||||||
|
return fmt.Errorf("key %s is expected to present", k)
|
||||||
|
} else if !reflect.DeepEqual(gotValue, v) {
|
||||||
|
return fmt.Errorf("key %s is not equal, got: %v, want: %v", k, gotValue, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFreePort() int {
|
||||||
|
listener, _ := net.Listen("tcp", ":0")
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
return listener.Addr().(*net.TCPAddr).Port
|
||||||
|
}
|
||||||
327
ozon/notifications/types.go
Normal file
327
ozon/notifications/types.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
package notifications
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Checking if the service is ready at initial connection and periodically after it
|
||||||
|
type pingRequest struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Date and time when the notification was sent in UTC format
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pingResponse struct {
|
||||||
|
// Application version
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// Application name
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Date and time when notification processing started in UTC format
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Common struct {
|
||||||
|
MessageType MessageType `json:"message_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New shipment
|
||||||
|
type NewPosting struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// Products information
|
||||||
|
Products []Product `json:"products"`
|
||||||
|
|
||||||
|
// Date and time when the shipment processing started in the UTC format
|
||||||
|
InProccessAt time.Time `json:"in_process_at"`
|
||||||
|
|
||||||
|
// Warehouse identifier where the products for this shipment are stored
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product struct {
|
||||||
|
// Product SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Product quantity
|
||||||
|
Quantity int64 `json:"quantity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shipment cancellation
|
||||||
|
type PostingCancelled struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// Products information
|
||||||
|
Products []Product `json:"products"`
|
||||||
|
|
||||||
|
// Previous shipment status
|
||||||
|
OldState string `json:"old_state"`
|
||||||
|
|
||||||
|
// New shipment status: posting_canceled—canceled
|
||||||
|
NewState string `json:"new_state"`
|
||||||
|
|
||||||
|
// Date and time when the shipment status was changed in UTC format
|
||||||
|
ChangedStateDate time.Time `json:"changed_state_date"`
|
||||||
|
|
||||||
|
// Information about cancellation reason
|
||||||
|
Reason Reason `json:"reason"`
|
||||||
|
|
||||||
|
// Warehouse identifier where the products for this shipment are stored
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reason struct {
|
||||||
|
// Cancellation reason identifier
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
|
||||||
|
// Cancellation reason
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shipment status change
|
||||||
|
type StateChanged struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// New shipment status
|
||||||
|
NewState string `json:"new_state"`
|
||||||
|
|
||||||
|
// Date and time when the shipment status was changed in UTC format
|
||||||
|
ChangedStateDate time.Time `json:"chagned_state_date"`
|
||||||
|
|
||||||
|
// Warehouse identifier where the products for this shipment are stored
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shipment shipping date change
|
||||||
|
type CutoffDateChanged struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// New shipping date and time in UTC format
|
||||||
|
NewCutoffDate time.Time `json:"new_cutoff_date"`
|
||||||
|
|
||||||
|
// Previous shipping date and time in UTC format
|
||||||
|
OldCutoffDate time.Time `json:"old_cutoff_date"`
|
||||||
|
|
||||||
|
// Warehouse identifier where the products for this shipment are stored
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shipment delivery date change
|
||||||
|
type DeliveryDateChanged struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Shipment number
|
||||||
|
PostingNumber string `json:"posting_number"`
|
||||||
|
|
||||||
|
// New delivery start date and time in UTC format
|
||||||
|
NewDeliveryDateBegin time.Time `json:"new_delivery_date_begin"`
|
||||||
|
|
||||||
|
// New delivery end date and time in UTC format
|
||||||
|
NewDeliveryDateEnd time.Time `json:"new_delivery_date_end"`
|
||||||
|
|
||||||
|
// Previous delivery start date and time in UTC format
|
||||||
|
OldDeliveryDateBegin time.Time `json:"old_delivery_date_begin"`
|
||||||
|
|
||||||
|
// Previous delivery end date and time in UTC format
|
||||||
|
OldDeliveryDateEnd time.Time `json:"old_delivery_date_end"`
|
||||||
|
|
||||||
|
// Warehouse identifier where the products for this shipment are stored
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product creation and update or processing error
|
||||||
|
type CreateOrUpdateItem struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Product identifier in the seller's system
|
||||||
|
OfferId string `json:"offer_id"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// An indication that errors occurred during the product creation or update
|
||||||
|
IsError bool `json:"is_error"`
|
||||||
|
|
||||||
|
// Update date and time
|
||||||
|
ChangedAt time.Time `json:"changed_at"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product price index change
|
||||||
|
type PriceIndexChanged struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Date and time of price index change
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// Product SKU
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Price index
|
||||||
|
PriceIndex int64 `json:"price_index"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stock change at the seller's warehouse
|
||||||
|
type StocksChanged struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Array with products data
|
||||||
|
Items []Item `json:"items"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
// Update date and time
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// Product SKU when working under the FBS or rFBS schemes
|
||||||
|
SKU int64 `json:"sku"`
|
||||||
|
|
||||||
|
// Product identifier
|
||||||
|
ProductId int64 `json:"product_id"`
|
||||||
|
|
||||||
|
// Array with product stocks data
|
||||||
|
Stocks []Stock `json:"stocks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stock struct {
|
||||||
|
// Warehouse identifier
|
||||||
|
WarehouseId int64 `json:"warehouse_id"`
|
||||||
|
|
||||||
|
// Total product stocks at the warehouse
|
||||||
|
Present int64 `json:"present"`
|
||||||
|
|
||||||
|
// Number of reserved products at the warehouse
|
||||||
|
Reserved int64 `json:"reserved"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// New message in chat
|
||||||
|
type NewMessage struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Chat identifier
|
||||||
|
ChatId string `json:"chat_id"`
|
||||||
|
|
||||||
|
// Chat type
|
||||||
|
ChatType string `json:"chat_type"`
|
||||||
|
|
||||||
|
// Message identifier
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
|
|
||||||
|
// Message creation date
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// Information about message sender
|
||||||
|
User User `json:"user"`
|
||||||
|
|
||||||
|
// Array with message content in Markdown format
|
||||||
|
Data []string `json:"data"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
// Sender identifier
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// Sender type
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message in chat has changed
|
||||||
|
type UpdateMessage struct {
|
||||||
|
NewMessage
|
||||||
|
|
||||||
|
// Message update date
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customer or support read your message
|
||||||
|
type MessageRead struct {
|
||||||
|
NewMessage
|
||||||
|
|
||||||
|
// Last read message identifier
|
||||||
|
LastReadMessageId string `json:"last_read_message_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chat is closed
|
||||||
|
type ChatClosed struct {
|
||||||
|
Common
|
||||||
|
|
||||||
|
// Chat identifier
|
||||||
|
ChatId string `json:"chat_id"`
|
||||||
|
|
||||||
|
// Chat type
|
||||||
|
ChatType string `json:"chat_type"`
|
||||||
|
|
||||||
|
// Information about the user who closed the chat
|
||||||
|
User User `json:"user"`
|
||||||
|
|
||||||
|
// User identifier
|
||||||
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// User type
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Seller identifier
|
||||||
|
SellerId string `json:"seller_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
// Notification is received
|
||||||
|
Result bool `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorResponse struct {
|
||||||
|
// Information about the error
|
||||||
|
Data errorData `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorData struct {
|
||||||
|
// Error code
|
||||||
|
Code string `json:"code"`
|
||||||
|
|
||||||
|
// Detailed error description
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
|
// Additional information
|
||||||
|
Details string `json:"details"`
|
||||||
|
}
|
||||||
@@ -655,6 +655,15 @@ type UpdatePricesPrice struct {
|
|||||||
// If the current price of the product is from 400 to 10 000 rubles inclusive, the difference between the values of price and old_price fields should be more than 5%, but not less than 20 rubles.
|
// If the current price of the product is from 400 to 10 000 rubles inclusive, the difference between the values of price and old_price fields should be more than 5%, but not less than 20 rubles.
|
||||||
Price string `json:"price"`
|
Price string `json:"price"`
|
||||||
|
|
||||||
|
// Attribute for enabling and disabling pricing strategies auto-application
|
||||||
|
//
|
||||||
|
// If you've previously enabled automatic application of pricing strategies and don't want to disable it, pass UNKNOWN in the next requests.
|
||||||
|
//
|
||||||
|
// If you pass `ENABLED` in this parameter, pass `strategy_id` in the `/v1/pricing-strategy/products/add` method request.
|
||||||
|
//
|
||||||
|
// If you pass `DISABLED` in this parameter, the product is removed from the strategy
|
||||||
|
PriceStrategyEnabled PriceStrategy `json:"price_strategy_enabled"`
|
||||||
|
|
||||||
// Product identifier
|
// Product identifier
|
||||||
ProductId int64 `json:"product_id"`
|
ProductId int64 `json:"product_id"`
|
||||||
}
|
}
|
||||||
@@ -1903,6 +1912,9 @@ type GetProductPriceInfoResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetPRoductPriceInfoResultItem struct {
|
type GetPRoductPriceInfoResultItem struct {
|
||||||
|
// Maximum acquiring fee
|
||||||
|
Acquiring int32 `json:"acquiring"`
|
||||||
|
|
||||||
// Commissions information
|
// Commissions information
|
||||||
Commissions GetProductPriceInfoResultItemCommission `json:"commissions"`
|
Commissions GetProductPriceInfoResultItemCommission `json:"commissions"`
|
||||||
|
|
||||||
@@ -1961,10 +1973,10 @@ type GetProductPriceInfoResultItemCommission struct {
|
|||||||
// Pipeline from (FBS)
|
// Pipeline from (FBS)
|
||||||
FBSPipelineFrom float64 `json:"fbs_direct_flow_trans_min_amount"`
|
FBSPipelineFrom float64 `json:"fbs_direct_flow_trans_min_amount"`
|
||||||
|
|
||||||
// Shipment processing fee to (FBS)
|
// Minimal shipment processing fee (FBS) — 0 rubles
|
||||||
FBSShipmentProcessingToFee float64 `json:"fbs_first_mile_min_amount"`
|
FBSShipmentProcessingToFee float64 `json:"fbs_first_mile_min_amount"`
|
||||||
|
|
||||||
// Shipment processing fee from (FBS)
|
// Maximal shipment processing fee (FBS) — 25 rubles
|
||||||
FBSShipmentProcessingFromFee float64 `json:"Shipment processing fee from (FBS)"`
|
FBSShipmentProcessingFromFee float64 `json:"Shipment processing fee from (FBS)"`
|
||||||
|
|
||||||
// Return and cancellation fees, shipment processing (FBS)
|
// Return and cancellation fees, shipment processing (FBS)
|
||||||
@@ -1976,7 +1988,13 @@ type GetProductPriceInfoResultItemCommission struct {
|
|||||||
// Return and cancellation fees, pipeline from (FBS)
|
// Return and cancellation fees, pipeline from (FBS)
|
||||||
FBSReturnCancellationFromFees float64 `json:"fbs_return_flow_trans_min_amount"`
|
FBSReturnCancellationFromFees float64 `json:"fbs_return_flow_trans_min_amount"`
|
||||||
|
|
||||||
// Sales commission percentage (FBO and FBS)
|
// Sales commission percentage (FBO)
|
||||||
|
SalesCommissionFBORate float64 `json:"sales_percent_fbo"`
|
||||||
|
|
||||||
|
// Sales commission percentage (FBS)
|
||||||
|
SalesCommissionFBSRate float64 `json:"sales_percent_fbs"`
|
||||||
|
|
||||||
|
// Larger sales commission percentage among FBO and FBS
|
||||||
SalesCommissionRate float64 `json:"sales_percent"`
|
SalesCommissionRate float64 `json:"sales_percent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ func TestUpdatePrices(t *testing.T) {
|
|||||||
OldPrice: "0",
|
OldPrice: "0",
|
||||||
Price: "1448",
|
Price: "1448",
|
||||||
ProductId: 1386,
|
ProductId: 1386,
|
||||||
|
PriceStrategyEnabled: PriceStrategyUnknown,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2239,6 +2240,7 @@ func TestGetProductPriceInfo(t *testing.T) {
|
|||||||
"result": {
|
"result": {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
"acquiring": 0,
|
||||||
"product_id": 243686911,
|
"product_id": 243686911,
|
||||||
"offer_id": "356792",
|
"offer_id": "356792",
|
||||||
"price": {
|
"price": {
|
||||||
@@ -2254,9 +2256,28 @@ func TestGetProductPriceInfo(t *testing.T) {
|
|||||||
"marketing_seller_price": "",
|
"marketing_seller_price": "",
|
||||||
"auto_action_enabled": true
|
"auto_action_enabled": true
|
||||||
},
|
},
|
||||||
"price_index": "0.00",
|
"price_indexes": {
|
||||||
|
"external_index_data": {
|
||||||
|
"minimal_price": "string",
|
||||||
|
"minimal_price_currency": "string",
|
||||||
|
"price_index_value": 0
|
||||||
|
},
|
||||||
|
"ozon_index_data": {
|
||||||
|
"minimal_price": "string",
|
||||||
|
"minimal_price_currency": "string",
|
||||||
|
"price_index_value": 0
|
||||||
|
},
|
||||||
|
"price_index": "WITHOUT_INDEX",
|
||||||
|
"self_marketplaces_index_data": {
|
||||||
|
"minimal_price": "string",
|
||||||
|
"minimal_price_currency": "string",
|
||||||
|
"price_index_value": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
"commissions": {
|
"commissions": {
|
||||||
"sales_percent": 15,
|
"sales_percent": 15,
|
||||||
|
"sales_percent_fbo": 15,
|
||||||
|
"sales_percent_fbs": 0,
|
||||||
"fbo_fulfillment_amount": 0,
|
"fbo_fulfillment_amount": 0,
|
||||||
"fbo_direct_flow_trans_min_amount": 31,
|
"fbo_direct_flow_trans_min_amount": 31,
|
||||||
"fbo_direct_flow_trans_max_amount": 46.5,
|
"fbo_direct_flow_trans_max_amount": 46.5,
|
||||||
@@ -2265,7 +2286,7 @@ func TestGetProductPriceInfo(t *testing.T) {
|
|||||||
"fbo_return_flow_trans_min_amount": 21.7,
|
"fbo_return_flow_trans_min_amount": 21.7,
|
||||||
"fbo_return_flow_trans_max_amount": 21.7,
|
"fbo_return_flow_trans_max_amount": 21.7,
|
||||||
"fbs_first_mile_min_amount": 0,
|
"fbs_first_mile_min_amount": 0,
|
||||||
"fbs_first_mile_max_amount": 0,
|
"fbs_first_mile_max_amount": 25,
|
||||||
"fbs_direct_flow_trans_min_amount": 41,
|
"fbs_direct_flow_trans_min_amount": 41,
|
||||||
"fbs_direct_flow_trans_max_amount": 61.5,
|
"fbs_direct_flow_trans_max_amount": 61.5,
|
||||||
"fbs_deliv_to_customer_amount": 60,
|
"fbs_deliv_to_customer_amount": 60,
|
||||||
|
|||||||
Reference in New Issue
Block a user