Set member status to sustainer upon payment
Introduces a state machine for the payment status as well. refs #213
This commit is contained in:
parent
463bf34cdf
commit
e48132cf5f
1
Gemfile
1
Gemfile
@ -32,6 +32,7 @@ gem 'devise_ldap_authenticatable'
|
|||||||
gem 'net-ldap'
|
gem 'net-ldap'
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
|
gem 'aasm'
|
||||||
gem "image_processing", "~> 1.12.2"
|
gem "image_processing", "~> 1.12.2"
|
||||||
gem "rqrcode", "~> 2.0"
|
gem "rqrcode", "~> 2.0"
|
||||||
gem 'rails-settings-cached', '~> 2.8.3'
|
gem 'rails-settings-cached', '~> 2.8.3'
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
aasm (5.5.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
actioncable (8.0.2)
|
actioncable (8.0.2)
|
||||||
actionpack (= 8.0.2)
|
actionpack (= 8.0.2)
|
||||||
activesupport (= 8.0.2)
|
activesupport (= 8.0.2)
|
||||||
@ -526,6 +528,7 @@ PLATFORMS
|
|||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
aasm
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
bcrypt (~> 3.1)
|
bcrypt (~> 3.1)
|
||||||
capybara
|
capybara
|
||||||
|
@ -11,7 +11,7 @@ class Contributions::DonationsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@current_section = :contributions
|
@current_section = :contributions
|
||||||
@donations_completed = current_user.donations.completed.order('paid_at desc')
|
@donations_completed = current_user.donations.completed.order('paid_at desc')
|
||||||
@donations_pending = current_user.donations.processing.order('created_at desc')
|
@donations_processing = current_user.donations.processing.order('created_at desc')
|
||||||
|
|
||||||
if Setting.lndhub_enabled?
|
if Setting.lndhub_enabled?
|
||||||
begin
|
begin
|
||||||
@ -81,14 +81,11 @@ class Contributions::DonationsController < ApplicationController
|
|||||||
|
|
||||||
case invoice["status"]
|
case invoice["status"]
|
||||||
when "Settled"
|
when "Settled"
|
||||||
@donation.paid_at = DateTime.now
|
@donation.complete!
|
||||||
@donation.payment_status = "settled"
|
|
||||||
@donation.save!
|
|
||||||
flash_message = { success: "Thank you!" }
|
flash_message = { success: "Thank you!" }
|
||||||
when "Processing"
|
when "Processing"
|
||||||
unless @donation.processing?
|
unless @donation.processing?
|
||||||
@donation.payment_status = "processing"
|
@donation.start_processing!
|
||||||
@donation.save!
|
|
||||||
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
||||||
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
||||||
end
|
end
|
||||||
|
@ -10,9 +10,7 @@ class BtcpayCheckDonationJob < ApplicationJob
|
|||||||
|
|
||||||
case invoice["status"]
|
case invoice["status"]
|
||||||
when "Settled"
|
when "Settled"
|
||||||
donation.paid_at = DateTime.now
|
donation.complete!
|
||||||
donation.payment_status = "settled"
|
|
||||||
donation.save!
|
|
||||||
|
|
||||||
NotificationMailer.with(user: donation.user)
|
NotificationMailer.with(user: donation.user)
|
||||||
.bitcoin_donation_confirmed
|
.bitcoin_donation_confirmed
|
||||||
|
@ -1,23 +1,42 @@
|
|||||||
class Donation < ApplicationRecord
|
class Donation < ApplicationRecord
|
||||||
# Relations
|
include AASM
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
# Validations
|
|
||||||
validates_presence_of :user
|
validates_presence_of :user
|
||||||
validates_presence_of :donation_method,
|
validates_presence_of :donation_method,
|
||||||
inclusion: { in: %w[ custom btcpay lndhub ] }
|
inclusion: { in: %w[ custom btcpay lndhub ] }
|
||||||
validates_presence_of :payment_status, allow_nil: true,
|
validates_presence_of :payment_status, allow_nil: true,
|
||||||
inclusion: { in: %w[ processing settled ] }
|
inclusion: { in: %w[ pending processing settled ] }
|
||||||
validates_presence_of :paid_at, allow_nil: true
|
validates_presence_of :paid_at, allow_nil: true
|
||||||
validates_presence_of :amount_sats, allow_nil: true
|
validates_presence_of :amount_sats, allow_nil: true
|
||||||
validates_presence_of :fiat_amount, allow_nil: true
|
validates_presence_of :fiat_amount, allow_nil: true
|
||||||
validates_presence_of :fiat_currency, allow_nil: true,
|
validates_presence_of :fiat_currency, allow_nil: true,
|
||||||
inclusion: { in: %w[ EUR USD ] }
|
inclusion: { in: %w[ EUR USD ] }
|
||||||
|
|
||||||
#Scopes
|
scope :pending, -> { where(payment_status: "pending") }
|
||||||
scope :processing, -> { where(payment_status: "processing") }
|
scope :processing, -> { where(payment_status: "processing") }
|
||||||
scope :completed, -> { where(payment_status: "settled") }
|
scope :completed, -> { where(payment_status: "settled") }
|
||||||
|
|
||||||
|
aasm column: :payment_status do
|
||||||
|
state :pending, initial: true
|
||||||
|
state :processing
|
||||||
|
state :settled
|
||||||
|
|
||||||
|
event :start_processing do
|
||||||
|
transitions from: :pending, to: :processing
|
||||||
|
end
|
||||||
|
|
||||||
|
event :complete do
|
||||||
|
transitions from: :processing, to: :settled, after: [:set_paid_at, :set_sustainer_status]
|
||||||
|
transitions from: :pending, to: :settled, after: [:set_paid_at, :set_sustainer_status]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pending?
|
||||||
|
payment_status == "pending"
|
||||||
|
end
|
||||||
|
|
||||||
def processing?
|
def processing?
|
||||||
payment_status == "processing"
|
payment_status == "processing"
|
||||||
end
|
end
|
||||||
@ -25,4 +44,17 @@ class Donation < ApplicationRecord
|
|||||||
def completed?
|
def completed?
|
||||||
payment_status == "settled"
|
payment_status == "settled"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_paid_at
|
||||||
|
update paid_at: DateTime.now if paid_at.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_sustainer_status
|
||||||
|
user.add_member_status :sustainer
|
||||||
|
rescue => e
|
||||||
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||||
|
Rails.logger.error("Failed to set memberStatus: #{e.message}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% if @donations_pending.any? %>
|
<% if @donations_processing.any? %>
|
||||||
<section class="donation-list">
|
<section class="donation-list">
|
||||||
<h2>Pending</h2>
|
<h2>Pending</h2>
|
||||||
<%= render partial: "contributions/donations/list",
|
<%= render partial: "contributions/donations/list",
|
||||||
locals: { donations: @donations_pending } %>
|
locals: { donations: @donations_processing } %>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
class UpdatePaymentStatusToPending < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
Donation.where(payment_status: nil).update_all(payment_status: "pending")
|
||||||
|
Donation.where.not(payment_status: %w[pending processing settled]).update_all(payment_status: "pending")
|
||||||
|
end
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_05_17_105755) do
|
ActiveRecord::Schema[8.0].define(version: 2025_05_27_113805) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
create_table "active_storage_attachments", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "record_type", null: false
|
t.string "record_type", null: false
|
||||||
|
@ -8,16 +8,18 @@ RSpec.describe BtcpayCheckDonationJob, type: :job do
|
|||||||
user.donations.create!(
|
user.donations.create!(
|
||||||
donation_method: "btcpay",
|
donation_method: "btcpay",
|
||||||
btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3",
|
btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3",
|
||||||
paid_at: nil, payment_status: "processing",
|
paid_at: nil,
|
||||||
fiat_amount: 120, fiat_currency: "USD"
|
payment_status: "processing",
|
||||||
|
fiat_amount: 120,
|
||||||
|
fiat_currency: "USD"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
allow_any_instance_of(User).to receive(:ldap_entry).and_return({
|
||||||
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
|
uid: user.cn, ou: user.ou, mail: user.email, admin: nil, display_name: nil
|
||||||
display_name: nil
|
|
||||||
})
|
})
|
||||||
|
allow_any_instance_of(User).to receive(:add_member_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
after(:each) do
|
after(:each) do
|
||||||
@ -65,15 +67,20 @@ RSpec.describe BtcpayCheckDonationJob, type: :job do
|
|||||||
it "notifies the user via email" do
|
it "notifies the user via email" do
|
||||||
perform_enqueued_jobs(only: described_class) { job }
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
expect(enqueued_jobs.size).to eq(1)
|
expect(enqueued_jobs.size).to eq(1)
|
||||||
job = enqueued_jobs.select{|j| j['job_class'] == "ActionMailer::MailDeliveryJob"}.first
|
job = enqueued_jobs.select { |j| j['job_class'] == "ActionMailer::MailDeliveryJob" }.first
|
||||||
expect(job['arguments'][0]).to eq('NotificationMailer')
|
expect(job['arguments'][0]).to eq('NotificationMailer')
|
||||||
expect(job['arguments'][1]).to eq('bitcoin_donation_confirmed')
|
expect(job['arguments'][1]).to eq('bitcoin_donation_confirmed')
|
||||||
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq('gid://akkounts/User/1')
|
expect(job['arguments'][3]['params']['user']['_aj_globalid']).to eq(user.to_global_id.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not enqueue itself again" do
|
it "does not enqueue itself again" do
|
||||||
expect_any_instance_of(described_class).not_to receive(:re_enqueue_job)
|
expect_any_instance_of(described_class).not_to receive(:re_enqueue_job)
|
||||||
perform_enqueued_jobs(only: described_class) { job }
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the user's member status" do
|
||||||
|
expect_any_instance_of(User).to receive(:add_member_status).with(:sustainer)
|
||||||
|
perform_enqueued_jobs(only: described_class) { job }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -177,7 +177,7 @@ RSpec.describe "Donations", type: :request do
|
|||||||
.to_return(status: 200, headers: {}, body: invoice)
|
.to_return(status: 200, headers: {}, body: invoice)
|
||||||
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods")
|
stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods")
|
||||||
.to_return(status: 200, headers: {}, body: payments)
|
.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)
|
get confirm_btcpay_contributions_donation_path(subject)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -185,11 +185,16 @@ RSpec.describe "Donations", type: :request do
|
|||||||
subject.reload
|
subject.reload
|
||||||
expect(subject.paid_at).not_to be_nil
|
expect(subject.paid_at).not_to be_nil
|
||||||
expect(subject.amount_sats).to eq(2061)
|
expect(subject.amount_sats).to eq(2061)
|
||||||
|
expect(subject.payment_status).to eq("settled")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "redirects to the donations index" do
|
it "redirects to the donations index" do
|
||||||
expect(response).to redirect_to(contributions_donations_url)
|
expect(response).to redirect_to(contributions_donations_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "updates the user's member status" do
|
||||||
|
expect(user).to have_received(:add_member_status).with(:sustainer)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "amount in sats" do
|
describe "amount in sats" do
|
||||||
|
Loading…
x
Reference in New Issue
Block a user