invoq 0.2.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/README.md +84 -21
- data/lib/invoq/client.rb +6 -0
- data/lib/invoq/internal/request.rb +13 -1
- data/lib/invoq/invoices_resource.rb +17 -4
- data/lib/invoq/version.rb +1 -1
- data/lib/invoq/webhooks.rb +40 -14
- data/lib/invoq.rb +8 -0
- metadata +3 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d6735a9a9a69b7d895e5195f2c996067a5e6abc84535da30bb9c1994b235b15d
|
|
4
|
+
data.tar.gz: 8882cfaa9d72e9beda66de5bf69a213058164e2b0043aeb2681351f0838a9ad6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3ae2327b5bfed2af09db878361a2a15028f4943e905ec610884c1e36dc5cdacd75d2aa9385eda9f61dff1382739a58553a449f9233c2e4e160bf1136803f610
|
|
7
|
+
data.tar.gz: 43efaca949a9c6e2d9ddaf8d34a4381baf70a9a59eaf07902b32616269a57ec4b1aa7371bfdf49c99decaf0f0b2140ab641c8f4ebc9ef1cd5fca43e247fd013e
|
data/README.md
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
# invoq Ruby SDK
|
|
2
2
|
|
|
3
|
+
**English** · [Bahasa Indonesia](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.id.md) · [Español](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.es-419.md) · [Français](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.fr.md) · [Português](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.pt-BR.md) · [Tiếng Việt](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.vi.md) · [Türkçe](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.tr.md) · [ไทย](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.th.md) · [简体中文](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.zh-Hans.md) · [繁體中文](https://github.com/invoqmoney/sdk-ruby/blob/main/docs/README.zh-Hant.md)
|
|
4
|
+
|
|
3
5
|
Accept stablecoin payments with invoq from Ruby server code. This SDK wraps
|
|
4
6
|
invoq server APIs and verifies signed webhooks.
|
|
5
7
|
|
|
6
8
|
Use this gem only on your server. It handles secret keys and should not be
|
|
7
9
|
bundled into browser code.
|
|
8
10
|
|
|
11
|
+
**Coding with AI? Paste this.**
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Add stablecoin payments to my project with invoq. Start in test mode. Read the docs before you write any code: https://invoq.money/llms.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Server SDKs
|
|
18
|
+
|
|
19
|
+
Create invoices and verify webhooks from your backend in any of these languages — same REST API, same webhook signature. This repository is the Ruby SDK.
|
|
20
|
+
|
|
21
|
+
| Language | Repository |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Node.js | [github.com/invoqmoney/sdk-js](https://github.com/invoqmoney/sdk-js) (`@invoq/server`) |
|
|
24
|
+
| Python | [github.com/invoqmoney/sdk-python](https://github.com/invoqmoney/sdk-python) |
|
|
25
|
+
| PHP | [github.com/invoqmoney/sdk-php](https://github.com/invoqmoney/sdk-php) |
|
|
26
|
+
| Go | [github.com/invoqmoney/sdk-go](https://github.com/invoqmoney/sdk-go) |
|
|
27
|
+
| Rust | [github.com/invoqmoney/sdk-rust](https://github.com/invoqmoney/sdk-rust) |
|
|
28
|
+
| Ruby | **this repo** |
|
|
29
|
+
|
|
30
|
+
The browser side is the same for every backend: **`@invoq/checkout`** (JavaScript, in [github.com/invoqmoney/sdk-js](https://github.com/invoqmoney/sdk-js)) opens the in-page checkout modal for any frontend.
|
|
31
|
+
|
|
9
32
|
## Installation
|
|
10
33
|
|
|
11
34
|
Install the gem:
|
|
@@ -32,6 +55,9 @@ Requires Ruby 2.6 or newer.
|
|
|
32
55
|
3. In your project's **webhooks** settings, save your webhook URL. The webhook
|
|
33
56
|
secret (`whsec_...`) for that mode is shown once, when you first enable the
|
|
34
57
|
webhook. Store it right away. Webhook URLs must be public HTTPS URLs.
|
|
58
|
+
4. Set up your **Receiving wallet** before going live. Test invoices don't need
|
|
59
|
+
one; a live invoice with nowhere to settle fails with
|
|
60
|
+
`409 no_payment_options_available`.
|
|
35
61
|
|
|
36
62
|
Add both to your server environment:
|
|
37
63
|
|
|
@@ -80,7 +106,6 @@ Create an invoice:
|
|
|
80
106
|
```ruby
|
|
81
107
|
invoice = invoq.invoices.create(
|
|
82
108
|
amount: "129",
|
|
83
|
-
currency: "USD",
|
|
84
109
|
description: "SaaS boilerplate",
|
|
85
110
|
reference_id: "order_1234",
|
|
86
111
|
return_url: "https://merchant.example/thanks"
|
|
@@ -93,9 +118,9 @@ checkout_url = "https://pay.invoq.money/#{invoice_id}"
|
|
|
93
118
|
Notes:
|
|
94
119
|
|
|
95
120
|
- Use a server-side amount. Do not trust client-supplied amounts.
|
|
96
|
-
- `amount` is a decimal USD string from `"0.01"` to `"
|
|
97
|
-
to 2 decimal places, such as `"129"` or `"129.99"`.
|
|
98
|
-
|
|
121
|
+
- `amount` is a decimal USD string from `"0.01"` to `"1000000.00"` with up
|
|
122
|
+
to 2 decimal places, such as `"129"` or `"129.99"`. Currency is always USD,
|
|
123
|
+
and test or live comes from the key — neither is a request field.
|
|
99
124
|
- Use a stable, non-empty `reference_id` to map `invoice.paid` webhooks back to
|
|
100
125
|
your order. Creating again with the same `reference_id` and invoice terms
|
|
101
126
|
returns the existing invoice; different terms fail with a
|
|
@@ -114,11 +139,10 @@ Get an invoice:
|
|
|
114
139
|
invoice = invoq.invoices.get("inv_123")
|
|
115
140
|
```
|
|
116
141
|
|
|
117
|
-
`invoices.get` returns the public invoice shape used by hosted checkout
|
|
118
|
-
|
|
119
|
-
`
|
|
120
|
-
|
|
121
|
-
`invoice.paid` webhook when you need your merchant `reference_id`.
|
|
142
|
+
`invoices.get` returns the public invoice shape used by hosted checkout: the
|
|
143
|
+
create shape plus `amount_paid`, `project`, and `transfers`, minus
|
|
144
|
+
`reference_id`. Use the create response or the `invoice.paid` webhook when you
|
|
145
|
+
need your merchant `reference_id`.
|
|
122
146
|
|
|
123
147
|
Create a test payment:
|
|
124
148
|
|
|
@@ -143,8 +167,7 @@ webhook URL.
|
|
|
143
167
|
|
|
144
168
|
To receive webhooks on your machine, expose your local server with an HTTPS
|
|
145
169
|
tunnel such as ngrok or cloudflared and save the tunnel URL as your test webhook
|
|
146
|
-
URL in the dashboard.
|
|
147
|
-
check connectivity.
|
|
170
|
+
URL in the dashboard.
|
|
148
171
|
|
|
149
172
|
Each invoice method returns the response `data` object directly as a Ruby hash.
|
|
150
173
|
|
|
@@ -161,17 +184,40 @@ Share the link or redirect to it when an in-page checkout modal is not a fit.
|
|
|
161
184
|
## Inputs and responses
|
|
162
185
|
|
|
163
186
|
The SDK checks that `amount` values and `invoice_id` arguments are non-empty
|
|
164
|
-
strings before sending requests. The invoq API validates the amount format
|
|
165
|
-
range
|
|
187
|
+
strings before sending requests. The invoq API validates the amount format and
|
|
188
|
+
range.
|
|
166
189
|
|
|
167
190
|
Leave unset optional fields out of the request hash. When you include
|
|
168
191
|
`description` or `reference_id`, pass a string. `return_url` can be a string or
|
|
169
|
-
`nil`.
|
|
192
|
+
`nil`. Any other key in the hash is dropped rather than sent, because the API
|
|
193
|
+
rejects unknown body keys.
|
|
170
194
|
|
|
171
195
|
Amounts in responses are normalized to 4 decimal places: create with `"129"`
|
|
172
196
|
and the invoice returns `amount: "129.0000"`. Compare amounts numerically, not
|
|
173
197
|
as strings. `amount_due` is derived as `max(amount - amount_paid, 0)` and uses
|
|
174
|
-
the same 18-decimal scale as `amount_paid
|
|
198
|
+
the same 18-decimal scale as `amount_paid`; `amount_overpaid` is its mirror,
|
|
199
|
+
`max(amount_paid - amount, 0)`, so you never subtract money yourself.
|
|
200
|
+
|
|
201
|
+
Two status fields. `status` is the accounting one — `unpaid`, `partially_paid`,
|
|
202
|
+
`paid`, `settling`, `settled`, `review_required` — where the three paid-like
|
|
203
|
+
values differ only in how far the funds have moved to your wallet.
|
|
204
|
+
`checkout_status` is payer-facing — `open`, `confirming`, `expired`, `paid`,
|
|
205
|
+
`unavailable` — and never authorizes fulfillment. `payment_revision` is a
|
|
206
|
+
non-negative integer that increments whenever the confirmed payment set changes,
|
|
207
|
+
so you can discard a snapshot older than one you already hold.
|
|
208
|
+
|
|
209
|
+
`payment_options` holds the payment instructions, fixed at creation and `[]` in
|
|
210
|
+
test mode. Entries are discriminated by `status`, then `collection_method`: only
|
|
211
|
+
`"ready"` is payable, `"evm_deposit"` carries `deposit_address` and
|
|
212
|
+
`suggested_amount`, `"direct_exact"` carries `recipient_address` and an
|
|
213
|
+
`exact_amount` the buyer must send to the digit. `transfers` is the confirmed
|
|
214
|
+
receipt trail — `transaction_id`, `event_index`, `amount`,
|
|
215
|
+
`explorer_transaction_url` — and stays `[]` until a payment confirms. Full field
|
|
216
|
+
reference: [REST API docs](https://github.com/invoqmoney/api).
|
|
217
|
+
|
|
218
|
+
Identify a payment option by `chain_namespace`, `chain_reference`, and
|
|
219
|
+
`token_address`, never by its position in the array. `monitoring_ends_at` is the
|
|
220
|
+
end of the payment window, and is `nil` for test invoices.
|
|
175
221
|
|
|
176
222
|
## Webhooks
|
|
177
223
|
|
|
@@ -206,6 +252,11 @@ def handle_invoq_webhook(env)
|
|
|
206
252
|
fulfillment_key = invoice["reference_id"] || invoice.fetch("id")
|
|
207
253
|
|
|
208
254
|
# Fulfill the order for fulfillment_key idempotently.
|
|
255
|
+
elsif Invoq.invoice_payment_reversed?(event)
|
|
256
|
+
invoice = event.fetch("data").fetch("invoice")
|
|
257
|
+
fulfillment_key = invoice["reference_id"] || invoice.fetch("id")
|
|
258
|
+
|
|
259
|
+
# Hold or reverse the order for fulfillment_key.
|
|
209
260
|
end
|
|
210
261
|
|
|
211
262
|
[
|
|
@@ -222,11 +273,16 @@ from browser results.
|
|
|
222
273
|
|
|
223
274
|
When `Invoq.invoice_paid?(event)` is true, the invoice is ready for automatic
|
|
224
275
|
fulfillment; use the invoice `reference_id` or a stored invoice `id` to find
|
|
225
|
-
and fulfill your order. A `review_required` invoice
|
|
226
|
-
|
|
276
|
+
and fulfill your order. A `review_required` invoice emits no `invoice.paid`
|
|
277
|
+
until the review clears. If checkout reports `review_required`, show a
|
|
227
278
|
pending-review state and wait for a later `invoice.paid` webhook after review
|
|
228
279
|
is approved.
|
|
229
280
|
|
|
281
|
+
invoq also sends `invoice.payment_reversed` when a previously paid invoice drops
|
|
282
|
+
back below its amount — a chain reorg dropping a confirmed transfer, for
|
|
283
|
+
example. Catch it with `Invoq.invoice_payment_reversed?(event)` and hold or
|
|
284
|
+
reverse the fulfillment according to your own policy.
|
|
285
|
+
|
|
230
286
|
Important:
|
|
231
287
|
|
|
232
288
|
- Pass the exact raw request body string received by your Ruby framework.
|
|
@@ -236,12 +292,19 @@ Important:
|
|
|
236
292
|
- Use your webhook secret (`whsec_...`), not `INVOQ_SECRET_KEY`.
|
|
237
293
|
- Make fulfillment idempotent. Retried webhook deliveries can send the same
|
|
238
294
|
event more than once.
|
|
239
|
-
- Respond with a 2xx quickly. Any other status counts as a failed delivery
|
|
240
|
-
|
|
241
|
-
|
|
295
|
+
- Respond with a 2xx quickly. Any other status counts as a failed delivery and
|
|
296
|
+
is retried, including redirects and `4xx`, so a deploy window or a temporarily
|
|
297
|
+
misrouted path is retried rather than dropped. The ladder is 1 minute,
|
|
298
|
+
5 minutes, 30 minutes, then 2 hours, for 5 attempts in total.
|
|
299
|
+
- Deliveries can arrive out of order. Keep the snapshot with the highest
|
|
300
|
+
`payment_revision`.
|
|
242
301
|
|
|
243
302
|
`Invoq.invoice_paid?` accepts fulfillable `invoice.paid` events whose invoice
|
|
244
303
|
status is `paid`, `settling`, or `settled`; it rejects `review_required`.
|
|
304
|
+
`Invoq.invoice_payment_reversed?` accepts `invoice.payment_reversed` events
|
|
305
|
+
without checking the status at all: a reversal you drop leaves an order
|
|
306
|
+
fulfilled on a payment that no longer exists. An event type this SDK version
|
|
307
|
+
does not model still verifies and is returned as-is.
|
|
245
308
|
|
|
246
309
|
Webhook verification failures raise `Invoq::SignatureVerificationError`. The
|
|
247
310
|
SDK allows a 5-minute timestamp tolerance. The signature header is:
|
|
@@ -254,7 +317,7 @@ invoq-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
|
|
|
254
317
|
|
|
255
318
|
```ruby
|
|
256
319
|
begin
|
|
257
|
-
invoq.invoices.create(amount: "0.001"
|
|
320
|
+
invoq.invoices.create(amount: "0.001")
|
|
258
321
|
rescue Invoq::ApiError => error
|
|
259
322
|
warn error.status
|
|
260
323
|
warn error.code
|
data/lib/invoq/client.rb
CHANGED
|
@@ -18,6 +18,12 @@ module Invoq
|
|
|
18
18
|
raise Error, "invoq API key must be a non-empty string."
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# A control character in a key is either rejected deep in the transport or
|
|
22
|
+
# silently sent; reject it here so every SDK answers the same way.
|
|
23
|
+
if api_key.match?(/[\x00-\x1F\x7F]/)
|
|
24
|
+
raise Error, "invoq API key must not contain control characters."
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
@api_key = api_key
|
|
22
28
|
@api_origin = Invoq.normalize_api_origin(api_origin)
|
|
23
29
|
@timeout_ms = Invoq.normalize_timeout_ms(timeout_ms)
|
|
@@ -51,6 +51,15 @@ module Invoq
|
|
|
51
51
|
)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# A non-Hash data is a broken envelope; returning it defers the failure
|
|
55
|
+
# to the caller's first read.
|
|
56
|
+
unless payload["data"].is_a?(Hash)
|
|
57
|
+
raise Error.new(
|
|
58
|
+
"invoq API response data envelope was not an object.",
|
|
59
|
+
payload: payload
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
54
63
|
payload["data"]
|
|
55
64
|
rescue JSON::GeneratorError
|
|
56
65
|
raise Error, "Failed to encode invoq API request."
|
|
@@ -116,8 +125,11 @@ module Invoq
|
|
|
116
125
|
fields = value.each_with_object([]) do |field, result|
|
|
117
126
|
next unless field.is_a?(Hash)
|
|
118
127
|
|
|
128
|
+
# A location this version does not know is passed through, not
|
|
129
|
+
# dropped: the caller is already on an error path and needs the code
|
|
130
|
+
# and message. Only a structurally invalid entry is discarded.
|
|
119
131
|
location = field["location"]
|
|
120
|
-
next unless
|
|
132
|
+
next unless location.is_a?(String)
|
|
121
133
|
next unless field["field"].is_a?(String)
|
|
122
134
|
next unless field["code"].is_a?(String)
|
|
123
135
|
next unless field["message"].is_a?(String)
|
|
@@ -24,7 +24,7 @@ module Invoq
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def get(invoice_id)
|
|
27
|
-
id = encode_path_segment(
|
|
27
|
+
id = encode_path_segment(required_path_segment(invoice_id, "invoice_id"))
|
|
28
28
|
|
|
29
29
|
Internal::Request.json(
|
|
30
30
|
api_key: @api_key,
|
|
@@ -36,7 +36,7 @@ module Invoq
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def create_test_payment(invoice_id, input)
|
|
39
|
-
id = encode_path_segment(
|
|
39
|
+
id = encode_path_segment(required_path_segment(invoice_id, "invoice_id"))
|
|
40
40
|
|
|
41
41
|
Internal::Request.json(
|
|
42
42
|
api_key: @api_key,
|
|
@@ -54,15 +54,16 @@ module Invoq
|
|
|
54
54
|
raise Error, "request body must be a hash."
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
# Only these four fields exist: the API rejects unknown body keys, and
|
|
58
|
+
# currency (always USD) and mode (from the key) are not request fields,
|
|
59
|
+
# so anything else in the hash is dropped rather than forwarded.
|
|
57
60
|
body = {
|
|
58
61
|
"amount" => required_request_string(input_value(input, "amount"), "amount")
|
|
59
62
|
}
|
|
60
|
-
currency = optional_request_value(input, "currency")
|
|
61
63
|
description = optional_request_string(input, "description")
|
|
62
64
|
reference_id = optional_request_string(input, "reference_id")
|
|
63
65
|
return_url = optional_nullable_request_string(input, "return_url")
|
|
64
66
|
|
|
65
|
-
body["currency"] = currency unless currency.equal?(MISSING)
|
|
66
67
|
body["description"] = description unless description.equal?(MISSING)
|
|
67
68
|
body["reference_id"] = reference_id unless reference_id.equal?(MISSING)
|
|
68
69
|
body["return_url"] = return_url unless return_url.equal?(MISSING)
|
|
@@ -107,6 +108,18 @@ module Invoq
|
|
|
107
108
|
value
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
# A URL resolver pops "." and "..", so an id of either would call a
|
|
112
|
+
# different endpoint instead of 404ing. Percent-encoding is no help.
|
|
113
|
+
def required_path_segment(value, field)
|
|
114
|
+
segment = required_request_string(value, field)
|
|
115
|
+
|
|
116
|
+
if segment == "." || segment == ".."
|
|
117
|
+
raise Error, "#{field} must not be a path segment that resolves ('.' or '..')."
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
segment
|
|
121
|
+
end
|
|
122
|
+
|
|
110
123
|
def required_request_string(value, field)
|
|
111
124
|
unless value.is_a?(String) && !value.strip.empty?
|
|
112
125
|
raise Error, "#{field} must be a non-empty string."
|
data/lib/invoq/version.rb
CHANGED
data/lib/invoq/webhooks.rb
CHANGED
|
@@ -56,39 +56,65 @@ module Invoq
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def self.invoice_paid?(event)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
invoice = lifecycle_event_invoice(event, "invoice.paid")
|
|
60
|
+
|
|
61
|
+
# Paid-equivalent statuses only: review_required has money against it but
|
|
62
|
+
# is not cleared for fulfillment.
|
|
63
|
+
!invoice.nil? && invoice_paid_status?(invoice["status"])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.is_invoice_paid(event)
|
|
67
|
+
invoice_paid?(event)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.invoice_payment_reversed?(event)
|
|
71
|
+
# No status check, unlike the paid guard: rejecting an unrecognized status
|
|
72
|
+
# would drop the event and leave the order fulfilled on a payment that no
|
|
73
|
+
# longer exists.
|
|
74
|
+
!lifecycle_event_invoice(event, "invoice.payment_reversed").nil?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.is_invoice_payment_reversed(event)
|
|
78
|
+
invoice_payment_reversed?(event)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The fields both lifecycle events share, returned so each guard can apply
|
|
82
|
+
# its own status rule. nil when this is not a well-formed event of that type.
|
|
83
|
+
def self.lifecycle_event_invoice(event, type)
|
|
84
|
+
return nil unless event.is_a?(Hash)
|
|
85
|
+
return nil unless event["type"] == type
|
|
86
|
+
return nil unless event["id"].is_a?(String)
|
|
87
|
+
return nil unless invoice_mode?(event["mode"])
|
|
88
|
+
return nil unless event["created_at"].is_a?(String)
|
|
64
89
|
|
|
65
90
|
data = event["data"]
|
|
66
|
-
return
|
|
91
|
+
return nil unless data.is_a?(Hash)
|
|
67
92
|
|
|
68
93
|
invoice = data["invoice"]
|
|
69
|
-
return
|
|
94
|
+
return nil unless invoice.is_a?(Hash)
|
|
70
95
|
|
|
71
96
|
reference_id = invoice.key?("reference_id") ? invoice["reference_id"] : MISSING
|
|
72
97
|
fully_paid_at = invoice.key?("fully_paid_at") ? invoice["fully_paid_at"] : MISSING
|
|
73
98
|
|
|
74
|
-
invoice["id"].is_a?(String) &&
|
|
99
|
+
valid = invoice["id"].is_a?(String) &&
|
|
75
100
|
invoice_mode?(invoice["mode"]) &&
|
|
76
|
-
|
|
101
|
+
invoice["status"].is_a?(String) &&
|
|
77
102
|
invoice["amount"].is_a?(String) &&
|
|
78
103
|
invoice["currency"] == "USD" &&
|
|
79
104
|
invoice["amount_paid"].is_a?(String) &&
|
|
80
105
|
(reference_id.is_a?(String) || reference_id.nil?) &&
|
|
106
|
+
invoice["payment_revision"].is_a?(Integer) &&
|
|
81
107
|
(fully_paid_at.is_a?(String) || fully_paid_at.nil?)
|
|
82
|
-
end
|
|
83
108
|
|
|
84
|
-
|
|
85
|
-
invoice_paid?(event)
|
|
109
|
+
valid ? invoice : nil
|
|
86
110
|
end
|
|
111
|
+
private_class_method :lifecycle_event_invoice
|
|
87
112
|
|
|
88
113
|
def self.parse_signature_header(signature_header)
|
|
89
114
|
parts = {}
|
|
90
115
|
|
|
91
|
-
|
|
116
|
+
# -1 keeps trailing empty fields, so a trailing comma reaches the check below.
|
|
117
|
+
signature_header.split(",", -1).each do |part|
|
|
92
118
|
separator_index = part.index("=")
|
|
93
119
|
|
|
94
120
|
unless separator_index
|
|
@@ -146,7 +172,7 @@ module Invoq
|
|
|
146
172
|
def self.hmac_sha256_hex(secret, timestamp, raw_body)
|
|
147
173
|
OpenSSL::HMAC.hexdigest(
|
|
148
174
|
"SHA256",
|
|
149
|
-
secret.
|
|
175
|
+
secret.b,
|
|
150
176
|
"#{timestamp}.".b + body_bytes(raw_body)
|
|
151
177
|
)
|
|
152
178
|
end
|
data/lib/invoq.rb
CHANGED
|
@@ -21,4 +21,12 @@ module Invoq
|
|
|
21
21
|
def self.is_invoice_paid(event)
|
|
22
22
|
invoice_paid?(event)
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
def self.invoice_payment_reversed?(event)
|
|
26
|
+
Webhooks.invoice_payment_reversed?(event)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.is_invoice_payment_reversed(event)
|
|
30
|
+
invoice_payment_reversed?(event)
|
|
31
|
+
end
|
|
24
32
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: invoq
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- invoq
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: minitest
|
|
@@ -45,7 +44,6 @@ dependencies:
|
|
|
45
44
|
- !ruby/object:Gem::Version
|
|
46
45
|
version: '14.0'
|
|
47
46
|
description: Ruby SDK for invoq server APIs and webhook verification.
|
|
48
|
-
email:
|
|
49
47
|
executables: []
|
|
50
48
|
extensions: []
|
|
51
49
|
extra_rdoc_files: []
|
|
@@ -64,7 +62,6 @@ licenses:
|
|
|
64
62
|
- MIT
|
|
65
63
|
metadata:
|
|
66
64
|
allowed_push_host: https://rubygems.org
|
|
67
|
-
post_install_message:
|
|
68
65
|
rdoc_options: []
|
|
69
66
|
require_paths:
|
|
70
67
|
- lib
|
|
@@ -79,8 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
79
76
|
- !ruby/object:Gem::Version
|
|
80
77
|
version: '0'
|
|
81
78
|
requirements: []
|
|
82
|
-
rubygems_version:
|
|
83
|
-
signing_key:
|
|
79
|
+
rubygems_version: 4.0.16
|
|
84
80
|
specification_version: 4
|
|
85
81
|
summary: Ruby SDK for invoq stablecoin payment acceptance.
|
|
86
82
|
test_files: []
|