-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(manual-payments): Add create manual payment service
- Loading branch information
1 parent
a65e674
commit 0262dcf
Showing
5 changed files
with
354 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# frozen_string_literal: true | ||
|
||
module ManualPayments | ||
class CreateService < BaseService | ||
def initialize(invoice:, params:) | ||
@invoice = invoice | ||
@params = params | ||
|
||
super | ||
end | ||
|
||
def call | ||
check_preconditions | ||
return result if result.error | ||
|
||
amount_cents = params[:amount_cents] | ||
|
||
ActiveRecord::Base.transaction do | ||
payment = invoice.payments.create!( | ||
amount_cents:, | ||
reference: params[:reference], | ||
amount_currency: invoice.currency, | ||
status: 'succeeded', | ||
payable_payment_status: 'succeeded', | ||
payment_type: :manual | ||
) | ||
|
||
invoice.update!(total_paid_amount_cents: invoice.total_paid_amount_cents + amount_cents) | ||
|
||
result.payment = payment | ||
|
||
if invoice.payments.where(payable_payment_status: 'succeeded').sum(:amount_cents) == invoice.total_amount_cents | ||
payment.payable.update!(payment_status: 'succeeded') | ||
end | ||
|
||
Integrations::Aggregator::Payments::CreateJob.perform_later(payment:) if result.payment&.should_sync_payment? | ||
end | ||
|
||
result | ||
rescue ActiveRecord::RecordInvalid => e | ||
result.record_validation_failure!(record: e.record) | ||
end | ||
|
||
private | ||
|
||
attr_reader :invoice, :params | ||
|
||
def check_preconditions | ||
return result.forbidden_failure! unless License.premium? | ||
return result.not_found_failure!(resource: "invoice") unless invoice | ||
result.forbidden_failure! unless invoice.organization.premium_integrations.include?('manual_payments') | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# frozen_string_literal: true | ||
|
||
require "rails_helper" | ||
|
||
RSpec.describe ManualPayments::CreateService, type: :service do | ||
subject(:service) { described_class.new(invoice:, params:) } | ||
|
||
let(:invoice) { create(:invoice, customer:, organization:, total_amount_cents: 10000, status: :finalized) } | ||
let(:organization) { create(:organization, premium_integrations:) } | ||
let(:customer) { create(:customer, organization:) } | ||
let(:params) { {amount_cents:, reference: "ref1"} } | ||
let(:amount_cents) { 10000 } | ||
|
||
describe "#call" do | ||
context "when organization is not premium" do | ||
let(:premium_integrations) { %w[] } | ||
|
||
it "returns forbidden failure" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result).not_to be_success | ||
expect(result.error).to be_a(BaseService::ForbiddenFailure) | ||
end | ||
end | ||
end | ||
|
||
context "when organization is premium" do | ||
around { |test| lago_premium!(&test) } | ||
|
||
context "with the premium_integration disabled" do | ||
let(:premium_integrations) { %w[] } | ||
|
||
it "returns forbidden failure" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result).not_to be_success | ||
expect(result.error).to be_a(BaseService::ForbiddenFailure) | ||
end | ||
end | ||
end | ||
|
||
context "with the premium_integration enabled" do | ||
let(:premium_integrations) { %w[manual_payments] } | ||
|
||
context "when invoice does not exist" do | ||
let(:invoice) { nil } | ||
|
||
it "returns not found failure" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result).not_to be_success | ||
expect(result.error).to be_a(BaseService::NotFoundFailure) | ||
end | ||
end | ||
end | ||
|
||
context "when invoice's payment request is succeeded" do | ||
let(:payment_request) { create(:payment_request, payment_status: "succeeded") } | ||
|
||
before do | ||
create(:payment_request_applied_invoice, invoice:, payment_request:) | ||
end | ||
|
||
it "returns validation failure" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result).not_to be_success | ||
expect(result.error).to be_a(BaseService::ValidationFailure) | ||
end | ||
end | ||
end | ||
|
||
context "when payment amount cents is greater than invoice's remaining amount cents" do | ||
let(:amount_cents) { 10001 } | ||
|
||
it "returns validation failure" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result).not_to be_success | ||
expect(result.error).to be_a(BaseService::ValidationFailure) | ||
end | ||
end | ||
end | ||
|
||
context "when payment amount cents is smaller than invoice remaining amount cents" do | ||
let(:amount_cents) { 2000 } | ||
|
||
it "creates a payment" do | ||
result = service.call | ||
|
||
expect(result).to be_success | ||
expect(result.payment.payment_type).to eq("manual") | ||
end | ||
|
||
it "updates invoice's total paid amount cents" do | ||
expect { service.call }.to change(invoice, :total_paid_amount_cents).from(0).to(amount_cents) | ||
end | ||
|
||
context "when there is an integration customer" do | ||
let(:integration) do | ||
create( | ||
:netsuite_integration, | ||
organization:, | ||
settings: { | ||
account_id: "acc_12345", | ||
client_id: "cli_12345", | ||
script_endpoint_url: Faker::Internet.url, | ||
sync_payments: true | ||
} | ||
) | ||
end | ||
|
||
before { create(:netsuite_customer, integration:, customer:) } | ||
|
||
it "enqueues an aggregator payment job" do | ||
expect { service.call }.to have_enqueued_job(Integrations::Aggregator::Payments::CreateJob) | ||
end | ||
end | ||
end | ||
|
||
context "when payment amount cents is equal to invoice remaining amount cents" do | ||
let(:amount_cents) { 10000 } | ||
|
||
it "creates a payment" do | ||
result = service.call | ||
|
||
expect(result).to be_success | ||
expect(result.payment.payment_type).to eq("manual") | ||
end | ||
|
||
it "updates invoice's total paid amount cents" do | ||
expect { service.call }.to change(invoice, :total_paid_amount_cents).from(0).to(amount_cents) | ||
end | ||
|
||
it "updates invoice's payment status to suceeded" do | ||
result = service.call | ||
|
||
aggregate_failures do | ||
expect(result.payment.payable.payment_status).to eq("succeeded") | ||
expect(result.payment.payable_payment_status).to eq("succeeded") | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |