diff --git a/fixtures/fulfillment_accept.json b/fixtures/fulfillment_accept.json new file mode 100644 index 00000000..7e678f36 --- /dev/null +++ b/fixtures/fulfillment_accept.json @@ -0,0 +1,70 @@ +{ + "fulfillment_order": { + "id": 1046000828, + "shop_id": 548380009, + "order_id": 450789469, + "assigned_location_id": 24826418, + "request_status": "accepted", + "status": "in_progress", + "supported_actions": ["request_cancellation", "create_fulfillment"], + "destination": { + "id": 1046000815, + "address1": "Chestnut Street 92", + "address2": "", + "city": "Louisville", + "company": null, + "country": "United States", + "email": "bob.norman@mail.example.com", + "first_name": "Bob", + "last_name": "Norman", + "phone": "+1(502)-459-2181", + "province": "Kentucky", + "zip": "40202" + }, + "origin": { + "address1": null, + "address2": null, + "city": null, + "country_code": "DE", + "location_id": 24826418, + "name": "Apple Api Shipwire", + "phone": null, + "province": null, + "zip": null + }, + "line_items": [ + { + "id": 1058737564, + "shop_id": 548380009, + "fulfillment_order_id": 1046000828, + "quantity": 1, + "line_item_id": 466157049, + "inventory_item_id": 39072856, + "fulfillable_quantity": 1, + "variant_id": 39072856 + }, + { + "id": 1058737565, + "shop_id": 548380009, + "fulfillment_order_id": 1046000828, + "quantity": 1, + "line_item_id": 518995019, + "inventory_item_id": 49148385, + "fulfillable_quantity": 1, + "variant_id": 49148385 + }, + { + "id": 1058737566, + "shop_id": 548380009, + "fulfillment_order_id": 1046000828, + "quantity": 1, + "line_item_id": 703073504, + "inventory_item_id": 457924702, + "fulfillable_quantity": 1, + "variant_id": 457924702 + } + ], + "outgoing_requests": [], + "fulfillment_service_handle": "shipwire-app" + } +} diff --git a/fixtures/fulfillment_event.json b/fixtures/fulfillment_event.json new file mode 100644 index 00000000..131541e8 --- /dev/null +++ b/fixtures/fulfillment_event.json @@ -0,0 +1,22 @@ +{ + "fulfillment_event": { + "id": 944956393, + "fulfillment_id": 255858046, + "status": "in_transit", + "message": null, + "happened_at": "2023-10-20T23:39:27-04:00", + "city": null, + "province": null, + "country": null, + "zip": null, + "address1": null, + "latitude": null, + "longitude": null, + "shop_id": 548380009, + "created_at": "2023-10-20T23:39:27-04:00", + "updated_at": "2023-10-20T23:39:27-04:00", + "estimated_delivery_at": null, + "order_id": 450789469, + "admin_graphql_api_id": "gid://shopify/FulfillmentEvent/944956393" + } +} diff --git a/fixtures/fulfillment_events.json b/fixtures/fulfillment_events.json new file mode 100644 index 00000000..35a645fa --- /dev/null +++ b/fixtures/fulfillment_events.json @@ -0,0 +1,24 @@ +{ + "fulfillment_events": [ + { + "id": 944956391, + "fulfillment_id": 255858046, + "status": "in_transit", + "message": null, + "happened_at": "2023-10-20T23:39:23-04:00", + "city": null, + "province": null, + "country": null, + "zip": null, + "address1": null, + "latitude": null, + "longitude": null, + "shop_id": 548380009, + "created_at": "2023-10-20T23:39:23-04:00", + "updated_at": "2023-10-20T23:39:23-04:00", + "estimated_delivery_at": null, + "order_id": 450789469, + "admin_graphql_api_id": "gid://shopify/FulfillmentEvent/944956391" + } + ] +} diff --git a/fixtures/fulfillment_reject.json b/fixtures/fulfillment_reject.json new file mode 100644 index 00000000..05b97c87 --- /dev/null +++ b/fixtures/fulfillment_reject.json @@ -0,0 +1,70 @@ +{ + "fulfillment_order": { + "id": 1046000830, + "shop_id": 548380009, + "order_id": 450789469, + "assigned_location_id": 24826418, + "request_status": "rejected", + "status": "open", + "supported_actions": ["request_fulfillment", "create_fulfillment"], + "destination": { + "id": 1046000817, + "address1": "Chestnut Street 92", + "address2": "", + "city": "Louisville", + "company": null, + "country": "United States", + "email": "bob.norman@mail.example.com", + "first_name": "Bob", + "last_name": "Norman", + "phone": "+1(502)-459-2181", + "province": "Kentucky", + "zip": "40202" + }, + "origin": { + "address1": null, + "address2": null, + "city": null, + "country_code": "DE", + "location_id": 24826418, + "name": "Apple Api Shipwire", + "phone": null, + "province": null, + "zip": null + }, + "line_items": [ + { + "id": 1058737570, + "shop_id": 548380009, + "fulfillment_order_id": 1046000830, + "quantity": 1, + "line_item_id": 466157049, + "inventory_item_id": 39072856, + "fulfillable_quantity": 1, + "variant_id": 39072856 + }, + { + "id": 1058737571, + "shop_id": 548380009, + "fulfillment_order_id": 1046000830, + "quantity": 1, + "line_item_id": 518995019, + "inventory_item_id": 49148385, + "fulfillable_quantity": 1, + "variant_id": 49148385 + }, + { + "id": 1058737572, + "shop_id": 548380009, + "fulfillment_order_id": 1046000830, + "quantity": 1, + "line_item_id": 703073504, + "inventory_item_id": 457924702, + "fulfillable_quantity": 1, + "variant_id": 457924702 + } + ], + "outgoing_requests": [], + "fulfillment_service_handle": "shipwire-app" + } +} diff --git a/fixtures/fulfillment_send.json b/fixtures/fulfillment_send.json new file mode 100644 index 00000000..b3117c78 --- /dev/null +++ b/fixtures/fulfillment_send.json @@ -0,0 +1,157 @@ +{ + "original_fulfillment_order": { + "id": 1046000829, + "shop_id": 548380009, + "order_id": 450789469, + "assigned_location_id": 24826418, + "request_status": "submitted", + "status": "open", + "supported_actions": ["cancel_fulfillment_order"], + "destination": { + "id": 1046000816, + "address1": "Chestnut Street 92", + "address2": "", + "city": "Louisville", + "company": null, + "country": "United States", + "email": "bob.norman@mail.example.com", + "first_name": "Bob", + "last_name": "Norman", + "phone": "+1(502)-459-2181", + "province": "Kentucky", + "zip": "40202" + }, + "origin": { + "address1": null, + "address2": null, + "city": null, + "country_code": "DE", + "location_id": 24826418, + "name": "Apple Api Shipwire", + "phone": null, + "province": null, + "zip": null + }, + "line_items": [ + { + "id": 1058737567, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 466157049, + "inventory_item_id": 39072856, + "fulfillable_quantity": 1, + "variant_id": 39072856 + }, + { + "id": 1058737568, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 518995019, + "inventory_item_id": 49148385, + "fulfillable_quantity": 1, + "variant_id": 49148385 + }, + { + "id": 1058737569, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 703073504, + "inventory_item_id": 457924702, + "fulfillable_quantity": 1, + "variant_id": 457924702 + } + ], + "outgoing_requests": [ + { + "message": "Fulfill this ASAP please.", + "request_options": { + "notify_customer": false + }, + "sent_at": "2023-10-03T13:22:49-04:00", + "kind": "fulfillment_request" + } + ], + "fulfillment_service_handle": "shipwire-app" + }, + "submitted_fulfillment_order": { + "id": 1046000829, + "shop_id": 548380009, + "order_id": 450789469, + "assigned_location_id": 24826418, + "request_status": "submitted", + "status": "open", + "supported_actions": ["cancel_fulfillment_order"], + "destination": { + "id": 1046000816, + "address1": "Chestnut Street 92", + "address2": "", + "city": "Louisville", + "company": null, + "country": "United States", + "email": "bob.norman@mail.example.com", + "first_name": "Bob", + "last_name": "Norman", + "phone": "+1(502)-459-2181", + "province": "Kentucky", + "zip": "40202" + }, + "origin": { + "address1": null, + "address2": null, + "city": null, + "country_code": "DE", + "location_id": 24826418, + "name": "Apple Api Shipwire", + "phone": null, + "province": null, + "zip": null + }, + "line_items": [ + { + "id": 1058737567, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 466157049, + "inventory_item_id": 39072856, + "fulfillable_quantity": 1, + "variant_id": 39072856 + }, + { + "id": 1058737568, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 518995019, + "inventory_item_id": 49148385, + "fulfillable_quantity": 1, + "variant_id": 49148385 + }, + { + "id": 1058737569, + "shop_id": 548380009, + "fulfillment_order_id": 1046000829, + "quantity": 1, + "line_item_id": 703073504, + "inventory_item_id": 457924702, + "fulfillable_quantity": 1, + "variant_id": 457924702 + } + ], + "outgoing_requests": [ + { + "message": "Fulfill this ASAP please.", + "request_options": { + "notify_customer": false + }, + "sent_at": "2023-10-03T13:22:49-04:00", + "kind": "fulfillment_request" + } + ], + "fulfillment_service_handle": "shipwire-app" + }, + "unsubmitted_fulfillment_order": null +} diff --git a/fulfillment_event.go b/fulfillment_event.go new file mode 100644 index 00000000..84b3a9ba --- /dev/null +++ b/fulfillment_event.go @@ -0,0 +1,89 @@ +package goshopify + +import ( + "fmt" +) + +const ( + fulfillmentEventBasePath = "orders" +) + +// FulfillmentEventService is an interface for interfacing with the fulfillment event service +// of the Shopify API. +// https://help.shopify.com/api/reference/fulfillmentevent +type FulfillmentEventService interface { + List(orderID int64, fulfillmentID int64) ([]FulfillmentEvent, error) + Get(orderID int64, fulfillmentID int64, eventID int64) (*FulfillmentEvent, error) + Create(orderID int64, fulfillmentID int64, event FulfillmentEvent) (*FulfillmentEvent, error) + Delete(orderID int64, fulfillmentID int64, eventID int64) error +} + +// FulfillmentEvent represents a Shopify fulfillment event. +type FulfillmentEvent struct { + ID int64 `json:"id"` + Address1 string `json:"address1"` + City string `json:"city"` + Country string `json:"country"` + CreatedAt string `json:"created_at"` + EstimatedDeliveryAt string `json:"estimated_delivery_at"` + FulfillmentID int64 `json:"fulfillment_id"` + HappenedAt string `json:"happened_at"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Message string `json:"message"` + OrderID int64 `json:"order_id"` + Province string `json:"province"` + ShopID int64 `json:"shop_id"` + Status string `json:"status"` + UpdatedAt string `json:"updated_at"` + Zip string `json:"zip"` +} + +type FulfillmentEventCreateRequest struct { + Event *FulfillmentEvent `json:"event"` +} + +type FulfillmentEventResource struct { + FulfillmentEvent *FulfillmentEvent `json:"fulfillment_event,omitempty"` + Event *FulfillmentEvent `json:"event,omitempty"` +} + +type FulfillmentEventsResource struct { + FulfillmentEvents []FulfillmentEvent `json:"fulfillment_events"` +} + +// FulfillmentEventServiceOp handles communication with the fulfillment event related methods of the Shopify API. +type FulfillmentEventServiceOp struct { + client *Client +} + +// List of all FulfillmentEvents for an order's fulfillment. The API returns the list under the 'fulfillment_events' key. +func (s *FulfillmentEventServiceOp) List(orderID int64, fulfillmentID int64) ([]FulfillmentEvent, error) { + path := fmt.Sprintf("%s/%d/fulfillments/%d/events.json", fulfillmentEventBasePath, orderID, fulfillmentID) + resource := new(FulfillmentEventsResource) + err := s.client.Get(path, resource, nil) + return resource.FulfillmentEvents, err +} + +// Get a single FulfillmentEvent. The API returns the event under the 'fulfillment_event' key. +func (s *FulfillmentEventServiceOp) Get(orderID int64, fulfillmentID int64, eventID int64) (*FulfillmentEvent, error) { + path := fmt.Sprintf("%s/%d/fulfillments/%d/events/%d.json", fulfillmentEventBasePath, orderID, fulfillmentID, eventID) + resource := new(FulfillmentEventResource) + err := s.client.Get(path, resource, nil) + return resource.FulfillmentEvent, err +} + +// Create a new FulfillmentEvent +func (s *FulfillmentEventServiceOp) Create(orderID int64, fulfillmentID int64, event FulfillmentEvent) (*FulfillmentEvent, error) { + path := fmt.Sprintf("%s/%d/fulfillments/%d/events.json", fulfillmentEventBasePath, orderID, fulfillmentID) + wrappedData := FulfillmentEventResource{Event: &event} + resource := new(FulfillmentEventResource) + err := s.client.Post(path, wrappedData, resource) + return resource.FulfillmentEvent, err +} + +// Delete an existing FulfillmentEvent +func (s *FulfillmentEventServiceOp) Delete(orderID int64, fulfillmentID int64, eventID int64) error { + path := fmt.Sprintf("%s/%d/fulfillments/%d/events/%d.json", fulfillmentEventBasePath, orderID, fulfillmentID, eventID) + return s.client.Delete(path) +} diff --git a/fulfillment_event_test.go b/fulfillment_event_test.go new file mode 100644 index 00000000..586ecd5b --- /dev/null +++ b/fulfillment_event_test.go @@ -0,0 +1,177 @@ +package goshopify + +import ( + "fmt" + "net/http" + "reflect" + "testing" + + "github.com/jarcoal/httpmock" +) + +func TestFulfillmentEventServiceOp_List(t *testing.T) { + setup() + defer teardown() + + orderID := int64(1234567890) + fulfillmentID := int64(987654321) + httpmock.RegisterResponder( + http.MethodGet, + fmt.Sprintf("https://fooshop.myshopify.com/%s/orders/%d/fulfillments/%d/events.json", client.pathPrefix, orderID, fulfillmentID), + httpmock.NewBytesResponder(200, loadFixture("fulfillment_events.json")), + ) + + fulfillmentEvents, err := client.FulfillmentEvent.List(orderID, fulfillmentID) + if err != nil { + t.Errorf("FulfillmentEvent.List returned error: %v", err) + } + + expected := []FulfillmentEvent{ + { + ID: 944956391, + FulfillmentID: 255858046, + Status: "in_transit", + Message: "", + HappenedAt: "2023-10-20T23:39:23-04:00", + City: "", + Province: "", + Country: "", + Zip: "", + Address1: "", + Latitude: 0, + Longitude: 0, + ShopID: 548380009, + CreatedAt: "2023-10-20T23:39:23-04:00", + UpdatedAt: "2023-10-20T23:39:23-04:00", + EstimatedDeliveryAt: "", + OrderID: 450789469, + }, + } + if !reflect.DeepEqual(fulfillmentEvents, expected) { + t.Errorf("FulfillmentEvent.List returned %+v, expected %+v", fulfillmentEvents, expected) + } +} + +func TestFulfillmentEventServiceOp_Get(t *testing.T) { + setup() + defer teardown() + + orderID := int64(1234567890) + fulfillmentID := int64(987654321) + eventID := int64(123123123) + + httpmock.RegisterResponder( + http.MethodGet, + fmt.Sprintf("https://fooshop.myshopify.com/%s/orders/%d/fulfillments/%d/events/%d.json", client.pathPrefix, orderID, fulfillmentID, eventID), + httpmock.NewBytesResponder(200, loadFixture("fulfillment_event.json")), + ) + + fulfillmentEvent, err := client.FulfillmentEvent.Get(orderID, fulfillmentID, eventID) + if err != nil { + t.Errorf("FulfillmentEvent.Get returned error: %v", err) + } + + expected := &FulfillmentEvent{ + ID: 944956393, + FulfillmentID: 255858046, + Status: "in_transit", + Message: "", + HappenedAt: "2023-10-20T23:39:27-04:00", + City: "", + Province: "", + Country: "", + Zip: "", + Address1: "", + Latitude: 0, + Longitude: 0, + ShopID: 548380009, + CreatedAt: "2023-10-20T23:39:27-04:00", + UpdatedAt: "2023-10-20T23:39:27-04:00", + EstimatedDeliveryAt: "", + OrderID: 450789469, + } + if !reflect.DeepEqual(fulfillmentEvent, expected) { + t.Errorf("FulfillmentEvent.Get returned %+v, expected %+v", fulfillmentEvent, expected) + } +} + +func TestFulfillmentEventServiceOp_Create(t *testing.T) { + setup() + defer teardown() + + orderID := int64(1234567890) + fulfillmentID := int64(987654321) + + httpmock.RegisterResponder( + http.MethodPost, + fmt.Sprintf("https://fooshop.myshopify.com/%s/orders/%d/fulfillments/%d/events.json", client.pathPrefix, orderID, fulfillmentID), + httpmock.NewBytesResponder(201, loadFixture("fulfillment_event.json")), + ) + + event := FulfillmentEvent{ + ID: 944956393, + FulfillmentID: 255858046, + Status: "in_transit", + Message: "", + HappenedAt: "2023-10-20T23:39:27-04:00", + City: "", + Province: "", + Country: "", + Zip: "", + Address1: "", + Latitude: 0, + Longitude: 0, + ShopID: 548380009, + CreatedAt: "2023-10-20T23:39:27-04:00", + UpdatedAt: "2023-10-20T23:39:27-04:00", + EstimatedDeliveryAt: "", + OrderID: 450789469, + } + createdEvent, err := client.FulfillmentEvent.Create(orderID, fulfillmentID, event) + if err != nil { + t.Errorf("FulfillmentEvent.Create returned error: %v", err) + } + + expected := &FulfillmentEvent{ + ID: 944956393, + FulfillmentID: 255858046, + Status: "in_transit", + Message: "", + HappenedAt: "2023-10-20T23:39:27-04:00", + City: "", + Province: "", + Country: "", + Zip: "", + Address1: "", + Latitude: 0, + Longitude: 0, + ShopID: 548380009, + CreatedAt: "2023-10-20T23:39:27-04:00", + UpdatedAt: "2023-10-20T23:39:27-04:00", + EstimatedDeliveryAt: "", + OrderID: 450789469, + } + if !reflect.DeepEqual(createdEvent, expected) { + t.Errorf("FulfillmentEvent.Create returned %+v, expected %+v", createdEvent, expected) + } +} + +func TestFulfillmentEventServiceOp_Delete(t *testing.T) { + setup() + defer teardown() + + orderID := int64(1234567890) + fulfillmentID := int64(987654321) + eventID := int64(123123123) + + httpmock.RegisterResponder( + http.MethodDelete, + fmt.Sprintf("https://fooshop.myshopify.com/%s/orders/%d/fulfillments/%d/events/%d.json", client.pathPrefix, orderID, fulfillmentID, eventID), + httpmock.NewStringResponder(200, ""), + ) + + err := client.FulfillmentEvent.Delete(orderID, fulfillmentID, eventID) + if err != nil { + t.Errorf("FulfillmentEvent.Delete returned error: %v", err) + } +} diff --git a/fulfillment_request.go b/fulfillment_request.go new file mode 100644 index 00000000..1f8c5ea1 --- /dev/null +++ b/fulfillment_request.go @@ -0,0 +1,70 @@ +package goshopify + +import "fmt" + +const ( + fulfillmentRequestBasePath = "fulfillment_orders" +) + +// FulfillmentRequestService is an interface for interfacing with the fulfillment request endpoints of the Shopify API. +// https://shopify.dev/docs/api/admin-rest/2023-10/resources/fulfillmentrequest +type FulfillmentRequestService interface { + Send(int64, FulfillmentRequest) (*FulfillmentOrder, error) + Accept(int64, FulfillmentRequest) (*FulfillmentOrder, error) + Reject(int64, FulfillmentRequest) (*FulfillmentOrder, error) +} + +type FulfillmentRequest struct { + Message string `json:"message,omitempty"` + FulfillmentOrderLineItems []FulfillmentOrderLineItem `json:"fulfillment_order_line_items,omitempty"` + Reason string `json:"reason,omitempty"` + LineItems []FulfillmentRequestLineItem `json:"line_items,omitempty"` +} + +type FulfillmentRequestOrderLineItem struct { + Id int64 `json:"id"` + Quantity int64 `json:"quantity"` +} + +type FulfillmentRequestLineItem struct { + FulfillmentOrderLineItemID int64 `json:"fulfillment_order_line_item_id,omitempty"` + Message string `json:"message,omitempty"` +} + +type FulfillmentRequestResource struct { + FulfillmentOrder *FulfillmentOrder `json:"fulfillment_order,omitempty"` + FulfillmentRequest FulfillmentRequest `json:"fulfillment_request,omitempty"` + OriginalFulfillmentOrder *FulfillmentOrder `json:"original_fulfillment_order,omitempty"` +} + +// FulfillmentRequestServiceOp handles communication with the fulfillment request related methods of the Shopify API. +type FulfillmentRequestServiceOp struct { + client *Client +} + +// Send sends a fulfillment request to the fulfillment service of a fulfillment order. +func (s *FulfillmentRequestServiceOp) Send(fulfillmentOrderID int64, request FulfillmentRequest) (*FulfillmentOrder, error) { + path := fmt.Sprintf("%s/%d/fulfillment_request.json", fulfillmentRequestBasePath, fulfillmentOrderID) + wrappedData := FulfillmentRequestResource{FulfillmentRequest: request} + resource := new(FulfillmentRequestResource) + err := s.client.Post(path, wrappedData, resource) + return resource.OriginalFulfillmentOrder, err +} + +// Accept accepts a fulfillment request sent to a fulfillment service for a fulfillment order. +func (s *FulfillmentRequestServiceOp) Accept(fulfillmentOrderID int64, request FulfillmentRequest) (*FulfillmentOrder, error) { + path := fmt.Sprintf("%s/%d/fulfillment_request/accept.json", fulfillmentRequestBasePath, fulfillmentOrderID) + wrappedData := map[string]interface{}{"fulfillment_request": request} + resource := new(FulfillmentRequestResource) + err := s.client.Post(path, wrappedData, resource) + return resource.FulfillmentOrder, err +} + +// Reject rejects a fulfillment request sent to a fulfillment service for a fulfillment order. +func (s *FulfillmentRequestServiceOp) Reject(fulfillmentOrderID int64, request FulfillmentRequest) (*FulfillmentOrder, error) { + path := fmt.Sprintf("%s/%d/fulfillment_request/reject.json", fulfillmentRequestBasePath, fulfillmentOrderID) + wrappedData := map[string]interface{}{"fulfillment_request": request} + resource := new(FulfillmentRequestResource) + err := s.client.Post(path, wrappedData, resource) + return resource.FulfillmentOrder, err +} diff --git a/fulfillment_request_test.go b/fulfillment_request_test.go new file mode 100644 index 00000000..9ba8914c --- /dev/null +++ b/fulfillment_request_test.go @@ -0,0 +1,241 @@ +package goshopify + +import ( + "fmt" + "net/http" + "reflect" + "testing" + + "github.com/jarcoal/httpmock" +) + +func TestFulfillmentRequestServiceOp_Send(t *testing.T) { + setup() + defer teardown() + + fulfillmentOrderID := int64(1046000829) + message := "Fulfill this ASAP please." + httpmock.RegisterResponder( + http.MethodPost, + fmt.Sprintf("https://fooshop.myshopify.com/%s/fulfillment_orders/%d/fulfillment_request.json", client.pathPrefix, fulfillmentOrderID), + httpmock.NewBytesResponder(200, loadFixture("fulfillment_send.json")), + ) + + result, err := client.FulfillmentRequest.Send(fulfillmentOrderID, FulfillmentRequest{Message: message}) + if err != nil { + t.Errorf("FulfillmentRequest.Send returned error: %v", err) + } + + expected := &FulfillmentOrder{ + Id: 1046000829, + ShopId: 548380009, + OrderId: 450789469, + AssignedLocationId: 24826418, + RequestStatus: "submitted", + Status: "open", + SupportedActions: []string{"cancel_fulfillment_order"}, + Destination: FulfillmentOrderDestination{ + Id: 1046000816, + Address1: "Chestnut Street 92", + City: "Louisville", + Company: "", + Country: "United States", + Email: "bob.norman@mail.example.com", + FirstName: "Bob", + LastName: "Norman", + Phone: "+1(502)-459-2181", + Province: "Kentucky", + Zip: "40202", + }, + LineItems: []FulfillmentOrderLineItem{ + { + Id: 1058737567, + ShopId: 548380009, + FulfillmentOrderId: 1046000829, + Quantity: 1, + LineItemId: 466157049, + InventoryItemId: 39072856, + FulfillableQuantity: 1, + VariantId: 39072856, + }, + { + Id: 1058737568, + ShopId: 548380009, + FulfillmentOrderId: 1046000829, + Quantity: 1, + LineItemId: 518995019, + InventoryItemId: 49148385, + FulfillableQuantity: 1, + VariantId: 49148385, + }, + { + Id: 1058737569, + ShopId: 548380009, + FulfillmentOrderId: 1046000829, + Quantity: 1, + LineItemId: 703073504, + InventoryItemId: 457924702, + FulfillableQuantity: 1, + VariantId: 457924702, + }, + }, + } + + if !reflect.DeepEqual(result, expected) { + t.Errorf("FulfillmentRequest.Send returned %+v, expected %+v", result, expected) + } +} + +func TestFulfillmentRequestServiceOp_Accept(t *testing.T) { + setup() + defer teardown() + + fulfillmentOrderID := int64(1046000828) + message := "We will start processing your fulfillment on the next business day." + + httpmock.RegisterResponder( + http.MethodPost, + fmt.Sprintf("https://fooshop.myshopify.com/%s/fulfillment_orders/%d/fulfillment_request/accept.json", client.pathPrefix, fulfillmentOrderID), + httpmock.NewBytesResponder(200, loadFixture("fulfillment_accept.json")), + ) + + result, err := client.FulfillmentRequest.Accept(fulfillmentOrderID, FulfillmentRequest{Message: message}) + if err != nil { + t.Errorf("FulfillmentRequest.Accept returned error: %v", err) + } + + expected := &FulfillmentOrder{ + Id: 1046000828, + ShopId: 548380009, + OrderId: 450789469, + AssignedLocationId: 24826418, + RequestStatus: "accepted", + Status: "in_progress", + SupportedActions: []string{"request_cancellation", "create_fulfillment"}, + Destination: FulfillmentOrderDestination{ + Id: 1046000815, + Address1: "Chestnut Street 92", + City: "Louisville", + Company: "", + Country: "United States", + Email: "bob.norman@mail.example.com", + FirstName: "Bob", + LastName: "Norman", + Phone: "+1(502)-459-2181", + Province: "Kentucky", + Zip: "40202", + }, + LineItems: []FulfillmentOrderLineItem{ + { + Id: 1058737564, + ShopId: 548380009, + FulfillmentOrderId: 1046000828, + Quantity: 1, + LineItemId: 466157049, + InventoryItemId: 39072856, + FulfillableQuantity: 1, + VariantId: 39072856, + }, + { + Id: 1058737565, + ShopId: 548380009, + FulfillmentOrderId: 1046000828, + Quantity: 1, + LineItemId: 518995019, + InventoryItemId: 49148385, + FulfillableQuantity: 1, + VariantId: 49148385, + }, + { + Id: 1058737566, + ShopId: 548380009, + FulfillmentOrderId: 1046000828, + Quantity: 1, + LineItemId: 703073504, + InventoryItemId: 457924702, + FulfillableQuantity: 1, + VariantId: 457924702, + }, + }, + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("FulfillmentRequest.Accept returned %+v, expected %+v", result, expected) + } +} + +func TestFulfillmentRequestServiceOp_Reject(t *testing.T) { + setup() + defer teardown() + + fulfillmentOrderID := int64(1046000830) + rejectionMessage := "Not enough inventory on hand to complete the work." + + httpmock.RegisterResponder( + http.MethodPost, + fmt.Sprintf("https://fooshop.myshopify.com/%s/fulfillment_orders/%d/fulfillment_request/reject.json", client.pathPrefix, fulfillmentOrderID), + httpmock.NewBytesResponder(200, loadFixture("fulfillment_reject.json")), + ) + + result, err := client.FulfillmentRequest.Reject(fulfillmentOrderID, FulfillmentRequest{Message: rejectionMessage}) + if err != nil { + t.Errorf("FulfillmentRequest.Reject returned error: %v", err) + } + + expected := &FulfillmentOrder{ + Id: 1046000830, + ShopId: 548380009, + OrderId: 450789469, + AssignedLocationId: 24826418, + RequestStatus: "rejected", + Status: "open", + SupportedActions: []string{"request_fulfillment", "create_fulfillment"}, + Destination: FulfillmentOrderDestination{ + Id: 1046000817, + Address1: "Chestnut Street 92", + City: "Louisville", + Company: "", + Country: "United States", + Email: "bob.norman@mail.example.com", + FirstName: "Bob", + LastName: "Norman", + Phone: "+1(502)-459-2181", + Province: "Kentucky", + Zip: "40202", + }, + LineItems: []FulfillmentOrderLineItem{ + { + Id: 1058737570, + ShopId: 548380009, + FulfillmentOrderId: 1046000830, + Quantity: 1, + LineItemId: 466157049, + InventoryItemId: 39072856, + FulfillableQuantity: 1, + VariantId: 39072856, + }, + { + Id: 1058737571, + ShopId: 548380009, + FulfillmentOrderId: 1046000830, + Quantity: 1, + LineItemId: 518995019, + InventoryItemId: 49148385, + FulfillableQuantity: 1, + VariantId: 49148385, + }, + { + Id: 1058737572, + ShopId: 548380009, + FulfillmentOrderId: 1046000830, + Quantity: 1, + LineItemId: 703073504, + InventoryItemId: 457924702, + FulfillableQuantity: 1, + VariantId: 457924702, + }, + }, + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("FulfillmentRequest.Reject returned %+v, expected %+v", result, expected) + } +} diff --git a/fulfillment_service.go b/fulfillment_service.go index 046b84d8..b5d2185e 100644 --- a/fulfillment_service.go +++ b/fulfillment_service.go @@ -6,8 +6,7 @@ const ( fulfillmentServiceBasePath = "fulfillment_services" ) -// FulfillmentServiceService is an interface for interfacing with the fulfillment service -// of the Shopify API. +// FulfillmentServiceService is an interface for interfacing with the fulfillment service of the Shopify API. // https://help.shopify.com/api/reference/fulfillmentservice type FulfillmentServiceService interface { List(interface{}) ([]FulfillmentServiceData, error) @@ -33,6 +32,7 @@ type FulfillmentServiceData struct { AdminGraphqlApiId string `json:"admin_graphql_api_id,omitempty"` PermitsSkuSharing bool `json:"permits_sku_sharing,omitempty"` RequiresShippingMethod bool `json:"requires_shipping_method,omitempty"` + Format string `json:"format,omitempty"` } type FulfillmentServiceResource struct { diff --git a/goshopify.go b/goshopify.go index 6ebf565c..07626039 100644 --- a/goshopify.go +++ b/goshopify.go @@ -126,6 +126,8 @@ type Client struct { FulfillmentOrder FulfillmentOrderService GraphQL GraphQLService AssignedFulfillmentOrder AssignedFulfillmentOrderService + FulfillmentEvent FulfillmentEventService + FulfillmentRequest FulfillmentRequestService } // A general response error that follows a similar layout to Shopify's response @@ -310,6 +312,8 @@ func NewClient(app App, shopName, token string, opts ...Option) *Client { c.FulfillmentOrder = &FulfillmentOrderServiceOp{client: c} c.GraphQL = &GraphQLServiceOp{client: c} c.AssignedFulfillmentOrder = &AssignedFulfillmentOrderServiceOp{client: c} + c.FulfillmentEvent = &FulfillmentEventServiceOp{client: c} + c.FulfillmentRequest = &FulfillmentRequestServiceOp{client: c} // apply any options for _, opt := range opts {