spree_square 0.1.1 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +21 -0
- data/app/controllers/spree_square/webhooks_controller.rb +9 -1
- data/app/models/spree/line_item_decorator.rb +11 -0
- data/app/models/spree_square/credential.rb +7 -0
- data/app/models/spree_square/webhook_event.rb +5 -0
- data/config/brakeman.ignore +28 -0
- data/config/initializers/spree_admin_square_tables.rb +11 -2
- data/db/migrate/20260809140001_create_spree_square_webhook_events.rb +11 -1
- data/db/migrate/20260810180001_create_spree_square_credentials.rb +8 -1
- data/lib/spree_square/version.rb +1 -1
- data/lib/tasks/spree_square_demo.rake +39 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 199628697d1e2ef689462c4e4895ec7a90a271e5663cb3de8777533b15cdee50
|
|
4
|
+
data.tar.gz: 4d0137620c684345e4c4fa32bc8b75381d45e4992f773174a60ea0009ab4d55b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96d1f3c46e20844a731be1d12a1616b2cd9f06d1be0160122615f491b5fc40ce6a0e865cb14eeb373c93b1c1e3f5e8fa460d0933c978f93fcc380d53576aabc7
|
|
7
|
+
data.tar.gz: 072bdfa3d7dee63493118d655ffaaa3df3e55cf9dc4dcddf39b6cb006057ef52218a2851f6444a2ea461de5bcaab3ef8c82edc36e8a589501ffa20defbe592df
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.2
|
|
6
|
+
|
|
7
|
+
- Two new demo menu items (a build-your-own bowl with stacked modifier lists, a combo meal with
|
|
8
|
+
entree/drink/side) with real Square modifier lists — the first end-to-end proof the modifier
|
|
9
|
+
system renders against real Square catalog data, not just specs.
|
|
10
|
+
- Fixed a real bug: removing a modifier-bearing line item from the cart raised a foreign-key
|
|
11
|
+
violation (`spree_square_line_item_modifiers` had no `dependent: :destroy`).
|
|
12
|
+
- Fixed a real bug: the Square Orders / Square Webhooks admin pages 500'd
|
|
13
|
+
(`undefined method 'new_admin_square_order_mapping_url'`) the moment either table was empty —
|
|
14
|
+
`Spree.admin.tables.register` defaults to expecting a `:new` route that doesn't exist on these
|
|
15
|
+
read-only, index-only resources. Added `new_resource: false` to both.
|
|
16
|
+
- Fixed a real bug: installing on MySQL couldn't even run migrations — MySQL rejects a literal
|
|
17
|
+
`DEFAULT` value on `JSON`/`TEXT`/`BLOB` columns outright, which canceled every migration after
|
|
18
|
+
`CreateSpreeSquareWebhookEvents`/`CreateSpreeSquareCredentials` in migration order. Moved both
|
|
19
|
+
defaults (`WebhookEvent#payload`, `Credential#scopes`) to the model layer instead, verified
|
|
20
|
+
against a real MySQL 8.0 database.
|
|
21
|
+
- Fixed a Brakeman warning: `WebhooksController` never actually had `protect_from_forgery`
|
|
22
|
+
configured (it doesn't inherit the host app's `ApplicationController`). Added it explicitly
|
|
23
|
+
with `:null_session` — correct for a signature-verified webhook endpoint with no session to
|
|
24
|
+
protect; `:exception` would break every real webhook delivery.
|
|
25
|
+
|
|
5
26
|
## 0.1.1
|
|
6
27
|
|
|
7
28
|
- OAuth now requests `PAYMENTS_READ` in addition to the existing scopes. Without it, reading
|
|
@@ -4,7 +4,15 @@ module SpreeSquare
|
|
|
4
4
|
# work synchronously: verify, record, ack, hand off to a job. Square treats
|
|
5
5
|
# a slow or non-2xx response as a delivery failure and retries.
|
|
6
6
|
class WebhooksController < ActionController::Base
|
|
7
|
-
skip_before_action :verify_authenticity_token
|
|
7
|
+
# Explicit, not just `skip_before_action :verify_authenticity_token` —
|
|
8
|
+
# this controller doesn't inherit the host app's ApplicationController
|
|
9
|
+
# (which is where `protect_from_forgery` normally gets declared), so
|
|
10
|
+
# Brakeman's ForgerySetting check correctly flags it as never actually
|
|
11
|
+
# configured either way. `:null_session` degrades a forged/missing
|
|
12
|
+
# token to an empty session instead of raising — appropriate here since
|
|
13
|
+
# this endpoint is Square-signature-verified, not session-authenticated,
|
|
14
|
+
# so there's no session to protect in the first place.
|
|
15
|
+
protect_from_forgery with: :null_session
|
|
8
16
|
|
|
9
17
|
def create
|
|
10
18
|
raw_body = request.raw_post
|
|
@@ -5,6 +5,17 @@ module Spree
|
|
|
5
5
|
# SpreeSquare-prefixed one) means nothing ever triggers loading this file
|
|
6
6
|
# at all, silently, since nothing references the name Zeitwerk expects.
|
|
7
7
|
module LineItemDecorator
|
|
8
|
+
def self.prepended(base)
|
|
9
|
+
# No `dependent: :destroy` here originally meant removing a line item
|
|
10
|
+
# that had modifier selections hit a foreign-key violation instead of
|
|
11
|
+
# actually removing it (spree_square_line_item_modifiers.line_item_id
|
|
12
|
+
# has no ON DELETE behavior beyond Postgres's RESTRICT default) — only
|
|
13
|
+
# surfaced once a real modifier-bearing item was added to a cart and
|
|
14
|
+
# then removed, which nothing exercised before.
|
|
15
|
+
base.has_many :square_line_item_modifiers, class_name: 'SpreeSquare::LineItemModifier',
|
|
16
|
+
foreign_key: 'line_item_id', dependent: :destroy
|
|
17
|
+
end
|
|
18
|
+
|
|
8
19
|
# Transient carrier for selected modifier ids from add-to-cart through to
|
|
9
20
|
# SpreeSquare::Cart::AddItem, which reads it right after `super` to build
|
|
10
21
|
# the persistent LineItemModifier snapshot rows. Never persisted itself —
|
|
@@ -17,6 +17,13 @@ module SpreeSquare
|
|
|
17
17
|
|
|
18
18
|
belongs_to :store, class_name: 'Spree::Store'
|
|
19
19
|
|
|
20
|
+
# Ruby-level, not a DB-level `default: []` on the migration — MySQL
|
|
21
|
+
# rejects a literal DEFAULT on a JSON column outright (see that
|
|
22
|
+
# migration's own comment). The :square_credential factory relies on
|
|
23
|
+
# this (never sets `scopes` explicitly), so it has to actually default
|
|
24
|
+
# to an empty array, not just avoid raising.
|
|
25
|
+
attribute :scopes, default: -> { [] }
|
|
26
|
+
|
|
20
27
|
encrypts :access_token, :refresh_token
|
|
21
28
|
|
|
22
29
|
validates :store, presence: true, uniqueness: true
|
|
@@ -5,6 +5,11 @@ module SpreeSquare
|
|
|
5
5
|
class WebhookEvent < Spree.base_class
|
|
6
6
|
self.table_name = 'spree_square_webhook_events'
|
|
7
7
|
|
|
8
|
+
# Ruby-level, not a DB-level `default: {}` on the migration — MySQL
|
|
9
|
+
# rejects a literal DEFAULT on a JSON column outright (see that
|
|
10
|
+
# migration's own comment). This works identically on every adapter.
|
|
11
|
+
attribute :payload, default: -> { {} }
|
|
12
|
+
|
|
8
13
|
validates :square_event_id, presence: true, uniqueness: true
|
|
9
14
|
validates :event_type, presence: true
|
|
10
15
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ignored_warnings": [
|
|
3
|
+
{
|
|
4
|
+
"warning_type": "Cross-Site Request Forgery",
|
|
5
|
+
"warning_code": 86,
|
|
6
|
+
"fingerprint": "1bed78a0df37b0520a169a67b62ba7df8b4af42e6f12844ac923bfde7958fc14",
|
|
7
|
+
"check_name": "ForgerySetting",
|
|
8
|
+
"message": "`protect_from_forgery` should be configured with `with: :exception`",
|
|
9
|
+
"file": "app/controllers/spree_square/webhooks_controller.rb",
|
|
10
|
+
"line": 15,
|
|
11
|
+
"link": "https://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
|
|
12
|
+
"code": "protect_from_forgery(:with => :null_session)",
|
|
13
|
+
"render_path": null,
|
|
14
|
+
"location": {
|
|
15
|
+
"type": "controller",
|
|
16
|
+
"controller": "SpreeSquare::WebhooksController"
|
|
17
|
+
},
|
|
18
|
+
"user_input": null,
|
|
19
|
+
"confidence": "Medium",
|
|
20
|
+
"cwe_id": [
|
|
21
|
+
352
|
|
22
|
+
],
|
|
23
|
+
"note": "Intentional, not a false positive to fix: this is a webhook endpoint authenticated by Square's HMAC signature (SpreeSquare::WebhookVerifier), not by a Rails session, so there's no session-based state for CSRF to protect in the first place. `:exception` (Brakeman's preferred default) would make every legitimate webhook POST raise ActionController::InvalidAuthenticityToken, since Square never sends a Rails CSRF token — that would break the endpoint entirely, not harden it. `:null_session` is the correct, standard Rails pattern for an unauthenticated-by-session API/webhook endpoint."
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"updated": "2026-08-13 23:32:22 +0530",
|
|
27
|
+
"brakeman_version": "8.0.5"
|
|
28
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
Rails.application.config.after_initialize do
|
|
2
|
-
|
|
2
|
+
# new_resource: false — read-only support/diagnostic tables (no
|
|
3
|
+
# `:new`/`:create` route exists; only: [:index] in config/routes.rb). The
|
|
4
|
+
# default (true) crashes with a routing error the moment the table is
|
|
5
|
+
# ever empty, because the "no resource found" empty-state partial builds
|
|
6
|
+
# a `new_object_url` link unconditionally unless told not to. Found live
|
|
7
|
+
# via the sibling spree_doordash gem's own admin pages, which hit the
|
|
8
|
+
# same bug and fixed it in spree_admin_doordash_tables.rb.
|
|
9
|
+
Spree.admin.tables.register(:square_order_mappings, model_class: SpreeSquare::OrderMapping,
|
|
10
|
+
search_param: :square_order_id_cont, new_resource: false)
|
|
3
11
|
|
|
4
12
|
Spree.admin.tables.square_order_mappings.add :order_number,
|
|
5
13
|
label: :order,
|
|
@@ -42,7 +50,8 @@ Rails.application.config.after_initialize do
|
|
|
42
50
|
default: true,
|
|
43
51
|
position: 50
|
|
44
52
|
|
|
45
|
-
Spree.admin.tables.register(:square_webhook_events, model_class: SpreeSquare::WebhookEvent,
|
|
53
|
+
Spree.admin.tables.register(:square_webhook_events, model_class: SpreeSquare::WebhookEvent,
|
|
54
|
+
search_param: :event_type_cont, new_resource: false)
|
|
46
55
|
|
|
47
56
|
Spree.admin.tables.square_webhook_events.add :event_type,
|
|
48
57
|
label: :event_type,
|
|
@@ -12,7 +12,17 @@ class CreateSpreeSquareWebhookEvents < ActiveRecord::Migration[8.1]
|
|
|
12
12
|
# JSON querying — application code only ever reads/writes it as a
|
|
13
13
|
# plain Ruby hash, so the Postgres jsonb-vs-json performance distinction
|
|
14
14
|
# doesn't apply.
|
|
15
|
-
|
|
15
|
+
#
|
|
16
|
+
# No `default: {}` here — MySQL rejects a literal DEFAULT on
|
|
17
|
+
# BLOB/TEXT/GEOMETRY/JSON columns outright ("BLOB, TEXT, GEOMETRY or
|
|
18
|
+
# JSON column 'payload' can't have a default value"), which silently
|
|
19
|
+
# canceled every migration after this one in CI's MySQL job — every
|
|
20
|
+
# spree_square_* table after this one in migration order was just
|
|
21
|
+
# missing. Found via a real MySQL CI failure, not from docs. The
|
|
22
|
+
# default now lives on the model instead (see WebhookEvent's own
|
|
23
|
+
# `attribute :payload, default: -> { {} }`), which works identically
|
|
24
|
+
# across every adapter.
|
|
25
|
+
t.json :payload, null: false
|
|
16
26
|
t.datetime :processed_at
|
|
17
27
|
t.string :status, null: false, default: 'pending' # pending, processed, failed
|
|
18
28
|
t.text :error_message
|
|
@@ -18,7 +18,14 @@ class CreateSpreeSquareCredentials < ActiveRecord::Migration[8.1]
|
|
|
18
18
|
t.datetime :refresh_token_expires_at
|
|
19
19
|
# `t.json`, not `t.jsonb`/`array: true` — the extension's own dummy
|
|
20
20
|
# app (spec/dummy) runs on SQLite, which supports neither.
|
|
21
|
-
|
|
21
|
+
#
|
|
22
|
+
# No `default: []` here — MySQL rejects a literal DEFAULT on JSON
|
|
23
|
+
# columns entirely, which canceled every migration after this one in
|
|
24
|
+
# CI's MySQL job. Default now lives on the model instead (see
|
|
25
|
+
# Credential's own `attribute :scopes, default: -> { [] }`) — the
|
|
26
|
+
# :square_credential factory relies on this default (never sets
|
|
27
|
+
# `scopes` explicitly), so this had to move, not just disappear.
|
|
28
|
+
t.json :scopes, null: false
|
|
22
29
|
|
|
23
30
|
t.timestamps
|
|
24
31
|
end
|
data/lib/spree_square/version.rb
CHANGED
|
@@ -59,6 +59,42 @@ namespace :spree_square do
|
|
|
59
59
|
menu.each do |category_name, items|
|
|
60
60
|
items.each do |item|
|
|
61
61
|
item_temp_id = "#item-#{item['image_slug']}"
|
|
62
|
+
|
|
63
|
+
# Modifier lists (Square has no true nested-modifier concept — a
|
|
64
|
+
# "build your own" item is just several independent modifier lists
|
|
65
|
+
# stacked on one item, each its own CatalogObject nested inline the
|
|
66
|
+
# same way ITEM_VARIATION is nested under item_data.variations
|
|
67
|
+
# above). modifier_list_info is what actually attaches a list to
|
|
68
|
+
# this item; the MODIFIER_LIST objects themselves go in the same
|
|
69
|
+
# top-level `objects` array as everything else in the batch.
|
|
70
|
+
modifier_list_info = Array(item['modifier_lists']).map.with_index do |mod_list, list_index|
|
|
71
|
+
list_temp_id = "#{item_temp_id}-modlist-#{list_index}"
|
|
72
|
+
objects << {
|
|
73
|
+
type: 'MODIFIER_LIST',
|
|
74
|
+
id: list_temp_id,
|
|
75
|
+
modifier_list_data: {
|
|
76
|
+
name: mod_list['name'],
|
|
77
|
+
selection_type: mod_list['selection_type'],
|
|
78
|
+
modifiers: mod_list['options'].map.with_index do |option, option_index|
|
|
79
|
+
{
|
|
80
|
+
type: 'MODIFIER',
|
|
81
|
+
id: "#{list_temp_id}-opt-#{option_index}",
|
|
82
|
+
modifier_data: {
|
|
83
|
+
name: option['name'],
|
|
84
|
+
price_money: { amount: option['price_cents'], currency: 'USD' }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
{
|
|
91
|
+
modifier_list_id: list_temp_id,
|
|
92
|
+
min_selected_modifiers: mod_list['min_selected'],
|
|
93
|
+
max_selected_modifiers: mod_list['max_selected'],
|
|
94
|
+
enabled: true
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
62
98
|
objects << {
|
|
63
99
|
type: 'ITEM',
|
|
64
100
|
id: item_temp_id,
|
|
@@ -82,8 +118,9 @@ namespace :spree_square do
|
|
|
82
118
|
price_money: { amount: item['price_cents'], currency: 'USD' }
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
|
-
]
|
|
86
|
-
|
|
121
|
+
],
|
|
122
|
+
modifier_list_info: modifier_list_info
|
|
123
|
+
}.compact_blank
|
|
87
124
|
}
|
|
88
125
|
end
|
|
89
126
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_square
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amit Solanki
|
|
@@ -164,6 +164,7 @@ files:
|
|
|
164
164
|
- app/views/spree/admin/square_webhook_events/index.html.erb
|
|
165
165
|
- bin/importmap
|
|
166
166
|
- bin/rails
|
|
167
|
+
- config/brakeman.ignore
|
|
167
168
|
- config/importmap.rb
|
|
168
169
|
- config/initializers/spree.rb
|
|
169
170
|
- config/initializers/spree_admin_square_navigation.rb
|