invoq 0.2.1 → 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 +66 -26
- 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 +1 -1
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,6 +1,6 @@
|
|
|
1
1
|
# invoq Ruby SDK
|
|
2
2
|
|
|
3
|
-
**English** · [Bahasa Indonesia](
|
|
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
4
|
|
|
5
5
|
Accept stablecoin payments with invoq from Ruby server code. This SDK wraps
|
|
6
6
|
invoq server APIs and verifies signed webhooks.
|
|
@@ -8,6 +8,12 @@ invoq server APIs and verifies signed webhooks.
|
|
|
8
8
|
Use this gem only on your server. It handles secret keys and should not be
|
|
9
9
|
bundled into browser code.
|
|
10
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
|
+
|
|
11
17
|
## Server SDKs
|
|
12
18
|
|
|
13
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.
|
|
@@ -49,6 +55,9 @@ Requires Ruby 2.6 or newer.
|
|
|
49
55
|
3. In your project's **webhooks** settings, save your webhook URL. The webhook
|
|
50
56
|
secret (`whsec_...`) for that mode is shown once, when you first enable the
|
|
51
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`.
|
|
52
61
|
|
|
53
62
|
Add both to your server environment:
|
|
54
63
|
|
|
@@ -97,7 +106,6 @@ Create an invoice:
|
|
|
97
106
|
```ruby
|
|
98
107
|
invoice = invoq.invoices.create(
|
|
99
108
|
amount: "129",
|
|
100
|
-
currency: "USD",
|
|
101
109
|
description: "SaaS boilerplate",
|
|
102
110
|
reference_id: "order_1234",
|
|
103
111
|
return_url: "https://merchant.example/thanks"
|
|
@@ -110,9 +118,9 @@ checkout_url = "https://pay.invoq.money/#{invoice_id}"
|
|
|
110
118
|
Notes:
|
|
111
119
|
|
|
112
120
|
- Use a server-side amount. Do not trust client-supplied amounts.
|
|
113
|
-
- `amount` is a decimal USD string from `"0.01"` to `"
|
|
114
|
-
to 2 decimal places, such as `"129"` or `"129.99"`.
|
|
115
|
-
|
|
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.
|
|
116
124
|
- Use a stable, non-empty `reference_id` to map `invoice.paid` webhooks back to
|
|
117
125
|
your order. Creating again with the same `reference_id` and invoice terms
|
|
118
126
|
returns the existing invoice; different terms fail with a
|
|
@@ -131,12 +139,10 @@ Get an invoice:
|
|
|
131
139
|
invoice = invoq.invoices.get("inv_123")
|
|
132
140
|
```
|
|
133
141
|
|
|
134
|
-
`invoices.get` returns the public invoice shape used by hosted checkout
|
|
135
|
-
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
include `reference_id`. Use the create response or `invoice.paid` webhook when
|
|
139
|
-
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`.
|
|
140
146
|
|
|
141
147
|
Create a test payment:
|
|
142
148
|
|
|
@@ -161,8 +167,7 @@ webhook URL.
|
|
|
161
167
|
|
|
162
168
|
To receive webhooks on your machine, expose your local server with an HTTPS
|
|
163
169
|
tunnel such as ngrok or cloudflared and save the tunnel URL as your test webhook
|
|
164
|
-
URL in the dashboard.
|
|
165
|
-
check connectivity.
|
|
170
|
+
URL in the dashboard.
|
|
166
171
|
|
|
167
172
|
Each invoice method returns the response `data` object directly as a Ruby hash.
|
|
168
173
|
|
|
@@ -179,22 +184,40 @@ Share the link or redirect to it when an in-page checkout modal is not a fit.
|
|
|
179
184
|
## Inputs and responses
|
|
180
185
|
|
|
181
186
|
The SDK checks that `amount` values and `invoice_id` arguments are non-empty
|
|
182
|
-
strings before sending requests. The invoq API validates the amount format
|
|
183
|
-
range
|
|
187
|
+
strings before sending requests. The invoq API validates the amount format and
|
|
188
|
+
range.
|
|
184
189
|
|
|
185
190
|
Leave unset optional fields out of the request hash. When you include
|
|
186
191
|
`description` or `reference_id`, pass a string. `return_url` can be a string or
|
|
187
|
-
`nil`.
|
|
192
|
+
`nil`. Any other key in the hash is dropped rather than sent, because the API
|
|
193
|
+
rejects unknown body keys.
|
|
188
194
|
|
|
189
195
|
Amounts in responses are normalized to 4 decimal places: create with `"129"`
|
|
190
196
|
and the invoice returns `amount: "129.0000"`. Compare amounts numerically, not
|
|
191
197
|
as strings. `amount_due` is derived as `max(amount - amount_paid, 0)` and uses
|
|
192
198
|
the same 18-decimal scale as `amount_paid`; `amount_overpaid` is its mirror,
|
|
193
199
|
`max(amount_paid - amount, 0)`, so you never subtract money yourself.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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.
|
|
198
221
|
|
|
199
222
|
## Webhooks
|
|
200
223
|
|
|
@@ -229,6 +252,11 @@ def handle_invoq_webhook(env)
|
|
|
229
252
|
fulfillment_key = invoice["reference_id"] || invoice.fetch("id")
|
|
230
253
|
|
|
231
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.
|
|
232
260
|
end
|
|
233
261
|
|
|
234
262
|
[
|
|
@@ -245,11 +273,16 @@ from browser results.
|
|
|
245
273
|
|
|
246
274
|
When `Invoq.invoice_paid?(event)` is true, the invoice is ready for automatic
|
|
247
275
|
fulfillment; use the invoice `reference_id` or a stored invoice `id` to find
|
|
248
|
-
and fulfill your order. A `review_required` invoice
|
|
249
|
-
|
|
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
|
|
250
278
|
pending-review state and wait for a later `invoice.paid` webhook after review
|
|
251
279
|
is approved.
|
|
252
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
|
+
|
|
253
286
|
Important:
|
|
254
287
|
|
|
255
288
|
- Pass the exact raw request body string received by your Ruby framework.
|
|
@@ -259,12 +292,19 @@ Important:
|
|
|
259
292
|
- Use your webhook secret (`whsec_...`), not `INVOQ_SECRET_KEY`.
|
|
260
293
|
- Make fulfillment idempotent. Retried webhook deliveries can send the same
|
|
261
294
|
event more than once.
|
|
262
|
-
- Respond with a 2xx quickly. Any other status counts as a failed delivery
|
|
263
|
-
|
|
264
|
-
|
|
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`.
|
|
265
301
|
|
|
266
302
|
`Invoq.invoice_paid?` accepts fulfillable `invoice.paid` events whose invoice
|
|
267
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.
|
|
268
308
|
|
|
269
309
|
Webhook verification failures raise `Invoq::SignatureVerificationError`. The
|
|
270
310
|
SDK allows a 5-minute timestamp tolerance. The signature header is:
|
|
@@ -277,7 +317,7 @@ invoq-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
|
|
|
277
317
|
|
|
278
318
|
```ruby
|
|
279
319
|
begin
|
|
280
|
-
invoq.invoices.create(amount: "0.001"
|
|
320
|
+
invoq.invoices.create(amount: "0.001")
|
|
281
321
|
rescue Invoq::ApiError => error
|
|
282
322
|
warn error.status
|
|
283
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
|