spree_square 0.2.0 → 0.2.1
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25d852608d74d0163f8216bfe8daaa44b0d0cff76612deb563aaee475fbf6fc7
|
|
4
|
+
data.tar.gz: d5d1b302b8713ef27a7b89a58490225b2903a3e6f263ba23efc6822983facb90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c16da8f9c347d08bc694092c3babb2bda2a96c483d1972ce83b24f578893715357f8d2fa36f6a476969b9589abab4b635e5ba73776e373a8e50073c7a3db6c20
|
|
7
|
+
data.tar.gz: 8c6681d1a46fefe6fce793f03d6f6a3a668618f3e958691dd27fbb8b8f25dae061ffebdb38daccbc57d1174d700342e9124307e7dc66916a8c55d837295c3e63
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.1
|
|
6
|
+
|
|
7
|
+
Fixes a real bug in `v0.2.0`, found running it against production traffic (not caught by specs —
|
|
8
|
+
sandbox/local dev has no registered webhook to race against): the concurrency fix for the composite
|
|
9
|
+
tax category race (see `v0.2.0`'s own changelog entry) was itself Postgres-only
|
|
10
|
+
(`pg_advisory_xact_lock`), breaking outright on the gem's own SQLite/MySQL-tested dummy app, and
|
|
11
|
+
production actually hit the race it was meant to prevent — this rake task's own `CatalogImporter.call`
|
|
12
|
+
ran concurrently with a real `catalog.version.updated` webhook Square's own `update_item_taxes` call
|
|
13
|
+
fired back mid-run, leaving 44 products correctly on one "Sales Tax" category and 1 stray product on a
|
|
14
|
+
duplicate with its own duplicate rate (same 8% amount either way — no wrong tax charged, but a real
|
|
15
|
+
data duplication). Replaced with `SpreeSquare::TaxCombination`, a real unique index raced via
|
|
16
|
+
`create_or_find_by!` — portable across every adapter this gem supports. `v0.2.0` is yanked; upgrade
|
|
17
|
+
directly to this version.
|
|
18
|
+
|
|
5
19
|
## 0.2.0
|
|
6
20
|
|
|
7
21
|
- **Sales tax, sourced from Square's own catalog tax config.** Square's Catalog API has no concept
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SpreeSquare
|
|
2
|
+
# One row per distinct combination of Square tax ids an item carries at
|
|
3
|
+
# once, keyed by `signature` (sorted, comma-joined SpreeSquare::TaxMapping
|
|
4
|
+
# ids) — the race-safe find-or-create key `create_or_find_by!` needs. See
|
|
5
|
+
# CatalogObjectMapper#composite_tax_category for why this exists: without
|
|
6
|
+
# a real unique index to race against, two concurrent imports resolving
|
|
7
|
+
# the same combination for the first time could each create their own
|
|
8
|
+
# Spree::TaxCategory.
|
|
9
|
+
class TaxCombination < Spree.base_class
|
|
10
|
+
self.table_name = 'spree_square_tax_combinations'
|
|
11
|
+
|
|
12
|
+
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
|
13
|
+
|
|
14
|
+
# Deliberately NO `uniqueness: true` here — that's a validation-layer
|
|
15
|
+
# SELECT-then-INSERT check with exactly the same race this table exists
|
|
16
|
+
# to close. `create_or_find_by!` (see CatalogObjectMapper#
|
|
17
|
+
# composite_tax_category) depends on the real DB-level unique index
|
|
18
|
+
# (the migration's `add_index ... unique: true`) raising
|
|
19
|
+
# ActiveRecord::RecordNotUnique on the losing INSERT, which is the one
|
|
20
|
+
# thing it explicitly rescues — a uniqueness validation raises
|
|
21
|
+
# RecordInvalid instead, which it does not, and defeats the whole
|
|
22
|
+
# point (confirmed live: adding this validation back made the second,
|
|
23
|
+
# non-racing call in this file's own idempotency spec raise instead of
|
|
24
|
+
# transparently finding the row).
|
|
25
|
+
validates :signature, presence: true
|
|
26
|
+
|
|
27
|
+
def self.signature_for(tax_mapping_ids)
|
|
28
|
+
tax_mapping_ids.sort.join(',')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -313,33 +313,47 @@ module SpreeSquare
|
|
|
313
313
|
# will resolve it correctly once the tax itself is known.
|
|
314
314
|
return non_taxable_category if tax_mappings.empty?
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
create_composite_tax_category!(tax_mappings)
|
|
316
|
+
category = composite_tax_category(tax_mappings)
|
|
317
|
+
ensure_tax_rates!(category, tax_mappings)
|
|
318
|
+
category
|
|
320
319
|
end
|
|
321
320
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
321
|
+
# A real bug found running this against production: the same catalog
|
|
322
|
+
# import can run concurrently more than once (this rake task's own
|
|
323
|
+
# `CatalogImporter.call` racing a real `catalog.version.updated`
|
|
324
|
+
# webhook that Square's own `update_item_taxes` call fires back mid-run
|
|
325
|
+
# — sandbox/local dev, with no registered webhook, never exercises
|
|
326
|
+
# this). Two processes each finding "no matching category yet" and
|
|
327
|
+
# independently creating one left two "Sales Tax" categories with
|
|
328
|
+
# duplicate rates. `SpreeSquare::TaxCombination#signature` is a real
|
|
329
|
+
# unique index — `create_or_find_by!` races against it exactly the way
|
|
330
|
+
# Rails intends (portable across Postgres/MySQL/SQLite, unlike a
|
|
331
|
+
# database-specific advisory lock): the losing side's block-created
|
|
332
|
+
# Spree::TaxCategory rolls back with it (create_or_find_by! wraps the
|
|
333
|
+
# attempt in its own transaction) and it transparently re-finds what
|
|
334
|
+
# the winner created instead.
|
|
335
|
+
def composite_tax_category(tax_mappings)
|
|
336
|
+
signature = SpreeSquare::TaxCombination.signature_for(tax_mappings.map(&:id))
|
|
337
|
+
|
|
338
|
+
# `create_or_find_by!`'s block runs while *building* the record, before
|
|
339
|
+
# the create attempt that its own race-safety depends on — so it can
|
|
340
|
+
# run more than once even with no real race at all (confirmed live: a
|
|
341
|
+
# second, non-concurrent call in the same process re-runs this block
|
|
342
|
+
# and only then discovers the TaxCombination already exists). The
|
|
343
|
+
# block itself must therefore be safe to run repeatedly — plain
|
|
344
|
+
# `Spree::TaxCategory.create!` isn't, since core validates `name`
|
|
345
|
+
# uniqueness and a second run raises `RecordInvalid` before this
|
|
346
|
+
# method's own race-safety ever gets a chance to matter.
|
|
347
|
+
combination = SpreeSquare::TaxCombination.create_or_find_by!(signature: signature) do |c|
|
|
348
|
+
name = tax_mappings.map(&:name).join(' + ').presence || 'Square Tax'
|
|
349
|
+
c.tax_category = Spree::TaxCategory.find_by(name: name) || Spree::TaxCategory.create!(name: name)
|
|
336
350
|
end
|
|
351
|
+
|
|
352
|
+
combination.tax_category
|
|
337
353
|
end
|
|
338
354
|
|
|
339
|
-
def
|
|
355
|
+
def ensure_tax_rates!(category, tax_mappings)
|
|
340
356
|
zone = tax_zone!
|
|
341
|
-
name = tax_mappings.map(&:name).join(' + ').presence || 'Square Tax'
|
|
342
|
-
category = Spree::TaxCategory.find_by(name: name) || Spree::TaxCategory.create!(name: name)
|
|
343
357
|
|
|
344
358
|
tax_mappings.each do |tm|
|
|
345
359
|
next if SpreeSquare::TaxCategoryMapping.exists?(tax_category_id: category.id, tax_mapping_id: tm.id)
|
|
@@ -353,9 +367,14 @@ module SpreeSquare
|
|
|
353
367
|
calculator_type: 'Spree::Calculator::DefaultTax'
|
|
354
368
|
)
|
|
355
369
|
SpreeSquare::TaxCategoryMapping.create!(tax_category: category, tax_mapping: tm, tax_rate: rate)
|
|
370
|
+
rescue ActiveRecord::RecordNotUnique
|
|
371
|
+
# A concurrent import created this exact (category, tax) row first
|
|
372
|
+
# (same class of race `composite_tax_category` guards against, just
|
|
373
|
+
# a narrower window) — the orphaned rate this attempt already
|
|
374
|
+
# created never got a join row and is safe to drop; the other
|
|
375
|
+
# side's row is what matters.
|
|
376
|
+
rate&.destroy
|
|
356
377
|
end
|
|
357
|
-
|
|
358
|
-
category
|
|
359
378
|
end
|
|
360
379
|
|
|
361
380
|
def non_taxable_category
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateSpreeSquareTaxCombinations < ActiveRecord::Migration[8.1]
|
|
2
|
+
def change
|
|
3
|
+
# Resolves the race `create_composite_tax_category!` used to have: two
|
|
4
|
+
# concurrent imports (this rake task's own CatalogImporter.call racing a
|
|
5
|
+
# real catalog.version.updated webhook Square fires back mid-run —
|
|
6
|
+
# confirmed live against production, never exercised by sandbox/local
|
|
7
|
+
# dev with no registered webhook) each finding "no matching category
|
|
8
|
+
# yet" and independently creating one. `signature` is a unique,
|
|
9
|
+
# deterministic key for one exact combination of Square tax ids
|
|
10
|
+
# (sorted, joined) — `create_or_find_by!` against it is a portable
|
|
11
|
+
# (Postgres/MySQL/SQLite) race-safe find-or-create, unlike a
|
|
12
|
+
# Postgres-only advisory lock.
|
|
13
|
+
create_table :spree_square_tax_combinations do |t|
|
|
14
|
+
t.string :signature, null: false
|
|
15
|
+
t.references :tax_category, null: false, foreign_key: { to_table: :spree_tax_categories }
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :spree_square_tax_combinations, :signature, unique: true
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/spree_square/version.rb
CHANGED
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.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amit Solanki
|
|
@@ -143,6 +143,7 @@ files:
|
|
|
143
143
|
- app/models/spree_square/order_mapping.rb
|
|
144
144
|
- app/models/spree_square/product_modifier_list.rb
|
|
145
145
|
- app/models/spree_square/tax_category_mapping.rb
|
|
146
|
+
- app/models/spree_square/tax_combination.rb
|
|
146
147
|
- app/models/spree_square/tax_mapping.rb
|
|
147
148
|
- app/models/spree_square/taxon_mapping.rb
|
|
148
149
|
- app/models/spree_square/webhook_event.rb
|
|
@@ -187,6 +188,7 @@ files:
|
|
|
187
188
|
- db/migrate/20260810180001_create_spree_square_credentials.rb
|
|
188
189
|
- db/migrate/20260820180001_create_spree_square_tax_mappings.rb
|
|
189
190
|
- db/migrate/20260820180002_create_spree_square_tax_category_mappings.rb
|
|
191
|
+
- db/migrate/20260820190001_create_spree_square_tax_combinations.rb
|
|
190
192
|
- lib/generators/spree_square/install/install_generator.rb
|
|
191
193
|
- lib/spree_square.rb
|
|
192
194
|
- lib/spree_square/configuration.rb
|