Giter Club home page Giter Club logo

go-shopify's Introduction

DEPRECATION NOTICE

Continuing support for the go-shopify library will be at Bold Commerce's fork over here. Please open issues and pull requests over there.

go-shopify

Another Shopify Api Library in Go.

Note: The library does not have implementations of all Shopify resources, but it is being used in production by Conversio and should be stable for usage. PRs for new resources and endpoints are welcome, or you can simply implement some yourself as-you-go. See the section "Using your own models" for more info.

Build Status codecov

Install

$ go get github.com/getconversio/go-shopify

Use

import "github.com/getconversio/go-shopify"

This gives you access to the goshopify package.

Oauth

If you don't have an access token yet, you can obtain one with the oauth flow. Something like this will work:

// Create an app somewhere.
app := goshopify.App{
    ApiKey: "abcd",
    ApiSecret: "efgh",
    RedirectUrl: "https://example.com/shopify/callback",
    Scope: "read_products,read_orders",
}

// Create an oauth-authorize url for the app and redirect to it.
// In some request handler, you probably want something like this:
func MyHandler(w http.ResponseWriter, r *http.Request) {
    shopName := r.URL.Query().Get("shop")
    authUrl := app.AuthorizeURL(shopName)
    http.Redirect(w, r, authUrl, http.StatusFound)
}

// Fetch a permanent access token in the callback
func MyCallbackHandler(w http.ResponseWriter, r *http.Request) {
    // Check that the callback signature is valid
    if ok, _ := app.VerifyAuthorizationURL(r.URL); !ok {
        http.Error(w, "Invalid Signature", http.StatusUnauthorized)
        return
    }

    query := r.URL.Query()
    shopName := query.Get("shop")
    code := query.Get("code")
    token, err := app.GetAccessToken(shopName, code)

    // Do something with the token, like store it in a DB.
}

Api calls with a token

With a permanent access token, you can make API calls like this:

// Create an app somewhere.
app := goshopify.App{
    ApiKey: "abcd",
    ApiSecret: "efgh",
    RedirectUrl: "https://example.com/shopify/callback",
    Scope: "read_products",
}

// Create a new API client
client := goshopify.NewClient(app, "shopname", "token")

// Fetch the number of products.
numProducts, err := client.Product.Count(nil)

Private App Auth

Private Shopify apps use basic authentication and do not require going through the OAuth flow. Here is an example:

// Create an app somewhere.
app := goshopify.App{
	ApiKey: "apikey",
	Password: "apipassword",
}

// Create a new API client (notice the token parameter is the empty string)
client := goshopify.NewClient(app, "shopname", "")

// Fetch the number of products.
numProducts, err := client.Product.Count(nil)

Query options

Most API functions take an options interface{} as parameter. You can use one from the library or create your own. For example, to fetch the number of products created after January 1, 2016, you can do:

// Create standard CountOptions
date := time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)
options := goshopify.CountOptions{createdAtMin: date}

// Use the options when calling the API.
numProducts, err := client.Product.Count(options)

The options are parsed with Google's go-querystring library so you can use custom options like this:

// Create custom options for the orders.
// Notice the `url:"status"` tag
options := struct {
    Status string `url:"status"`
}{"any"}

// Fetch the order count for orders with status="any"
orderCount, err := client.Order.Count(options)

Using your own models

Not all endpoints are implemented right now. In those case, feel free to implement them and make a PR, or you can create your own struct for the data and use NewRequest with the API client. This is how the existing endpoints are implemented.

For example, let's say you want to fetch webhooks. There's a helper function Get specifically for fetching stuff so this will work:

// Declare a model for the webhook
type Webhook struct {
    ID int         `json:"id"`
    Address string `json:"address"`
}

// Declare a model for the resource root.
type WebhooksResource struct {
    Webhooks []Webhook `json:"webhooks"`
}

func FetchWebhooks() ([]Webhook, error) {
    path := "admin/webhooks.json"
    resource := new(WebhooksResoure)
    client := goshopify.NewClient(app, "shopname", "token")

    // resource gets modified when calling Get
    err := client.Get(path, resource, nil)

    return resource.Webhooks, err
}

Webhooks verification

In order to be sure that a webhook is sent from ShopifyApi you could easily verify it with the VerifyWebhookRequest method.

For example:

func ValidateWebhook(httpRequest *http.Request) (bool) {
    shopifyApp := goshopify.App{ApiSecret: "ratz"}
    return shopifyApp.VerifyWebhookRequest(httpRequest)
}

Develop and test

There's nothing special to note about the tests except that if you have Docker and Compose installed, you can test like this:

$ docker-compose build dev
$ docker-compose run --rm dev

Testing the package is the default command for the dev container. To create a coverage profile:

$ docker-compose run --rm dev bash -c 'go test -coverprofile=coverage.out ./... && go tool cover -html coverage.out -o coverage.html'

go-shopify's People

Contributors

amwolff avatar andrewhoff avatar aryy avatar creativecactus avatar davidtonghelix avatar dbertouille avatar digiexchris avatar dlebech avatar fodawim avatar gir avatar jared-fraser avatar jerairrest avatar marinx avatar orian avatar rachelluli-guetta avatar ranabram avatar romainmenke avatar spprichard avatar stefanosala avatar sudomake avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-shopify's Issues

Order NoteAttributes can be multiple return types

Hello!

I don't know if others have witnessed this yet but it looks like Shopify can return either a string, number, or bool as a NoteAttribute Value in an order meaning that unmarshalling into the following struct can lead to an type error.

type NoteAttribute struct {
	Name  string `json:"Name"`
	Value string `json:"Value"`
}

I'm not 100% sure what we should do to handle this other than manually creating the struct.

Private Apps Use Different Authentication

There are two different types of applications that can access the Shopify APIs. Both types use different authentication.

Public applications

Public applications authenticate to Shopify by providing the X-Shopify-Access-Token header field in each HTTP request to the Shopify API. This access token is obtained through an OAuth handshake. That looks to be what go-shopify supports currently.

Private applications

Private applications can interact with the Shopify API on behalf of only one particular store. These applications authenticate with Shopify through basic HTTP authentication. The required credentials must be generated from the Shopify admin of the store that you want to connect with your application. Details

Making Requests

Would it be better to have PublicApp/PrivateApp and PublicClient/PrivateClient types to handle both types of authentication?

Adding product to sales channel

I am currently attempting to add a product to my private store.

The adding to the store works when the scope is web however I am unsure how to add the product to the 'sales channel' so that it is publicly visible. I can't seem to find a field in the struct that lets me do this.

The Shopify API docs suggest it is the published attribute, how do I set the item as visible in the online store?

Any help would be much appreciated!

Looking for new maintainers of go-shopify

TL;DR: we are looking for new maintainers for go-shopify. Please reach out if you or your organization is interesting in taking the lead :-)

Even though go-shopify is a fairly small library, I think it is important that the library is well-maintained, given the popularity of both Shopify app development and Golang. Two years after creating go-shopify, it seems that the library is one of the first results when searching for a Shopify Go library which creates certain expectations as well.

However, I am actually not writing any Go code anymore and neither is @getconversio, so it would make sense to get some more knowledgeable people on board that can help shape the future of the library, especially the possible breaking changes that are probably necessary to make the library slightly more useful (and/or modern? ๐Ÿš… )

My old colleagues at Conversio have tried reaching out to @Shopify to hear if they would take ownership going forward, but have been unsuccessful in getting a reply so far. We hope that someone in the community, who are actively using go-shopify, will be willing to take over the maintainer hat ๐ŸŽฉ

Going by the contributors list, @jared-fraser @aryy @amwolff @dbertouille @fodawim @RanAbram @davidtonghelix @andrewhoff have all had two or more commits in 2018 (apologies for the direct @-mention ๐Ÿ™‚) so perhaps some of you would be interested in this?

I think the perfect solution would be to hand over the stewardship to another organization with at least two developers that are actively using go-shopify already. I think this might both create some coherence and slightly easier maintenance and communication for new features. I'm not abandoning go-shopify for now by the way. I'll continue to look at pull requests in the near-future.

Thank you! ๐Ÿป

Rate limiting

Hello!

I wanted to get your thoughts on how to rate limit this package? Traditionally I have used the X-Shopify-Shop-Api-Call-Limit to make sure were not going to exceed our call limit for proceeding calls. Should we also return the current state with API calls? Or is there a better way of handling this?

Thanks!

Unable to nullify string attribute using the update function...

Currently, string values on any of the data structs have the omitempty tag associated with them. Because of this, if I try to set a string value to it's nil value "", json.Unmarshal and json.Marshal leave off that attribute. This is a problem when trying to update a string field that I want to unset.

Steps to reproduce (using a variant as example):

  • Create a variant with v.InventoryManagement = "shopify"
  • Set v.InventoryManagement = ""
  • Call shopify.Variant.Updated(v)

Expected: The returned updated variant should have v.InventoryManagement == "" and that variant should no longer be set to have shopify track inventory.

Actual: The returned updated variant has v.InventoryManagement == "shopify" and that variant has no changes to it's inventory management in the admin console.

Proposed fix: Convert the string values to string pointers so that they can be explicitly nil-able.

If I'm thinking about the problem incorrectly, I'd love some feedback on how to unset string properties.

IDs should use int64 instead of int

For some objects Shopify is using a global ID space and recently the threshold of 2^31-1 has been exceeded.

The Go's int size is at least 32 bits, but not guaranteed to be bigger (e.g. int64).

I propose to switch all int ids into int64.

Multiple Scopes per App

Is it possible to request multiple scopes per request? Seems like it is only possible to request one.

type App struct {
	ApiKey      string
	ApiSecret   string
	RedirectUrl string
	Scope       string
	Password    string
}
func (app App) AuthorizeUrl(shopName string, state string) string {
	shopUrl, _ := url.Parse(ShopBaseUrl(shopName))
	shopUrl.Path = "/admin/oauth/authorize"
	query := shopUrl.Query()
	query.Set("client_id", app.ApiKey)
	query.Set("redirect_uri", app.RedirectUrl)
	query.Set("scope", app.Scope)
	query.Set("state", state)
	shopUrl.RawQuery = query.Encode()
	return shopUrl.String()
}

romainmenke@2edf7a8

This demonstrates how it could be added in a non breaking way.

Unknown Error when doing POST

I might be just doing something completely wrong. But that error is extra vague and doesn't help at all.

type RecurringAppData struct {
	Name      string  `json:"name"`
	Price     float32 `json:"price"`
	ReturnURL string  `json:"return_url"`
	Test      bool    `json:"test"`
	TrialDays int     `json:"trial_days"`
}
type RecurringApplicationCharge struct {
	ID                 int32     `json:"id,omitempty"`
	Name               string    `json:"name,omitempty"`
	APIClientID        string    `json:"api_client_id,omitempty"`
	Price              string    `json:"price,omitempty"`
	Status             string    `json:"status,omitempty"`
	ReturnURL          string    `json:"returl_url,omitempty"`
	BillingOn          time.Time `json:"billing_on,omitempty"`
	CreatedAt          time.Time `json:"created_at,omitempty"`
	UpdatedAt          time.Time `json:"updated_at,omitempty"`
	Test               bool      `json:"test,omitempty"`
	ActivatedOn        time.Time `json:"activated_on,omitempty"`
	TrialEndsOn        time.Time `json:"trial_ends_on,omitempty"`
	CancelledOn        time.Time `json:"cancelled_on,omitempty"`
	TrialDays          int8      `json:"trial_days,omitempty"`
	DecoratedReturnURL string    `json:"decorated_return_url,omitempty"`
	ConfirmationURL    string    `json:"confirmation_url,omitempty"`
}
type RecurringApplicationResource struct {
	RecurringApplicationCharge RecurringApplicationCharge `json:"recurring_application_charge"`
}
resource := &RecurringApplicationResource{}
client := goshopify.NewClient(*app, shop, token)
returnURL, urlErr := getReturnURL()
data = createChargeResource(plan, price, returnURL, trial)
err := client.Post("admin/recurring_application_charges.json", data, resource)

If you check err, you get: Error: Unknown Error
The resource is instantiated with initial values only

Confusion between Shop Name and Domain in NewClient

Since New Client takes "shopName", my insinct was to pass Client.Name for that argument when making another new client later in the lifecycle for the same shop. The value from shopName is actually saved under Client.Domain. Passing Shop.Name resulted in a invalid memory address or nil pointer dereference for u := c.baseURL.ResolveReference(rel), since the value passed was not a url.

I'm just posting this in case someone else does this, although perhaps there could be a function that builds a new client from a Shop object.

ShippingLines.ID should be string

Hi
Currently ShippingLines.ID's datatype is int. While unmarshalling the JSON it fails since the data from Shopify has in string format.
Could this be fixed quick?

Thanks
Suman

style: initialisms do not follow standard go style guidlines

app := goshopify.App{
    ApiKey: "abcd",
    ApiSecret: "efgh",
    RedirectUrl: "https://example.com/shopify/callback",
    Scope: "read_products,read_orders",
}

should look like:

app := goshopify.App{
    APIKey: "abcd",
    APISecret: "efgh",
    RedirectURL: "https://example.com/shopify/callback",
    Scope: "read_products,read_orders",
}

I understand the backwards compatibility thing, but maybe we can start working on version 2.X.X and fix this?

Cites:
https://github.com/golang/lint/blob/master/lint.go#L749

Context in requests - beginning of a breaking change discussion

It should be possible to add context.Context to requests. It's particularly useful for a request we want to control a timeout, so one can use context.WithTimeout().

Not sure about how to make it as the current implementation assumes that options is just query.Values.
The go' standard way is to pass context as first argument.

Decoding Error Handling

I'm currently struggling to find a good way to find more information about an error I am seeing.

invalid character '<' looking for beginning of value

It appears that HTML is being returned. However, I can't find a way to get more information since it is intermittent and I can not manually reproduce it.

What would people think about a custom error on decoding errors. This error would contain the response body and HTTP status code to help in tracking down the issue. Something along the lines of,

type ResponseDecodingError struct {
	Message          string
	HTTPResponseBody []byte
	HTTPResponseCode int
}

I feel like this should contain enough information to allow debugging of errors that the library cannot handle.

Open to thoughts on if there is a better way to handle this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.