digiwin_dsp 0.2.2 → 0.2.4

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: 85730a801ceb67eaeafaffe50bdbdcf971c33005651e2c3d4483a78f1e076bc4
4
- data.tar.gz: a8e441947b5b0b99a437d8572181a29aeac923890fdf76dcff7ef44c7a88e412
3
+ metadata.gz: feebd7c9c5d89e9d6306b00ecccb402151c9e299201ecf3b98cfbf7c9a1590a1
4
+ data.tar.gz: 7c9fbb5aa913649c417a4f23caf6bf9f3b10bc92b6c2efb34e9cc1cdce8808ad
5
5
  SHA512:
6
- metadata.gz: 1b79afb15f4661863fbf0a0cff9a6ac8812fc1d36c52cae6119d488f8eef8fcde5913cecbca5974ae4df2cda4f2cd3bfaf4c7405413a456c1a0afb06d738d139
7
- data.tar.gz: 33a97756f3d48e8384631b7706ba5bf8a8bf83586221315d24a71710284494ff4ebb3d013c4aa8ec0fc2254aa9dd9448d0f1e33714dc279870d477e5b0b057df
6
+ metadata.gz: 292e2d148f9af342959a826cbed33999cbd81b169719fa79882b309bfdfb6b1800e88ed18dfcd3d0210784d85f7df9dea1e02e63b0a0f9e67e276e235c7c403d
7
+ data.tar.gz: 437cd4412d96aa9914b99cea75ca5ce06274870aada7a2462b53d64e18eb5ebb03b9dbee07bd78cadab08880652aabe49e7ce88ff987c043be7530ff031c3712
data/CHANGELOG.md CHANGED
@@ -6,6 +6,45 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.4] - 2026-05-22
10
+
11
+ ### Added
12
+
13
+ - **`Resources::Base`** — shared parent for the 4 resource classes. Each subclass now declares two constants (`PATH`, `SERIALIZER`); the `.create` class shortcut, `#create` instance flow, and the `response_detail` safety check live in one place.
14
+ - **VCR replay coverage.** New `spec/digiwin_dsp/live_replay_spec.rb` + cassettes under `spec/fixtures/cassettes/` verify the gem parses real DSP UAT responses correctly. Credentials filtered via `spec/support/vcr.rb`. Re-record with `RECORD_CASSETTES=1` + real env vars.
15
+ - **`dotenv` auto-loaded in `bin/console`.** Drops a `.env.local` into the IRB session so manual UAT testing is plug-and-play (`bin/console` → `DigiwinDsp::Resources::Order.create(...)`).
16
+ - **GitHub Release auto-created by `release.yml`.** Workflow now extracts the version's CHANGELOG section and posts it with the built `.gem` artifact — no more manual `gh release create` after each version bump.
17
+
18
+ ### Docs
19
+
20
+ - Idempotency section in README clarified: DSP UAT ignores `X-Idempotency-Key` (live-verified). Dedup is natural-key only (`form_no + platform_id`). The kwarg is still useful as a trace ID.
21
+
22
+ ## [0.2.3] - 2026-05-22
23
+
24
+ ### Docs
25
+
26
+ - Document the `order_status` enum (`"2"` cancel / `"3"` new / `"5"` invoice / `"7"` return) in README and `docs/dsp-api-spec.md`. DSP rejects others with `WrongStatus:order_status錯誤,請固定給N(...)`. All four live-verified against UAT. The OpenAPI examples don't surface this constraint, so live smoke had to discover each value.
27
+
28
+ ### Changed
29
+
30
+ - `scripts/smoke.rb` accepts `ORDER_STATUS=…` env override so operators can probe DSP's WrongStatus path without editing the script.
31
+
32
+ ## [0.2.2] - 2026-05-21
33
+
34
+ ### Fixed
35
+
36
+ - **`Message:"DSP 序號驗證失敗"` now classifies as
37
+ `DigiwinDsp::AuthenticationError`.** Discovered via live UAT smoke
38
+ test: when the `DSP-api-key` header is missing or invalid, DSP
39
+ returns HTTP 200 (not 401/403) with `Status:Failure` and that
40
+ message. Previously fell through to generic `Error`, defeating
41
+ typed-rescue logic.
42
+
43
+ ### Docs
44
+
45
+ - `docs/dsp-api-spec.md` documents the auth-failure envelope shape +
46
+ the form_no-prefix behavior on `Message`.
47
+
9
48
  ## [0.2.1] - 2026-05-21
10
49
 
11
50
  ### Fixed
@@ -140,7 +179,9 @@ Initial release. Covers the four Self-hosted Website Module (自有官網模組)
140
179
  - The gem is **synchronous on purpose**. Callers wrap requests in their own background job runner (e.g. ActiveJob) when needed.
141
180
  - Idempotency: clients can send `X-Idempotency-Key` via the `idempotency_key:` kwarg. DSP also dedupes server-side by `form_no + platform_id`.
142
181
 
143
- [Unreleased]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.2...HEAD
182
+ [Unreleased]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.4...HEAD
183
+ [0.2.4]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.3...v0.2.4
184
+ [0.2.3]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.2...v0.2.3
144
185
  [0.2.2]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.1...v0.2.2
145
186
  [0.2.1]: https://github.com/7a6163/digiwin_dsp/compare/v0.2.0...v0.2.1
146
187
  [0.2.0]: https://github.com/7a6163/digiwin_dsp/compare/v0.1.1...v0.2.0
data/README.md CHANGED
@@ -141,6 +141,17 @@ Each has its own required-field set (8 / 11 / 19 fields respectively). Inspect `
141
141
  DigiwinDsp::Serializers::CancellationSerializer::REQUIRED_FIELDS
142
142
  ```
143
143
 
144
+ ### `order_status` enum
145
+
146
+ Each endpoint requires a specific `order_status` value inside `request_detail`. DSP rejects others with `WrongStatus:order_status錯誤,請固定給N(...)`. The OpenAPI examples don't document this — verified live against UAT 2026-05-21:
147
+
148
+ | Resource | `order_status` | Meaning |
149
+ |---|---|---|
150
+ | `Resources::Order` | `"3"` | 新增訂單 (new order) |
151
+ | `Resources::Cancellation` | `"2"` | 取消訂單 (cancel order) |
152
+ | `Resources::Invoice` | `"5"` | 發票更新 (invoice update) |
153
+ | `Resources::Return` | `"7"` | 退貨訂單 (return order) |
154
+
144
155
  ### Idempotency
145
156
 
146
157
  Pass `idempotency_key:` to attach an `X-Idempotency-Key` request header. DSP also dedupes server-side by `form_no + platform_id` and returns `Duplicated:訂單不可重複` on a re-send (mapped to `DuplicateRequestError`).
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DigiwinDsp
4
+ module Resources
5
+ # Subclasses declare two constants:
6
+ # PATH — endpoint path (e.g. "/v1/SalesOrder/add")
7
+ # SERIALIZER — a module/class responding to `.serialize(records, digi_header:)`
8
+ #
9
+ # Base then handles the .create class shortcut, the instance #create flow,
10
+ # and the response_detail safety check.
11
+ class Base
12
+ def self.create(records, idempotency_key: nil, digi_header: nil)
13
+ new.create(records, idempotency_key: idempotency_key, digi_header: digi_header)
14
+ end
15
+
16
+ def initialize(client = Client.new)
17
+ @client = client
18
+ end
19
+
20
+ def create(records, idempotency_key: nil, digi_header: nil)
21
+ body = self.class::SERIALIZER.serialize(records, digi_header: digi_header)
22
+ response = @client.post(self.class::PATH, body, idempotency_key: idempotency_key)
23
+ response.fetch("response_detail") do
24
+ raise DigiwinDsp::ServerError, "DSP returned Status=Success without response_detail"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,24 +2,9 @@
2
2
 
3
3
  module DigiwinDsp
4
4
  module Resources
5
- class Cancellation
5
+ class Cancellation < Base
6
6
  PATH = "/v1/SalesOrder/cancel"
7
-
8
- def self.create(records, idempotency_key: nil, digi_header: nil)
9
- new.create(records, idempotency_key: idempotency_key, digi_header: digi_header)
10
- end
11
-
12
- def initialize(client = Client.new)
13
- @client = client
14
- end
15
-
16
- def create(records, idempotency_key: nil, digi_header: nil)
17
- body = Serializers::CancellationSerializer.serialize(records, digi_header: digi_header)
18
- response = @client.post(PATH, body, idempotency_key: idempotency_key)
19
- response.fetch("response_detail") do
20
- raise DigiwinDsp::ServerError, "DSP returned Status=Success without response_detail"
21
- end
22
- end
7
+ SERIALIZER = Serializers::CancellationSerializer
23
8
  end
24
9
  end
25
10
  end
@@ -2,24 +2,9 @@
2
2
 
3
3
  module DigiwinDsp
4
4
  module Resources
5
- class Invoice
5
+ class Invoice < Base
6
6
  PATH = "/v1/SalesOrder/invoice"
7
-
8
- def self.create(records, idempotency_key: nil, digi_header: nil)
9
- new.create(records, idempotency_key: idempotency_key, digi_header: digi_header)
10
- end
11
-
12
- def initialize(client = Client.new)
13
- @client = client
14
- end
15
-
16
- def create(records, idempotency_key: nil, digi_header: nil)
17
- body = Serializers::InvoiceSerializer.serialize(records, digi_header: digi_header)
18
- response = @client.post(PATH, body, idempotency_key: idempotency_key)
19
- response.fetch("response_detail") do
20
- raise DigiwinDsp::ServerError, "DSP returned Status=Success without response_detail"
21
- end
22
- end
7
+ SERIALIZER = Serializers::InvoiceSerializer
23
8
  end
24
9
  end
25
10
  end
@@ -2,24 +2,9 @@
2
2
 
3
3
  module DigiwinDsp
4
4
  module Resources
5
- class Order
5
+ class Order < Base
6
6
  PATH = "/v1/SalesOrder/add"
7
-
8
- def self.create(records, idempotency_key: nil, digi_header: nil)
9
- new.create(records, idempotency_key: idempotency_key, digi_header: digi_header)
10
- end
11
-
12
- def initialize(client = Client.new)
13
- @client = client
14
- end
15
-
16
- def create(records, idempotency_key: nil, digi_header: nil)
17
- body = Serializers::SalesOrderSerializer.serialize(records, digi_header: digi_header)
18
- response = @client.post(PATH, body, idempotency_key: idempotency_key)
19
- response.fetch("response_detail") do
20
- raise DigiwinDsp::ServerError, "DSP returned Status=Success without response_detail"
21
- end
22
- end
7
+ SERIALIZER = Serializers::SalesOrderSerializer
23
8
  end
24
9
  end
25
10
  end
@@ -2,24 +2,9 @@
2
2
 
3
3
  module DigiwinDsp
4
4
  module Resources
5
- class Return
5
+ class Return < Base
6
6
  PATH = "/v1/SalesOrder/return"
7
-
8
- def self.create(records, idempotency_key: nil, digi_header: nil)
9
- new.create(records, idempotency_key: idempotency_key, digi_header: digi_header)
10
- end
11
-
12
- def initialize(client = Client.new)
13
- @client = client
14
- end
15
-
16
- def create(records, idempotency_key: nil, digi_header: nil)
17
- body = Serializers::ReturnSerializer.serialize(records, digi_header: digi_header)
18
- response = @client.post(PATH, body, idempotency_key: idempotency_key)
19
- response.fetch("response_detail") do
20
- raise DigiwinDsp::ServerError, "DSP returned Status=Success without response_detail"
21
- end
22
- end
7
+ SERIALIZER = Serializers::ReturnSerializer
23
8
  end
24
9
  end
25
10
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DigiwinDsp
4
- VERSION = "0.2.2"
4
+ VERSION = "0.2.4"
5
5
  end
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.2.2
4
+ version: 0.2.4
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-21 00:00:00.000000000 Z
11
+ date: 2026-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -68,6 +68,7 @@ files:
68
68
  - lib/digiwin_dsp/authenticator.rb
69
69
  - lib/digiwin_dsp/client.rb
70
70
  - lib/digiwin_dsp/configuration.rb
71
+ - lib/digiwin_dsp/resources/base.rb
71
72
  - lib/digiwin_dsp/resources/cancellation.rb
72
73
  - lib/digiwin_dsp/resources/invoice.rb
73
74
  - lib/digiwin_dsp/resources/order.rb