simple_connect-client 0.1.0 → 0.3.0

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: e1ab061c2118ee363fa9fdc9c1f69518a247cf55297d5b8dbf0de893c3507823
4
- data.tar.gz: ce307f5e3403aead072705a621115308a573da3839bf5121b2e80b14a40503bc
3
+ metadata.gz: 80608e802aa6273143cb24d056f08abc1541dac6223371eae39c2611ed2ab99e
4
+ data.tar.gz: 9c8e9130fd84bda6b19eaccf5f57a68ff81dc186070f19ef1834114981dbb8bb
5
5
  SHA512:
6
- metadata.gz: cdc8f0ea63a0d3a133a871e0a0cd9ab21649d70ece906e1b54a14f7f36254186e80a5015e4890e12d9b4fdb673e89cb6a29aa941708e2c73bdef60761a7ed996
7
- data.tar.gz: f800d93c034416cd8b143283f8fe9a354bc768e932f7ad399476e0ee134599adedaf59c7cec3ccd04ff330253bab4efae2ade766a0d879f6bb45cd301dddf35d
6
+ metadata.gz: ad8d5b28164c2598aeb32ee7bf0db42eec12ab15a0fed501d12b4cd9e33dfda11ac69ae24e21be0def68773c1119c40d020d31c0e77aae5b993458a1cc02e7b9
7
+ data.tar.gz: 2d578d852fa29a83400e6fecd49918cbddc118ebe7e846f64496e04d79b88ea7c3d3844c4f6f2c22c55900085e3b6872c2b8cc1a0a04810cc9c5b1cc63abfd29
data/CHANGELOG.md CHANGED
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-04-30
11
+
12
+ ### Added
13
+
14
+ - `events.deliver` response now embeds the persisted event-log row plus
15
+ the (optional) linked WhatsApp message under the same
16
+ `event_log` / `message` keys that `events.detail` already uses. The
17
+ new `DeliverResponse#event` returns this as an `EventResponse` —
18
+ callers can read `result.data.event.status`, `.error_text`,
19
+ `.skipped?`, `.dispatched?`, `.message`, etc., to see at a glance
20
+ whether the event was actually queued for dispatch (`"received"`) or
21
+ skipped because the integration is paused / the per-event flow is
22
+ disabled (both still return 202 server-side, because the event was
23
+ logged for audit). Previously the only deliver-time signal was
24
+ `#log_id` / `#event_id`, so a paused integration looked identical to
25
+ a healthy one from the sender's perspective.
26
+ - `EventResponse` is now reused for both endpoints — no new response
27
+ class. Its existing public surface is unchanged.
28
+
29
+ ### Compatibility
30
+
31
+ - **Server requirement**: the new `event_log` / `message` fields are
32
+ populated by SimpleWaConnect server ≥ v0.3.0. Older servers return
33
+ the v0.2.0 shape (`status`, `log_id`, `event_id`, `duplicate`,
34
+ `used_previous_secret`); `DeliverResponse#event` returns `nil` in
35
+ that case so existing code continues to work unmodified.
36
+ - The flat `#log_id` / `#event_id` accessors on `DeliverResponse` are
37
+ preserved — existing callers do not need to change anything.
38
+
39
+ ## [0.2.0] - 2026-04-21
40
+
41
+ ### Changed
42
+
43
+ - **BREAKING** — outgoing event body reshaped to match the new server
44
+ contract. The flat envelope (`{ event, event_id, occurred_at, language,
45
+ customer_name, customer_mobile_no, ... }`) is now nested as
46
+ `{ "recipient": { name, mobile_no }, "event": { event_id, name,
47
+ occurred_at, language, ...fields } }`. The server (SimpleWaConnect ≥ this
48
+ release) rejects the old flat shape with 422.
49
+ - **BREAKING** — `events.deliver` signature simplified to mirror the wire
50
+ body: `deliver(event_key, recipient:, event: {})`. `recipient` is a hash
51
+ (`mobile_no:` required, `name:` optional); `event` is a hash of
52
+ event-specific fields plus optional envelope overrides (`event_id`,
53
+ `occurred_at`, `language`). Old callers that passed `customer_mobile_no:`
54
+ as routing info, a positional `fields` hash, or envelope kwargs directly
55
+ (`event_id:`, `language:`, `occurred_at:`) must move all of those into
56
+ the two hashes.
57
+
10
58
  ## [0.1.0] - 2026-04-20
11
59
 
12
60
  ### Added
@@ -32,5 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
32
80
  - Ruby 3.2+.
33
81
  - No runtime gem dependencies (stdlib only).
34
82
 
35
- [unreleased]: https://github.com/GemsEssence/SimpleWaConnect/compare/simple_connect-client-v0.1.0...HEAD
83
+ [unreleased]: https://github.com/GemsEssence/SimpleWaConnect/compare/simple_connect-client-v0.3.0...HEAD
84
+ [0.3.0]: https://github.com/GemsEssence/SimpleWaConnect/compare/simple_connect-client-v0.2.0...simple_connect-client-v0.3.0
85
+ [0.2.0]: https://github.com/GemsEssence/SimpleWaConnect/compare/simple_connect-client-v0.1.0...simple_connect-client-v0.2.0
36
86
  [0.1.0]: https://github.com/GemsEssence/SimpleWaConnect/releases/tag/simple_connect-client-v0.1.0
data/README.md CHANGED
@@ -71,19 +71,34 @@ The client groups calls into two resource objects — `events` and `integrations
71
71
  ```ruby
72
72
  SIMPLECONNECT.events.deliver(
73
73
  "customer_payment_received",
74
- customer_name: "Ramesh Kumar",
75
- customer_mobile_no: "+919812345678",
76
- agency_name: "Acme Dairy",
77
- payment_date: "2026-04-16",
78
- payment_mode: "UPI",
79
- payment_amount: "450.00",
80
- customer_total_due_amount: "0.00",
81
- event_id: "pp_payment_#{payment.id}" # idempotency key
74
+ recipient: {
75
+ mobile_no: "+919812345678", # required — routing target
76
+ name: "Ramesh Kumar" # optional
77
+ },
78
+ event: {
79
+ event_id: "pp_payment_#{payment.id}", # idempotency key
80
+ customer_name: "Ramesh Kumar",
81
+ agency_name: "Acme Dairy",
82
+ payment_date: "2026-04-16",
83
+ payment_mode: "UPI",
84
+ payment_amount: "450.00",
85
+ customer_total_due_amount: "0.00"
86
+ }
82
87
  )
83
88
  ```
84
89
 
85
- Pass an explicit `event_id:` (unique per domain event) for safe retries —
86
- duplicate event_ids are treated as no-ops by the server.
90
+ The two hashes mirror the wire body 1:1:
91
+
92
+ - `recipient` is used by SimpleConnect to route the outbound WhatsApp message.
93
+ `mobile_no` is required, `name` is optional. Fields here are **not** accessible
94
+ to template variable mappings — to show the customer's name in a message body,
95
+ duplicate it under `event:` (e.g. `customer_name:`).
96
+ - `event` carries the envelope plus all template-variable fields. Pass an explicit
97
+ `event_id` (unique per domain event) for safe retries — duplicate `event_id`s
98
+ are treated as no-ops by the server. If omitted, the client generates
99
+ `evt_<hex>`. `occurred_at` defaults to now, `language` to `"en"`. Any `name:`
100
+ you pass inside `event` is ignored — it's always set to the positional
101
+ `event_key`.
87
102
 
88
103
  ### Fetch a previously-ingested event
89
104
 
@@ -113,7 +128,11 @@ Every resource call returns a `Result` struct with:
113
128
  | `data` | Response?| Typed response object (see below) when the body was parseable; `nil` otherwise |
114
129
 
115
130
  ```ruby
116
- result = SIMPLECONNECT.events.deliver("customer_payment_received", fields)
131
+ result = SIMPLECONNECT.events.deliver(
132
+ "customer_payment_received",
133
+ recipient: { mobile_no: "+919812345678", name: "Ramesh" },
134
+ event: fields
135
+ )
117
136
  if result.success?
118
137
  Rails.logger.info("delivered in #{result.attempts} attempt(s); id=#{result.data.event_id}")
119
138
  else
@@ -139,7 +158,9 @@ Always check `result.success?` first, then read `result.data`.
139
158
 
140
159
  ```ruby
141
160
  result = SIMPLECONNECT.events.deliver(
142
- "customer_payment_received", fields, event_id: "pp_pay_42"
161
+ "customer_payment_received",
162
+ recipient: { mobile_no: "+919812345678", name: "Ramesh" },
163
+ event: fields.merge(event_id: "pp_pay_42")
143
164
  )
144
165
 
145
166
  if result.success?
@@ -209,7 +230,11 @@ end
209
230
  ### Error path → `ErrorResponse`
210
231
 
211
232
  ```ruby
212
- result = SIMPLECONNECT.events.deliver("some_event", fields)
233
+ result = SIMPLECONNECT.events.deliver(
234
+ "some_event",
235
+ recipient: { mobile_no: "+919812345678" },
236
+ event: fields
237
+ )
213
238
 
214
239
  unless result.success?
215
240
  if result.data # ErrorResponse (or nil for non-JSON / network errors)
@@ -251,15 +276,15 @@ SIMPLECONNECT = SimpleConnect::Client.new(
251
276
  )
252
277
 
253
278
  class DeliverSimpleConnectEventJob < ApplicationJob
254
- def perform(event_key, fields, event_id:)
255
- SIMPLECONNECT.events.deliver(event_key, fields, event_id: event_id)
279
+ def perform(event_key, recipient, event)
280
+ SIMPLECONNECT.events.deliver(event_key, recipient: recipient, event: event)
256
281
  end
257
282
  end
258
283
 
259
284
  DeliverSimpleConnectEventJob.perform_later(
260
285
  "customer_payment_received",
261
- { customer_name: "...", ... },
262
- event_id: "pp_payment_#{payment.id}"
286
+ { mobile_no: "+919812345678", name: "Ramesh" },
287
+ { customer_name: "Ramesh", agency_name: "Acme", event_id: "pp_payment_#{payment.id}" }
263
288
  )
264
289
  ```
265
290
 
@@ -21,16 +21,37 @@ module SimpleConnect
21
21
  @event_keys = event_keys&.map(&:to_s)&.freeze
22
22
  end
23
23
 
24
- def deliver(event_key, fields = {}, event_id: nil, language: DEFAULT_LANGUAGE, occurred_at: nil, **extra_fields)
24
+ # Deliver a domain event.
25
+ #
26
+ # events.deliver(
27
+ # "customer_payment_received",
28
+ # recipient: { mobile_no: "+919812345678", name: "Ramesh Kumar" },
29
+ # event: {
30
+ # event_id: "pp_payment_42",
31
+ # customer_name: "Ramesh Kumar",
32
+ # payment_amount: "450.00"
33
+ # }
34
+ # )
35
+ #
36
+ # The two hashes mirror the wire body 1:1 — `recipient` is used for
37
+ # routing, `event` carries template-variable fields. The envelope
38
+ # (`event_id`, `occurred_at`, `language`) is auto-filled into `event`
39
+ # when omitted: `event_id` defaults to a random `evt_<hex>`,
40
+ # `occurred_at` to now, `language` to "en". `event[:name]` is always
41
+ # set to `event_key` — passing one is ignored.
42
+ def deliver(event_key, recipient:, event: {})
25
43
  event_key = event_key.to_s
26
44
  raise ArgumentError, "event_key is required" if event_key.empty?
45
+
46
+ recipient = stringify_keys(recipient)
47
+ raise ArgumentError, "recipient[:mobile_no] is required" if recipient["mobile_no"].to_s.strip.empty?
48
+
27
49
  if @event_keys && !@event_keys.include?(event_key)
28
50
  raise SimpleConnect::UnknownEventError,
29
51
  "Unknown event_key '#{event_key}'. Must be one of: #{@event_keys.join(", ")}"
30
52
  end
31
53
 
32
- merged_fields = stringify_keys(fields).merge(stringify_keys(extra_fields))
33
- body = build_body(event_key, merged_fields, event_id: event_id, language: language, occurred_at: occurred_at)
54
+ body = build_body(event_key, recipient: recipient, event: stringify_keys(event))
34
55
  attach_response(@request.post(@endpoint_uri, body: body), Responses::DeliverResponse)
35
56
  end
36
57
 
@@ -46,14 +67,21 @@ module SimpleConnect
46
67
  URI.parse("#{@endpoint_uri}/#{URI.encode_www_form_component(event_id.to_s)}")
47
68
  end
48
69
 
49
- def build_body(event_key, fields, event_id:, language:, occurred_at:)
70
+ def build_body(event_key, recipient:, event:)
50
71
  envelope = {
51
- "event" => event_key,
52
- "event_id" => resolve_event_id(event_id),
53
- "occurred_at" => format_timestamp(occurred_at),
54
- "language" => resolve_language(language)
72
+ "event_id" => resolve_event_id(event["event_id"]),
73
+ "name" => event_key,
74
+ "occurred_at" => format_timestamp(event["occurred_at"]),
75
+ "language" => resolve_language(event["language"])
55
76
  }
56
- envelope.merge(stringify_keys(fields)).to_json
77
+
78
+ {
79
+ "recipient" => {
80
+ "name" => recipient["name"].to_s,
81
+ "mobile_no" => recipient["mobile_no"].to_s
82
+ },
83
+ "event" => event.merge(envelope)
84
+ }.to_json
57
85
  end
58
86
 
59
87
  def resolve_event_id(value)
@@ -4,7 +4,7 @@ module SimpleConnect
4
4
  # Entry point for the SimpleWaConnect integration client. Assembles the
5
5
  # signed-request transport once, then exposes two resource objects:
6
6
  #
7
- # SIMPLECONNECT.events.deliver(event_key, fields, event_id: ...)
7
+ # SIMPLECONNECT.events.deliver(event_key, recipient: { mobile_no: }, event: { ... })
8
8
  # SIMPLECONNECT.events.detail(event_id)
9
9
  # SIMPLECONNECT.integrations.verify
10
10
  #
@@ -5,14 +5,32 @@ module SimpleConnect
5
5
  # Wraps the `events.deliver` acknowledgement.
6
6
  #
7
7
  # Server returns:
8
- # 202 on a new event → { status, log_id, event_id }
9
- # 200 on an idempotent replay → { status, log_id, event_id, duplicate: true }
8
+ # 202 on a new event → { status, log_id, event_id, event_log, message }
9
+ # 200 on a duplicate replay → { status, log_id, event_id, event_log, message, duplicate: true }
10
10
  # Either code is a success; consumers check `#duplicate?` to distinguish.
11
11
  # If the request was signed with a recently-rotated previous secret, the
12
12
  # server adds `"used_previous_secret": true` at the top level — a hint
13
13
  # to rotate credentials before the grace window ends.
14
+ #
15
+ # As of server v0.3.0, the response also carries the persisted
16
+ # event-log row + symmetric message under the same `event_log` /
17
+ # `message` keys that `events.detail` uses, so the same `EventResponse`
18
+ # parser wraps both. `#event` returns that wrapped row — the caller can
19
+ # read `#event.status`, `#event.error_text`, `#event.skipped?`, etc., to
20
+ # see at a glance whether the event was actually queued for dispatch
21
+ # (`"received"`) or skipped because the integration is paused / the
22
+ # per-event flow is disabled. Without this, a paused integration looked
23
+ # identical to a live one from the sender's perspective.
24
+ #
25
+ # `#event.message` is almost always `nil` at deliver time — dispatch is
26
+ # async and the outbound Message does not exist yet at the moment of
27
+ # the ack; populated only on duplicate replays of an already-dispatched
28
+ # event.
29
+ #
30
+ # The flat `#log_id` / `#event_id` accessors are preserved for backward
31
+ # compatibility.
14
32
  class DeliverResponse
15
- attr_reader :status, :log_id, :event_id
33
+ attr_reader :status, :log_id, :event_id, :event
16
34
 
17
35
  def initialize(json)
18
36
  @json = json.is_a?(Hash) ? json : {}
@@ -21,6 +39,7 @@ module SimpleConnect
21
39
  @event_id = @json["event_id"]
22
40
  @duplicate = @json["duplicate"] == true
23
41
  @used_previous_secret = @json["used_previous_secret"] == true
42
+ @event = @json["event_log"].is_a?(Hash) ? EventResponse.new(@json) : nil
24
43
  end
25
44
 
26
45
  def duplicate?
@@ -2,9 +2,15 @@
2
2
 
3
3
  module SimpleConnect
4
4
  module Responses
5
- # Wraps the `events.detail` success payload. Holds the event-log row
6
- # and, optionally, a nested `MessageResponse` if a WhatsApp message is
7
- # linked to the event.
5
+ # Wraps the persisted event-log row and (optionally) the linked
6
+ # WhatsApp message. Returned from two endpoints:
7
+ #
8
+ # * `events.detail` — the full GET payload, where the server wraps
9
+ # the log under an `"event_log"` key alongside `"message"`.
10
+ # * `events.deliver` (server ≥ 0.3.0) — the same shape, embedded
11
+ # under the `"event"` key on the deliver ack so callers know
12
+ # immediately whether the event was queued (`"received"`) or
13
+ # skipped (`"skipped_paused"`, `"skipped_disabled"`, …).
8
14
  class EventResponse
9
15
  attr_reader :event_id, :event_key, :status, :occurred_at, :created_at,
10
16
  :updated_at, :error_text, :payload, :message
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleConnect
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_connect-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ramkrishan Patidar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-20 00:00:00.000000000 Z
11
+ date: 2026-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake