require 'rails_helper' require 'webmock/rspec' RSpec.describe "Donations", type: :request do let(:user) { create :user, cn: 'jimmy', ou: 'kosmos.org' } before do Warden.test_mode! login_as user, scope: :user end after { Warden.test_reset! } describe "#create" do describe "with disabled methods" do before do Setting.btcpay_enabled = false end it "returns a 403" do post "/contributions/donations", params: { donation_method: "btcpay" } expect(response).to have_http_status(:forbidden) end end describe "with fake methods" do it "returns a 403" do post "/contributions/donations", params: { donation_method: "remotestorage" } expect(response).to have_http_status(:forbidden) end end describe "with invalid fiat currency" do it "returns a 422" do post "/contributions/donations", params: { donation_method: "btcpay", amount: "10", currency: "GBP" } expect(response).to have_http_status(:unprocessable_entity) end end describe "with bad amount" do it "returns a 422" do post "/contributions/donations", params: { donation_method: "btcpay", amount: "" } expect(response).to have_http_status(:unprocessable_entity) end end describe "with BTCPay" do before { Setting.btcpay_enabled = true } describe "amount in EUR" do before do expect(BtcpayManager::CreateInvoice).to receive(:call) .with(amount: 25, currency: "EUR", redirect_url: "http://www.example.com/contributions/donations/1/confirm_btcpay") .and_return({ "id" => "Q9GBe143HJIkdpZeH4Ftx5", "amount" => "25", "currency" => "EUR", "checkoutLink" => "#{Setting.btcpay_api_url}/i/Q9GBe143HJIkdpZeH4Ftx5", "expirationTime" => 1707908626, "checkout" => { "redirectURL" => "http://www.example.com/contributions/donations/1/confirm_btcpay" } }) post "/contributions/donations", params: { donation_method: "btcpay", amount: "25", currency: "EUR", public_name: "Mickey" } end it "creates a new donation record" do expect(user.donations.count).to eq(1) donation = user.donations.first expect(donation.donation_method).to eq("btcpay") expect(donation.payment_method).to be_nil expect(donation.paid_at).to be_nil expect(donation.public_name).to eq("Mickey") expect(donation.amount_sats).to be_nil expect(donation.fiat_amount).to eq(2500) expect(donation.fiat_currency).to eq("EUR") expect(donation.btcpay_invoice_id).to eq("Q9GBe143HJIkdpZeH4Ftx5") end it "redirects to the BTCPay checkout page" do expect(response).to redirect_to("https://btcpay.example.com/i/Q9GBe143HJIkdpZeH4Ftx5") end end describe "amount in sats" do before do expect(BtcpayManager::CreateInvoice).to receive(:call) .with(amount: 0.0001, currency: "BTC", redirect_url: "http://www.example.com/contributions/donations/1/confirm_btcpay") .and_return({ "id" => "Q9GBe143HJIkdpZeH4Ftx5", "amount" => "0.0001", "currency" => "BTC", "checkoutLink" => "#{Setting.btcpay_api_url}/i/Q9GBe143HJIkdpZeH4Ftx5", "expirationTime" => 1707908626, "checkout" => { "redirectURL" => "http://www.example.com/contributions/donations/1/confirm_btcpay" } }) post "/contributions/donations", params: { donation_method: "btcpay", amount: "10000", currency: "sats", public_name: "Garret Holmes" } end it "creates a new donation record" do expect(user.donations.count).to eq(1) donation = user.donations.first expect(donation.donation_method).to eq("btcpay") expect(donation.payment_method).to be_nil expect(donation.paid_at).to be_nil expect(donation.public_name).to eq("Garret Holmes") expect(donation.amount_sats).to eq(10000) expect(donation.fiat_amount).to be_nil expect(donation.fiat_currency).to be_nil expect(donation.btcpay_invoice_id).to eq("Q9GBe143HJIkdpZeH4Ftx5") end it "redirects to the BTCPay checkout page" do expect(response).to redirect_to("https://btcpay.example.com/i/Q9GBe143HJIkdpZeH4Ftx5") end end end end describe "#confirm_btcpay" do before { Setting.btcpay_enabled = true } describe "with donation of another user" do let(:other_user) { create :user, id: 3, cn: "carl", ou: 'kosmos.org', email: "carl@example.com" } before do @donation = other_user.donations.create!( donation_method: "btcpay", btcpay_invoice_id: "123abc", fiat_amount: 25, fiat_currency: "EUR", paid_at: nil ) get confirm_btcpay_contributions_donation_path(@donation.id) end it "returns a 404" do expect(response).to have_http_status(:not_found) end end describe "with confirmed donation" do before do @donation = user.donations.create!( donation_method: "btcpay", btcpay_invoice_id: "123abc", fiat_amount: 25, fiat_currency: "EUR", paid_at: "2024-02-16", payment_status: "settled" ) get confirm_btcpay_contributions_donation_path(@donation.id) end it "redirects to the donations index" do expect(response).to redirect_to(contributions_donations_url) end end describe "settled via Lightning" do describe "amount in EUR" do subject do user.donations.create!( donation_method: "btcpay", btcpay_invoice_id: "MCkDbf2cUgBuuisUCgnRnb", fiat_amount: 25, fiat_currency: "EUR", paid_at: nil ) end before do invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_eur_settled_invoice.json") payments = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_eur_settled_payments.json") stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb") .to_return(status: 200, headers: {}, body: invoice) stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods") .to_return(status: 200, headers: {}, body: payments) allow(user).to receive(:add_member_status).with(:sustainer).and_return(["sustainer"]) get confirm_btcpay_contributions_donation_path(subject) end it "updates the donation record" do subject.reload expect(subject.paid_at).not_to be_nil expect(subject.amount_sats).to eq(2061) expect(subject.payment_status).to eq("settled") end it "redirects to the donations index" do expect(response).to redirect_to(contributions_donations_url) end it "updates the user's member status" do expect(user).to have_received(:add_member_status).with(:sustainer) end end describe "amount in sats" do subject do user.donations.create!( donation_method: "btcpay", btcpay_invoice_id: "JxjfeJi1TtX8FcWSjEvGxg", amount_sats: 10000, fiat_amount: nil, fiat_currency: nil, paid_at: nil ) end before do invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_sats_settled_invoice.json") payments = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_sats_settled_payments.json") stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/JxjfeJi1TtX8FcWSjEvGxg") .to_return(status: 200, headers: {}, body: invoice) stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/JxjfeJi1TtX8FcWSjEvGxg/payment-methods") .to_return(status: 200, headers: {}, body: payments) expect(BtcpayManager::FetchExchangeRate).to receive(:call) .with(fiat_currency: "EUR").and_return(48532.00) get confirm_btcpay_contributions_donation_path(subject) end it "updates the donation record" do subject.reload expect(subject.paid_at).not_to be_nil expect(subject.amount_sats).to eq(10000) expect(subject.fiat_amount).to eq(485) expect(subject.fiat_currency).to eq("EUR") end it "redirects to the donations index" do expect(response).to redirect_to(contributions_donations_url) end end end describe "on-chain" do describe "waiting for confirmations" do subject do user.donations.create!( donation_method: "btcpay", btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3", fiat_amount: 120, fiat_currency: "USD", paid_at: nil ) end before do invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/onchain_eur_processing_invoice.json") payments = File.read("#{Rails.root}/spec/fixtures/btcpay/onchain_eur_processing_payments.json") stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/K4e31MhbLKmr3D7qoNYRd3") .to_return(status: 200, headers: {}, body: invoice) stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/K4e31MhbLKmr3D7qoNYRd3/payment-methods") .to_return(status: 200, headers: {}, body: payments) get confirm_btcpay_contributions_donation_path(subject) end it "updates the donation record" do subject.reload expect(subject.paid_at).to be_nil expect(subject.amount_sats).to eq(191354) expect(subject.payment_status).to eq("processing") end it "enqueues a job to periodically check the invoice status" do expect(enqueued_jobs.size).to eq(1) expect(enqueued_jobs.first["job_class"]).to eq("BtcpayCheckDonationJob") expect(enqueued_jobs.first['arguments'][0]["_aj_globalid"]).to eq("gid://akkounts/Donation/#{subject.id}") end it "redirects to the donations index" do expect(response).to redirect_to(contributions_donations_url) end end end end end