Compare commits

...

66 Commits

Author SHA1 Message Date
Bastian de Byl
623060eef4 feat!: update module path to git.debyl.io
All checks were successful
Release / release (push) Successful in 21s
BREAKING CHANGE: Module path changed from github.com/debyltech/go-snipcart
to git.debyl.io/debyltech/go-snipcart

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:59:18 -05:00
gitea-actions[bot]
891ff0daef chore(release): 0.6.0 [skip ci]
# [0.6.0](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.2...v0.6.0) (2026-01-23)

### Features

* add price, url, image, description, archived fields to Product struct ([ae0e5fc](ae0e5fc379))
2026-01-22 19:54:03 -05:00
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
Bastian de Byl
687a5df39f added ValidateWebhook(token) func to client 2023-05-03 14:20:15 -04:00
Bastian de Byl
131af89f4f moved enums to enums.go, added SendNotification, Notification structs & enums 2023-04-12 16:56:01 -04:00
Bastian de Byl
b93afeec2f added Created and Modified times to SnipcartOrder 2023-04-09 18:31:53 -04:00
Bastian de Byl
bc56b4d16a 8677r5eju added initial .drone.yml 2023-04-06 23:34:18 -04:00
Bastian de Byl
b08b64f5b2 added SnipcartTax and SnipcartWebhookTaxResponse 2023-04-06 17:59:48 -04:00
Bastian de Byl
ad93704f41 renamed Order.ShippingRate to Order.ShippingRateId 2023-04-03 21:20:22 -04:00
Bastian de Byl
b97ee3132b consolidated webhook order to just order 2023-03-29 13:24:09 -04:00
Bastian de Byl
cfdde462f4 added more order struct details 2023-03-29 13:14:30 -04:00
Bastian de Byl
e039b583e2 fixed Curreny to Currency typo 2023-03-13 00:26:11 -04:00
Bastian de Byl
26571a41fa changed ShippingAddress State to Province 2023-03-13 00:16:22 -04:00
Bastian de Byl
559a100e90 added more missing OrderEventContent fields 2023-03-13 00:14:47 -04:00
Bastian de Byl
6ff851823e added missing OrderEventContent fields 2023-03-13 00:12:11 -04:00
Bastian de Byl
db6e320b7f added webhook specific structs 2023-03-13 00:07:50 -04:00
Bastian de Byl
563e6baeda main added Metadata field to SnipcartOrder struct 2023-03-02 17:19:50 -05:00
Bastian de Byl
e1f7e99027 moved webhook to go-snipcart-webhook repo 2023-02-26 21:23:37 -05:00
Bastian de Byl
36ca8d6f83 added initial webhook main function 2023-02-26 20:36:12 -05:00
Bastian de Byl
d182b35d56 updated helper module 2023-02-26 15:26:59 -05:00
Bastian de Byl
1d5b44c7e9 renamed SnipcartProvider to Client 2023-02-26 14:41:04 -05:00
Bastian de Byl
84941b7acd corrected Price related field types 2023-02-26 14:17:31 -05:00
Bastian de Byl
28f77f63b9 added Total and Subtotal to SnipcartOrder 2023-02-26 14:16:26 -05:00
Bastian de Byl
2a97bca474 added TotalPrice to SnipcartItem 2023-02-26 14:14:52 -05:00
Bastian de Byl
2d91d1d122 corrected SnipcartItem 2023-02-26 14:11:55 -05:00
Bastian de Byl
13f482e50a fixed UpdateOrder method from downstream helper 2023-02-26 02:47:32 -05:00
Bastian de Byl
bfb6497d25 fixed UpdateOrder method 2023-02-26 02:38:14 -05:00
Bastian de Byl
82b88cbaf5 fixed data Put in UpdateOrder again with bytes buffer 2023-02-26 02:14:57 -05:00
Bastian de Byl
994cabc20e fixed data Put in UpdateOrder 2023-02-26 02:13:20 -05:00
Bastian de Byl
bde3d07c28 added unique struct for UpdateOrder 2023-02-26 02:08:36 -05:00
Bastian de Byl
0bf0abd435 fixed order pointer in UpdateOrder 2023-02-26 02:04:24 -05:00
Bastian de Byl
82106ee4e0 added UpdateOrder function, fixed response statuscode handling 2023-02-26 02:01:02 -05:00
Bastian de Byl
c1091f12b0 changed ShippingCost type to float64 in SnipcartOrder 2023-02-26 01:32:24 -05:00
Bastian de Byl
f80f35f5d5 changed dimension types in SnipcartItem struct 2023-02-26 01:31:28 -05:00
14 changed files with 660 additions and 82 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}"
}
]
]
}

28
CHANGELOG.md Normal file
View File

@@ -0,0 +1,28 @@
# [0.6.0](https://git.debyl.io/debyltech/go-snipcart/compare/v0.5.2...v0.6.0) (2026-01-23)
### Features
* add price, url, image, description, archived fields to Product struct ([ae0e5fc](https://git.debyl.io/debyltech/go-snipcart/commit/ae0e5fc37953b1645eb6bf75f6c9fb291dd0a223))
## [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)
}
}

32
example_update_order.go Normal file
View File

@@ -0,0 +1,32 @@
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)
updateOrder := snipcart.SnipcartOrderUpdate{
ShippingRateId: "b1f5a5bca34d4e9ea7a55c011b22644f;5677a809435d46cbbb5dda2485295326",
}
response, err := Client.UpdateOrder("e6e72c95-31df-4594-b9a3-8603ce3914c8", &updateOrder)
if err != nil {
log.Fatal(err)
}
for k, v := range response.Items {
log.Printf("%v: %v\n", k, v)
}
}

View File

@@ -7,10 +7,6 @@ import (
"github.com/debyltech/go-snipcart/snipcart" "github.com/debyltech/go-snipcart/snipcart"
) )
const (
configFile = "config.json"
)
func main() { func main() {
snipcartApiKey := flag.String("key", "", "Snipcart API Key") snipcartApiKey := flag.String("key", "", "Snipcart API Key")
flag.Parse() flag.Parse()
@@ -19,9 +15,9 @@ func main() {
log.Fatal("missing -key flag") log.Fatal("missing -key flag")
} }
snipcartProvider := snipcart.NewSnipcartProvider(*snipcartApiKey) Client := snipcart.NewClient(*snipcartApiKey)
response, err := snipcartProvider.GetOrdersByStatus(snipcart.Processed) response, err := Client.GetOrdersByStatus(snipcart.Processed)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@@ -0,0 +1,33 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"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.GetOrder("b35990df-c0ca-4014-94de-1caa7bd7bb51")
if err != nil {
log.Fatal(err)
}
byteResponse, err := json.Marshal(response)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(byteResponse))
}

6
go.mod
View File

@@ -1,8 +1,8 @@
module github.com/debyltech/go-snipcart module git.debyl.io/debyltech/go-snipcart
go 1.20 go 1.19
require ( require (
github.com/debyltech/go-helpers v0.0.0-20230224002154-eb55816c71ec github.com/debyltech/go-helpers v1.1.1
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"`
}

21
snipcart/enums.go Normal file
View File

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

View File

@@ -1,13 +0,0 @@
package snipcart
type OrderStatus string
const (
Processed OrderStatus = "Processed"
Disputed = "Disputed"
Shipped = "Shipped"
Delivered = "Delivered"
Pending = "Pending"
Cancelled = "Cancelled"
Dispatched = "Dispatched"
)

View File

@@ -5,85 +5,214 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"time"
helper "github.com/debyltech/go-helpers" 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"
)
var (
orderUri = apiUri + ordersPath
)
type SnipcartProvider struct {
SnipcartKey string
AuthBase64 string
Limit int
}
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 float64 `json:"quantity"` Quantity int `json:"quantity"`
TotalWeight float64 `json:"totalWeight,omitempty"` TotalWeight float64 `json:"totalWeight,omitempty"`
CustomFieldsJSON string `json:"customFieldsJson"` TotalPrice float64 `json:"totalPrice,omitempty"`
Length string `json:"length,omitempty"` CustomFields []CustomField `json:"customFields"`
Width string `json:"width,omitempty"` Length float64 `json:"length,omitempty"`
Height string `json:"height,omitempty"` Width float64 `json:"width,omitempty"`
Height float64 `json:"height,omitempty"`
Weight float64 `json:"weight,omitempty"` Weight float64 `json:"weight,omitempty"`
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"`
Modified time.Time `json:"modificationDate"`
Completed time.Time `json:"completionDate"`
Invoice string `json:"invoiceNumber"` Invoice string `json:"invoiceNumber"`
Subtotal float64 `json:"subtotal,omitempty"`
Currency string `json:"currency,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"`
Email string `json:"email"` ShippingAddressSameAsBilling bool `json:"shippingAddressSameAsBilling,omitempty"`
Name string `json:"shippingAddressName"` ShippingAddress Address `json:"shippingAddress,omitempty"`
Address1 string `json:"shippingAddressAddress1"` Name string `json:"shippingAddressName,omitempty"`
Address2 string `json:"shippingAddressAddress2"` Company string `json:"shippingAddressCompanyName,omitempty"`
City string `json:"shippingAddressCity"` Address1 string `json:"shippingAddressAddress1,omitempty"`
Province string `json:"shippingAddressProvince"` Address2 string `json:"shippingAddressAddress2,omitempty"`
Country string `json:"shippingAddressCountry"` City string `json:"shippingAddressCity,omitempty"`
PostalCode string `json:"shippingAddressPostalCode"` Province string `json:"shippingAddressProvince,omitempty"`
Phone string `json:"shippingAddressPhone"` Country string `json:"shippingAddressCountry,omitempty"`
PostalCode string `json:"shippingAddressPostalCode,omitempty"`
Phone string `json:"shippingAddressPhone,omitempty"`
Email string `json:"email,omitempty"`
TrackingNumber string `json:"trackingNumber"` TrackingNumber string `json:"trackingNumber"`
TrackingUrl string `json:"trackingUrl"` TrackingUrl string `json:"trackingUrl"`
ShippingCost string `json:"shippingFees"` ShippingCost float64 `json:"shippingFees"`
Items []SnipcartItem `json:"items"` ShippingProvider string `json:"shippingProvider,omitempty"`
ShippingMethod string `json:"shippingMethod,omitempty"`
ShippingRateId string `json:"shippingRateUserDefinedId,omitempty"`
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"`
} }
type SnipcartOrders struct { type OrderUpdate struct {
Status OrderStatus `json:"status"`
PaymentStatus string `json:"paymentStatus,omitempty"`
TrackingNumber string `json:"trackingNumber,omitempty"`
TrackingUrl string `json:"trackingUrl,omitempty"`
ShippingRateId string `json:"shippingRateUserDefinedId,omitempty"`
Metadata any `json:"metadata,omitempty"`
}
type Orders struct {
TotalItems int TotalItems int
Items []SnipcartOrder Offest int
Limit int
Items []Order
} }
func NewSnipcartProvider(snipcartApiKey string) SnipcartProvider { type Notification struct {
return SnipcartProvider{ Type NotificationType `json:"type"`
SnipcartKey: snipcartApiKey, DeliveryMethod string `json:"deliveryMethod"`
Message string `json:"message,omitempty"`
}
type NotificationResponse struct {
Id string `json:"id"`
Created time.Time `json:"creationDate"`
Type NotificationType `json:"type"`
DeliveryMethod string `json:"deliveryMethod"`
Body string `json:"body"`
Message string `json:"message"`
Subject string `json:"subject"`
SentOn time.Time `json:"sentOn"`
}
type ProductVariant struct {
Stock int `json:"stock"`
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 *SnipcartProvider) 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
} }
if response.Status != "200 OK" { if response.StatusCode < 200 && response.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected response received: %s", response.Status) return nil, fmt.Errorf("unexpected response received: %s", response.Status)
} }
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
@@ -92,18 +221,38 @@ func (s *SnipcartProvider) GetOrder(token string) (*SnipcartOrder, error) {
return &order, nil return &order, nil
} }
func (s *SnipcartProvider) GetOrders(queries map[string]string) (*SnipcartOrders, error) { func (s *Client) GetOrderNotifications(token string) (*OrderNotifications, error) {
response, err := helper.Get(orderUri, "Basic", s.AuthBase64, queries) response, err := helper.Get(orderUri+"/"+token+"/"+orderNotificationPath, "Basic", s.AuthBase64, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if response.Status != "200 OK" { if response.StatusCode < 200 && response.StatusCode >= 300 {
return nil, fmt.Errorf("unexpected response received: %s", response.Status) return nil, fmt.Errorf("unexpected response received: %s", response.Status)
} }
defer response.Body.Close() defer response.Body.Close()
var orders SnipcartOrders 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)
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 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
@@ -112,7 +261,7 @@ func (s *SnipcartProvider) GetOrders(queries map[string]string) (*SnipcartOrders
return &orders, nil return &orders, nil
} }
func (s *SnipcartProvider) 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")
} }
@@ -120,7 +269,7 @@ func (s *SnipcartProvider) GetOrdersByStatus(status OrderStatus) (*SnipcartOrder
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
@@ -128,3 +277,84 @@ 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 *OrderUpdate) (*Order, error) {
response, err := helper.Put(orderUri+"/"+token, "Basic", s.AuthBase64, orderUpdate)
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 responseOrder Order
err = json.NewDecoder(response.Body).Decode(&responseOrder)
if err != nil {
return nil, err
}
return &responseOrder, nil
}
func (s *Client) SendNotification(token string, notification *Notification) (*NotificationResponse, error) {
response, err := helper.Post(orderUri+"/"+token+"/notifications", "Basic", s.AuthBase64, notification)
if err != nil {
return nil, err
}
defer response.Body.Close()
var responseNotification NotificationResponse
err = json.NewDecoder(response.Body).Decode(&responseNotification)
if err != nil {
return nil, err
}
return &responseNotification, nil
}
func (s *Client) GetProducts(queries map[string]string) (*ProductsResponse, error) {
response, err := helper.Get(productsUri, "Basic", s.AuthBase64, queries)
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
}
return &products, 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
}

86
snipcart/webhook.go Normal file
View File

@@ -0,0 +1,86 @@
package snipcart
import (
"encoding/base64"
"fmt"
"net/http"
)
type ShippingError struct {
Key string `json:"key"`
Message string `json:"message"`
}
type ShippingErrors struct {
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
}