Skip to content

Commit

Permalink
feat(dunning): Add skeleton for creating a payment request
Browse files Browse the repository at this point in the history
  • Loading branch information
rsempe committed Aug 16, 2024
1 parent d2a7250 commit faeb90e
Show file tree
Hide file tree
Showing 13 changed files with 210 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 @@ -106,6 +106,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
1 change: 1 addition & 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.

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
41 changes: 41 additions & 0 deletions spec/requests/api/v1/payment_requests_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,47 @@
RSpec.describe Api::V1::PaymentRequestsController, type: :request do
let(:organization) { create(:organization) }

describe "create" do
let(:customer) { create(:customer, organization:) }
let(:params) do
{
email: customer.email,
external_customer_id: customer.external_id
}
end

context "when customer is not found" do
let(:params) do
{external_customer_id: "unknown"}
end

it "returns a not found error" do
post_with_token(organization, "/api/v1/payment_requests", {payment_request: params})
expect(response).to have_http_status(:not_found)
end
end

it "delegates to PaymentRequests::CreateService", :aggregate_failures do
payment_request = create(:payment_request)
allow(PaymentRequests::CreateService).to receive(:call).and_return(
BaseService::Result.new.tap { |r| r.payment_request = payment_request }
)

post_with_token(organization, "/api/v1/payment_requests", {payment_request: params})

expect(PaymentRequests::CreateService).to have_received(:call).with(
organization:,
params: {
email: customer.email,
external_customer_id: customer.external_id
}
)

expect(response).to have_http_status(:success)
expect(json[:payment_request][:lago_id]).to eq(payment_request.id)
end
end

describe "index" do
it "returns organization's payment requests", :aggregate_failures do
first_customer = create(:customer, organization:)
Expand Down

0 comments on commit faeb90e

Please sign in to comment.