diff --git a/app/models/alaveteli_pro/invoice.rb b/app/models/alaveteli_pro/invoice.rb index b982ba10e8..750fb0827c 100644 --- a/app/models/alaveteli_pro/invoice.rb +++ b/app/models/alaveteli_pro/invoice.rb @@ -10,7 +10,7 @@ def open? end def paid? - status == 'paid' + status == 'paid' && amount_paid > 0 end # attributes @@ -20,10 +20,11 @@ def date # charge def charge - @charge ||= Stripe::Charge.retrieve(__getobj__.charge) + charge_id = __getobj__.charge + @charge ||= Stripe::Charge.retrieve(charge_id) if charge_id end - delegate :receipt_url, to: :charge + delegate :receipt_url, to: :charge, allow_nil: true private diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 5a0822f0a9..2f26951658 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -2,6 +2,8 @@ ## Highlighted Features +* Fix rendering invoices page when there are discounted Pro subscription (Graeme + Porteous) * Drop support for Ruby 3.0 (Graeme Porteous) * Allow projects owners to publish datasets (Graeme Porteous) * Add comment deletion (Helen Cross, Graeme Porteous, Gareth Rees) diff --git a/spec/models/alaveteli_pro/invoice_spec.rb b/spec/models/alaveteli_pro/invoice_spec.rb new file mode 100644 index 0000000000..ce0ef784ee --- /dev/null +++ b/spec/models/alaveteli_pro/invoice_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +RSpec.describe AlaveteliPro::Invoice, type: :model do + let(:invoice) { AlaveteliPro::Invoice.new(stripe_invoice) } + + let(:stripe_invoice) do + double('Stripe::Invoice', + status: 'open', charge: 'ch_123', date: 1722211200, amount_paid: 0) + end + + let(:stripe_charge) do + double('Stripe::Charge', receipt_url: 'http://example.com/receipt') + end + + before do + allow(Stripe::Charge). + to receive(:retrieve).with('ch_123').and_return(stripe_charge) + end + + describe '#open?' do + it 'returns true when the status is open' do + expect(invoice).to be_open + end + + it 'returns false when the status is not open' do + allow(stripe_invoice).to receive(:status).and_return('paid') + expect(invoice).not_to be_open + end + end + + describe '#paid?' do + it 'returns true when the status is paid and an amount has been paid' do + allow(stripe_invoice).to receive(:status).and_return('paid') + allow(stripe_invoice).to receive(:amount_paid).and_return(1000) + expect(invoice).to be_paid + end + + it 'returns false when 100% discounted' do + allow(stripe_invoice).to receive(:status).and_return('paid') + expect(invoice).not_to be_paid + end + + it 'returns false when the status is not paid' do + allow(stripe_invoice).to receive(:amount_paid).and_return(1000) + expect(invoice).not_to be_paid + end + end + + describe '#date' do + it 'returns a date object for the invoice' do + with_env_tz 'UTC' do + expect(invoice.date).to eq(Date.new(2024, 7, 29)) + end + end + end + + describe '#charge' do + it 'returns a Stripe::Charge object' do + expect(invoice.charge).to eq(stripe_charge) + end + + it 'memoizes the Stripe::Charge object' do + expect(Stripe::Charge).to receive(:retrieve).once.with('ch_123') + 2.times { invoice.charge } + end + end + + describe '#receipt_url' do + it 'delegates receipt_url to the charge' do + expect(invoice.receipt_url).to eq('http://example.com/receipt') + end + + it 'returns nil when there is no charge' do + allow(stripe_invoice).to receive(:charge).and_return(nil) + expect(invoice.receipt_url).to be_nil + end + end + + describe '#method_missing' do + it 'forwards missing methods to the original object' do + allow(stripe_invoice). + to receive(:some_missing_method).and_return('result') + expect(invoice.some_missing_method).to eq('result') + end + end +end