Skip to content

Commit

Permalink
feat(payment-receipts): Add model methods and serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
ivannovosad committed Feb 21, 2025
1 parent 3ae16fa commit faeb279
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 32 deletions.
4 changes: 3 additions & 1 deletion app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class Organization < ApplicationRecord

EMAIL_SETTINGS = [
"invoice.finalized",
"credit_note.created"
"credit_note.created",
"payment_receipt.created"
].freeze

has_many :api_keys
Expand Down Expand Up @@ -78,6 +79,7 @@ class Organization < ApplicationRecord
remove_branding_watermark
manual_payments
from_email
issue_receipts
preview
].freeze
PREMIUM_INTEGRATIONS = INTEGRATIONS - %w[anrok]
Expand Down
21 changes: 21 additions & 0 deletions app/models/payment_receipt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

class PaymentReceipt < ApplicationRecord
belongs_to :payment

scope :for_organization, lambda { |organization|
payables_join = ActiveRecord::Base.sanitize_sql_array([
<<~SQL,
INNER JOIN payments
ON payments.id = payment_receipts.payment_id
LEFT JOIN invoices
ON invoices.id = payments.payable_id
AND payments.payable_type = 'Invoice'
AND invoices.organization_id = :org_id
AND invoices.status IN (:visible_statuses)
LEFT JOIN payment_requests
ON payment_requests.id = payments.payable_id
AND payments.payable_type = 'PaymentRequest'
AND payment_requests.organization_id = :org_id
SQL
{org_id: organization.id, visible_statuses: Invoice::VISIBLE_STATUS.values}
])
joins(payables_join)
.where("invoices.id IS NOT NULL OR payment_requests.id IS NOT NULL")
}
end

# == Schema Information
Expand Down
20 changes: 20 additions & 0 deletions app/serializers/v1/payment_receipt_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

module V1
class PaymentReceiptSerializer < ModelSerializer
def serialize
{
lago_id: model.id,
number: model.reload.number,
payment: payment,
created_at: model.created_at.iso8601
}
end

private

def payment
::V1::PaymentSerializer.new(model.payment).serialize
end
end
end
13 changes: 12 additions & 1 deletion app/serializers/v1/payment_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module V1
class PaymentSerializer < ModelSerializer
def serialize
{
payload = {
lago_id: model.id,
invoice_ids: invoice_id,
amount_cents: model.amount_cents,
Expand All @@ -14,10 +14,21 @@ def serialize
external_payment_id: model.provider_payment_id,
created_at: model.created_at.iso8601
}

payload.merge!(payment_receipt) if include?(:payment_receipt)
payload
end

private

def payment_receipt
{
payment_receipt: model.payment_receipt ?
::V1::PaymentReceiptSerializer.new(model.payment_receipt).serialize :
{}
}
end

def invoice_id
model.payable.is_a?(Invoice) ? [model.payable.id] : model.payable.invoice_ids
end
Expand Down
7 changes: 7 additions & 0 deletions schema.graphql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions spec/models/invoice_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
it { is_expected.to have_many(:applied_payment_requests).class_name("PaymentRequest::AppliedInvoice") }
it { is_expected.to have_many(:payment_requests).through(:applied_payment_requests) }
it { is_expected.to have_many(:payments) }
it { is_expected.to have_many(:payment_receipts).through(:payments) }

it { is_expected.to have_many(:applied_usage_thresholds) }
it { is_expected.to have_many(:usage_thresholds).through(:applied_usage_thresholds) }
Expand Down
2 changes: 1 addition & 1 deletion spec/models/organization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
end

it "is valid with email_settings" do
organization.email_settings = ["invoice.finalized", "credit_note.created"]
organization.email_settings = ["invoice.finalized", "credit_note.created", "payment_receipt.created"]

expect(organization).to be_valid
end
Expand Down
40 changes: 40 additions & 0 deletions spec/models/payment_receipt_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,44 @@
subject(:payment_receipt) { build(:payment_receipt) }

it { is_expected.to belong_to(:payment) }

describe ".for_organization" do
subject(:result) { described_class.for_organization(organization) }

let(:organization) { create(:organization) }
let(:visible_invoice) { create(:invoice, organization:, status: Invoice::VISIBLE_STATUS[:finalized]) }
let(:invisible_invoice) { create(:invoice, organization:, status: Invoice::INVISIBLE_STATUS[:generating]) }
let(:payment_request) { create(:payment_request, organization:) }
let(:other_org_payment_request) { create(:payment_request) }

let(:visible_invoice_payment) { create(:payment, payable: visible_invoice) }
let!(:visible_invoice_payment_receipt) { create(:payment_receipt, payment: visible_invoice_payment) }

let(:invisible_invoice_payment) { create(:payment, payable: invisible_invoice) }
let!(:invisible_invoice_payment_receipt) { create(:payment_receipt, payment: invisible_invoice_payment) }

let(:payment_request_payment) { create(:payment, payable: payment_request) }
let!(:payment_request_payment_receipt) { create(:payment_receipt, payment: payment_request_payment) }

let(:other_org_invoice_payment) { create(:payment) }
let!(:other_org_invoice_payment_receipt) do
create(:payment_receipt, payment: other_org_invoice_payment)
end

let(:other_org_payment_request_payment) { create(:payment, payable: other_org_payment_request) }

let(:other_org_payment_request_payment_receipt) do
create(:payment_receipt, payment: other_org_payment_request_payment)
end

it "returns payments and payment requests for the organization's visible invoices" do
payments = subject

expect(payments).to include(visible_invoice_payment_receipt)
expect(payments).to include(payment_request_payment_receipt)
expect(payments).not_to include(invisible_invoice_payment_receipt)
expect(payments).not_to include(other_org_invoice_payment_receipt)
expect(payments).not_to include(other_org_payment_request_payment_receipt)
end
end
end
1 change: 1 addition & 0 deletions spec/models/payment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
it_behaves_like "paper_trail traceable"

it { is_expected.to have_many(:integration_resources) }
it { is_expected.to have_one(:payment_receipt) }
it { is_expected.to belong_to(:payable) }
it { is_expected.to delegate_method(:customer).to(:payable) }
it { is_expected.to validate_presence_of(:payment_type) }
Expand Down
27 changes: 27 additions & 0 deletions spec/serializers/v1/payment_receipt_serializer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe ::V1::PaymentReceiptSerializer do
subject(:serializer) do
described_class.new(payment_receipt, root_name: "payment_receipt")
end

let(:payment_receipt) { create(:payment_receipt) }

it "serializes the object" do
result = JSON.parse(serializer.to_json)

expect(result["payment_receipt"]).to include(
"lago_id" => payment_receipt.id,
"number" => payment_receipt.number,
"created_at" => payment_receipt.created_at.iso8601
)

expect(result["payment_receipt"]["payment"]).to include(
"lago_id" => payment_receipt.payment.id,
"amount_cents" => payment_receipt.payment.amount_cents,
"amount_currency" => payment_receipt.payment.amount_currency
)
end
end
97 changes: 68 additions & 29 deletions spec/serializers/v1/payment_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,46 @@

RSpec.describe ::V1::PaymentSerializer do
subject(:serializer) do
described_class.new(payment, root_name: "payment")
described_class.new(payment, root_name: "payment", includes:)
end

context "when payable is an invoice" do
let(:payment) { create(:payment) }

it "serializes the object" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]).to include(
"lago_id" => payment.id,
"invoice_ids" => [payment.payable.id],
"amount_cents" => payment.amount_cents,
"amount_currency" => payment.amount_currency,
"payment_status" => payment.payable_payment_status,
"type" => payment.payment_type,
"reference" => payment.reference,
"external_payment_id" => payment.provider_payment_id,
"created_at" => payment.created_at.iso8601
)
context "when includes is empty" do
let(:includes) { [] }

it "serializes the object" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]).to include(
"lago_id" => payment.id,
"invoice_ids" => [payment.payable.id],
"amount_cents" => payment.amount_cents,
"amount_currency" => payment.amount_currency,
"payment_status" => payment.payable_payment_status,
"type" => payment.payment_type,
"reference" => payment.reference,
"external_payment_id" => payment.provider_payment_id,
"created_at" => payment.created_at.iso8601
)
end
end

context "when includes payment_receipt is set" do
let(:payment_receipt) { create(:payment_receipt) }
let(:payment) { payment_receipt.payment }
let(:includes) { %i[payment_receipt] }

it "includes the payment receipt" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]["payment_receipt"]).to include(
"lago_id" => payment_receipt.id,
"number" => payment_receipt.number,
"created_at" => payment_receipt.created_at.iso8601
)
end
end
end

Expand All @@ -35,20 +55,39 @@
create(:payment_request_applied_invoice, payment_request:)
end

it "serializes the object" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]).to include(
"lago_id" => payment.id,
"invoice_ids" => payment_request.invoice_ids,
"amount_cents" => payment.amount_cents,
"amount_currency" => payment.amount_currency,
"payment_status" => payment.payable_payment_status,
"type" => payment.payment_type,
"reference" => payment.reference,
"external_payment_id" => payment.provider_payment_id,
"created_at" => payment.created_at.iso8601
)
context "when includes is empty" do
let(:includes) { [] }

it "serializes the object" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]).to include(
"lago_id" => payment.id,
"invoice_ids" => payment_request.invoice_ids,
"amount_cents" => payment.amount_cents,
"amount_currency" => payment.amount_currency,
"payment_status" => payment.payable_payment_status,
"type" => payment.payment_type,
"reference" => payment.reference,
"external_payment_id" => payment.provider_payment_id,
"created_at" => payment.created_at.iso8601
)
end
end

context "when includes payment_receipt is set" do
let(:includes) { %i[payment_receipt] }
let!(:payment_receipt) { create(:payment_receipt, payment:) }

it "includes the payment receipt" do
result = JSON.parse(serializer.to_json)

expect(result["payment"]["payment_receipt"]).to include(
"lago_id" => payment_receipt.id,
"number" => payment_receipt.number,
"created_at" => payment_receipt.created_at.iso8601
)
end
end
end
end

0 comments on commit faeb279

Please sign in to comment.