Skip to content

Commit 1d59dc9

Browse files
committed
move function to Payments module
1 parent 0d7ca83 commit 1d59dc9

File tree

3 files changed

+98
-101
lines changed

3 files changed

+98
-101
lines changed

lib/algora/payments/jobs/execute_pending_transfers.ex

Lines changed: 3 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,107 +4,10 @@ defmodule Algora.Payments.Jobs.ExecutePendingTransfers do
44
queue: :transfers,
55
max_attempts: 1
66

7-
import Ecto.Changeset
8-
import Ecto.Query
9-
10-
alias Algora.MoneyUtils
11-
alias Algora.Payments.Account
12-
alias Algora.Payments.Transaction
13-
alias Algora.Repo
14-
alias Algora.Util
15-
16-
require Logger
7+
alias Algora.Payments
178

189
@impl Oban.Worker
19-
def perform(%Oban.Job{args: %{user_id: user_id, group_id: group_id}}) do
20-
pending_amount = get_pending_amount(user_id)
21-
22-
with {:ok, account} <- Repo.fetch_by(Account, user_id: user_id, provider: :stripe, payouts_enabled: true),
23-
true <- Money.positive?(pending_amount) do
24-
initialize_and_execute_transfer(user_id, group_id, pending_amount, account)
25-
else
26-
_ -> :ok
27-
end
28-
end
29-
30-
defp get_pending_amount(user_id) do
31-
total_credits =
32-
Repo.one(
33-
from(t in Transaction,
34-
where: t.user_id == ^user_id,
35-
where: t.type == :credit,
36-
where: t.status == :succeeded,
37-
select: sum(t.net_amount)
38-
)
39-
) || Money.zero(:USD)
40-
41-
total_transfers =
42-
Repo.one(
43-
from(t in Transaction,
44-
where: t.user_id == ^user_id,
45-
where: t.type == :transfer,
46-
where: t.status == :succeeded or t.status == :processing or t.status == :initialized,
47-
select: sum(t.net_amount)
48-
)
49-
) || Money.zero(:USD)
50-
51-
Money.sub!(total_credits, total_transfers)
52-
end
53-
54-
defp initialize_and_execute_transfer(user_id, group_id, pending_amount, account) do
55-
with {:ok, transaction} <- initialize_transfer(user_id, group_id, pending_amount),
56-
{:ok, transfer} <- execute_transfer(transaction, account) do
57-
{:ok, transfer}
58-
else
59-
error ->
60-
Logger.error("Failed to execute transfer: #{inspect(error)}")
61-
error
62-
end
63-
end
64-
65-
defp initialize_transfer(user_id, group_id, pending_amount) do
66-
%Transaction{}
67-
|> change(%{
68-
provider: "stripe",
69-
type: :credit,
70-
status: :initialized,
71-
user_id: user_id,
72-
gross_amount: pending_amount,
73-
net_amount: pending_amount,
74-
total_fee: Money.zero(:USD),
75-
group_id: group_id
76-
})
77-
|> Algora.Validations.validate_positive(:gross_amount)
78-
|> Algora.Validations.validate_positive(:net_amount)
79-
|> foreign_key_constraint(:user_id)
80-
|> Repo.insert()
81-
end
82-
83-
defp execute_transfer(transaction, account) do
84-
# TODO: set other params
85-
# TODO: provide idempotency key
86-
case Stripe.Transfer.create(%{
87-
amount: MoneyUtils.to_minor_units(transaction.net_amount),
88-
currency: to_string(transaction.net_amount.currency),
89-
destination: account.stripe_account_id
90-
}) do
91-
{:ok, transfer} ->
92-
# it's fine if this fails since we'll receive a webhook
93-
_result = try_update_transaction(transaction, transfer)
94-
{:ok, transfer}
95-
96-
{:error, error} ->
97-
{:error, error}
98-
end
99-
end
100-
101-
defp try_update_transaction(transaction, transfer) do
102-
transaction
103-
|> change(%{
104-
status: if(transfer.status == :succeeded, do: :succeeded, else: :failed),
105-
provider_id: transfer.id,
106-
provider_meta: Util.normalize_struct(transfer)
107-
})
108-
|> Repo.update()
10+
def perform(%Oban.Job{args: %{user_id: user_id}}) do
11+
Payments.execute_pending_transfers(user_id)
10912
end
11013
end

lib/algora/payments/payments.ex

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
defmodule Algora.Payments do
22
@moduledoc false
3+
import Ecto.Changeset
34
import Ecto.Query
45

56
alias Algora.Accounts
67
alias Algora.Accounts.User
8+
alias Algora.MoneyUtils
79
alias Algora.Payments.Account
810
alias Algora.Payments.Customer
911
alias Algora.Payments.PaymentMethod
@@ -248,4 +250,96 @@ defmodule Algora.Payments do
248250
Repo.delete(account)
249251
end
250252
end
253+
254+
def execute_pending_transfers(user_id) do
255+
pending_amount = get_pending_amount(user_id)
256+
257+
with {:ok, account} <- Repo.fetch_by(Account, user_id: user_id, provider: "stripe", payouts_enabled: true),
258+
true <- Money.positive?(pending_amount) do
259+
initialize_and_execute_transfer(user_id, pending_amount, account)
260+
else
261+
_ -> {:ok, nil}
262+
end
263+
end
264+
265+
defp get_pending_amount(user_id) do
266+
total_credits =
267+
Repo.one(
268+
from(t in Transaction,
269+
where: t.user_id == ^user_id,
270+
where: t.type == :credit,
271+
where: t.status == :succeeded,
272+
select: sum(t.net_amount)
273+
)
274+
) || Money.zero(:USD)
275+
276+
total_transfers =
277+
Repo.one(
278+
from(t in Transaction,
279+
where: t.user_id == ^user_id,
280+
where: t.type == :transfer,
281+
where: t.status == :succeeded or t.status == :processing or t.status == :initialized,
282+
select: sum(t.net_amount)
283+
)
284+
) || Money.zero(:USD)
285+
286+
Money.sub!(total_credits, total_transfers)
287+
end
288+
289+
defp initialize_and_execute_transfer(user_id, pending_amount, account) do
290+
with {:ok, transaction} <- initialize_transfer(user_id, pending_amount),
291+
{:ok, transfer} <- execute_transfer(transaction, account) do
292+
{:ok, transfer}
293+
else
294+
error ->
295+
Logger.error("Failed to execute transfer: #{inspect(error)}")
296+
error
297+
end
298+
end
299+
300+
defp initialize_transfer(user_id, pending_amount) do
301+
%Transaction{}
302+
|> change(%{
303+
id: Nanoid.generate(),
304+
provider: "stripe",
305+
type: :transfer,
306+
status: :initialized,
307+
user_id: user_id,
308+
gross_amount: pending_amount,
309+
net_amount: pending_amount,
310+
total_fee: Money.zero(:USD)
311+
})
312+
|> Algora.Validations.validate_positive(:gross_amount)
313+
|> Algora.Validations.validate_positive(:net_amount)
314+
|> foreign_key_constraint(:user_id)
315+
|> Repo.insert()
316+
end
317+
318+
defp execute_transfer(transaction, account) do
319+
# TODO: set other params
320+
# TODO: provide idempotency key
321+
case Algora.Stripe.create_transfer(%{
322+
amount: MoneyUtils.to_minor_units(transaction.net_amount),
323+
currency: to_string(transaction.net_amount.currency),
324+
destination: account.provider_id
325+
}) do
326+
{:ok, transfer} ->
327+
# it's fine if this fails since we'll receive a webhook
328+
_result = try_update_transaction(transaction, transfer)
329+
{:ok, transfer}
330+
331+
{:error, error} ->
332+
{:error, error}
333+
end
334+
end
335+
336+
defp try_update_transaction(transaction, transfer) do
337+
transaction
338+
|> change(%{
339+
status: if(transfer.status == :succeeded, do: :succeeded, else: :failed),
340+
provider_id: transfer.id,
341+
provider_meta: Util.normalize_struct(transfer)
342+
})
343+
|> Repo.update()
344+
end
251345
end

lib/algora_web/controllers/webhooks/stripe_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule AlgoraWeb.Webhooks.StripeController do
3737
|> Enum.map(fn %{user_id: user_id} -> user_id end)
3838
|> Enum.uniq()
3939
|> Enum.reduce_while(:ok, fn user_id, :ok ->
40-
case %{user_id: user_id, group_id: group_id}
40+
case %{user_id: user_id}
4141
|> ExecutePendingTransfers.new()
4242
|> Oban.insert() do
4343
{:ok, _job} -> {:cont, :ok}

0 commit comments

Comments
 (0)