Add invoice creation, quoting, and webhook signature verification
Finishes the two gaps that mattered most for real use: Invoices#create
(POST invoices) + Invoices#quote (POST invoices/{id}/quote, returning
the new Quote model with the actual payable bolt11 — an invoice alone
has nothing payable until quoted) and Webhook.valid_signature?
(HMAC-SHA256 over the raw body via X-Webhook-Signature, per Strike's own
documented reference implementation, timing-safe compare). Also finishes
Invoices#find (was a stub with a leftover binding.pry) and adds
WebhookEvent to parse the {id, eventType, data: {entityId, changes}}
payload shape — deliberately doesn't expose changed values, since
Strike's webhooks never carry them; a real value only ever comes from
re-fetching the resource.
Client gains #post/#patch (only #get existed before), needed for both
of the above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199wudpuAtkhouHKSEGxzpa
This commit is contained in:
1 parent
9b0351576a
commit
90ab7c74b1
11 files changed
+308
-4
No files matched your search
@@ -22,6 +22,7 @@ RSpec.describe StrikeMe::API::Invoices do
|
||||
'receiverId' => '41952e07-483c-4cd7-afa4-b6a8ec729809',
|
||||
'payerId' => 'f75c22bf-625f-4c47-acdf-acb9c596bb8c'
|
||||
)
|
||||
expect(list.first.id ).to eq '97dca64c-6dde-4a87-b970-279f1a8e0ac5'
|
||||
expect(list.first.created).to be_a Time
|
||||
|
||||
expect(list.last.attributes).to eq(
|
||||
@@ -37,4 +38,62 @@ RSpec.describe StrikeMe::API::Invoices do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find' do
|
||||
before do
|
||||
stub_request(:get, 'https://api.strike.me/v1/invoices/97dca64c-6dde-4a87-b970-279f1a8e0ac5')
|
||||
.to_return(status: 200, body: '{"invoiceId":"97dca64c-6dde-4a87-b970-279f1a8e0ac5","amount":{"amount":"0.00014","currency":"BTC"},"state":"PAID","created":"2025-08-08T22:28:44.195825+00:00","description":"Hi there","issuerId":"41952e07-483c-4cd7-afa4-b6a8ec729809","receiverId":"41952e07-483c-4cd7-afa4-b6a8ec729809","payerId":"f75c22bf-625f-4c47-acdf-acb9c596bb8c"}', headers: {'Content-Type' => 'application/json'})
|
||||
end
|
||||
|
||||
it 'finds and returns the proper object' do
|
||||
invoice = subject.find('97dca64c-6dde-4a87-b970-279f1a8e0ac5')
|
||||
|
||||
expect(invoice).to be_a StrikeMe::API::Invoices::Invoice
|
||||
expect(invoice.id).to eq '97dca64c-6dde-4a87-b970-279f1a8e0ac5'
|
||||
expect(invoice).to be_paid
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create' do
|
||||
before do
|
||||
stub_request(:post, 'https://api.strike.me/v1/invoices')
|
||||
.with(body: {amount: {amount: '10.00', currency: 'USD'}, description: 'Table 3'})
|
||||
.to_return(status: 201, body: '{"invoiceId":"602cc206-6b4a-42dd-bc89-9a534bb04034","amount":{"amount":"10.00","currency":"USD"},"state":"UNPAID","created":"2026-09-15T12:00:00+00:00","description":"Table 3","issuerId":"41952e07-483c-4cd7-afa4-b6a8ec729809","receiverId":"41952e07-483c-4cd7-afa4-b6a8ec729809"}', headers: {'Content-Type' => 'application/json'})
|
||||
end
|
||||
|
||||
it 'issues a new invoice, unpaid, with no bolt11 yet — that only exists once quoted' do
|
||||
invoice = subject.create(amount: '10.00', currency: 'USD', description: 'Table 3')
|
||||
|
||||
expect(invoice).to be_a StrikeMe::API::Invoices::Invoice
|
||||
expect(invoice.id).to eq '602cc206-6b4a-42dd-bc89-9a534bb04034'
|
||||
expect(invoice).to be_unpaid
|
||||
end
|
||||
|
||||
it 'omits correlationId/description entirely when not given, rather than sending them null' do
|
||||
stub = stub_request(:post, 'https://api.strike.me/v1/invoices')
|
||||
.with(body: {amount: {amount: '10.00', currency: 'USD'}})
|
||||
.to_return(status: 201, body: '{"invoiceId":"x","amount":{"amount":"10.00","currency":"USD"},"state":"UNPAID","created":"2026-09-15T12:00:00+00:00","issuerId":"a","receiverId":"a"}', headers: {'Content-Type' => 'application/json'})
|
||||
|
||||
subject.create(amount: '10.00', currency: 'USD')
|
||||
expect(stub).to have_been_requested
|
||||
end
|
||||
end
|
||||
|
||||
describe '#quote' do
|
||||
before do
|
||||
stub_request(:post, 'https://api.strike.me/v1/invoices/602cc206-6b4a-42dd-bc89-9a534bb04034/quote')
|
||||
.with(body: {})
|
||||
.to_return(status: 201, body: '{"quoteId":"a1b2c3","lnInvoice":"lnbc100n1p...","expiration":"2099-01-01T00:00:00+00:00","expirationInSec":300,"targetAmount":{"amount":"10.00","currency":"USD"},"sourceAmount":{"amount":"0.00014","currency":"BTC"},"conversionRate":{"amount":"71428.57","sourceCurrency":"BTC","targetCurrency":"USD"}}', headers: {'Content-Type' => 'application/json'})
|
||||
end
|
||||
|
||||
it 'returns a Quote carrying the actual payable bolt11' do
|
||||
quote = subject.quote('602cc206-6b4a-42dd-bc89-9a534bb04034')
|
||||
|
||||
expect(quote).to be_a StrikeMe::API::Invoices::Quote
|
||||
expect(quote.bolt11).to eq 'lnbc100n1p...'
|
||||
expect(quote.id).to eq 'a1b2c3'
|
||||
expect(quote.expiration).to be_a Time
|
||||
expect(quote).not_to be_expired
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user