Skip to content
This repository was archived by the owner on Sep 11, 2018. It is now read-only.

Commit 175a42d

Browse files
girandrewhoff
authored andcommitted
Add Fulfillment Service (#115)
* Add Fulfillment Model Support (#17)
1 parent 80079f3 commit 175a42d

File tree

7 files changed

+580
-23
lines changed

7 files changed

+580
-23
lines changed

fixtures/fulfillment.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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}]}]}}

fulfillment.go

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package goshopify
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
// FulfillmentService is an interface for interfacing with the fulfillment endpoints
9+
// of the Shopify API.
10+
// https://help.shopify.com/api/reference/fulfillment
11+
type FulfillmentService interface {
12+
List(interface{}) ([]Fulfillment, error)
13+
Count(interface{}) (int, error)
14+
Get(int, interface{}) (*Fulfillment, error)
15+
Create(Fulfillment) (*Fulfillment, error)
16+
Update(Fulfillment) (*Fulfillment, error)
17+
Complete(int) (*Fulfillment, error)
18+
Transition(int) (*Fulfillment, error)
19+
Cancel(int) (*Fulfillment, error)
20+
}
21+
22+
// FulfillmentsService is an interface for other Shopify resources
23+
// to interface with the fulfillment endpoints of the Shopify API.
24+
// https://help.shopify.com/api/reference/fulfillment
25+
type FulfillmentsService interface {
26+
ListFulfillments(int, interface{}) ([]Fulfillment, error)
27+
CountFulfillments(int, interface{}) (int, error)
28+
GetFulfillment(int, int, interface{}) (*Fulfillment, error)
29+
CreateFulfillment(int, Fulfillment) (*Fulfillment, error)
30+
UpdateFulfillment(int, Fulfillment) (*Fulfillment, error)
31+
CompleteFulfillment(int, int) (*Fulfillment, error)
32+
TransitionFulfillment(int, int) (*Fulfillment, error)
33+
CancelFulfillment(int, int) (*Fulfillment, error)
34+
}
35+
36+
// FulfillmentServiceOp handles communication with the fulfillment
37+
// related methods of the Shopify API.
38+
type FulfillmentServiceOp struct {
39+
client *Client
40+
resource string
41+
resourceID int
42+
}
43+
44+
// Fulfillment represents a Shopify fulfillment.
45+
type Fulfillment struct {
46+
ID int `json:"id,omitempty"`
47+
OrderID int `json:"order_id,omitempty"`
48+
LocationID int `json:"location_id,omitempty"`
49+
Status string `json:"status,omitempty"`
50+
CreatedAt *time.Time `json:"created_at,omitempty"`
51+
Service string `json:"service,omitempty"`
52+
UpdatedAt *time.Time `json:"updated_at,omitempty"`
53+
TrackingCompany string `json:"tracking_company,omitempty"`
54+
ShipmentStatus string `json:"shipment_status,omitempty"`
55+
TrackingNumber string `json:"tracking_number,omitempty"`
56+
TrackingNumbers []string `json:"tracking_numbers,omitempty"`
57+
TrackingUrl string `json:"tracking_url,omitempty"`
58+
TrackingUrls []string `json:"tracking_urls,omitempty"`
59+
Receipt Receipt `json:"receipt,omitempty"`
60+
LineItems []LineItem `json:"line_items,omitempty"`
61+
NotifyCustomer bool `json:"notify_customer,omitempty"`
62+
}
63+
64+
// Receipt represents a Shopify receipt.
65+
type Receipt struct {
66+
TestCase bool `json:"testcase,omitempty"`
67+
Authorization string `json:"authorization,omitempty"`
68+
}
69+
70+
// FulfillmentResource represents the result from the fulfillments/X.json endpoint
71+
type FulfillmentResource struct {
72+
Fulfillment *Fulfillment `json:"fulfillment"`
73+
}
74+
75+
// FulfillmentsResource represents the result from the fullfilments.json endpoint
76+
type FulfillmentsResource struct {
77+
Fulfillments []Fulfillment `json:"fulfillments"`
78+
}
79+
80+
// List fulfillments
81+
func (s *FulfillmentServiceOp) List(options interface{}) ([]Fulfillment, error) {
82+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
83+
path := fmt.Sprintf("%s.json", prefix)
84+
resource := new(FulfillmentsResource)
85+
err := s.client.Get(path, resource, options)
86+
return resource.Fulfillments, err
87+
}
88+
89+
// Count fulfillments
90+
func (s *FulfillmentServiceOp) Count(options interface{}) (int, error) {
91+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
92+
path := fmt.Sprintf("%s/count.json", prefix)
93+
return s.client.Count(path, options)
94+
}
95+
96+
// Get individual fulfillment
97+
func (s *FulfillmentServiceOp) Get(fulfillmentID int, options interface{}) (*Fulfillment, error) {
98+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
99+
path := fmt.Sprintf("%s/%d.json", prefix, fulfillmentID)
100+
resource := new(FulfillmentResource)
101+
err := s.client.Get(path, resource, options)
102+
return resource.Fulfillment, err
103+
}
104+
105+
// Create a new fulfillment
106+
func (s *FulfillmentServiceOp) Create(fulfillment Fulfillment) (*Fulfillment, error) {
107+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
108+
path := fmt.Sprintf("%s.json", prefix)
109+
wrappedData := FulfillmentResource{Fulfillment: &fulfillment}
110+
resource := new(FulfillmentResource)
111+
err := s.client.Post(path, wrappedData, resource)
112+
return resource.Fulfillment, err
113+
}
114+
115+
// Update an existing fulfillment
116+
func (s *FulfillmentServiceOp) Update(fulfillment Fulfillment) (*Fulfillment, error) {
117+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
118+
path := fmt.Sprintf("%s/%d.json", prefix, fulfillment.ID)
119+
wrappedData := FulfillmentResource{Fulfillment: &fulfillment}
120+
resource := new(FulfillmentResource)
121+
err := s.client.Put(path, wrappedData, resource)
122+
return resource.Fulfillment, err
123+
}
124+
125+
// Complete an existing fulfillment
126+
func (s *FulfillmentServiceOp) Complete(fulfillmentID int) (*Fulfillment, error) {
127+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
128+
path := fmt.Sprintf("%s/%d/complete.json", prefix, fulfillmentID)
129+
resource := new(FulfillmentResource)
130+
err := s.client.Post(path, nil, resource)
131+
return resource.Fulfillment, err
132+
}
133+
134+
// Transition an existing fulfillment
135+
func (s *FulfillmentServiceOp) Transition(fulfillmentID int) (*Fulfillment, error) {
136+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
137+
path := fmt.Sprintf("%s/%d/open.json", prefix, fulfillmentID)
138+
resource := new(FulfillmentResource)
139+
err := s.client.Post(path, nil, resource)
140+
return resource.Fulfillment, err
141+
}
142+
143+
// Cancel an existing fulfillment
144+
func (s *FulfillmentServiceOp) Cancel(fulfillmentID int) (*Fulfillment, error) {
145+
prefix := FulfillmentPathPrefix(s.resource, s.resourceID)
146+
path := fmt.Sprintf("%s/%d/cancel.json", prefix, fulfillmentID)
147+
resource := new(FulfillmentResource)
148+
err := s.client.Post(path, nil, resource)
149+
return resource.Fulfillment, err
150+
}

fulfillment_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package goshopify
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
"time"
7+
8+
httpmock "gopkg.in/jarcoal/httpmock.v1"
9+
)
10+
11+
func FulfillmentTests(t *testing.T, fulfillment Fulfillment) {
12+
// Check that ID is assigned to the returned fulfillment
13+
expectedInt := 1022782888
14+
if fulfillment.ID != expectedInt {
15+
t.Errorf("Fulfillment.ID returned %+v, expected %+v", fulfillment.ID, expectedInt)
16+
}
17+
}
18+
19+
func TestFulfillmentList(t *testing.T) {
20+
setup()
21+
defer teardown()
22+
23+
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments.json",
24+
httpmock.NewStringResponder(200, `{"fulfillments": [{"id":1},{"id":2}]}`))
25+
26+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
27+
28+
fulfillments, err := fulfillmentService.List(nil)
29+
if err != nil {
30+
t.Errorf("Fulfillment.List returned error: %v", err)
31+
}
32+
33+
expected := []Fulfillment{{ID: 1}, {ID: 2}}
34+
if !reflect.DeepEqual(fulfillments, expected) {
35+
t.Errorf("Fulfillment.List returned %+v, expected %+v", fulfillments, expected)
36+
}
37+
}
38+
39+
func TestFulfillmentCount(t *testing.T) {
40+
setup()
41+
defer teardown()
42+
43+
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/count.json",
44+
httpmock.NewStringResponder(200, `{"count": 3}`))
45+
46+
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/count.json?created_at_min=2016-01-01T00%3A00%3A00Z",
47+
httpmock.NewStringResponder(200, `{"count": 2}`))
48+
49+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
50+
51+
cnt, err := fulfillmentService.Count(nil)
52+
if err != nil {
53+
t.Errorf("Fulfillment.Count returned error: %v", err)
54+
}
55+
56+
expected := 3
57+
if cnt != expected {
58+
t.Errorf("Fulfillment.Count returned %d, expected %d", cnt, expected)
59+
}
60+
61+
date := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)
62+
cnt, err = fulfillmentService.Count(CountOptions{CreatedAtMin: date})
63+
if err != nil {
64+
t.Errorf("Fulfillment.Count returned error: %v", err)
65+
}
66+
67+
expected = 2
68+
if cnt != expected {
69+
t.Errorf("Fulfillment.Count returned %d, expected %d", cnt, expected)
70+
}
71+
}
72+
73+
func TestFulfillmentGet(t *testing.T) {
74+
setup()
75+
defer teardown()
76+
77+
httpmock.RegisterResponder("GET", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1.json",
78+
httpmock.NewStringResponder(200, `{"fulfillment": {"id":1}}`))
79+
80+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
81+
82+
fulfillment, err := fulfillmentService.Get(1, nil)
83+
if err != nil {
84+
t.Errorf("Fulfillment.Get returned error: %v", err)
85+
}
86+
87+
expected := &Fulfillment{ID: 1}
88+
if !reflect.DeepEqual(fulfillment, expected) {
89+
t.Errorf("Fulfillment.Get returned %+v, expected %+v", fulfillment, expected)
90+
}
91+
}
92+
93+
func TestFulfillmentCreate(t *testing.T) {
94+
setup()
95+
defer teardown()
96+
97+
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments.json",
98+
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json")))
99+
100+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
101+
102+
fulfillment := Fulfillment{
103+
LocationID: 905684977,
104+
TrackingNumber: "123456789",
105+
TrackingUrls: []string{
106+
"https://shipping.xyz/track.php?num=123456789",
107+
"https://anothershipper.corp/track.php?code=abc",
108+
},
109+
NotifyCustomer: true,
110+
}
111+
112+
returnedFulfillment, err := fulfillmentService.Create(fulfillment)
113+
if err != nil {
114+
t.Errorf("Fulfillment.Create returned error: %v", err)
115+
}
116+
117+
FulfillmentTests(t, *returnedFulfillment)
118+
}
119+
120+
func TestFulfillmentUpdate(t *testing.T) {
121+
setup()
122+
defer teardown()
123+
124+
httpmock.RegisterResponder("PUT", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1022782888.json",
125+
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json")))
126+
127+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
128+
129+
fulfillment := Fulfillment{
130+
ID: 1022782888,
131+
TrackingNumber: "987654321",
132+
}
133+
134+
returnedFulfillment, err := fulfillmentService.Update(fulfillment)
135+
if err != nil {
136+
t.Errorf("Fulfillment.Update returned error: %v", err)
137+
}
138+
139+
FulfillmentTests(t, *returnedFulfillment)
140+
}
141+
142+
func TestFulfillmentComplete(t *testing.T) {
143+
setup()
144+
defer teardown()
145+
146+
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/complete.json",
147+
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json")))
148+
149+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
150+
151+
returnedFulfillment, err := fulfillmentService.Complete(1)
152+
if err != nil {
153+
t.Errorf("Fulfillment.Complete returned error: %v", err)
154+
}
155+
156+
FulfillmentTests(t, *returnedFulfillment)
157+
}
158+
159+
func TestFulfillmentTransition(t *testing.T) {
160+
setup()
161+
defer teardown()
162+
163+
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/open.json",
164+
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json")))
165+
166+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
167+
168+
returnedFulfillment, err := fulfillmentService.Transition(1)
169+
if err != nil {
170+
t.Errorf("Fulfillment.Transition returned error: %v", err)
171+
}
172+
173+
FulfillmentTests(t, *returnedFulfillment)
174+
}
175+
176+
func TestFulfillmentCancel(t *testing.T) {
177+
setup()
178+
defer teardown()
179+
180+
httpmock.RegisterResponder("POST", "https://fooshop.myshopify.com/admin/orders/123/fulfillments/1/cancel.json",
181+
httpmock.NewBytesResponder(200, loadFixture("fulfillment.json")))
182+
183+
fulfillmentService := &FulfillmentServiceOp{client: client, resource: ordersResourceName, resourceID: 123}
184+
185+
returnedFulfillment, err := fulfillmentService.Cancel(1)
186+
if err != nil {
187+
t.Errorf("Fulfillment.Cancel returned error: %v", err)
188+
}
189+
190+
FulfillmentTests(t, *returnedFulfillment)
191+
}

0 commit comments

Comments
 (0)