Compare commits

...

33 Commits

Author SHA1 Message Date
Bastian de Byl
ae0e5fc379 feat: add price, url, image, description, archived fields to Product struct
All checks were successful
Release / release (push) Successful in 24s
Extends the Product struct to include additional fields from the Snipcart
Products API response, enabling filtering of archived products and access
to product metadata like images and URLs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:51:24 -05:00
Bastian de Byl
914e49e9be chore: add InProgress status and document all order statuses
Added missing InProgress status constant and added comments to
document the meaning of each order status.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:58:43 -05:00
gitea-actions[bot]
43eed98f7c chore(release): 0.5.2 [skip ci]
## [0.5.2](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.1...v0.5.2) (2026-01-04)

### Bug Fixes

* use fedora runner label for Gitea Actions ([de552fc](de552fc8d4))
2026-01-03 22:44:41 -05:00
Bastian de Byl
de552fc8d4 fix: use fedora runner label for Gitea Actions
All checks were successful
Release / release (push) Successful in 21s
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 22:43:39 -05:00
gitea-actions[bot]
70d6955a63 chore(release): 0.5.1 [skip ci]
## [0.5.1](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.0...v0.5.1) (2026-01-03)

### Bug Fixes

* migrate semantic-release from GitHub to Gitea ([3b19919](3b1991970b))
* remove persist-credentials to allow semantic-release push ([a8646a6](a8646a6d52))
2026-01-03 17:24:30 -05:00
Bastian de Byl
a8646a6d52 fix: remove persist-credentials to allow semantic-release push
All checks were successful
Release / release (push) Successful in 23s
2026-01-03 17:24:06 -05:00
Bastian de Byl
3b1991970b fix: migrate semantic-release from GitHub to Gitea
Some checks failed
Release / release (push) Failing after 19s
- Replace @semantic-release/github with @saithodev/semantic-release-gitea
- Configure giteaUrl for git.debyl.io
- Use Gitea's automatic GITHUB_TOKEN as GITEA_TOKEN
- Set git author/committer for bot commits
2026-01-02 17:27:31 -05:00
semantic-release-bot
dd69d72adb chore(release): 0.5.0 [skip ci]
# [0.5.0](https://git.debyl.io/debyltech/go-snipcart/compare/v0.4.1...v0.5.0) (2026-01-02)

### Features

* add discount and savings fields to Order struct ([043da74](043da741fa))
2026-01-01 23:24:29 -05:00
Bastian de Byl
8bcd564447 chore: add semantic-release workflow and configuration
Some checks failed
Release / release (push) Failing after 41s
Added GitHub Actions workflow for automatic versioning and releases using semantic-release with Angular commit convention.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 11:11:16 -04:00
Bastian de Byl
043da741fa feat: add discount and savings fields to Order struct
Added Discount struct with comprehensive discount information and added discount-related fields (Discounts, SavedAmount, TotalRebateRate) to Order struct to expose discount data from Snipcart API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 10:49:36 -04:00
Bastian de Byl
a2fdeafc3c noticket - add missing shippingAddressSameAsBilling order field 2024-11-05 18:42:03 -05:00
Bastian de Byl
5e2a2afd42 added OrderNotification, OrderNotifications, client.GetOrderNotifications 2023-09-25 13:05:05 -04:00
Bastian de Byl
d7c5fd3b74 updated helpers for Get request fix 2023-09-13 13:48:50 -04:00
Bastian de Byl
b586a3184c added TotalTaxes to Order 2023-08-11 11:29:15 -04:00
Bastian de Byl
805491571c added missing Completed field for completion date of order 2023-07-29 17:34:01 -04:00
Bastian de Byl
10a9427598 corrected CustomField.Options field to string from []string 2023-07-27 13:48:13 -04:00
Bastian de Byl
3816c67b95 added more data types to CustomField struct 2023-07-27 13:44:06 -04:00
Bastian de Byl
c18376739b added TotalTaxable to Order 2023-07-03 13:31:26 -04:00
Bastian de Byl
6d85149be8 corrected Taxes to Order (new OrderTax struct) 2023-07-03 13:21:06 -04:00
Bastian de Byl
cf4ad9642d added Taxes to Order (omitempty) 2023-07-03 13:18:42 -04:00
Bastian de Byl
e1adf8cf4f added Offset and Limit to Orders struct 2023-07-03 12:14:40 -04:00
Bastian de Byl
73195a090f added ShippingError and ShippingErrors struct 2023-06-06 19:54:21 -04:00
Bastian de Byl
db81f246bf corrected TaxContent time for epoch (unix time) int from time.Time 2023-06-06 19:12:01 -04:00
Bastian de Byl
9c7218f937 added TaxWebhook struct 2023-06-06 19:07:14 -04:00
Bastian de Byl
543935289f removed unused snipcart/client.go file 2023-06-06 18:38:42 -04:00
Bastian de Byl
a4127264ef expanded webhook tax coverage, moved and renamed items to exclude Snipcart preamble 2023-06-06 18:28:10 -04:00
Bastian de Byl
5a74c2bae8 main corrected Operation in ProductCustomField to float64 from int 2023-05-12 15:23:14 -04:00
Bastian de Byl
d7f1571e09 main added CustomFields to snipcart product 2023-05-12 15:20:36 -04:00
Bastian de Byl
f18e21f181 main return only one product for GetProductById 2023-05-12 10:46:54 -04:00
Bastian de Byl
d56cf589da main correct the output of GetProductById 2023-05-12 03:14:07 -04:00
Bastian de Byl
9e631a91b1 corrected SnipcartProductsResponse, added GetProductById 2023-05-12 00:36:50 -04:00
Bastian de Byl
e575ff03a8 added GetProducts, SnipcartProductsResponse, SnipcartProductVariant 2023-05-12 00:01:55 -04:00
Bastian de Byl
0ec82e6397 modified NewClient to return ptr 2023-05-03 14:22:29 -04:00
11 changed files with 500 additions and 134 deletions

42
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Release
on:
push:
branches: [main]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
release:
runs-on: fedora
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- name: Install semantic-release
run: |
npm install -g semantic-release \
@semantic-release/commit-analyzer \
@semantic-release/release-notes-generator \
@semantic-release/changelog \
@saithodev/semantic-release-gitea \
@semantic-release/git
- name: Release
env:
# Use Gitea's automatic token (requires contents: write permission above)
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: gitea-actions[bot]
GIT_AUTHOR_EMAIL: gitea-actions[bot]@users.noreply.git.debyl.io
GIT_COMMITTER_NAME: gitea-actions[bot]
GIT_COMMITTER_EMAIL: gitea-actions[bot]@users.noreply.git.debyl.io
run: npx semantic-release

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.json *.json
!.releaserc.json
*.sum *.sum
config/* config/*

44
.releaserc.json Normal file
View File

@@ -0,0 +1,44 @@
{
"branches": ["main"],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "angular",
"releaseRules": [
{"message": "*#patch*", "release": "patch"},
{"message": "*#minor*", "release": "minor"},
{"message": "*#major*", "release": "major"},
{"type": "fix", "release": "patch"},
{"type": "feat", "release": "minor"},
{"type": "perf", "release": "patch"}
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "angular"
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@saithodev/semantic-release-gitea",
{
"giteaUrl": "https://git.debyl.io"
}
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}

21
CHANGELOG.md Normal file
View File

@@ -0,0 +1,21 @@
## [0.5.2](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.1...v0.5.2) (2026-01-04)
### Bug Fixes
* use fedora runner label for Gitea Actions ([de552fc](https://git.debyl.io/debyltech/go-snipcart/commit/de552fc8d4c37fb5af5e93ce4f4b8ae6e8e4bfcc))
## [0.5.1](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.0...v0.5.1) (2026-01-03)
### Bug Fixes
* migrate semantic-release from GitHub to Gitea ([3b19919](https://git.debyl.io/debyltech/go-snipcart/commit/3b1991970b64ac8c983006d4cf18bc6e080d6511))
* remove persist-credentials to allow semantic-release push ([a8646a6](https://git.debyl.io/debyltech/go-snipcart/commit/a8646a6d5258435b26a052ad57136032bb949ec7))
# [0.5.0](https://git.debyl.io/debyltech/go-snipcart/compare/v0.4.1...v0.5.0) (2026-01-02)
### Features
* add discount and savings fields to Order struct ([043da74](https://git.debyl.io/debyltech/go-snipcart/commit/043da741fafd24e3fb0bddbe06880c81c1b64b1f))

31
example_get_orders_by.go Normal file
View File

@@ -0,0 +1,31 @@
package main
import (
"flag"
"log"
"github.com/debyltech/go-snipcart/snipcart"
)
func main() {
snipcartApiKey := flag.String("key", "", "Snipcart API Key")
flag.Parse()
if *snipcartApiKey == "" {
log.Fatal("missing -key flag")
}
Client := snipcart.NewClient(*snipcartApiKey)
response, err := Client.GetOrders(map[string]string{
"placedBy": "bastian@bdebyl.net",
})
if err != nil {
log.Fatal(err)
}
log.Println("no errors continuing")
for k, v := range response.Items {
log.Printf("%v: %v\n", k, v)
}
}

View File

@@ -18,10 +18,10 @@ func main() {
Client := snipcart.NewClient(*snipcartApiKey) Client := snipcart.NewClient(*snipcartApiKey)
updateOrder := snipcart.SnipcartOrderUpdate{ updateOrder := snipcart.SnipcartOrderUpdate{
Status: snipcart.Delivered, ShippingRateId: "b1f5a5bca34d4e9ea7a55c011b22644f;5677a809435d46cbbb5dda2485295326",
} }
response, err := Client.UpdateOrder("b35990df-c0ca-4014-94de-1caa7bd7bb51", &updateOrder) response, err := Client.UpdateOrder("e6e72c95-31df-4594-b9a3-8603ce3914c8", &updateOrder)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

3
go.mod
View File

@@ -3,7 +3,6 @@ module github.com/debyltech/go-snipcart
go 1.19 go 1.19
require ( require (
github.com/debyltech/go-helpers v1.1.0 github.com/debyltech/go-helpers v1.1.1
github.com/debyltech/go-shippr v0.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
) )

47
snipcart/common.go Normal file
View File

@@ -0,0 +1,47 @@
package snipcart
const (
defaultLimit = 50
apiUri = "https://app.snipcart.com"
ordersPath = "/api/orders"
productsPath = "/api/products"
validationPath = "/api/requestvalidation/"
)
var (
orderUri = apiUri + ordersPath
productsUri = apiUri + productsPath
validationUri = apiUri + validationPath
orderNotificationPath = "notifications"
)
type Address struct {
FullName string `json:"fullName"`
FirstName string `json:"firstName"`
Name string `json:"name"`
Company string `json:"company"`
Address1 string `json:"address1"`
Address2 string `json:"address2"`
FullAddress string `json:"fullAddress"`
City string `json:"city"`
Country string `json:"country"`
PostalCode string `json:"postalCode"`
Province string `json:"province"`
Phone string `json:"phone"`
VatNumber string `json:"vatNumber,omitempty"`
}
type Client struct {
Key string
AuthBase64 string
Limit int
}
type CustomField struct {
Name string `json:"name"`
Value string `json:"value"`
Type string `json:"type,omitempty"`
Options string `json:"options,omitempty"`
Required bool `json:"required"`
}

View File

@@ -4,17 +4,18 @@ type OrderStatus string
type NotificationType string type NotificationType string
const ( const (
Processed OrderStatus = "Processed" InProgress OrderStatus = "InProgress" // Open cart, not finalized
Disputed = "Disputed" Pending OrderStatus = "Pending" // Waiting for payment/processing
Shipped = "Shipped" Processed OrderStatus = "Processed" // Order completed
Delivered = "Delivered" Disputed OrderStatus = "Disputed" // Order has dispute
Pending = "Pending" Shipped OrderStatus = "Shipped" // Order shipped
Cancelled = "Cancelled" Delivered OrderStatus = "Delivered" // Order delivered
Dispatched = "Dispatched" Dispatched OrderStatus = "Dispatched" // Order dispatched
Cancelled OrderStatus = "Cancelled" // Order cancelled
Comment NotificationType = "Comment" Comment NotificationType = "Comment"
OrderStatusChanged = "OrderStatusChanged" OrderStatusChanged NotificationType = "OrderStatusChanged"
OrderShipped = "OrderShipped" OrderShipped NotificationType = "OrderShipped"
TrackingNumber = "TrackingNumber" TrackingNumber NotificationType = "TrackingNumber"
Invoice = "Invice" Invoice NotificationType = "Invice"
) )

View File

@@ -5,44 +5,20 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http"
"time" "time"
helper "github.com/debyltech/go-helpers/json" helper "github.com/debyltech/go-helpers/json"
"github.com/skip2/go-qrcode" "github.com/skip2/go-qrcode"
) )
const ( type Item struct {
defaultLimit = 50
apiUri = "https://app.snipcart.com"
ordersPath = "/api/orders"
validationPath = "/api/requestvalidation/"
)
var (
orderUri = apiUri + ordersPath
validationUri = apiUri + validationPath
)
type Client struct {
SnipcartKey string
AuthBase64 string
Limit int
}
type SnipcartCustomField struct {
Name string `json:"name"`
Value string `json:"value"`
}
type SnipcartItem struct {
UUID string `json:"uniqueId"` UUID string `json:"uniqueId"`
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Quantity int `json:"quantity"` Quantity int `json:"quantity"`
TotalWeight float64 `json:"totalWeight,omitempty"` TotalWeight float64 `json:"totalWeight,omitempty"`
TotalPrice float64 `json:"totalPrice,omitempty"` TotalPrice float64 `json:"totalPrice,omitempty"`
CustomFields []SnipcartCustomField `json:"customFields"` CustomFields []CustomField `json:"customFields"`
Length float64 `json:"length,omitempty"` Length float64 `json:"length,omitempty"`
Width float64 `json:"width,omitempty"` Width float64 `json:"width,omitempty"`
Height float64 `json:"height,omitempty"` Height float64 `json:"height,omitempty"`
@@ -50,17 +26,68 @@ type SnipcartItem struct {
Shippable bool `json:"shippable,omitempty"` Shippable bool `json:"shippable,omitempty"`
} }
type SnipcartOrder struct { type Discount struct {
ID string `json:"id"`
DiscountID string `json:"discountId"`
Name string `json:"name"`
Code string `json:"code,omitempty"`
Trigger string `json:"trigger,omitempty"`
Type string `json:"type,omitempty"`
Rate float64 `json:"rate,omitempty"`
Amount float64 `json:"amount,omitempty"`
AmountSaved float64 `json:"amountSaved,omitempty"`
NormalizedRate float64 `json:"normalizedRate,omitempty"`
Combinable bool `json:"combinable"`
HasSavedAmount bool `json:"hasSavedAmount"`
MaxDiscountsPerItem *int `json:"maxDiscountsPerItem,omitempty"`
TotalToReach *float64 `json:"totalToReach,omitempty"`
ProductIds string `json:"productIds,omitempty"`
Categories string `json:"categories,omitempty"`
NumberOfUsages int `json:"numberOfUsages,omitempty"`
AppliesOnAllRecurring bool `json:"appliesOnAllRecurringOrders"`
CreationDate string `json:"creationDate,omitempty"`
ModificationDate string `json:"modificationDate,omitempty"`
}
type OrderTax struct {
Name string `json:"taxName"`
Rate float64 `json:"taxRate"`
Amount float64 `json:"amount"`
NumberForInvoice string `json:"numberForInvoice"`
}
type OrderNotification struct {
Id string `json:"id"`
Created time.Time `json:"creationDate"`
Type NotificationType `json:"type"`
DeliveryMethod string `json:"deliveryMethod"`
Message string `json:"message,omitempty"`
SendDate time.Time `json:"sentOn,omitempty"`
Subject string `json:"subject,omitempty"`
}
type OrderNotifications struct {
TotalNotifications int `json:"totalItems"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Notifications []OrderNotification `json:"items"`
}
type Order struct {
Token string `json:"token"` Token string `json:"token"`
Created time.Time `json:"creationDate"` Created time.Time `json:"creationDate"`
Modified time.Time `json:"modificationDate"` Modified time.Time `json:"modificationDate"`
Completed time.Time `json:"completionDate"`
Invoice string `json:"invoiceNumber"` Invoice string `json:"invoiceNumber"`
Subtotal float64 `json:"subtotal,omitempty"` Subtotal float64 `json:"subtotal,omitempty"`
Currency string `json:"currency,omitempty"` Currency string `json:"currency,omitempty"`
Total float64 `json:"grandTotal,omitempty"` Total float64 `json:"grandTotal,omitempty"`
TotalTaxable float64 `json:"taxableTotal,omitempty"`
TotalTaxes float64 `json:"taxesTotal,omitempty"`
Status string `json:"status"` Status string `json:"status"`
TotalWeight float64 `json:"totalWeight"` TotalWeight float64 `json:"totalWeight"`
ShippingAddress SnipcartShippingAddress `json:"shippingAddress,omitempty"` ShippingAddressSameAsBilling bool `json:"shippingAddressSameAsBilling,omitempty"`
ShippingAddress Address `json:"shippingAddress,omitempty"`
Name string `json:"shippingAddressName,omitempty"` Name string `json:"shippingAddressName,omitempty"`
Company string `json:"shippingAddressCompanyName,omitempty"` Company string `json:"shippingAddressCompanyName,omitempty"`
Address1 string `json:"shippingAddressAddress1,omitempty"` Address1 string `json:"shippingAddressAddress1,omitempty"`
@@ -77,11 +104,15 @@ type SnipcartOrder struct {
ShippingProvider string `json:"shippingProvider,omitempty"` ShippingProvider string `json:"shippingProvider,omitempty"`
ShippingMethod string `json:"shippingMethod,omitempty"` ShippingMethod string `json:"shippingMethod,omitempty"`
ShippingRateId string `json:"shippingRateUserDefinedId,omitempty"` ShippingRateId string `json:"shippingRateUserDefinedId,omitempty"`
Items []SnipcartItem `json:"items"` Discounts []Discount `json:"discounts,omitempty"`
SavedAmount float64 `json:"savedAmount,omitempty"`
TotalRebateRate float64 `json:"totalRebateRate,omitempty"`
Items []Item `json:"items"`
Taxes []OrderTax `json:"taxes,omitempty"`
Metadata any `json:"metadata"` Metadata any `json:"metadata"`
} }
type SnipcartOrderUpdate struct { type OrderUpdate struct {
Status OrderStatus `json:"status"` Status OrderStatus `json:"status"`
PaymentStatus string `json:"paymentStatus,omitempty"` PaymentStatus string `json:"paymentStatus,omitempty"`
TrackingNumber string `json:"trackingNumber,omitempty"` TrackingNumber string `json:"trackingNumber,omitempty"`
@@ -90,25 +121,20 @@ type SnipcartOrderUpdate struct {
Metadata any `json:"metadata,omitempty"` Metadata any `json:"metadata,omitempty"`
} }
type SnipcartOrders struct { type Orders struct {
TotalItems int TotalItems int
Items []SnipcartOrder Offest int
Limit int
Items []Order
} }
type SnipcartTax struct { type Notification struct {
Name string `json:"name"`
Amount float64 `json:"amount"`
NumberForInvoice string `json:"numberForInvoice"`
Rate float64 `json:"rate"`
}
type SnipcartNotification struct {
Type NotificationType `json:"type"` Type NotificationType `json:"type"`
DeliveryMethod string `json:"deliveryMethod"` DeliveryMethod string `json:"deliveryMethod"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
} }
type SnipcartNotificationResponse struct { type NotificationResponse struct {
Id string `json:"id"` Id string `json:"id"`
Created time.Time `json:"creationDate"` Created time.Time `json:"creationDate"`
Type NotificationType `json:"type"` Type NotificationType `json:"type"`
@@ -119,14 +145,63 @@ type SnipcartNotificationResponse struct {
SentOn time.Time `json:"sentOn"` SentOn time.Time `json:"sentOn"`
} }
func NewClient(snipcartApiKey string) Client { type ProductVariant struct {
return Client{ Stock int `json:"stock"`
SnipcartKey: snipcartApiKey, Variation []any `json:"variation"`
AllowBackorder bool `json:"allowOutOfStockPurchases"`
}
type ProductCustomField struct {
Name string `json:"name"`
Placeholder string `json:"placeholder"`
DisplayValue string `json:"displayValue"`
Type string `json:"type"`
Options string `json:"options"`
Required bool `json:"required"`
Value string `json:"value"`
Operation float64 `json:"operation"`
OptionsArray []string `json:"optionsArray"`
}
type Product struct {
Token string `json:"id"`
Id string `json:"userDefinedId"`
Name string `json:"name"`
Price float64 `json:"price"`
Url string `json:"url"`
Image string `json:"image"`
Description string `json:"description"`
Archived bool `json:"archived"`
Stock int `json:"stock"`
TotalStock int `json:"totalStock"`
AllowBackorder bool `json:"allowOutOfStockPurchases"`
CustomFields []ProductCustomField `json:"customFields"`
Variants []ProductVariant `json:"variants"`
}
type ProductsResponse struct {
Keywords string `json:"keywords"`
UserDefinedId string `json:"userDefinedId"`
Archived bool `json:"archived"`
From time.Time `json:"from"`
To time.Time `json:"to"`
OrderBy string `json:"orderBy"`
Paginated bool `json:"hasMoreResults"`
TotalItems int `json:"totalItems"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Sort []any `json:"sort"`
Items []Product `json:"items"`
}
func NewClient(snipcartApiKey string) *Client {
return &Client{
Key: snipcartApiKey,
AuthBase64: base64.StdEncoding.EncodeToString([]byte(snipcartApiKey + ":")), AuthBase64: base64.StdEncoding.EncodeToString([]byte(snipcartApiKey + ":")),
} }
} }
func (s *Client) GetOrder(token string) (*SnipcartOrder, error) { func (s *Client) GetOrder(token string) (*Order, error) {
response, err := helper.Get(orderUri+"/"+token, "Basic", s.AuthBase64, nil) response, err := helper.Get(orderUri+"/"+token, "Basic", s.AuthBase64, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -137,7 +212,7 @@ func (s *Client) GetOrder(token string) (*SnipcartOrder, error) {
defer response.Body.Close() defer response.Body.Close()
var order SnipcartOrder var order Order
err = json.NewDecoder(response.Body).Decode(&order) err = json.NewDecoder(response.Body).Decode(&order)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -146,7 +221,27 @@ func (s *Client) GetOrder(token string) (*SnipcartOrder, error) {
return &order, nil return &order, nil
} }
func (s *Client) GetOrders(queries map[string]string) (*SnipcartOrders, error) { func (s *Client) GetOrderNotifications(token string) (*OrderNotifications, error) {
response, err := helper.Get(orderUri+"/"+token+"/"+orderNotificationPath, "Basic", s.AuthBase64, nil)
if err != nil {
return nil, err
}
if response.StatusCode < 200 && response.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected response received: %s", response.Status)
}
defer response.Body.Close()
var notifications OrderNotifications
err = json.NewDecoder(response.Body).Decode(&notifications)
if err != nil {
return nil, err
}
return &notifications, nil
}
func (s *Client) GetOrders(queries map[string]string) (*Orders, error) {
response, err := helper.Get(orderUri, "Basic", s.AuthBase64, queries) response, err := helper.Get(orderUri, "Basic", s.AuthBase64, queries)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -157,7 +252,7 @@ func (s *Client) GetOrders(queries map[string]string) (*SnipcartOrders, error) {
defer response.Body.Close() defer response.Body.Close()
var orders SnipcartOrders var orders Orders
err = json.NewDecoder(response.Body).Decode(&orders) err = json.NewDecoder(response.Body).Decode(&orders)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -166,7 +261,7 @@ func (s *Client) GetOrders(queries map[string]string) (*SnipcartOrders, error) {
return &orders, nil return &orders, nil
} }
func (s *Client) GetOrdersByStatus(status OrderStatus) (*SnipcartOrders, error) { func (s *Client) GetOrdersByStatus(status OrderStatus) (*Orders, error) {
if status == "" { if status == "" {
return nil, errors.New("status is not set") return nil, errors.New("status is not set")
} }
@@ -174,7 +269,7 @@ func (s *Client) GetOrdersByStatus(status OrderStatus) (*SnipcartOrders, error)
return s.GetOrders(map[string]string{"status": string(status)}) return s.GetOrders(map[string]string{"status": string(status)})
} }
func (o *SnipcartOrder) TokenPNGBase64() (string, error) { func (o *Order) TokenPNGBase64() (string, error) {
img, err := qrcode.Encode("order:"+o.Token, qrcode.Medium, 128) img, err := qrcode.Encode("order:"+o.Token, qrcode.Medium, 128)
if err != nil { if err != nil {
return "", err return "", err
@@ -183,7 +278,7 @@ func (o *SnipcartOrder) TokenPNGBase64() (string, error) {
return base64.StdEncoding.EncodeToString(img), nil return base64.StdEncoding.EncodeToString(img), nil
} }
func (s *Client) UpdateOrder(token string, orderUpdate *SnipcartOrderUpdate) (*SnipcartOrder, error) { func (s *Client) UpdateOrder(token string, orderUpdate *OrderUpdate) (*Order, error) {
response, err := helper.Put(orderUri+"/"+token, "Basic", s.AuthBase64, orderUpdate) response, err := helper.Put(orderUri+"/"+token, "Basic", s.AuthBase64, orderUpdate)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -194,7 +289,7 @@ func (s *Client) UpdateOrder(token string, orderUpdate *SnipcartOrderUpdate) (*S
defer response.Body.Close() defer response.Body.Close()
var responseOrder SnipcartOrder var responseOrder Order
err = json.NewDecoder(response.Body).Decode(&responseOrder) err = json.NewDecoder(response.Body).Decode(&responseOrder)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -203,7 +298,7 @@ func (s *Client) UpdateOrder(token string, orderUpdate *SnipcartOrderUpdate) (*S
return &responseOrder, nil return &responseOrder, nil
} }
func (s *Client) SendNotification(token string, notification *SnipcartNotification) (*SnipcartNotificationResponse, error) { func (s *Client) SendNotification(token string, notification *Notification) (*NotificationResponse, error) {
response, err := helper.Post(orderUri+"/"+token+"/notifications", "Basic", s.AuthBase64, notification) response, err := helper.Post(orderUri+"/"+token+"/notifications", "Basic", s.AuthBase64, notification)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -211,7 +306,7 @@ func (s *Client) SendNotification(token string, notification *SnipcartNotificati
defer response.Body.Close() defer response.Body.Close()
var responseNotification SnipcartNotificationResponse var responseNotification NotificationResponse
err = json.NewDecoder(response.Body).Decode(&responseNotification) err = json.NewDecoder(response.Body).Decode(&responseNotification)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -220,26 +315,46 @@ func (s *Client) SendNotification(token string, notification *SnipcartNotificati
return &responseNotification, nil return &responseNotification, nil
} }
func (s *Client) ValidateWebhook(token string) error { func (s *Client) GetProducts(queries map[string]string) (*ProductsResponse, error) {
validateRequest, err := http.NewRequest("GET", validationUri+token, nil) response, err := helper.Get(productsUri, "Basic", s.AuthBase64, queries)
if err != nil { if err != nil {
return err return nil, err
}
if response.StatusCode < 200 && response.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected response received: %s", response.Status)
} }
client := &http.Client{} defer response.Body.Close()
auth := base64.StdEncoding.EncodeToString([]byte(s.SnipcartKey + ":")) var products ProductsResponse
validateRequest.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth)) err = json.NewDecoder(response.Body).Decode(&products)
validateRequest.Header.Set("Accept", "application/json")
validateResponse, err := client.Do(validateRequest)
if err != nil { if err != nil {
return fmt.Errorf("error validating webhook: %s", err.Error()) return nil, err
} }
if validateResponse.StatusCode < 200 || validateResponse.StatusCode >= 300 { return &products, nil
return fmt.Errorf("non-2XX status code for validating webhook: %d", validateResponse.StatusCode)
} }
return nil func (s *Client) GetProductById(id string) (*Product, error) {
response, err := helper.Get(productsUri, "Basic", s.AuthBase64, map[string]string{"userDefinedId": id})
if err != nil {
return nil, err
}
if response.StatusCode < 200 && response.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected response received: %s", response.Status)
}
defer response.Body.Close()
var products ProductsResponse
err = json.NewDecoder(response.Body).Decode(&products)
if err != nil {
return nil, err
}
if len(products.Items) < 1 {
return nil, fmt.Errorf("no products with id '%s'", id)
}
return &products.Items[0], nil
} }

View File

@@ -1,21 +1,86 @@
package snipcart package snipcart
type SnipcartShippingAddress struct { import (
FullName string `json:"fullName"` "encoding/base64"
FirstName string `json:"firstName"` "fmt"
Name string `json:"name"` "net/http"
Company string `json:"company"` )
Address1 string `json:"address1"`
Address2 string `json:"address2"` type ShippingError struct {
FullAddress string `json:"fullAddress"` Key string `json:"key"`
City string `json:"city"` Message string `json:"message"`
Country string `json:"country"`
PostalCode string `json:"postalCode"`
Province string `json:"province"`
Phone string `json:"phone"`
VatNumber string `json:"vatNumber,omitempty"`
} }
type SnipcartWebhookTaxResponse struct { type ShippingErrors struct {
Taxes []SnipcartTax `json:"taxes"` Errors []ShippingError `json:"errors"`
}
type TaxShippingInfo struct {
Fees float64 `json:"fees"`
Method string `json:"method"`
}
type TaxContent struct {
Created int `json:"creationDate"`
Modified int `json:"modificationDate"`
Token string `json:"token"`
Email string `json:"email"`
ShipToBillingAddress bool `json:"shipToBillingAddress"`
BillingAddress Address `json:"billingAddress"`
ShippingAddress Address `json:"shippingAddress"`
InvoiceNumber string `json:"invoiceNumber"`
ShippingInformation TaxShippingInfo `json:"shippingInformation"`
Items []Item `json:"items"`
Discounts []any `json:"discounts"`
CustomFields []CustomField `json:"customFields"`
Plans []any `json:"plans"`
Refunds []any `json:"refunds"`
Taxes []any `json:"taxes"`
Currency string `json:"currency"`
Total float64 `json:"total"`
DiscountsTotal float64 `json:"discountsTotal"`
ItemsTotal float64 `json:"itemsTotal"`
TaxesTotal float64 `json:"taxesTotal"`
PlansTotal float64 `json:"plansTotal"`
TaxProvider any `json:"taxProvider"`
Metadata any `json:"metadata"`
}
type TaxWebhook struct {
Content TaxContent `json:"content"`
}
type Tax struct {
Name string `json:"name"`
Amount float64 `json:"amount"`
NumberForInvoice string `json:"numberForInvoice"`
Rate float64 `json:"rate"`
}
type TaxResponse struct {
Taxes []Tax `json:"taxes"`
}
func (s *Client) ValidateWebhook(token string) error {
validateRequest, err := http.NewRequest("GET", validationUri+token, nil)
if err != nil {
return err
}
client := &http.Client{}
auth := base64.StdEncoding.EncodeToString([]byte(s.Key + ":"))
validateRequest.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth))
validateRequest.Header.Set("Accept", "application/json")
validateResponse, err := client.Do(validateRequest)
if err != nil {
return fmt.Errorf("error validating webhook: %s", err.Error())
}
if validateResponse.StatusCode < 200 || validateResponse.StatusCode >= 300 {
return fmt.Errorf("non-2XX status code for validating webhook: %d", validateResponse.StatusCode)
}
return nil
} }