Add CheckCompatibility and GetItemByGroupID

This commit is contained in:
JBP
2019-08-18 19:08:21 +02:00
parent 7484ba3df6
commit 345ff16666
7 changed files with 236 additions and 48 deletions

172
browse.go
View File

@@ -375,3 +375,175 @@ func (s *BrowseService) GetItem(ctx context.Context, itemID string, opts ...Opt)
var it Item
return it, s.client.Do(ctx, req, &it)
}
// ItemsByGroup represents eBay items by group.
type ItemsByGroup struct {
Items []struct {
ItemID string `json:"itemId"`
SellerItemRevision string `json:"sellerItemRevision"`
Title string `json:"title"`
ShortDescription string `json:"shortDescription"`
Price struct {
Value string `json:"value"`
Currency string `json:"currency"`
} `json:"price"`
CategoryPath string `json:"categoryPath"`
Condition string `json:"condition"`
ConditionID string `json:"conditionId"`
ItemLocation struct {
City string `json:"city"`
Country string `json:"country"`
} `json:"itemLocation"`
Image struct {
ImageURL string `json:"imageUrl"`
} `json:"image"`
Color string `json:"color"`
Material string `json:"material"`
Pattern string `json:"pattern"`
SizeType string `json:"sizeType"`
Brand string `json:"brand"`
ItemEndDate time.Time `json:"itemEndDate"`
Seller struct {
Username string `json:"username"`
FeedbackPercentage string `json:"feedbackPercentage"`
FeedbackScore int `json:"feedbackScore"`
} `json:"seller"`
EstimatedAvailabilities []struct {
DeliveryOptions []string `json:"deliveryOptions"`
AvailabilityThresholdType string `json:"availabilityThresholdType"`
AvailabilityThreshold int `json:"availabilityThreshold"`
EstimatedAvailabilityStatus string `json:"estimatedAvailabilityStatus"`
EstimatedSoldQuantity int `json:"estimatedSoldQuantity"`
} `json:"estimatedAvailabilities"`
ShippingOptions []struct {
ShippingServiceCode string `json:"shippingServiceCode"`
TrademarkSymbol string `json:"trademarkSymbol,omitempty"`
ShippingCarrierCode string `json:"shippingCarrierCode,omitempty"`
Type string `json:"type"`
ShippingCost struct {
Value string `json:"value"`
Currency string `json:"currency"`
} `json:"shippingCost"`
QuantityUsedForEstimate int `json:"quantityUsedForEstimate"`
MinEstimatedDeliveryDate time.Time `json:"minEstimatedDeliveryDate"`
MaxEstimatedDeliveryDate time.Time `json:"maxEstimatedDeliveryDate"`
ShipToLocationUsedForEstimate struct {
Country string `json:"country"`
} `json:"shipToLocationUsedForEstimate"`
AdditionalShippingCostPerUnit struct {
Value string `json:"value"`
Currency string `json:"currency"`
} `json:"additionalShippingCostPerUnit"`
ShippingCostType string `json:"shippingCostType"`
} `json:"shippingOptions"`
ShipToLocations struct {
RegionIncluded []struct {
RegionName string `json:"regionName"`
RegionType string `json:"regionType"`
} `json:"regionIncluded"`
RegionExcluded []struct {
RegionName string `json:"regionName"`
RegionType string `json:"regionType"`
} `json:"regionExcluded"`
} `json:"shipToLocations"`
ReturnTerms struct {
ReturnsAccepted bool `json:"returnsAccepted"`
RefundMethod string `json:"refundMethod"`
ReturnMethod string `json:"returnMethod"`
ReturnShippingCostPayer string `json:"returnShippingCostPayer"`
ReturnPeriod struct {
Value int `json:"value"`
Unit string `json:"unit"`
} `json:"returnPeriod"`
} `json:"returnTerms"`
LocalizedAspects []struct {
Type string `json:"type"`
Name string `json:"name"`
Value string `json:"value"`
} `json:"localizedAspects"`
TopRatedBuyingExperience bool `json:"topRatedBuyingExperience"`
BuyingOptions []string `json:"buyingOptions"`
PrimaryItemGroup struct {
ItemGroupID string `json:"itemGroupId"`
ItemGroupType string `json:"itemGroupType"`
ItemGroupHref string `json:"itemGroupHref"`
ItemGroupTitle string `json:"itemGroupTitle"`
ItemGroupImage struct {
ImageURL string `json:"imageUrl"`
} `json:"itemGroupImage"`
ItemGroupAdditionalImages []struct {
ImageURL string `json:"imageUrl"`
} `json:"itemGroupAdditionalImages"`
} `json:"primaryItemGroup"`
EnabledForGuestCheckout bool `json:"enabledForGuestCheckout"`
AdultOnly bool `json:"adultOnly"`
CategoryID string `json:"categoryId"`
} `json:"items"`
CommonDescriptions []struct {
Description string `json:"description"`
ItemIds []string `json:"itemIds"`
} `json:"commonDescriptions"`
}
// GetItemByGroupID retrieves the details of the individual items in an item group.
//
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/getItemsByItemGroup
func (s *BrowseService) GetItemByGroupID(ctx context.Context, groupID string, opts ...Opt) (ItemsByGroup, error) {
u := fmt.Sprintf("buy/browse/v1/item/get_items_by_item_group?item_group_id=%s", groupID)
req, err := s.client.NewRequest(http.MethodGet, u, nil, opts...)
if err != nil {
return ItemsByGroup{}, err
}
var it ItemsByGroup
return it, s.client.Do(ctx, req, &it)
}
// CompatibilityProperty represents a product property.
type CompatibilityProperty struct {
Name string `json:"name"`
Value string `json:"value"`
}
// Compatibility represents an item compatibility.
type Compatibility struct {
CompatibilityStatus string `json:"compatibilityStatus"`
Warnings []struct {
Category string `json:"category"`
Domain string `json:"domain"`
ErrorID int `json:"errorId"`
InputRefIds []string `json:"inputRefIds"`
LongMessage string `json:"longMessage"`
Message string `json:"message"`
OutputRefIds []string `json:"outputRefIds"`
Parameters []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"parameters"`
Subdomain string `json:"subdomain"`
} `json:"warnings"`
}
// Valid values for the "compatibilityStatus" compatibility field.
const (
BrowseCheckComoatibilityCompatible = "COMPATIBLE"
BrowseCheckComoatibilityNotCompatible = "NOT_COMPATIBLE"
BrowseCheckComoatibilityUndertermined = "UNDETERMINED"
)
// CheckCompatibility checks a product is compatible with the specified item.
//
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/checkCompatibility
func (s *BrowseService) CheckCompatibility(ctx context.Context, itemID, marketplaceID string, properties []CompatibilityProperty, opts ...Opt) (Compatibility, error) {
type payload struct {
CompatibilityProperties []CompatibilityProperty `json:"compatibilityProperties"`
}
pl := payload{properties}
u := fmt.Sprintf("buy/browse/v1/item/%s/check_compatibility", itemID)
opts = append(opts, OptBuyMarketplace(marketplaceID))
req, err := s.client.NewRequest(http.MethodPost, u, &pl, opts...)
if err != nil {
return Compatibility{}, err
}
var c Compatibility
return c, s.client.Do(ctx, req, &c)
}

View File

@@ -3,6 +3,7 @@ package ebay_test
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"testing"
@@ -70,3 +71,46 @@ func TestGettItem(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "PRODUCT", item.ItemID)
}
func TestGetItemByGroupID(t *testing.T) {
client, mux, teardown := setup(t)
defer teardown()
mux.HandleFunc("/buy/browse/v1/item/get_items_by_item_group", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Fatalf("expected GET method, got: %s", r.Method)
}
fmt.Fprintf(w, `{"items": [{"itemId": "%s"}]}`, r.URL.Query().Get("item_group_id"))
})
it, err := client.Buy.Browse.GetItemByGroupID(context.Background(), "151915076499")
assert.Nil(t, err)
assert.Equal(t, "151915076499", it.Items[0].ItemID)
}
func TestCheckCompatibility(t *testing.T) {
client, mux, teardown := setup(t)
defer teardown()
mux.HandleFunc("/buy/browse/v1/item/v1|202117468662|0/check_compatibility", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Fatalf("expected POST method, got: %s", r.Method)
}
assert.Equal(t, ebay.BuyMarketplaceUSA, r.Header.Get("X-EBAY-C-MARKETPLACE-ID"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("%+v", err)
}
assert.Equal(t, `{"compatibilityProperties":[{"name":"0","value":"1"},{"name":"2","value":"3"}]}
`, string(body))
fmt.Fprintf(w, `{"compatibilityStatus": "NOT_COMPATIBLE", "warnings": [{"category" : "category"}]}`)
})
compatibilityProperties := []ebay.CompatibilityProperty{
{Name: "0", Value: "1"},
{Name: "2", Value: "3"},
}
compatibility, err := client.Buy.Browse.CheckCompatibility(context.Background(), "v1|202117468662|0", ebay.BuyMarketplaceUSA, compatibilityProperties)
assert.Nil(t, err)
assert.Equal(t, "NOT_COMPATIBLE", compatibility.CompatibilityStatus)
assert.Equal(t, "category", compatibility.Warnings[0].Category)
}

17
ebay.go
View File

@@ -14,9 +14,10 @@ import (
"github.com/pkg/errors"
)
// eBay URLs.
const (
baseURL = "https://api.ebay.com/"
sandboxBaseURL = "https://api.sandbox.ebay.com/"
BaseURL = "https://api.ebay.com/"
SandboxBaseURL = "https://api.sandbox.ebay.com/"
)
// Some eBay API scopes.
@@ -47,13 +48,13 @@ type Client struct {
// NewClient returns a new eBay API client.
// If a nil httpClient is provided, http.DefaultClient will be used.
func NewClient(httpclient *http.Client) *Client {
return newClient(httpclient, baseURL)
return newClient(httpclient, BaseURL)
}
// NewSandboxClient returns a new eBay sandbox API client.
// If a nil httpClient is provided, http.DefaultClient will be used.
func NewSandboxClient(httpclient *http.Client) *Client {
return newClient(httpclient, sandboxBaseURL)
return newClient(httpclient, SandboxBaseURL)
}
// NewCustomClient returns a new custom eBay API client.
@@ -118,12 +119,13 @@ func (c *Client) NewRequest(method, url string, body interface{}, opts ...Opt) (
// Do sends an API request and stores the JSON decoded value into v.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) error {
dump, _ := httputil.DumpRequest(req, true)
resp, err := c.client.Do(req.WithContext(ctx))
if err != nil {
return errors.WithStack(err)
}
defer resp.Body.Close()
if err := CheckResponse(req, resp); err != nil {
if err := CheckResponse(req, resp, string(dump)); err != nil {
return err
}
if v == nil {
@@ -165,12 +167,11 @@ func (e *ErrorData) Error() string {
}
// CheckResponse checks the API response for errors, and returns them if present.
func CheckResponse(req *http.Request, resp *http.Response) error {
func CheckResponse(req *http.Request, resp *http.Response, dump string) error {
if s := resp.StatusCode; 200 <= s && s < 300 {
return nil
}
dump, _ := httputil.DumpRequest(req, true)
errorData := &ErrorData{response: resp, requestDump: string(dump)}
errorData := &ErrorData{response: resp, requestDump: dump}
_ = json.NewDecoder(resp.Body).Decode(errorData)
return errorData
}

View File

@@ -38,7 +38,7 @@ func TestNewRequest(t *testing.T) {
func TestCheckResponseNoError(t *testing.T) {
resp := &http.Response{StatusCode: 200}
assert.Nil(t, ebay.CheckResponse(&http.Request{}, resp))
assert.Nil(t, ebay.CheckResponse(&http.Request{}, resp, ""))
}
func TestCheckResponse(t *testing.T) {
@@ -67,7 +67,7 @@ func TestCheckResponse(t *testing.T) {
]
}`
resp := &http.Response{StatusCode: 400, Body: ioutil.NopCloser(bytes.NewBufferString(body))}
err, ok := ebay.CheckResponse(&http.Request{URL: &url.URL{}}, resp).(*ebay.ErrorData)
err, ok := ebay.CheckResponse(&http.Request{URL: &url.URL{}}, resp, "").(*ebay.ErrorData)
assert.True(t, ok)
assert.Equal(t, 1, len(err.Errors))
assert.Equal(t, 15008, err.Errors[0].ErrorID)

View File

@@ -120,8 +120,6 @@ const (
//
// eBay API docs: https://developer.ebay.com/api-docs/buy/offer/resources/bidding/methods/placeProxyBid
func (s *OfferService) PlaceProxyBid(ctx context.Context, itemID, marketplaceID, maxAmount, currency string, userConsentAdultOnlyItem bool, opts ...Opt) (ProxyBid, error) {
u := fmt.Sprintf("buy/offer/v1_beta/bidding/%s/place_proxy_bid", itemID)
opts = append(opts, OptBuyMarketplace(marketplaceID))
type userConsent struct {
AdultOnlyItem bool `json:"adultOnlyItem,omitempty"`
}
@@ -139,6 +137,8 @@ func (s *OfferService) PlaceProxyBid(ctx context.Context, itemID, marketplaceID,
if userConsentAdultOnlyItem {
pl.UserConsent = &userConsent{userConsentAdultOnlyItem}
}
u := fmt.Sprintf("buy/offer/v1_beta/bidding/%s/place_proxy_bid", itemID)
opts = append(opts, OptBuyMarketplace(marketplaceID))
req, err := s.client.NewRequest(http.MethodPost, u, &pl, opts...)
if err != nil {
return ProxyBid{}, err

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"testing"
"github.com/jybp/ebay"
@@ -43,23 +42,17 @@ func TestPlaceProxyBid(t *testing.T) {
if r.Method != "POST" {
t.Fatalf("expected POST method, got: %s", r.Method)
}
marketplaceID := r.Header.Get("X-EBAY-C-MARKETPLACE-ID")
assert.Equal(t, ebay.BuyMarketplaceUSA, r.Header.Get("X-EBAY-C-MARKETPLACE-ID"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatalf("%+v", err)
http.Error(w, err.Error(), 500)
return
}
escapedBody := strconv.Quote(string(body))
escapedBody = escapedBody[1 : len(escapedBody)-1]
fmt.Fprintf(w, `{"proxyBidId": "%s_%s"}`, escapedBody, marketplaceID)
assert.Equal(t, `{"maxAmount":{"currency":"USD","value":"1.23"},"userConsent":{"adultOnlyItem":true}}
`, string(body))
fmt.Fprintf(w, `{"proxyBidId": "123"}`)
})
bid, err := client.Buy.Offer.PlaceProxyBid(context.Background(), "v1|202117468662|0", ebay.BuyMarketplaceUSA, "1.23", "USD", false)
bid, err := client.Buy.Offer.PlaceProxyBid(context.Background(), "v1|202117468662|0", ebay.BuyMarketplaceUSA, "1.23", "USD", true)
assert.Nil(t, err)
assert.Equal(t, "{\"maxAmount\":{\"currency\":\"USD\",\"value\":\"1.23\"}}\n_EBAY_US", bid.ProxyBidID)
bid, err = client.Buy.Offer.PlaceProxyBid(context.Background(), "v1|202117468662|0", ebay.BuyMarketplaceUSA, "1.23", "USD", true)
assert.Nil(t, err)
assert.Equal(t, "{\"maxAmount\":{\"currency\":\"USD\",\"value\":\"1.23\"},\"userConsent\":{\"adultOnlyItem\":true}}\n_EBAY_US", bid.ProxyBidID)
assert.Equal(t, `123`, bid.ProxyBidID)
}

View File

@@ -9,7 +9,6 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"testing"
"time"
@@ -137,26 +136,5 @@ func TestAuction(t *testing.T) {
if err != nil && !ebay.IsError(err, ebay.ErrGetBiddingNoBiddingActivity) {
t.Fatalf("Expected error code %d, got %+v.", ebay.ErrGetBiddingNoBiddingActivity, err)
}
var bidValue, bidCurrency string
if len(bid.SuggestedBidAmounts) > 0 {
bidValue = bid.SuggestedBidAmounts[0].Value
bidCurrency = bid.SuggestedBidAmounts[0].Currency
} else {
bidValue = it.CurrentBidPrice.Value
v, err := strconv.ParseFloat(bidValue, 64)
if err != nil {
t.Fatal(err)
}
v += 2
bidValue = fmt.Sprintf("%.2f", v)
bidCurrency = it.CurrentBidPrice.Currency
}
_, err = client.Buy.Offer.PlaceProxyBid(ctx, it.ItemID, ebay.BuyMarketplaceUSA, bidValue, bidCurrency, false)
if err != nil {
t.Fatal(err)
}
t.Logf("Successfully bid %+v.", bid.SuggestedBidAmounts[0])
t.Logf("bidding: %+v", bid)
}