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 +4 -4
- data/CHANGELOG.md +51 -1
- data/README.md +42 -17
- data/lib/simple_connect/api/events.rb +37 -9
- data/lib/simple_connect/client.rb +1 -1
- data/lib/simple_connect/responses/deliver_response.rb +22 -3
- data/lib/simple_connect/responses/event_response.rb +9 -3
- data/lib/simple_connect/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 80608e802aa6273143cb24d056f08abc1541dac6223371eae39c2611ed2ab99e
|
|
4
|
+
data.tar.gz: 9c8e9130fd84bda6b19eaccf5f57a68ff81dc186070f19ef1834114981dbb8bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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(
|
|
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",
|
|
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(
|
|
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,
|
|
255
|
-
SIMPLECONNECT.events.deliver(event_key,
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
70
|
+
def build_body(event_key, recipient:, event:)
|
|
50
71
|
envelope = {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"occurred_at" => format_timestamp(occurred_at),
|
|
54
|
-
"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
|
-
|
|
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,
|
|
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
|
|
9
|
-
# 200 on
|
|
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
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
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.
|
|
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-
|
|
11
|
+
date: 2026-04-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|