spree-kashflow 0.1.2 → 0.1.3

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: d20212552f51bc299bb20645fc8f5dbca833b2bdb63a1678d752b29a15f00a17
4
- data.tar.gz: fb65959bc02ff1ac1b1eb7b38257ca03b9fba95386efb49b40baf91378b7c0bb
3
+ metadata.gz: bf3ea17173c688a2ae5facaa72f582c02b2bb56b3d013d4d64e7467104b200b6
4
+ data.tar.gz: 14b81280691c79a6d6501cb4ae1e173a1e094be9c504bf90bcfb7de9cb40bc93
5
5
  SHA512:
6
- metadata.gz: a257c8a10a8576b39a5b58613568cc1faf751f3fa28caa505df9d190404be40d12e0e52272a48c3f2f141cc7e2b0c540b59bf32bb2501d7c2dd60b01b31ecf9b
7
- data.tar.gz: 19f304c825178155659864ba3b064507ade831b27632d385befd8d27a60f84ccd74471b0eef8ce94cea0f1bd1391ce900fc034af404377e578d87e292527a276
6
+ metadata.gz: 4909476c53475670965c33e28bf2ec279e7551bbac94a7a43ddd3777d72577569eb84ec7fc9d216f23afa79c7e2989efef18a3deb8757fea76c20fdc04c51d2e
7
+ data.tar.gz: 289bc0e640aab7cea52cb96c2b019a00dd11e9bc010035b45056d947990152317755863978b5d16e071b5196a19ffc24429d9544c1fd9de7dfd34244a300f19e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ - **Fix: `upsert_customer` only ever inserted.** KashFlow publishes no
6
+ `InsertOrUpdateCustomer`, and the client called `InsertCustomer`
7
+ unconditionally with no lookup. 0.1.1 made customer codes stable per customer
8
+ — which is what keeps one KashFlow customer per Spree customer rather than one
9
+ per order — and that turned the missing lookup into a hard failure: the insert
10
+ succeeded exactly once, and the same customer's *second* order was rejected
11
+ with `Customer Code is not unique`, posting no invoice. `upsert_customer` now
12
+ looks the code up with `GetCustomer` and either `UpdateCustomer`s the existing
13
+ record or `InsertCustomer`s a new one, so it is idempotent across repeated
14
+ syncs.
15
+ - `CustomerID` is prepended to the update payload rather than merged onto the
16
+ end, because it is the first element of the WSDL's `Customer` sequence and an
17
+ ASMX endpoint enforcing that sequence drops or mis-binds an out-of-order
18
+ element rather than raising — an update whose id was dropped would write to
19
+ the wrong customer, or to none, and still return successfully.
20
+ - How KashFlow signals "no such customer" on `GetCustomer` is undocumented, so
21
+ both plausible shapes are treated as absent: an empty result, and an in-band
22
+ business rejection. Only `ApiError` is swallowed; an authentication or
23
+ transport failure keeps propagating rather than being read as "absent" and
24
+ turned back into a duplicate insert.
25
+
3
26
  ## 0.1.2
4
27
 
5
28
  - **Fix: every order sync still failed against a live KashFlow account**, now on
@@ -87,14 +87,28 @@ module Spree
87
87
  end
88
88
 
89
89
  ##
90
- # Creates or updates a customer in KashFlow.
91
- #
92
- # @param payload [Hash] a KashFlow `Customer` structure
93
- # @return [Integer] the KashFlow customer id
90
+ # Creates or updates a customer in KashFlow, keyed on the customer `Code`.
91
+ #
92
+ # KashFlow publishes no `InsertOrUpdateCustomer`, so an upsert is a lookup
93
+ # followed by an `UpdateCustomer` or an `InsertCustomer`. The lookup is not
94
+ # optional: customer codes are stable per customer (that is what keeps one
95
+ # KashFlow customer per Spree customer rather than one per order), so an
96
+ # unconditional insert succeeds exactly once and every subsequent order for
97
+ # that customer is rejected with "Customer Code is not unique" — with no
98
+ # invoice posted.
99
+ #
100
+ # @param payload [Hash] a KashFlow `Customer` structure, whose `"Code"`
101
+ # identifies the customer
102
+ # @return [Integer] the KashFlow customer id, existing or newly assigned
94
103
  # @raise [Spree::Kashflow::ApiError] when the request fails, or when
95
104
  # KashFlow returns no usable customer id
105
+ # @raise [Spree::Kashflow::AuthenticationError] when KashFlow rejects the
106
+ # credentials
96
107
  #
97
108
  def upsert_customer(payload)
109
+ existing_id = customer_id_for_code(payload["Code"])
110
+ return update_customer(existing_id, payload) if existing_id
111
+
98
112
  response = call(:insert_customer, {"custr" => payload})
99
113
  result = response.dig("InsertCustomerResponse", "InsertCustomerResult")
100
114
  assert_identifier!(result, "customer id")
@@ -196,6 +210,55 @@ module Spree
196
210
  raise TransportError, e.message
197
211
  end
198
212
 
213
+ ##
214
+ # Looks a customer up by its KashFlow `Code`.
215
+ #
216
+ # How KashFlow signals "no such customer" here is undocumented, and no
217
+ # spec that stubs the SOAP layer can settle it, so both shapes it can
218
+ # plausibly take are treated as absent: an empty `GetCustomerResult`, and
219
+ # an in-band business rejection. Only {ApiError} is swallowed — an
220
+ # authentication or transport failure must keep propagating, since
221
+ # reading either as "absent" would turn it into a duplicate insert and
222
+ # put the caller back on the collision this method exists to avoid.
223
+ #
224
+ # @param code [String, nil] the KashFlow customer code
225
+ # @return [Integer, nil] the customer id, or nil when no customer holds
226
+ # that code
227
+ #
228
+ def customer_id_for_code(code)
229
+ return nil if code.to_s.strip.empty?
230
+
231
+ result = call(:get_customer, {"CustomerCode" => code})
232
+ .dig("GetCustomerResponse", "GetCustomerResult")
233
+ return nil unless result.is_a?(Hash)
234
+
235
+ identifier = result["CustomerID"].to_i
236
+ identifier.zero? ? nil : identifier
237
+ rescue ApiError
238
+ nil
239
+ end
240
+
241
+ ##
242
+ # Updates an existing KashFlow customer in place.
243
+ #
244
+ # `CustomerID` is prepended rather than merged onto the end because it is
245
+ # the first element of the WSDL's `Customer` sequence, and an ASMX
246
+ # endpoint enforcing that sequence drops or mis-binds an out-of-order
247
+ # element rather than raising — an update whose id was dropped would write
248
+ # to the wrong customer, or to none, and still return successfully.
249
+ #
250
+ # `UpdateCustomerResult` is a `s:string`, not the customer id, so the id
251
+ # is carried over from the lookup instead of being read off the response.
252
+ #
253
+ # @param identifier [Integer] the KashFlow customer id
254
+ # @param payload [Hash] a KashFlow `Customer` structure
255
+ # @return [Integer] the customer id that was updated
256
+ #
257
+ def update_customer(identifier, payload)
258
+ call(:update_customer, {"custr" => {"CustomerID" => identifier}.merge(payload)})
259
+ identifier
260
+ end
261
+
199
262
  ##
200
263
  # Rewrites every temporal value in an outgoing message to xsd `dateTime`.
201
264
  #
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Spree
4
4
  module Kashflow
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree-kashflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aypex