forked from bold-commerce/go-shopify
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Fulfillment Service (bold-commerce#115)
* Add Fulfillment Model Support (bold-commerce#17)
- Loading branch information
1 parent
80079f3
commit 175a42d
Showing
7 changed files
with
580 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"fulfillment":{"id":1022782888,"order_id":450789469,"status":"success","created_at":"2018-07-05T13:08:39-04:00","service":"manual","updated_at":"2018-07-05T13:08:40-04:00","tracking_company":"Bluedart","shipment_status":null,"location_id":905684977,"tracking_number":"123456789","tracking_numbers":["123456789"],"tracking_url":"https://shipping.xyz/track.php?num=123456789","tracking_urls":["https://shipping.xyz/track.php?num=123456789","https://anothershipper.corp/track.php?code=abc"],"receipt":{},"name":"#1001.1","admin_graphql_api_id":"gid://shopify/Fulfillment/1022782888","line_items":[{"id":466157049,"variant_id":39072856,"title":"IPod Nano - 8gb","quantity":1,"price":"199.00","sku":"IPOD2008GREEN","variant_title":"green","vendor":null,"fulfillment_service":"manual","product_id":632910392,"requires_shipping":true,"taxable":true,"gift_card":false,"name":"IPod Nano - 8gb - green","variant_inventory_management":"shopify","properties":[{"name":"Custom Engraving Front","value":"Happy Birthday"},{"name":"Custom Engraving Back","value":"Merry Christmas"}],"product_exists":true,"fulfillable_quantity":0,"grams":200,"total_discount":"0.00","fulfillment_status":"fulfilled","discount_allocations":[],"admin_graphql_api_id":"gid://shopify/LineItem/466157049","tax_lines":[{"title":"State Tax","price":"3.98","rate":0.06}]},{"id":518995019,"variant_id":49148385,"title":"IPod Nano - 8gb","quantity":1,"price":"199.00","sku":"IPOD2008RED","variant_title":"red","vendor":null,"fulfillment_service":"manual","product_id":632910392,"requires_shipping":true,"taxable":true,"gift_card":false,"name":"IPod Nano - 8gb - red","variant_inventory_management":"shopify","properties":[],"product_exists":true,"fulfillable_quantity":0,"grams":200,"total_discount":"0.00","fulfillment_status":"fulfilled","discount_allocations":[],"admin_graphql_api_id":"gid://shopify/LineItem/518995019","tax_lines":[{"title":"State Tax","price":"3.98","rate":0.06}]},{"id":703073504,"variant_id":457924702,"title":"IPod Nano - 8gb","quantity":1,"price":"199.00","sku":"IPOD2008BLACK","variant_title":"black","vendor":null,"fulfillment_service":"manual","product_id":632910392,"requires_shipping":true,"taxable":true,"gift_card":false,"name":"IPod Nano - 8gb - black","variant_inventory_management":"shopify","properties":[],"product_exists":true,"fulfillable_quantity":0,"grams":200,"total_discount":"0.00","fulfillment_status":"fulfilled","discount_allocations":[],"admin_graphql_api_id":"gid://shopify/LineItem/703073504","tax_lines":[{"title":"State Tax","price":"3.98","rate":0.06}]}]}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package goshopify | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
// FulfillmentService is an interface for interfacing with the fulfillment endpoints | ||
// of the Shopify API. | ||
// https://help.shopify.com/api/reference/fulfillment | ||
type FulfillmentService interface { | ||
List(interface{}) ([]Fulfillment, error) | ||
Count(interface{}) (int, error) | ||
Get(int, interface{}) (*Fulfillment, error) | ||
Create(Fulfillment) (*Fulfillment, error) | ||
Update(Fulfillment) (*Fulfillment, error) | ||
Complete(int) (*Fulfillment, error) | ||
Transition(int) (*Fulfillment, error) | ||
Cancel(int) (*Fulfillment, error) | ||
} | ||
|
||
// FulfillmentsService is an interface for other Shopify resources | ||
// to interface with the fulfillment endpoints of the Shopify API. | ||
// https://help.shopify.com/api/reference/fulfillment | ||
type FulfillmentsService interface { | ||
ListFulfillments(int, interface{}) ([]Fulfillment, error) | ||
CountFulfillments(int, interface{}) (int, error) | ||
GetFulfillment(int, int, interface{}) (*Fulfillment, error) | ||
CreateFulfillment(int, Fulfillment) (*Fulfillment, error) | ||
UpdateFulfillment(int, Fulfillment) (*Fulfillment, error) | ||
CompleteFulfillment(int, int) (*Fulfillment, error) | ||
TransitionFulfillment(int, int) (*Fulfillment, error) | ||
CancelFulfillment(int, int) (*Fulfillment, error) | ||
} | ||
|
||
// FulfillmentServiceOp handles communication with the fulfillment | ||
// related methods of the Shopify API. | ||
type FulfillmentServiceOp struct { | ||
client *Client | ||
resource string | ||
resourceID int | ||
} | ||
|
||
// Fulfillment represents a Shopify fulfillment. | ||
type Fulfillment struct { | ||
ID int `json:"id,omitempty"` | ||
OrderID int `json:"order_id,omitempty"` | ||
LocationID int `json:"location_id,omitempty"` | ||
Status string `json:"status,omitempty"` | ||
CreatedAt *time.Time `json:"created_at,omitempty"` | ||
Service string `json:"service,omitempty"` | ||
UpdatedAt *time.Time `json:"updated_at,omitempty"` | ||
TrackingCompany string `json:"tracking_company,omitempty"` | ||
ShipmentStatus string `json:"shipment_status,omitempty"` | ||
TrackingNumber string `json:"tracking_number,omitempty"` | ||
TrackingNumbers []string `json:"tracking_numbers,omitempty"` | ||
TrackingUrl string `json:"tracking_url,omitempty"` | ||
TrackingUrls []string `json:"tracking_urls,omitempty"` | ||
Receipt Receipt `json:"receipt,omitempty"` | ||
LineItems []LineItem `json:"line_items,omitempty"` | ||
NotifyCustomer bool `json:"notify_customer,omitempty"` | ||
} | ||
|
||
// Receipt represents a Shopify receipt. | ||
type Receipt struct { | ||
TestCase bool `json:"testcase,omitempty"` | ||
Authorization string `json:"authorization,omitempty"` | ||
} | ||
|
||
// FulfillmentResource represents the result from the fulfillments/X.json endpoint | ||
type FulfillmentResource struct { | ||
Fulfillment *Fulfillment `json:"fulfillment"` | ||
} | ||
|
||
// FulfillmentsResource represents the result from the fullfilments.json endpoint | ||
type FulfillmentsResource struct { | ||
Fulfillments []Fulfillment `json:"fulfillments"` | ||
} | ||
|
||
// List fulfillments | ||
func (s *FulfillmentServiceOp) List(options interface{}) ([]Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s.json", prefix) | ||
resource := new(FulfillmentsResource) | ||
err := s.client.Get(path, resource, options) | ||
return resource.Fulfillments, err | ||
} | ||
|
||
// Count fulfillments | ||
func (s *FulfillmentServiceOp) Count(options interface{}) (int, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/count.json", prefix) | ||
return s.client.Count(path, options) | ||
} | ||
|
||
// Get individual fulfillment | ||
func (s *FulfillmentServiceOp) Get(fulfillmentID int, options interface{}) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/%d.json", prefix, fulfillmentID) | ||
resource := new(FulfillmentResource) | ||
err := s.client.Get(path, resource, options) | ||
return resource.Fulfillment, err | ||
} | ||
|
||
// Create a new fulfillment | ||
func (s *FulfillmentServiceOp) Create(fulfillment Fulfillment) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s.json", prefix) | ||
wrappedData := FulfillmentResource{Fulfillment: &fulfillment} | ||
resource := new(FulfillmentResource) | ||
err := s.client.Post(path, wrappedData, resource) | ||
return resource.Fulfillment, err | ||
} | ||
|
||
// Update an existing fulfillment | ||
func (s *FulfillmentServiceOp) Update(fulfillment Fulfillment) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/%d.json", prefix, fulfillment.ID) | ||
wrappedData := FulfillmentResource{Fulfillment: &fulfillment} | ||
resource := new(FulfillmentResource) | ||
err := s.client.Put(path, wrappedData, resource) | ||
return resource.Fulfillment, err | ||
} | ||
|
||
// Complete an existing fulfillment | ||
func (s *FulfillmentServiceOp) Complete(fulfillmentID int) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/%d/complete.json", prefix, fulfillmentID) | ||
resource := new(FulfillmentResource) | ||
err := s.client.Post(path, nil, resource) | ||
return resource.Fulfillment, err | ||
} | ||
|
||
// Transition an existing fulfillment | ||
func (s *FulfillmentServiceOp) Transition(fulfillmentID int) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/%d/open.json", prefix, fulfillmentID) | ||
resource := new(FulfillmentResource) | ||
err := s.client.Post(path, nil, resource) | ||
return resource.Fulfillment, err | ||
} | ||
|
||
// Cancel an existing fulfillment | ||
func (s *FulfillmentServiceOp) Cancel(fulfillmentID int) (*Fulfillment, error) { | ||
prefix := FulfillmentPathPrefix(s.resource, s.resourceID) | ||
path := fmt.Sprintf("%s/%d/cancel.json", prefix, fulfillmentID) | ||
resource := new(FulfillmentResource) | ||
err := s.client.Post(path, nil, resource) | ||
return resource.Fulfillment, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
package goshopify | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
"time" | ||
|
||
httpmock "gopkg.in/jarcoal/httpmock.v1" | ||
) | ||
|
||
func FulfillmentTests(t *testing.T, fulfillment Fulfillment) { | ||
// Check that ID is assigned to the returned fulfillment | ||
expectedInt := 1022782888 | ||
if fulfillment.ID != expectedInt { | ||
t.Errorf("Fulfillment.ID returned %+v, expected %+v", fulfillment.ID, expectedInt) | ||
} | ||
} | ||
|
||
func TestFulfillmentList(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments.json", | ||
httpmock.NewStringResponder(200, `{"fulfillments": [{"id":1},{"id":2}]}`)) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
fulfillments, err := fulfillmentService.List(nil) | ||
if err != nil { | ||
t.Errorf("Fulfillment.List returned error: %v", err) | ||
} | ||
|
||
expected := []Fulfillment{{ID: 1}, {ID: 2}} | ||
if !reflect.DeepEqual(fulfillments, expected) { | ||
t.Errorf("Fulfillment.List returned %+v, expected %+v", fulfillments, expected) | ||
} | ||
} | ||
|
||
func TestFulfillmentCount(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/count.json", | ||
httpmock.NewStringResponder(200, `{"count": 3}`)) | ||
|
||
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/count.json?created_at_min=2016-01-01T00%3A00%3A00Z", | ||
httpmock.NewStringResponder(200, `{"count": 2}`)) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
cnt, err := fulfillmentService.Count(nil) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Count returned error: %v", err) | ||
} | ||
|
||
expected := 3 | ||
if cnt != expected { | ||
t.Errorf("Fulfillment.Count returned %d, expected %d", cnt, expected) | ||
} | ||
|
||
date := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC) | ||
cnt, err = fulfillmentService.Count(CountOptions{CreatedAtMin: date}) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Count returned error: %v", err) | ||
} | ||
|
||
expected = 2 | ||
if cnt != expected { | ||
t.Errorf("Fulfillment.Count returned %d, expected %d", cnt, expected) | ||
} | ||
} | ||
|
||
func TestFulfillmentGet(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1.json", | ||
httpmock.NewStringResponder(200, `{"fulfillment": {"id":1}}`)) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
fulfillment, err := fulfillmentService.Get(1, nil) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Get returned error: %v", err) | ||
} | ||
|
||
expected := &Fulfillment{ID: 1} | ||
if !reflect.DeepEqual(fulfillment, expected) { | ||
t.Errorf("Fulfillment.Get returned %+v, expected %+v", fulfillment, expected) | ||
} | ||
} | ||
|
||
func TestFulfillmentCreate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments.json", | ||
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json"))) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
fulfillment := Fulfillment{ | ||
LocationID: 905684977, | ||
TrackingNumber: "123456789", | ||
TrackingUrls: []string{ | ||
"https://shipping.xyz/track.php?num=123456789", | ||
"https://anothershipper.corp/track.php?code=abc", | ||
}, | ||
NotifyCustomer: true, | ||
} | ||
|
||
returnedFulfillment, err := fulfillmentService.Create(fulfillment) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Create returned error: %v", err) | ||
} | ||
|
||
FulfillmentTests(t, *returnedFulfillment) | ||
} | ||
|
||
func TestFulfillmentUpdate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("PUT", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1022782888.json", | ||
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json"))) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
fulfillment := Fulfillment{ | ||
ID: 1022782888, | ||
TrackingNumber: "987654321", | ||
} | ||
|
||
returnedFulfillment, err := fulfillmentService.Update(fulfillment) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Update returned error: %v", err) | ||
} | ||
|
||
FulfillmentTests(t, *returnedFulfillment) | ||
} | ||
|
||
func TestFulfillmentComplete(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/complete.json", | ||
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json"))) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
returnedFulfillment, err := fulfillmentService.Complete(1) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Complete returned error: %v", err) | ||
} | ||
|
||
FulfillmentTests(t, *returnedFulfillment) | ||
} | ||
|
||
func TestFulfillmentTransition(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/open.json", | ||
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json"))) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
returnedFulfillment, err := fulfillmentService.Transition(1) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Transition returned error: %v", err) | ||
} | ||
|
||
FulfillmentTests(t, *returnedFulfillment) | ||
} | ||
|
||
func TestFulfillmentCancel(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/cancel.json", | ||
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json"))) | ||
|
||
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123} | ||
|
||
returnedFulfillment, err := fulfillmentService.Cancel(1) | ||
if err != nil { | ||
t.Errorf("Fulfillment.Cancel returned error: %v", err) | ||
} | ||
|
||
FulfillmentTests(t, *returnedFulfillment) | ||
} |
Oops, something went wrong.