digiwin_dsp 0.3.0 → 0.3.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: 6a72275c796b57bd858c4d7c5a67b7074e2738985ae92a9011836c0db9383321
4
- data.tar.gz: 78db9041211bbc64cf7b9cb9cbfccb5f1dbef00efbfc4d5aa7b7a9e44d458f63
3
+ metadata.gz: 3cfde42cbd2b4448e9c5e4927e9ecb65f61ae19a46474154ae1349d73cd2461c
4
+ data.tar.gz: 6d35ad2fd9430c5440f626b15f3c98c9a8dd627c2732bb349bc355f6693d9de3
5
5
  SHA512:
6
- metadata.gz: 82cc7dbb4ca9194b851ee1bdcd853fc6c084d13ae41d6d1c38ebefec8fba80be722bf40e77e332db7ef1e821aec8d8522358740bcd4531e21744fcb66844f9fa
7
- data.tar.gz: ba67eb5115c8aa4d978b71f52c1ac063eb2643bdaccdfa30cb9ed3f3dfd1022b3378497c04cfc468d317a211453d983751ce2cfba635eeceb6d9e8c98044655c
6
+ metadata.gz: 95d2787c766fa0a316542b37d0b5620738e9dcff687562875698e5c1d0f3a3ffaaa2e93a81f24b34e0f6e27047a9c53d22e1368725879f36a6f48116b02f2b7c
7
+ data.tar.gz: c8f1b7781c06e3fa745d4e2a6770d7442dd443a38e10ca4ce6767b5aaa82bbf1016ef8aeb961922ebe9f41321d6702be561a1c82810223335a0a340c6f6eb27b
data/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2026-06-12
10
+
11
+ Hardening patch from a full gem + docs review. All fixes grounded in the vendor YAML specs.
12
+
13
+ ### Fixed
14
+
15
+ - **TLS failures now raise `DigiwinDsp::NetworkError`.** `Faraday::SSLError` sits directly under `Faraday::Error` (not `ConnectionFailed`), so certificate problems previously leaked as raw Faraday exceptions that `rescue DigiwinDsp::Error` could not catch.
16
+ - **Three more DSP failure messages classify into typed exceptions** instead of falling through to generic `Error`:
17
+ - `Shipped:訂單已出貨,不可取消` (DSPOOFFICIAL002) → `ValidationError` — permanent; the order left the warehouse
18
+ - `Processing:新增訂單處理中,不可取消` (DSPOOFFICIAL002) → `RateLimitError` — retryable once ERP processes the add
19
+ - `SalesNotCreate:銷貨單未成立` (DSPOOFFICIAL004) → `RateLimitError` — retryable once ERP converts the sales doc
20
+ Sidekiq/ActiveJob retry strategies can now distinguish "don't retry" from "retry later" on cancel and invoice flows.
21
+ - **`Webhooks::Event.parse_json` / `.extract_request` are now private class methods.** They were internal helpers accidentally exposed on all three event classes.
22
+ - **`WebhookSubscription` rejects non-`https://` callback addresses** at registration time. DSPOOFFICIAL100 mandates HTTPS callbacks, and with no HMAC signing a plaintext callback would be indefensible. (Previously the README claimed this was enforced when it wasn't.)
23
+
24
+ ### Docs
25
+
26
+ - `dsp-api-spec.md`: corrected the e-invoice carrier field name — `Resources::Invoice` sends **`carrier_type`** (DSPOOFFICIAL004:164), not `carrier_code`. `carrier_code` is only on `Resources::Order` and the inbound webhook. Optional fields aren't validated client-side, so the wrong name silently drops carrier data.
27
+ - `dsp-api-spec.md`: failure-message table now covers 002 (cancel) and 004 (invoice) sources, not just 001.
28
+ - README: webhook security bullet now describes the actual https enforcement.
29
+
9
30
  ## [0.3.0] - 2026-05-28
10
31
 
11
32
  DSP webhook support (DSPOOFFICIAL100). Lets a Rails app register a callback URL with DSP, then receive and parse the three documented ERP-originated push events: inventory updates, shipping/logistics status, and invoice issuance.
@@ -205,7 +226,8 @@ Initial release. Covers the four Self-hosted Website Module (自有官網模組)
205
226
  - The gem is **synchronous on purpose**. Callers wrap requests in their own background job runner (e.g. ActiveJob) when needed.
206
227
  - Idempotency: clients can send `X-Idempotency-Key` via the `idempotency_key:` kwarg. DSP also dedupes server-side by `form_no + platform_id`.
207
228
 
208
- [Unreleased]: https://github.com/7a6163/digiwin_dsp/compare/v0.3.0...HEAD
229
+ [Unreleased]: https://github.com/7a6163/digiwin_dsp/compare/v0.3.1...HEAD
230
+ [0.3.1]: https://github.com/7a6163/digiwin_dsp/compare/v0.3.0...v0.3.1
209
231
  [0.3.0]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.4...v0.3.0
210
232
  [0.2.4]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.3...v0.2.4
211
233
  [0.2.3]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.2...v0.2.3
data/README.md CHANGED
@@ -213,7 +213,7 @@ end
213
213
  ```
214
214
 
215
215
  > ⚠️ **DSP does NOT sign inbound webhooks.** There is no HMAC header. Defend the callback endpoint with:
216
- > - HTTPS-only (the gem's `address` validation requires this implicitly via `allowed_hosts` if you register through it)
216
+ > - HTTPS-only `WebhookSubscription` rejects non-`https://` addresses at registration time (DSP's own spec mandates HTTPS callbacks)
217
217
  > - An unguessable URL path (treat it as a secret)
218
218
  > - An IP allowlist for DSP's egress range if your network team can get one
219
219
  > - Replying `200 OK` within 30 seconds (DSP will retry and may eventually block your endpoint if too many calls fail)
@@ -33,6 +33,9 @@ module DigiwinDsp
33
33
  [/Duplicated:/, DuplicateRequestError], # order already exists
34
34
  [/Processing:資料處理中/, RateLimitError], # transient; retry later
35
35
  [/Processing:取消訂單處理中/, ValidationError], # cancel in flight
36
+ [/Processing:新增訂單處理中/, RateLimitError], # add in flight; cancel retryable after ERP processes (002)
37
+ [/Shipped:/, ValidationError], # already shipped — cancel permanently impossible (002)
38
+ [/SalesNotCreate:/, RateLimitError], # ERP hasn't converted sales doc yet; invoice retryable (004)
36
39
  [/WrongStatus:/, ValidationError], # bad payload
37
40
  [/系統異常:/, ServerError] # DSP internal error
38
41
  ].freeze
@@ -52,7 +55,10 @@ module DigiwinDsp
52
55
  req.body = body
53
56
  end
54
57
  handle_response(response)
55
- rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
58
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
59
+ # SSLError sits directly under Faraday::Error (not ConnectionFailed),
60
+ # so it needs an explicit entry — otherwise TLS failures leak as raw
61
+ # Faraday exceptions that `rescue DigiwinDsp::Error` won't catch.
56
62
  raise NetworkError, e.message
57
63
  end
58
64
 
@@ -43,6 +43,11 @@ module DigiwinDsp
43
43
  "action must be one of #{ACTIONS.inspect} (got #{action.inspect})"
44
44
  end
45
45
  raise DigiwinDsp::ValidationError, "address is required" if address.nil? || address.to_s.empty?
46
+
47
+ # DSPOOFFICIAL100 mandates HTTPS for the callback ("必須在 30 秒內以
48
+ # HTTPS 回應") — and with no HMAC signing, a plaintext callback URL
49
+ # would be indefensible anyway.
50
+ raise DigiwinDsp::ValidationError, "address must be an https:// URL (got #{address.inspect})" unless address.start_with?("https://")
46
51
  return unless address.length > ADDRESS_MAX_LENGTH
47
52
 
48
53
  raise DigiwinDsp::ValidationError,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DigiwinDsp
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -30,6 +30,8 @@ module DigiwinDsp
30
30
  raise(ParseError, "envelope missing digi_body.std_data.parameter.request")
31
31
  end
32
32
 
33
+ private_class_method :parse_json, :extract_request
34
+
33
35
  def initialize(digi_header:, request:, raw:)
34
36
  @digi_header = digi_header
35
37
  @request = request
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: digiwin_dsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zac
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-28 00:00:00.000000000 Z
11
+ date: 2026-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday