Skip to content

Commit

Permalink
feat(dunning): Add skeleton for creating a payment request (#2426)
Browse files Browse the repository at this point in the history
## Context

We want to be able to manually request payment of the overdue balance
and send emails for reminders.


https://getlago.canny.io/feature-requests/p/send-reminders-for-overdue-invoices

## Description

The goal of this PR is to add the graphql mutation and the API endpoint
for creating a payment request.
  • Loading branch information
rsempe authored Aug 19, 2024
1 parent a6a3632 commit ed0daa1
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 1 deletion.
1 change: 1 addition & 0 deletions app/config/permissions/definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,4 @@ organization:
delete:
payment_requests:
view: true
create:
1 change: 1 addition & 0 deletions app/config/permissions/role-finance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ organization:
delete: false
payment_requests:
view: true
create: true
1 change: 1 addition & 0 deletions app/config/permissions/role-manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ organization:
delete: false
payment_requests:
view: true
create: true
21 changes: 21 additions & 0 deletions app/controllers/api/v1/payment_requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
module Api
module V1
class PaymentRequestsController < Api::BaseController
def create
result = PaymentRequests::CreateService.call(
organization: current_organization,
params: create_params.to_h.deep_symbolize_keys
)

if result.success?
render(json: ::V1::PaymentRequestSerializer.new(result.payment_request, root_name: "payment_request"))
else
render_error_response(result)
end
end

def index
result = PaymentRequestsQuery.call(
organization: current_organization,
Expand Down Expand Up @@ -30,6 +43,14 @@ def index

private

def create_params
params.require(:payment_request).permit(
:email,
:external_customer_id,
:lago_invoice_ids
)
end

def index_filters
params.permit(:external_customer_id)
end
Expand Down
23 changes: 23 additions & 0 deletions app/graphql/mutations/payment_requests/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Mutations
module PaymentRequests
class Create < BaseMutation
include AuthenticableApiUser
include RequiredOrganization

REQUIRED_PERMISSION = "payment_requests:create"

graphql_name "CreatePaymentRequest"
description "Creates a payment request"

input_object_class Types::PaymentRequests::CreateInput
type Types::PaymentRequests::Object

def resolve(**args)
result = ::PaymentRequests::CreateService.call(organization: current_organization, params: args)
result.success? ? result.payment_request : result_error(result)
end
end
end
end
2 changes: 2 additions & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class MutationType < Types::BaseObject
field :revoke_membership, mutation: Mutations::Memberships::Revoke
field :update_membership, mutation: Mutations::Memberships::Update

field :create_payment_request, mutation: Mutations::PaymentRequests::Create

field :create_password_reset, mutation: Mutations::PasswordResets::Create
field :reset_password, mutation: Mutations::PasswordResets::Reset

Expand Down
14 changes: 14 additions & 0 deletions app/graphql/types/payment_requests/create_input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Types
module PaymentRequests
class CreateInput < Types::BaseInputObject
graphql_name "PaymentRequestCreateInput"

argument :external_customer_id, String, required: true

argument :email, String, required: false
argument :lago_invoice_ids, [String], required: false
end
end
end
39 changes: 39 additions & 0 deletions app/services/payment_requests/create_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module PaymentRequests
class CreateService < BaseService
def initialize(organization:, params:)
@organization = organization
@params = params

super
end

def call
return result.not_found_failure!(resource: "customer") unless customer

ActiveRecord::Base.transaction do
# TODO: Create payable group for the overdue invoices

# TODO: Create payment request for the payable group

# TODO: Send payment_request.created webhook

# TODO: When payment provider is set: Create payment intent for the overdue invoices
# TODO: When payment provider is not set: Send email to the customer

# result.payment_request = payment_request
end

result
end

private

attr_reader :organization, :params

def customer
@customer ||= organization.customers.find_by(external_id: params[:external_customer_id])
end
end
end
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
put :refresh, on: :member
put :finalize, on: :member
end
resources :payment_requests, only: %i[index]
resources :payment_requests, only: %i[create index]
resources :plans, param: :code
resources :taxes, param: :code
resources :wallet_transactions, only: :create
Expand Down
24 changes: 24 additions & 0 deletions schema.graphql

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

118 changes: 118 additions & 0 deletions schema.json

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

47 changes: 47 additions & 0 deletions spec/graphql/mutations/payment_requests/create_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Mutations::PaymentRequests::Create, type: :graphql do
let(:required_permission) { "payment_requests:create" }
let(:membership) { create(:membership) }
let(:organization) { membership.organization }
let(:customer) { create(:customer, organization:) }
let(:invoice1) { create(:invoice, organization:) }
let(:invoice2) { create(:invoice, organization:) }

let(:input) do
{
email: "john.doe@example.com",
externalCustomerId: customer.external_id,
lagoInvoiceIds: [invoice1.id, invoice2.id]
}
end

let(:mutation) do
<<-GQL
mutation($input: PaymentRequestCreateInput!) {
createPaymentRequest(input: $input) {
id
email
customer { id }
invoices { id }
}
}
GQL
end

it "creates a payment request" do
result = execute_graphql(
current_user: membership.user,
current_organization: membership.organization,
permissions: required_permission,
query: mutation,
variables: {input:}
)

expect(result["data"]).to include(
"createPaymentRequest" => nil
)
end
end
Loading

0 comments on commit ed0daa1

Please sign in to comment.