go-shopify

repository·master·Indexed 19 days ago

https://github.com/bold-commerce/go-shopify

A Go library for interacting with the Shopify API. It supports OAuth flows for public apps, basic authentication for private apps, and provides a structured way to interact with Shopify resources. Features include client configuration for API versions and retries, webhook request verification, and services for managing abandoned checkouts, application charges, blog articles, and API permissions.

Tokens
62.8K
Snippets
176
Records
207
Agent score
63%

What's inside go-shopify

  1. Develop and test the library

    master

    Testing is performed using docker and docker-compose.

    Using Make (Recommended):

    • make: Builds the go-shopify:latest container.
    • make test: Runs tests inside the container.
    • make clean: Removes the build container and coverage output.
    • make coverage: Generates and opens coverage.html.

    Manual Commands:

    • Build: docker-compose build test
    • Run tests: docker-compose run --rm tests
    • Generate coverage: docker-compose run --rm dev sh -c 'go test -coverprofile=coverage.out ./... && go tool cover -html coverage.out -o coverage.html'
  2. Implement custom endpoints using your own models

    master

    If an endpoint is not implemented in the library, you can use the client.Get method to fetch data into your own custom structs. You must define models that match the expected JSON structure from Shopify.

    type Webhook struct {
        Id int         `json:"id"`
        Address string `json:"address"`
    }
    
    type WebhooksResource struct {
        Webhooks []Webhook `json:"webhooks"`
    }
    
    func FetchWebhooks() ([]Webhook, error) {
        path := "admin/webhooks.json"
        resource := new(WebhooksResource)
        client, _ := goshopify.NewClient(app, "shopname", "token")
    
        // resource gets modified by the Get call
        err := client.Get(path, resource, nil)
    
        return resource.Webhooks, err
    }
  3. Implement OAuth flow for Shopify apps

    master

    If you do not have a permanent access token, you can use the goshopify.App struct to manage the OAuth flow.

    1. Initialize the App: Define your ApiKey, ApiSecret, RedirectUrl, and Scope.
    2. Authorize: Use app.AuthorizeUrl(shopName, state) to generate the URL for redirection.
    3. Verify and Exchange: In your callback handler, use app.VerifyAuthorizationURL(r.URL) to validate the signature, then call app.GetAccessToken(ctx, shopName, code) to retrieve the permanent token.
    // Create an app
    app := goshopify.App{
        ApiKey: "abcd",
        ApiSecret: "efgh",
        RedirectUrl: "https://example.com/shopify/callback",
        Scope: "read_products,read_orders",
    }
    
    // 1. Generate Authorize URL
    authUrl := app.AuthorizeUrl(shopName, state)
    
    // 2. In callback, verify and get token
    if ok, _ := app.VerifyAuthorizationURL(r.URL); !ok {
        // handle invalid signature
    }
    
    ctx := context.TODO()
    token, err := app.GetAccessToken(ctx, shopName, code)
  4. How customer pagination works

    master

    To handle large sets of customers, use ListWithPagination. This method returns a slice of Customer objects and a *Pagination object. If pagination.NextPageOptions is not nil, you can pass those options back into a subsequent ListWithPagination call to retrieve the next page of results.

    // Manual pagination loop example
    options := interface{}(nil)
    for {
    	customers, pagination, err := client.CustomerService.ListWithPagination(ctx, options)
    	if err != nil {
    		break
    	}
    	// Process customers...
    
    	if pagination.NextPageOptions == nil {
    		break
    	}
    	options = pagination.NextPageOptions
    }
  5. Implement Shipping Rate Responses

    master

    When Shopify sends a request to your CallbackUrl, your server must respond with a ShippingRateResponse object. This object contains an array of ShippingRate objects that customers will see at checkout.

    ShippingRate Fields:

    • ServiceName: The name of the rate (e.g., "Expedited Mail").
    • Description: A description of the rate (e.g., "Includes tracking").
    • ServiceCode: A unique code for the rate (e.g., "expedited_mail").
    • Currency: The currency of the rate.
    • TotalPrice: The total price in the currency's smallest unit (e.g., cents). This uses decimal.Decimal.
    • PhoneRequired: Whether the customer must provide a phone number.
    • MinDeliveryDate / MaxDeliveryDate: Optional time bounds for the rate validity.
    response := goshopify.ShippingRateResponse{
    	Rates: []goshopify.ShippingRate{
    		{
    			ServiceName: "Standard Shipping",
    			Description: "3-5 business days",
    			ServiceCode: "standard",
    			Currency:    "USD",
    			TotalPrice:   decimal.NewFromFloat(500), // $5.00
    		},
    	},
    }
  6. Understand the Order data structure

    master

    The Order struct represents a full Shopify order. Key fields include:

    • Id: The unique identifier for the order.
    • LineItems: A slice of LineItem objects containing product details, quantities, and prices.
    • TotalWeight: Total weight of the order.
    • FinancialStatus: The current payment status (OrderFinancialStatus).
    • FulfillmentStatus: The current shipping status (OrderFulfillmentStatus).
    • Customer: Pointer to the Customer resource.
    • TotalPrice: The total price of the order using decimal.Decimal for precision.
    • TaxLines: A slice of TaxLine objects describing taxes applied.
  7. Fulfill items via FulfillmentOrders

    master

    To fulfill specific quantities of items within a Fulfillment Order, use the LineItemByFulfillmentOrder and LineItemByFulfillmentOrderItemQuantity structures. This is common in newer Shopify API workflows.

    Fields:

    • LineItemByFulfillmentOrder.FulfillmentOrderId: The ID of the fulfillment order.
    • LineItemByFulfillmentOrder.FulfillmentOrderLineItems: A list of items and the quantities to fulfill.
    • LineItemByFulfillmentOrderItemQuantity.Id: The line item ID.
    • LineItemByFulfillmentOrderItemQuantity.Quantity: The amount to fulfill.
  8. Understand ShippingRateRequest and ShippingRateAddress

    master

    When Shopify calls your carrier service callback URL, it sends a ShippingRateRequest. This request contains the context needed to calculate rates.

    Request Structure:

    • Rate: A ShippingRateQuery containing:
      • Origin: The starting ShippingRateAddress.
      • Destination: The target ShippingRateAddress.
      • Items: A list of LineItem objects being shipped.
      • Currency: The requested currency.
      • Locale: The requested locale.

    Address Fields: For API-created carrier services, you should primarily use these fields in ShippingRateAddress:

    • Address1, Address2, City, Province, Zip (via PostalCode), Country.

    Note: Other fields like Address3, Fax, or CompanyName may be present for specific legacy providers but are typically null for standard API integrations.

    // The incoming payload from Shopify will unmarshal into:
    goshopify.ShippingRateRequest
  9. Configure PriceRule prerequisites

    master

    The PriceRule struct provides helper methods to set various prerequisite conditions for a discount rule. These methods handle the internal creation of prerequisite sub-structs and can also clear them by passing nil.

    • SetPrerequisiteSubtotalRange(greaterThanOrEqualTo *string) error: Sets the minimum subtotal required for the rule to apply. The string must be a valid decimal value. Returns an error if the string is not a valid decimal.
    • SetPrerequisiteQuantityRange(greaterThanOrEqualTo *int): Sets the minimum quantity required for the rule to apply.
    • SetPrerequisiteShippingPriceRange(lessThanOrEqualTo *string) error: Sets the maximum shipping price allowed for the rule to apply. The string must be a valid decimal value. Returns an error if the string is not a valid decimal.
    • SetPrerequisiteToEntitlementQuantityRatio(prerequisiteQuantity *int, entitledQuantity *int): Sets the ratio between required items and entitled items (e.g., for 'Buy X, Get Y' rules).
    // Example: Setting a subtotal prerequisite
    val := "50.00"
    err := priceRule.SetPrerequisiteSubtotalRange(&val)
    
    // Example: Setting a quantity prerequisite
    qty := 2
    priceRule.SetPrerequisiteQuantityRange(&qty)
    
    // Example: Setting a Buy X Get Y ratio
    pReq := 1
    eEnt := 1
    priceRule.SetPrerequisiteToEntitlementQuantityRatio(&pReq, &eEnt)
  10. Initialize a new Shopify API Client

    master

    To interact with the Shopify Admin API, you must create a Client instance. You can do this using NewClient (which returns an error) or MustNewClient (which panics on error).

    Both functions require an App configuration and the shop's domain (e.g., "theshop.myshopify.com" or just "theshop"), along with an access token.

    Note that each store requires its own Client instance because the baseURL is set on a per-store basis.

    app := goshopify.App{
        ApiKey:    "your_api_key",
        ApiSecret: "your_api_secret",
    }
    
    // Using NewClient
    client, err := goshopify.NewClient(app, "theshop", "your_access_token")
    if err != nil {
        // handle error
    }
  11. Run tests using Docker Compose

    master

    You can run the project's test suite within a Docker container using the provided test service in docker-compose.yml. This service builds the image from the current directory and executes go test -v -cover ./... inside the container, mapping the local source code to /go/src/github.com/bold-commerce/go-shopify to ensure tests run against your local changes.

    docker-compose up test