portage-ucp-bigcommerce 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +126 -0
- data/exe/portage-ucp-bigcommerce +31 -0
- data/lib/portage/ucp/bigcommerce/adapter.rb +224 -0
- data/lib/portage/ucp/bigcommerce/client.rb +89 -0
- data/lib/portage/ucp/bigcommerce/errors.rb +23 -0
- data/lib/portage/ucp/bigcommerce/mapper.rb +175 -0
- data/lib/portage/ucp/bigcommerce/version.rb +7 -0
- data/lib/portage/ucp/bigcommerce.rb +6 -0
- metadata +129 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 825aa48c9aba12a11801d6512c4400019a727fc6f0d76c4dd6073f16e7c81482
|
|
4
|
+
data.tar.gz: 8a710386fdfe7d542f6a22d8c5c3a518541f8a19a68149283eac96d77ddf440d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cd5bd467d2c454680e20e943b2a7095df5ede07fa6a9c6fbbd1a7641e7319faf2bc8386b902e7d6c2cbe0238dc858beb6aa1af7bfb7df346c46016afc96c4d73
|
|
7
|
+
data.tar.gz: a6f1a7a5cab4ba465b5602669646cbb74c86f55ed1bc0a4c8a9de4ee0f2c47fc8352f5ea7e6145ad5659e4b2c1827429815b180948fda66473873367b64fff4f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format loosely follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project is
|
|
5
|
+
pre-1.0, so APIs may still shift between minor versions.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - Unreleased
|
|
8
|
+
|
|
9
|
+
- Initial pre-release. BigCommerce adapter against the v3 Catalog/Carts/
|
|
10
|
+
Checkouts APIs and v2 Orders API.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tom Whitbread
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# portage-ucp-bigcommerce
|
|
2
|
+
|
|
3
|
+
BigCommerce adapter for [`portage-ucp`](../portage-ucp). Implements `Portage::Ucp::Adapter` against a BigCommerce store's v3 Catalog/Carts/Checkouts APIs and v2 Orders API. Generic only — no merchant-specific business logic. Plain `Net::HTTP`, no `bigcommerce_api` runtime dependency.
|
|
4
|
+
|
|
5
|
+
## What it covers
|
|
6
|
+
|
|
7
|
+
| UCP capability | Backing BigCommerce API | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `dev.ucp.shopping.catalog` | v3 Catalog | `search_catalog`, `get_product` |
|
|
10
|
+
| `dev.ucp.shopping.cart` | v3 Carts | `get_cart`, `create_cart`, `update_cart`, `cancel_cart` |
|
|
11
|
+
| `dev.ucp.shopping.checkout` | v3 Checkouts (shares its cart's id), plus Orders + Payments to complete | `create_checkout`, `get_checkout`, `update_checkout`, `complete_checkout`, `cancel_checkout` |
|
|
12
|
+
| `dev.ucp.shopping.order` | v2 Orders | `get_order` |
|
|
13
|
+
| `dev.ucp.shopping.identity` | — | not implemented; BigCommerce's storefront Customer Login API is a separate concern from the store-owner API account used here |
|
|
14
|
+
|
|
15
|
+
Unlike Shopify/WooCommerce, BigCommerce's Checkout **is** a distinct resource from its Cart — but they share the same id, created together by a single `POST /carts` call. `update_cart`/`update_checkout` are full-replacement: the Carts API has no atomic "replace all lines" endpoint, so the adapter adds the desired lines *before* removing the old ones — BigCommerce auto-deletes a cart when its last remaining line item is removed, so removing old lines first would destroy the cart the call is supposed to be updating. `cancel_cart` deletes the cart resource outright (BigCommerce is the only one of the three adapters whose Carts API actually supports this). Mutating methods dedup by `idempotency_key` in-process so a dropped-connection retry can't double-charge.
|
|
16
|
+
|
|
17
|
+
Unlike Shopify/Wix, there's no `AccessTokenFetcher` — a BigCommerce API account (Settings → API → Create API Account) hands back a static `client_id`/`access_token` pair with no expiry, same posture as `portage-ucp-woocommerce`. The separate OAuth authorization-code flow BigCommerce uses for public Marketplace apps is a different concern, out of scope here.
|
|
18
|
+
|
|
19
|
+
## ⚠️ Unverified against a live store
|
|
20
|
+
|
|
21
|
+
Built from BigCommerce's documented REST API shapes, not run against a real store yet. Before relying on this in production, confirm:
|
|
22
|
+
|
|
23
|
+
- **`#complete_checkout`** — creates an order from the checkout, mints a single-order Payment Access Token, then submits `payment_token` as a tokenized instrument to the separate Payments API (`payments.bigcommerce.com`). The exact `payment_instrument` shape a given gateway expects (`payment_gateway_id:`) is gateway-specific and hasn't been confirmed against a live store with a real gateway installed.
|
|
24
|
+
- **Cart/Checkout line item price fields** — `Mapper.cart_line_item` assumes plain decimal fields (`sale_price`, `extended_sale_price`); some BigCommerce API surfaces instead nest money under a `{value:, currency:}` sub-object.
|
|
25
|
+
- **`Order#permalink_url`** — built from BigCommerce's documented storefront order-status route, not confirmed against a live storefront theme.
|
|
26
|
+
- **Order fulfillment** — BigCommerce core has no per-line-item fulfillment tracking, so every order line is given the same coarse status derived from the order's own `status_id`, not a real per-line signal.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# Gemfile
|
|
32
|
+
gem "portage-ucp-bigcommerce"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bundle install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Setup
|
|
40
|
+
|
|
41
|
+
You need your store hash (from the BigCommerce control panel URL or API path), and a client_id/access_token pair from an API account (Settings → API → Create API Account — grant the scopes you need for Products/Carts/Checkouts/Orders), your store's currency (the Catalog product resource doesn't return one), and — only if you'll call `complete_checkout` — the payment gateway id you want to submit orders through.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require "portage/ucp/bigcommerce"
|
|
45
|
+
|
|
46
|
+
client = Portage::Ucp::BigCommerce::Client.new(
|
|
47
|
+
store_hash: ENV.fetch("BIGCOMMERCE_STORE_HASH"),
|
|
48
|
+
client_id: ENV.fetch("BIGCOMMERCE_CLIENT_ID"),
|
|
49
|
+
access_token: ENV.fetch("BIGCOMMERCE_ACCESS_TOKEN")
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
adapter = Portage::Ucp::BigCommerce::Adapter.new(
|
|
53
|
+
client: client,
|
|
54
|
+
site_url: "https://your-shop.example.com",
|
|
55
|
+
currency: "USD",
|
|
56
|
+
payment_gateway_id: "stripe" # only required for #complete_checkout
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Catalog
|
|
64
|
+
products = adapter.search_catalog(query: "hoodie", limit: 10)
|
|
65
|
+
product = adapter.get_product(product_id: products.first.id)
|
|
66
|
+
|
|
67
|
+
# Cart — cart_id comes back from the create response
|
|
68
|
+
cart = adapter.create_cart(
|
|
69
|
+
line_items: [{ product_id: product.variants.first[:id], quantity: 2 }],
|
|
70
|
+
idempotency_key: SecureRandom.uuid
|
|
71
|
+
)
|
|
72
|
+
cart = adapter.update_cart(cart_id: cart.id, line_items: [], idempotency_key: SecureRandom.uuid) # empties cart
|
|
73
|
+
|
|
74
|
+
# Checkout — shares the same id as the cart it was created from
|
|
75
|
+
checkout = adapter.create_checkout(
|
|
76
|
+
line_items: [{ product_id: product.variants.first[:id], quantity: 1 }],
|
|
77
|
+
idempotency_key: SecureRandom.uuid
|
|
78
|
+
)
|
|
79
|
+
checkout = adapter.complete_checkout(
|
|
80
|
+
checkout_id: checkout.id,
|
|
81
|
+
payment_token: single_use_token_from_payment_handler,
|
|
82
|
+
idempotency_key: SecureRandom.uuid
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Order
|
|
86
|
+
order = adapter.get_order(order_id: checkout_order_id) # only once linked post-completion
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Wiring into portage-ucp
|
|
90
|
+
|
|
91
|
+
Drop the adapter into a `Dispatcher` (or the MCP server) the same as any other backend:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
dispatcher = Portage::Ucp::Dispatcher.new(adapter: adapter)
|
|
95
|
+
|
|
96
|
+
dispatcher.call(
|
|
97
|
+
capability: "dev.ucp.shopping.cart",
|
|
98
|
+
action: "create",
|
|
99
|
+
arguments: { line_items: [{ product_id: variant_id, quantity: 1 }], idempotency_key: SecureRandom.uuid }
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Because `link_identity` is left unoverridden, `Capability#advertised_for?` simply won't advertise `dev.ucp.shopping.identity` for this adapter — callers get an absent capability, not a 500.
|
|
104
|
+
|
|
105
|
+
## Errors
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Portage::Ucp::BigCommerce::Error # base class
|
|
109
|
+
Portage::Ucp::BigCommerce::ApiError # any non-2xx response from the Admin or Payments API
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
bundle exec rspec # tests (WebMock-stubbed, no live store needed)
|
|
116
|
+
bundle exec rubocop # lint
|
|
117
|
+
|
|
118
|
+
# verify a real store's client_id/access_token by listing one product
|
|
119
|
+
BIGCOMMERCE_STORE_HASH=abc123 \
|
|
120
|
+
BIGCOMMERCE_CLIENT_ID=... BIGCOMMERCE_ACCESS_TOKEN=... \
|
|
121
|
+
bundle exec rake bigcommerce_smoke_test
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
[MIT](LICENSE) — Copyright (c) 2026 Tom Whitbread.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Standalone MCP server over stdio. Configure the store connection via env
|
|
5
|
+
# vars, and — since this gem ships with permissive-nothing defaults (see
|
|
6
|
+
# README's "Security hooks") — wire a real Authenticator/RateLimiter/etc. via
|
|
7
|
+
# a config file loaded with PORTAGE_UCP_CONFIG, the same way `rackup -r` or
|
|
8
|
+
# Sidekiq's `-r` work — see ../examples/portage_ucp.rb for a starting point.
|
|
9
|
+
# Without one, the server still starts but rejects every mutating call.
|
|
10
|
+
#
|
|
11
|
+
# BIGCOMMERCE_PAYMENT_GATEWAY_ID is optional — only needed to call
|
|
12
|
+
# #complete_checkout.
|
|
13
|
+
|
|
14
|
+
require "portage/ucp"
|
|
15
|
+
require "portage/ucp/bigcommerce"
|
|
16
|
+
|
|
17
|
+
require ENV["PORTAGE_UCP_CONFIG"] if ENV["PORTAGE_UCP_CONFIG"]
|
|
18
|
+
|
|
19
|
+
client = Portage::Ucp::BigCommerce::Client.new(
|
|
20
|
+
store_hash: ENV.fetch("BIGCOMMERCE_STORE_HASH"),
|
|
21
|
+
client_id: ENV.fetch("BIGCOMMERCE_CLIENT_ID"),
|
|
22
|
+
access_token: ENV.fetch("BIGCOMMERCE_ACCESS_TOKEN")
|
|
23
|
+
)
|
|
24
|
+
adapter = Portage::Ucp::BigCommerce::Adapter.new(
|
|
25
|
+
client: client,
|
|
26
|
+
site_url: ENV.fetch("BIGCOMMERCE_SITE_URL"),
|
|
27
|
+
currency: ENV.fetch("BIGCOMMERCE_CURRENCY", "USD"),
|
|
28
|
+
payment_gateway_id: ENV.fetch("BIGCOMMERCE_PAYMENT_GATEWAY_ID", nil)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
Portage::Ucp::Mcp::Server.build(adapter: adapter).start
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module Portage
|
|
4
|
+
module Ucp
|
|
5
|
+
module BigCommerce
|
|
6
|
+
# Generic Portage::Ucp::Adapter over a BigCommerce store's v3 Catalog/
|
|
7
|
+
# Carts/Checkouts APIs and v2 Orders API — no merchant-specific
|
|
8
|
+
# business logic, same posture as Portage::Ucp::Shopify::Adapter and
|
|
9
|
+
# Portage::Ucp::WooCommerce::Adapter.
|
|
10
|
+
#
|
|
11
|
+
# Deliberately doesn't override `link_identity`: BigCommerce's storefront
|
|
12
|
+
# customer login (JWT-based Customer Login API) is a separate concern
|
|
13
|
+
# from the store-owner API account this gem uses, so this generic
|
|
14
|
+
# adapter leaves it unimplemented — Capability#advertised_for? simply
|
|
15
|
+
# won't advertise dev.ucp.shopping.identity for this adapter, not a 500.
|
|
16
|
+
#
|
|
17
|
+
# Catalog and Order are read through the Admin API (v3 and v2
|
|
18
|
+
# respectively — Orders has never moved off v2). Cart and Checkout are
|
|
19
|
+
# read/written through the v3 Carts/Checkouts APIs: unlike Shopify/
|
|
20
|
+
# WooCommerce, BigCommerce's Checkout *is* a distinct resource from its
|
|
21
|
+
# Cart, but shares the same id — creating a Cart implicitly creates its
|
|
22
|
+
# matching Checkout.
|
|
23
|
+
#
|
|
24
|
+
# IMPORTANT CAVEAT: #complete_checkout creates an order from the
|
|
25
|
+
# checkout, mints a single-order Payment Access Token, then submits
|
|
26
|
+
# `payment_token` as a tokenized instrument to the separate Payments
|
|
27
|
+
# API (`payments.bigcommerce.com`). The exact `payment_instrument`
|
|
28
|
+
# shape a given payment gateway expects hasn't been confirmed against
|
|
29
|
+
# a live store with a real gateway installed — same "needs confirming
|
|
30
|
+
# against a live store" posture as Portage::Ucp::Shopify::Adapter and
|
|
31
|
+
# Portage::Ucp::WooCommerce::Adapter's own payment steps.
|
|
32
|
+
class Adapter < Portage::Ucp::Adapter
|
|
33
|
+
# Neither the Carts nor the Checkouts API takes an idempotency key
|
|
34
|
+
# natively, so §9a dedup comes from Support::Idempotency's in-process
|
|
35
|
+
# table.
|
|
36
|
+
include Portage::Ucp::Support::Idempotency
|
|
37
|
+
# BigCommerce's Checkout has no native status field of its own, and
|
|
38
|
+
# v2 Orders don't link back to the Checkout that produced them —
|
|
39
|
+
# both are tracked adapter-side via Support::CheckoutState, keyed by
|
|
40
|
+
# the shared cart/checkout id.
|
|
41
|
+
include Portage::Ucp::Support::CheckoutState
|
|
42
|
+
# The v3 and v2 APIs both answer a missing resource with a 404 rather
|
|
43
|
+
# than an empty body, which UCP's reads report as nil.
|
|
44
|
+
include Portage::Ucp::Support::NotFound
|
|
45
|
+
|
|
46
|
+
def initialize(client:, site_url:, currency:, payment_gateway_id: nil)
|
|
47
|
+
super()
|
|
48
|
+
@client = client
|
|
49
|
+
@site_url = site_url.chomp("/")
|
|
50
|
+
@currency = currency
|
|
51
|
+
@payment_gateway_id = payment_gateway_id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def search_catalog(query:, limit:)
|
|
55
|
+
path = "/catalog/products?keyword=#{URI.encode_www_form_component(query)}&limit=#{limit}&include=variants"
|
|
56
|
+
@client.v3_get(path)["data"].map { |node| Mapper.product(node, currency: @currency, site_url: @site_url) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_product(product_id:)
|
|
60
|
+
nil_on_not_found do
|
|
61
|
+
node = @client.v3_get("/catalog/products/#{product_id}?include=variants")["data"]
|
|
62
|
+
node && Mapper.product(node, currency: @currency, site_url: @site_url)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def get_cart(cart_id:)
|
|
67
|
+
nil_on_not_found do
|
|
68
|
+
node = fetch_cart_node(cart_id)
|
|
69
|
+
node && Mapper.cart(node, id: cart_id)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_cart(line_items:, idempotency_key:)
|
|
74
|
+
dedup(idempotency_key) do
|
|
75
|
+
node = @client.v3_post("/carts", { line_items: cart_lines(line_items) })["data"]
|
|
76
|
+
Mapper.cart(node, id: node["id"])
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Full replacement, matching UCP's real cart semantics: the Carts API
|
|
81
|
+
# has no atomic "replace all lines" endpoint either. New lines are
|
|
82
|
+
# added *before* the old ones are removed — BigCommerce auto-deletes
|
|
83
|
+
# the whole cart when its last remaining line item is removed, so
|
|
84
|
+
# removing old lines first (when the desired list is non-empty)
|
|
85
|
+
# would destroy the cart this call is supposed to be updating.
|
|
86
|
+
def update_cart(cart_id:, line_items:, idempotency_key:)
|
|
87
|
+
dedup(idempotency_key) { Mapper.cart(replace_cart_lines(cart_id, line_items), id: cart_id) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Unlike Shopify (no cancellation mutation at all) and WooCommerce
|
|
91
|
+
# (only a session cart to clear), BigCommerce's Carts API supports
|
|
92
|
+
# deleting the cart resource outright — the closest real equivalent
|
|
93
|
+
# to cancellation available on any of the three. The returned Cart
|
|
94
|
+
# reflects that the resource is now gone (empty line_items, no
|
|
95
|
+
# currency) rather than raising: schema-valid, just not information-
|
|
96
|
+
# complete, same posture as
|
|
97
|
+
# Portage::Ucp::Shopify::Adapter#link_cart_to_order's best-effort miss.
|
|
98
|
+
def cancel_cart(cart_id:, idempotency_key:)
|
|
99
|
+
dedup(idempotency_key) do
|
|
100
|
+
delete_cart(cart_id)
|
|
101
|
+
Mapper.cart({}, id: cart_id)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def create_checkout(line_items:, idempotency_key:)
|
|
106
|
+
dedup(idempotency_key) do
|
|
107
|
+
cart_node = @client.v3_post("/carts", { line_items: cart_lines(line_items) })["data"]
|
|
108
|
+
record_checkout_status(cart_node["id"], "incomplete")
|
|
109
|
+
Mapper.checkout(fetch_checkout_node(cart_node["id"]), id: cart_node["id"], status: "incomplete")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def get_checkout(checkout_id:)
|
|
114
|
+
nil_on_not_found do
|
|
115
|
+
node = fetch_checkout_node(checkout_id)
|
|
116
|
+
node && Mapper.checkout(node, id: checkout_id, status: checkout_status(checkout_id))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Full replacement, same rationale as #update_cart — BigCommerce's
|
|
121
|
+
# Checkout shares its underlying cart with #update_cart.
|
|
122
|
+
def update_checkout(checkout_id:, line_items:, idempotency_key:)
|
|
123
|
+
dedup(idempotency_key) do
|
|
124
|
+
replace_cart_lines(checkout_id, line_items)
|
|
125
|
+
record_checkout_status(checkout_id, "incomplete")
|
|
126
|
+
Mapper.checkout(fetch_checkout_node(checkout_id), id: checkout_id, status: "incomplete")
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def complete_checkout(checkout_id:, payment_token:, idempotency_key:)
|
|
131
|
+
dedup(idempotency_key) { submit_checkout(checkout_id, payment_token) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The Checkouts API has no cancellation endpoint for an in-progress
|
|
135
|
+
# checkout either (see #cancel_cart's contrast) — this just marks the
|
|
136
|
+
# tracked status canceled without touching the underlying cart.
|
|
137
|
+
def cancel_checkout(checkout_id:, idempotency_key:)
|
|
138
|
+
dedup(idempotency_key) do
|
|
139
|
+
record_checkout_status(checkout_id, "canceled")
|
|
140
|
+
Mapper.checkout(fetch_checkout_node(checkout_id), id: checkout_id, status: "canceled")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def get_order(order_id:)
|
|
145
|
+
nil_on_not_found do
|
|
146
|
+
node = @client.v2_get("/orders/#{order_id}")
|
|
147
|
+
next nil unless node["id"]
|
|
148
|
+
|
|
149
|
+
products = @client.v2_get("/orders/#{order_id}/products")
|
|
150
|
+
Mapper.order(node, products: products, site_url: @site_url,
|
|
151
|
+
checkout_id: checkout_id_for(order_id))
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
def fetch_cart_node(cart_id)
|
|
158
|
+
include_param = "line_items.physical_items.options,line_items.digital_items.options"
|
|
159
|
+
@client.v3_get("/carts/#{cart_id}?include=#{include_param}")["data"]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def fetch_checkout_node(checkout_id)
|
|
163
|
+
@client.v3_get("/checkouts/#{checkout_id}")["data"]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def delete_cart(cart_id)
|
|
167
|
+
nil_on_not_found { @client.v3_delete("/carts/#{cart_id}") }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Maps a UCP line item request straight to `{product_id, quantity}`.
|
|
171
|
+
# A real variant-level add needs `{product_id, variant_id}` together
|
|
172
|
+
# — this generic adapter, same as
|
|
173
|
+
# Portage::Ucp::WooCommerce::Adapter#cart_lines, assumes the given id
|
|
174
|
+
# already identifies the exact sellable unit and doesn't attempt to
|
|
175
|
+
# split it back into a product/variant pair.
|
|
176
|
+
def cart_lines(line_items)
|
|
177
|
+
line_items.map { |li| { product_id: li[:product_id].to_i, quantity: li[:quantity] } }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def cart_item_ids(node)
|
|
181
|
+
((node.dig("line_items", "physical_items") || []) + (node.dig("line_items", "digital_items") || []))
|
|
182
|
+
.map { |item| item["id"] }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def replace_cart_lines(cart_id, line_items)
|
|
186
|
+
current_item_ids = cart_item_ids(fetch_cart_node(cart_id))
|
|
187
|
+
@client.v3_post("/carts/#{cart_id}/items", { line_items: cart_lines(line_items) }) unless line_items.empty?
|
|
188
|
+
current_item_ids.each { |item_id| @client.v3_delete("/carts/#{cart_id}/items/#{item_id}") }
|
|
189
|
+
return {} if line_items.empty?
|
|
190
|
+
|
|
191
|
+
fetch_cart_node(cart_id)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# See the class-level CAVEAT: `payment_instrument` uses a single
|
|
195
|
+
# configurable gateway id (`payment_gateway_id:`) rather than a
|
|
196
|
+
# confirmed gateway-specific instrument shape.
|
|
197
|
+
def submit_checkout(checkout_id, payment_token)
|
|
198
|
+
unless @payment_gateway_id
|
|
199
|
+
raise Portage::Ucp::BigCommerce::Error,
|
|
200
|
+
"no payment_gateway_id configured on this Adapter"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
checkout_node = fetch_checkout_node(checkout_id)
|
|
204
|
+
order_id = @client.v3_post("/checkouts/#{checkout_id}/orders", {}).dig("data", "id")
|
|
205
|
+
access_token = @client.v3_post("/payments/access_tokens", { order: { id: order_id } }).dig("data", "id")
|
|
206
|
+
@client.process_payment(payment_access_token: access_token, order_id: order_id,
|
|
207
|
+
payment_instrument: { type: "tokenized_instrument", token: payment_token,
|
|
208
|
+
gateway: @payment_gateway_id })
|
|
209
|
+
|
|
210
|
+
record_checkout_status(checkout_id, "completed")
|
|
211
|
+
record_order_checkout(order_id, checkout_id)
|
|
212
|
+
Mapper.checkout(checkout_node, id: checkout_id, status: "completed", order: order_confirmation(order_id))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Same order-status URL pattern as Mapper.order's `permalink_url`.
|
|
216
|
+
def order_confirmation(order_id)
|
|
217
|
+
Portage::Ucp::OrderConfirmation.new(
|
|
218
|
+
id: order_id.to_s, permalink_url: "#{@site_url}/account.php?action=order_status&order_id=#{order_id}"
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Portage
|
|
5
|
+
module Ucp
|
|
6
|
+
module BigCommerce
|
|
7
|
+
# Minimal REST client over a BigCommerce store's Admin APIs.
|
|
8
|
+
#
|
|
9
|
+
# Deliberately plain Net::HTTP, not the `bigcommerce_api` gem — a
|
|
10
|
+
# generic adapter that any Ruby app can drop in only needs a store hash
|
|
11
|
+
# and a pair of static API account credentials, no framework coupling,
|
|
12
|
+
# trivially stubbable with WebMock.
|
|
13
|
+
#
|
|
14
|
+
# Unlike Shopify/Wix, there's no Portage::Ucp::BigCommerce::AccessTokenFetcher:
|
|
15
|
+
# a store-owner-created API account (Settings → API → Create API Account)
|
|
16
|
+
# hands back a client_id/access_token pair with no expiry, same static-
|
|
17
|
+
# credential posture as Portage::Ucp::WooCommerce::Client. The separate
|
|
18
|
+
# OAuth authorization-code flow BigCommerce uses for public Marketplace
|
|
19
|
+
# apps is a different concern, out of scope here.
|
|
20
|
+
#
|
|
21
|
+
# BigCommerce splits its Admin surface across two API generations under
|
|
22
|
+
# the same host: catalog/carts/checkouts live on v3, Orders is still v2
|
|
23
|
+
# only — both are exposed here rather than picking one.
|
|
24
|
+
#
|
|
25
|
+
# Completing a checkout also requires the separate Payments API, which
|
|
26
|
+
# lives on a *different* host (`payments.bigcommerce.com`) and is
|
|
27
|
+
# authorized with a short-lived payment access token rather than the
|
|
28
|
+
# store's own API credentials — see #process_payment.
|
|
29
|
+
class Client
|
|
30
|
+
include Portage::Ucp::Support::HttpClient
|
|
31
|
+
|
|
32
|
+
def initialize(store_hash:, client_id:, access_token:)
|
|
33
|
+
@store_hash = store_hash
|
|
34
|
+
@client_id = client_id
|
|
35
|
+
@access_token = access_token
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def v2_get(path)
|
|
39
|
+
admin_request(Net::HTTP::Get, "v2", path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def v2_post(path, body = {})
|
|
43
|
+
admin_request(Net::HTTP::Post, "v2", path, body)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def v3_get(path)
|
|
47
|
+
admin_request(Net::HTTP::Get, "v3", path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def v3_post(path, body = {})
|
|
51
|
+
admin_request(Net::HTTP::Post, "v3", path, body)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def v3_put(path, body = {})
|
|
55
|
+
admin_request(Net::HTTP::Put, "v3", path, body)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def v3_delete(path)
|
|
59
|
+
admin_request(Net::HTTP::Delete, "v3", path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Authorized with the payment access token minted by
|
|
63
|
+
# POST /v3/payments/access_tokens (see Adapter#submit_checkout), not
|
|
64
|
+
# the store's own client_id/access_token — BigCommerce scopes a
|
|
65
|
+
# payment token to a single order for a single completion attempt.
|
|
66
|
+
def process_payment(payment_access_token:, order_id:, payment_instrument:)
|
|
67
|
+
json_request(
|
|
68
|
+
Net::HTTP::Post, "https://payments.bigcommerce.com/stores/#{@store_hash}/payments",
|
|
69
|
+
body: { payment: { instrument: payment_instrument }, order: { id: order_id } },
|
|
70
|
+
headers: { "Authorization" => payment_access_token,
|
|
71
|
+
"Content-Type" => "application/vnd.bc.payments.v1+json",
|
|
72
|
+
"Accept" => "application/vnd.bc.payments.v1+json" }
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def admin_request(http_method, version, path, body = nil)
|
|
79
|
+
json_request(http_method, "https://api.bigcommerce.com/stores/#{@store_hash}/#{version}#{path}",
|
|
80
|
+
body: body,
|
|
81
|
+
headers: { "X-Auth-Client" => @client_id, "X-Auth-Token" => @access_token,
|
|
82
|
+
"Accept" => "application/json" })
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def api_error_class = Portage::Ucp::BigCommerce::ApiError
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Portage
|
|
2
|
+
module Ucp
|
|
3
|
+
module BigCommerce
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Raised for any non-2xx response from the Admin API (v2 Orders, v3
|
|
7
|
+
# Catalog/Carts/Checkouts) or the Payments API. BigCommerce's error body
|
|
8
|
+
# shape isn't uniform across API generations — v3 responses carry a
|
|
9
|
+
# `title`, v2 responses (and some v3 validation failures) carry an
|
|
10
|
+
# `errors` array/hash instead — so this tries both rather than assuming
|
|
11
|
+
# one.
|
|
12
|
+
class ApiError < Error
|
|
13
|
+
include Portage::Ucp::Support::ApiError
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def detail(body)
|
|
18
|
+
body["title"] || body["errors"] || body
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
module Portage
|
|
2
|
+
module Ucp
|
|
3
|
+
module BigCommerce
|
|
4
|
+
# Converts BigCommerce Admin API response bodies (v3 Catalog/Carts/
|
|
5
|
+
# Checkouts, v2 Orders) into the protocol-layer value objects from
|
|
6
|
+
# Portage::Ucp::ValueObjects — nothing BigCommerce-shaped is allowed to
|
|
7
|
+
# leak past this file.
|
|
8
|
+
#
|
|
9
|
+
# `currency` is threaded in by the caller everywhere it's needed: the
|
|
10
|
+
# v3 Catalog product resource doesn't carry a currency field at all
|
|
11
|
+
# (it's a store-wide default currency setting), unlike Cart/Checkout
|
|
12
|
+
# responses, which carry their own `currency.code`.
|
|
13
|
+
module Mapper
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def money(amount, currency)
|
|
17
|
+
Portage::Ucp::Support::Amounts.money(amount, currency)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def minor_units(amount)
|
|
21
|
+
Portage::Ucp::Support::Amounts.decimal_to_minor(amount)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# `site_url` is only used to turn `custom_url.url` (always store-
|
|
25
|
+
# relative, e.g. "/cold-brew/") into an absolute product URL — the v3
|
|
26
|
+
# Catalog resource never returns an absolute one itself.
|
|
27
|
+
def product(node, currency:, site_url:)
|
|
28
|
+
Portage::Ucp::Product.new(
|
|
29
|
+
id: node["id"].to_s,
|
|
30
|
+
title: node["name"],
|
|
31
|
+
description: node["description"],
|
|
32
|
+
price: money(node["price"], currency),
|
|
33
|
+
available: node["availability"] != "disabled",
|
|
34
|
+
variants: variants(node, currency),
|
|
35
|
+
url: node.dig("custom_url", "url") ? "#{site_url}#{node.dig('custom_url', 'url')}" : nil
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# A product with no configured variants has no real Catalog
|
|
40
|
+
# "variants" entries at all — it's its own single implicit variant,
|
|
41
|
+
# same as Portage::Ucp::WooCommerce::Mapper.variants for a simple product.
|
|
42
|
+
def variants(node, currency)
|
|
43
|
+
nodes = node["variants"]
|
|
44
|
+
unless nodes&.any?
|
|
45
|
+
return [{ id: node["id"].to_s, title: node["name"], available: node["availability"] != "disabled",
|
|
46
|
+
price: money(node["price"], currency) }]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
nodes.map { |v| variant(v, node, currency) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A variant's own `price` is 0/null when it doesn't override the
|
|
53
|
+
# base product price — falls back to the parent product's price in
|
|
54
|
+
# that case, same "inherits unless overridden" rule BigCommerce
|
|
55
|
+
# documents for variant pricing.
|
|
56
|
+
def variant(node, parent_node, currency)
|
|
57
|
+
title = (node["option_values"] || []).map { |ov| ov["label"] }.join(" / ")
|
|
58
|
+
price = node["price"].to_f.zero? ? parent_node["price"] : node["price"]
|
|
59
|
+
{ id: node["id"].to_s, title: title, available: !node["purchasing_disabled"], price: money(price, currency) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `id:` is caller-supplied rather than read off the response body's
|
|
63
|
+
# own top-level id in every call site: a v3 Cart's `id` field is a
|
|
64
|
+
# real, stable resource id (unlike WooCommerce's headerless Cart-
|
|
65
|
+
# Token), but callers pass it explicitly anyway so Adapter can reuse
|
|
66
|
+
# this same method for both the cart-creation response (id known
|
|
67
|
+
# from the body) and a checkout's embedded `cart` sub-object (no
|
|
68
|
+
# top-level id of its own).
|
|
69
|
+
def cart(node, id:)
|
|
70
|
+
currency = node.dig("currency", "code")
|
|
71
|
+
items = (node.dig("line_items", "physical_items") || []) + (node.dig("line_items", "digital_items") || [])
|
|
72
|
+
Portage::Ucp::Cart.new(
|
|
73
|
+
id: id,
|
|
74
|
+
line_items: items.map { |n| cart_line_item(n) },
|
|
75
|
+
currency: currency,
|
|
76
|
+
totals: Portage::Ucp::Support::Totals.summary(subtotal: minor_units(node["base_amount"]),
|
|
77
|
+
total: minor_units(node["cart_amount"]))
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# `status` isn't a v3 Checkout field either — BigCommerce's Checkout
|
|
82
|
+
# is the cart plus billing/consignment/order-linkage data, not a
|
|
83
|
+
# resource with its own lifecycle enum, so the Adapter tracks status
|
|
84
|
+
# itself and passes it in, same rationale as
|
|
85
|
+
# Portage::Ucp::Shopify::Mapper.checkout and Portage::Ucp::WooCommerce::Mapper.checkout.
|
|
86
|
+
def checkout(node, id:, status:, order: nil)
|
|
87
|
+
cart_node = node["cart"] || node
|
|
88
|
+
|
|
89
|
+
Portage::Ucp::Checkout.new(
|
|
90
|
+
id: id,
|
|
91
|
+
status: status,
|
|
92
|
+
line_items: cart(cart_node, id: id).line_items,
|
|
93
|
+
currency: cart_node.dig("currency", "code"),
|
|
94
|
+
totals: checkout_totals(node, cart_node),
|
|
95
|
+
links: [],
|
|
96
|
+
order: order
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def checkout_totals(node, cart_node)
|
|
101
|
+
Portage::Ucp::Support::Totals.summary(
|
|
102
|
+
subtotal: minor_units(node["subtotal"] || cart_node["base_amount"]),
|
|
103
|
+
tax: minor_units(node["tax_total"]),
|
|
104
|
+
total: minor_units(node["grand_total"] || cart_node["cart_amount"])
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# CAVEAT: assumes physical/digital cart line items carry plain
|
|
109
|
+
# decimal price fields (`sale_price`, `extended_sale_price`), the
|
|
110
|
+
# shape BigCommerce's own docs show — not confirmed against a live
|
|
111
|
+
# cart response, which some BigCommerce API surfaces instead nest
|
|
112
|
+
# under a `{value:, currency:}` sub-object.
|
|
113
|
+
def cart_line_item(node)
|
|
114
|
+
unit_price = minor_units(node["sale_price"])
|
|
115
|
+
line_total = minor_units(node["extended_sale_price"])
|
|
116
|
+
Portage::Ucp::LineItem.new(
|
|
117
|
+
id: node["id"],
|
|
118
|
+
item: Portage::Ucp::Item.new(id: (node["variant_id"] || node["product_id"]).to_s, title: node["name"],
|
|
119
|
+
price: unit_price, image_url: node["image_url"]),
|
|
120
|
+
quantity: node["quantity"],
|
|
121
|
+
totals: Portage::Ucp::Support::Totals.line(minor_units(node["extended_list_price"]), line_total)
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# BigCommerce's fixed, store-independent order status_id enum — see
|
|
126
|
+
# https://developer.bigcommerce.com/docs/rest-management/order-statuses.
|
|
127
|
+
# Core has no per-line-item fulfillment tracking (that's a shipping-
|
|
128
|
+
# app concern), so every order line is given the same coarse status
|
|
129
|
+
# derived from the order's own top-level status, same posture as
|
|
130
|
+
# Portage::Ucp::WooCommerce::Mapper::ORDER_LINE_ITEM_STATUS.
|
|
131
|
+
ORDER_STATUS = {
|
|
132
|
+
2 => "fulfilled", 8 => "fulfilled", 10 => "fulfilled",
|
|
133
|
+
4 => "removed", 5 => "removed", 6 => "removed", 14 => "removed"
|
|
134
|
+
}.freeze
|
|
135
|
+
|
|
136
|
+
# `permalink_url` is built from BigCommerce's documented storefront
|
|
137
|
+
# order-status route — not confirmed against a live storefront theme,
|
|
138
|
+
# same "needs confirming" posture as
|
|
139
|
+
# Portage::Ucp::WooCommerce::Mapper.order's order-received URL.
|
|
140
|
+
#
|
|
141
|
+
# `products` is the separate v2 `/orders/{id}/products` response —
|
|
142
|
+
# unlike WooCommerce/Shopify, a v2 Order's own resource doesn't embed
|
|
143
|
+
# its line items, so the Adapter fetches and merges them in.
|
|
144
|
+
def order(node, products:, site_url:, checkout_id: "")
|
|
145
|
+
currency = node["currency_code"]
|
|
146
|
+
status = Portage::Ucp::Support::LineItemStatus.from_table(ORDER_STATUS, node["status_id"])
|
|
147
|
+
Portage::Ucp::Order.new(
|
|
148
|
+
id: node["id"].to_s,
|
|
149
|
+
checkout_id: checkout_id,
|
|
150
|
+
permalink_url: "#{site_url}/account.php?action=order_status&order_id=#{node['id']}",
|
|
151
|
+
line_items: products.map { |n| order_line_item(n, status) },
|
|
152
|
+
fulfillment: Portage::Ucp::Fulfillment.new,
|
|
153
|
+
currency: currency,
|
|
154
|
+
totals: Portage::Ucp::Support::Totals.summary(subtotal: minor_units(node["subtotal_ex_tax"]),
|
|
155
|
+
total: minor_units(node["total_inc_tax"]))
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def order_line_item(node, status)
|
|
160
|
+
quantity = node["quantity"]
|
|
161
|
+
fulfilled = Portage::Ucp::Support::LineItemStatus.fulfilled_quantity(status, quantity)
|
|
162
|
+
Portage::Ucp::OrderLineItem.new(
|
|
163
|
+
id: node["id"].to_s,
|
|
164
|
+
item: Portage::Ucp::Item.new(id: node["product_id"].to_s, title: node["name"],
|
|
165
|
+
price: minor_units(node["price_ex_tax"])),
|
|
166
|
+
quantity: { original: quantity, total: quantity, fulfilled: fulfilled },
|
|
167
|
+
totals: Portage::Ucp::Support::Totals.line(minor_units(node["total_ex_tax"]),
|
|
168
|
+
minor_units(node["total_inc_tax"])),
|
|
169
|
+
status: status
|
|
170
|
+
)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: portage-ucp-bigcommerce
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tom Whitbread
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: portage-ucp
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.13'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.13'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rubocop
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.88'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.88'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.24'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.24'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: yard
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.9'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.9'
|
|
83
|
+
description: Implements Portage::Ucp::Adapter against a BigCommerce store's v3 Catalog/Carts/Checkouts
|
|
84
|
+
APIs and v2 Orders API. Generic only — no merchant-specific business logic. Plain
|
|
85
|
+
Net::HTTP, no bigcommerce/api runtime dependency.
|
|
86
|
+
email:
|
|
87
|
+
executables:
|
|
88
|
+
- portage-ucp-bigcommerce
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- LICENSE
|
|
94
|
+
- README.md
|
|
95
|
+
- exe/portage-ucp-bigcommerce
|
|
96
|
+
- lib/portage/ucp/bigcommerce.rb
|
|
97
|
+
- lib/portage/ucp/bigcommerce/adapter.rb
|
|
98
|
+
- lib/portage/ucp/bigcommerce/client.rb
|
|
99
|
+
- lib/portage/ucp/bigcommerce/errors.rb
|
|
100
|
+
- lib/portage/ucp/bigcommerce/mapper.rb
|
|
101
|
+
- lib/portage/ucp/bigcommerce/version.rb
|
|
102
|
+
homepage: https://github.com/tomtom87/Portage/tree/main/portage-ucp-bigcommerce
|
|
103
|
+
licenses:
|
|
104
|
+
- MIT
|
|
105
|
+
metadata:
|
|
106
|
+
source_code_uri: https://github.com/tomtom87/Portage/tree/main/portage-ucp-bigcommerce
|
|
107
|
+
changelog_uri: https://github.com/tomtom87/Portage/blob/main/portage-ucp-bigcommerce/CHANGELOG.md
|
|
108
|
+
rubygems_mfa_required: 'true'
|
|
109
|
+
post_install_message:
|
|
110
|
+
rdoc_options: []
|
|
111
|
+
require_paths:
|
|
112
|
+
- lib
|
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.2'
|
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - ">="
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '0'
|
|
123
|
+
requirements: []
|
|
124
|
+
rubygems_version: 3.5.22
|
|
125
|
+
signing_key:
|
|
126
|
+
specification_version: 4
|
|
127
|
+
summary: BigCommerce adapter for portage-ucp — standard catalog/cart/checkout/order
|
|
128
|
+
over MCP and UCP
|
|
129
|
+
test_files: []
|