From 5a5f62e98adba737ee623b37122e09440d2eda85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 4 Sep 2023 16:02:17 +0200 Subject: [PATCH] Refactor BTCPay service and API, add lightning balance --- .env.test | 5 +- app/controllers/api/btcpay_controller.rb | 21 +++++ app/controllers/api/kredits_controller.rb | 13 --- app/services/btc_pay.rb | 32 -------- .../fetch_lightning_wallet_balance.rb | 11 +++ .../fetch_onchain_wallet_balance.rb | 13 +++ app/services/btcpay_manager_service.rb | 22 ++++++ config/routes.rb | 3 +- spec/features/admin/settings_spec.rb | 2 +- spec/requests/api/btcpay_spec.rb | 79 +++++++++++++++++++ spec/requests/api/kredits_spec.rb | 43 ---------- 11 files changed, 152 insertions(+), 92 deletions(-) create mode 100644 app/controllers/api/btcpay_controller.rb delete mode 100644 app/controllers/api/kredits_controller.rb delete mode 100644 app/services/btc_pay.rb create mode 100644 app/services/btcpay_manager/fetch_lightning_wallet_balance.rb create mode 100644 app/services/btcpay_manager/fetch_onchain_wallet_balance.rb create mode 100644 app/services/btcpay_manager_service.rb create mode 100644 spec/requests/api/btcpay_spec.rb delete mode 100644 spec/requests/api/kredits_spec.rb diff --git a/.env.test b/.env.test index 33761c3..c032b0e 100644 --- a/.env.test +++ b/.env.test @@ -2,13 +2,14 @@ PRIMARY_DOMAIN=kosmos.org REDIS_URL='redis://localhost:6379/0' +BTCPAY_API_URL='http://btcpay.example.com/api/v1' +BTCPAY_STORE_ID='123456' + DISCOURSE_PUBLIC_URL='http://discourse.example.com' DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' EJABBERD_API_URL='http://xmpp.example.com/api' -BTCPAY_API_URL='http://btcpay.example.com/api/v1' - LNDHUB_API_URL='http://localhost:3026' LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' diff --git a/app/controllers/api/btcpay_controller.rb b/app/controllers/api/btcpay_controller.rb new file mode 100644 index 0000000..630a028 --- /dev/null +++ b/app/controllers/api/btcpay_controller.rb @@ -0,0 +1,21 @@ +class Api::BtcpayController < Api::BaseController + + def onchain_btc_balance + balance = BtcpayManager::FetchOnchainWalletBalance.call + render json: balance + rescue => error + Rails.logger.warn "Failed to fetch BTC wallet balance: #{error.message}" + render json: { error: 'Failed to fetch wallet balance' }, + status: 500 + end + + def lightning_btc_balance + balance = BtcpayManager::FetchLightningWalletBalance.call + render json: balance + rescue => error + Rails.logger.warn "Failed to fetch BTC lightning balance: #{error.message}" + render json: { error: 'Failed to fetch wallet balance' }, + status: 500 + end + +end diff --git a/app/controllers/api/kredits_controller.rb b/app/controllers/api/kredits_controller.rb deleted file mode 100644 index e54993d..0000000 --- a/app/controllers/api/kredits_controller.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Api::KreditsController < Api::BaseController - - def onchain_btc_balance - btcpay = BtcPay.new - balance = btcpay.onchain_wallet_balance - render json: balance - rescue => error - Rails.logger.warn "Failed to fetch kredits BTC wallet balance: #{error.message}" - render json: { error: 'Failed to fetch wallet balance' }, - status: 500 - end - -end diff --git a/app/services/btc_pay.rb b/app/services/btc_pay.rb deleted file mode 100644 index e612d0a..0000000 --- a/app/services/btc_pay.rb +++ /dev/null @@ -1,32 +0,0 @@ -# -# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ -# -class BtcPay - def initialize - @base_url = Setting.btcpay_api_url - @store_id = Setting.btcpay_store_id - @auth_token = Setting.btcpay_auth_token - end - - def onchain_wallet_balance - res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet" - - { - balance: res["balance"].to_f, - unconfirmed_balance: res["unconfirmedBalance"].to_f, - confirmed_balance: res["confirmedBalance"].to_f - } - end - - private - - def get(endpoint) - res = Faraday.get("#{@base_url}/#{endpoint}", {}, { - "Content-Type" => "application/json", - "Accept" => "application/json", - "Authorization" => "token #{@auth_token}" - }) - - JSON.parse(res.body) - end -end diff --git a/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb new file mode 100644 index 0000000..156d6aa --- /dev/null +++ b/app/services/btcpay_manager/fetch_lightning_wallet_balance.rb @@ -0,0 +1,11 @@ +module BtcpayManager + class FetchLightningWalletBalance < BtcpayManagerService + def call + res = get "stores/#{@store_id}/lightning/BTC/balance" + + { + balance: res["offchain"]["local"].to_i / 1000 # msats to sats + } + end + end +end diff --git a/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb new file mode 100644 index 0000000..2907d1b --- /dev/null +++ b/app/services/btcpay_manager/fetch_onchain_wallet_balance.rb @@ -0,0 +1,13 @@ +module BtcpayManager + class FetchOnchainWalletBalance < BtcpayManagerService + def call + res = get "stores/#{@store_id}/payment-methods/onchain/BTC/wallet" + + { + balance: (res["balance"].to_f * 100000000).to_i, # BTC to sats + unconfirmed_balance: (res["unconfirmedBalance"].to_f * 100000000).to_i, + confirmed_balance: (res["confirmedBalance"].to_f * 100000000).to_i + } + end + end +end diff --git a/app/services/btcpay_manager_service.rb b/app/services/btcpay_manager_service.rb new file mode 100644 index 0000000..1be3afc --- /dev/null +++ b/app/services/btcpay_manager_service.rb @@ -0,0 +1,22 @@ +# +# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ +# +class BtcpayManagerService < LdapService + def initialize + @base_url = Setting.btcpay_api_url + @store_id = Setting.btcpay_store_id + @auth_token = Setting.btcpay_auth_token + end + + private + + def get(endpoint) + res = Faraday.get("#{@base_url}/#{endpoint}", {}, { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => "token #{@auth_token}" + }) + + JSON.parse(res.body) + end +end diff --git a/config/routes.rb b/config/routes.rb index 3dc922a..e7fef10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -54,7 +54,8 @@ Rails.application.routes.draw do post 'webhooks/lndhub', to: 'webhooks#lndhub' namespace :api do - get 'kredits/onchain_btc_balance', to: 'kredits#onchain_btc_balance' + get 'btcpay/onchain_btc_balance', to: 'btcpay#onchain_btc_balance' + get 'btcpay/lightning_btc_balance', to: 'btcpay#lightning_btc_balance' end namespace :admin do diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb index 757f991..01a43f4 100644 --- a/spec/features/admin/settings_spec.rb +++ b/spec/features/admin/settings_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Admin/global settings', type: :feature do scenario "Opening service settings shows page for first service" do visit admin_settings_services_path - expect(current_url).to eq(admin_settings_services_url(params: { s: "discourse" })) + expect(current_url).to eq(admin_settings_services_url(params: { s: "btcpay" })) end scenario "View service settings" do diff --git a/spec/requests/api/btcpay_spec.rb b/spec/requests/api/btcpay_spec.rb new file mode 100644 index 0000000..7203df7 --- /dev/null +++ b/spec/requests/api/btcpay_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' +require 'webmock/rspec' + +RSpec.describe "/api/btcpay", type: :request do + + describe "GET /onchain_btc_balance" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 200, headers: {}, body: { + balance: 0.91108606, + unconfirmedBalance: 0, + confirmedBalance: 0.91108606 + }.to_json) + end + + it "returns a formatted result for the onchain wallet balance" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["balance"]).to eq(91108606) + expect(res["unconfirmed_balance"]).to eq(0) + expect(res["confirmed_balance"]).to eq(91108606) + end + + context "upstream request error" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 500, headers: {}, body: "") + end + + it "returns a formatted error" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:server_error) + + res = JSON.parse(response.body) + expect(res["error"]).not_to be_nil + end + end + end + + describe "GET /lightning_btc_balance" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/lightning/BTC/balance") + .to_return(status: 200, headers: {}, body: { + offchain: { + local: 4200000000 + }, + }.to_json) + end + + it "returns a formatted result for the onchain wallet balance" do + get api_btcpay_lightning_btc_balance_path + + expect(response).to have_http_status(:ok) + + res = JSON.parse(response.body) + expect(res["balance"]).to eq(4200000) + end + + context "upstream request error" do + before do + stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") + .to_return(status: 500, headers: {}, body: "") + end + + it "returns a formatted error" do + get api_btcpay_onchain_btc_balance_path + + expect(response).to have_http_status(:server_error) + + res = JSON.parse(response.body) + expect(res["error"]).not_to be_nil + end + end + end +end diff --git a/spec/requests/api/kredits_spec.rb b/spec/requests/api/kredits_spec.rb deleted file mode 100644 index 0e1e464..0000000 --- a/spec/requests/api/kredits_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'rails_helper' -require 'webmock/rspec' - -RSpec.describe "/api/kredits", type: :request do - - describe "GET /onchain_btc_balance" do - before do - stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") - .to_return(status: 200, headers: {}, body: { - balance: 0.91108606, - unconfirmedBalance: 0, - confirmedBalance: 0.91108606 - }.to_json) - end - - it "returns a formatted result for the onchain wallet balance" do - get api_kredits_onchain_btc_balance_path - - expect(response).to have_http_status(:ok) - - res = JSON.parse(response.body) - expect(res["balance"]).to eq(0.91108606) - expect(res["unconfirmed_balance"]).to eq(0) - expect(res["confirmed_balance"]).to eq(0.91108606) - end - - context "upstream request error" do - before do - stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/payment-methods/onchain/BTC/wallet") - .to_return(status: 500, headers: {}, body: "") - end - - it "returns a formatted error" do - get api_kredits_onchain_btc_balance_path - - expect(response).to have_http_status(:server_error) - - res = JSON.parse(response.body) - expect(res["error"]).not_to be_nil - end - end - end -end