Skip to content

Commit 2677c52

Browse files
committed
handle uncaptured charges
1 parent 89e89d6 commit 2677c52

File tree

7 files changed

+106
-78
lines changed

7 files changed

+106
-78
lines changed

lib/algora/accounts/accounts.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,15 @@ defmodule Algora.Accounts do
183183
where: tx.type == :credit,
184184
where: tx.status == :succeeded,
185185
where: tx.user_id == ^user.id,
186+
join: ltx in assoc(tx, :linked_transaction),
186187
left_join: bounty in assoc(tx, :bounty),
187188
left_join: tip in assoc(tx, :tip),
188189
join: t in Ticket,
189190
on: t.id == bounty.ticket_id or t.id == tip.ticket_id,
190191
left_join: r in assoc(t, :repository),
191192
as: :r,
192-
left_join: ro in assoc(r, :user),
193+
left_join: ro in User,
194+
on: fragment("? = (case when ? is null then ? else ? end)", ro.id, r.user_id, ltx.user_id, r.user_id),
193195
# order_by: ^[desc: order_by],
194196
order_by: [desc: sum(tx.net_amount)],
195197
group_by: [ro.id],

lib/algora/payments/payments.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,13 @@ defmodule Algora.Payments do
157157
end
158158

159159
def list_transactions(criteria \\ []) do
160-
Transaction
161-
|> where([t], ^Enum.to_list(criteria))
160+
criteria
161+
|> Enum.reduce(Transaction, fn {key, value}, query ->
162+
case value do
163+
v when is_list(v) -> where(query, [t], field(t, ^key) in ^v)
164+
v -> where(query, [t], field(t, ^key) == ^v)
165+
end
166+
end)
162167
|> preload(linked_transaction: :user)
163168
|> order_by([t], desc: t.inserted_at)
164169
|> Repo.all()

lib/algora/payments/schemas/transaction.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ defmodule Algora.Payments.Transaction do
77
alias Algora.Types.Money
88

99
@transaction_types [:charge, :transfer, :reversal, :debit, :credit, :deposit, :withdrawal]
10-
@transaction_statuses [:initialized, :processing, :succeeded, :failed, :canceled]
10+
@transaction_statuses [:initialized, :processing, :requires_capture, :succeeded, :failed, :canceled]
1111

1212
@derive {Inspect, except: [:provider_meta]}
1313
typed_schema "transactions" do

lib/algora_web/controllers/webhooks/stripe_controller.ex

Lines changed: 71 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -129,77 +129,87 @@ defmodule AlgoraWeb.Webhooks.StripeController do
129129
end
130130

131131
defp process_charge_succeeded(
132-
%Stripe.Event{type: "charge.succeeded", data: %{object: %Stripe.Charge{id: charge_id}}},
132+
%Stripe.Event{type: "charge.succeeded", data: %{object: %Stripe.Charge{id: charge_id, captured: captured}}},
133133
group_id
134134
)
135135
when is_binary(group_id) do
136136
Repo.transact(fn ->
137+
status = if captured, do: :succeeded, else: :requires_capture
138+
succeeded_at = if captured, do: DateTime.utc_now()
139+
137140
{_, txs} =
138141
Repo.update_all(from(t in Transaction, where: t.group_id == ^group_id, select: t),
139-
set: [status: :succeeded, succeeded_at: DateTime.utc_now()]
142+
set: [
143+
status: status,
144+
succeeded_at: succeeded_at,
145+
provider: "stripe",
146+
provider_id: charge_id,
147+
provider_charge_id: charge_id
148+
]
140149
)
141150

142-
Repo.update_all(from(t in Transaction, where: t.group_id == ^group_id),
143-
set: [provider: "stripe", provider_id: charge_id]
144-
)
145-
146-
bounty_ids = txs |> Enum.map(& &1.bounty_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
147-
tip_ids = txs |> Enum.map(& &1.tip_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
148-
contract_ids = txs |> Enum.map(& &1.contract_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
149-
claim_ids = txs |> Enum.map(& &1.claim_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
150-
151-
Repo.update_all(from(b in Bounty, where: b.id in ^bounty_ids), set: [status: :paid])
152-
Repo.update_all(from(t in Tip, where: t.id in ^tip_ids), set: [status: :paid])
153-
Repo.update_all(from(c in Contract, where: c.id in ^contract_ids), set: [status: :paid])
154-
# TODO: add and use a new "paid" status for claims
155-
Repo.update_all(from(c in Claim, where: c.id in ^claim_ids), set: [status: :approved])
156-
157-
activities_result =
158-
txs
159-
|> Enum.filter(&(&1.type == :credit))
160-
|> Enum.reduce_while(:ok, fn tx, :ok ->
161-
case Repo.insert_activity(tx, %{type: :transaction_succeeded, notify_users: [tx.user_id]}) do
162-
{:ok, _} -> {:cont, :ok}
163-
error -> {:halt, error}
164-
end
165-
end)
166-
167-
jobs_result =
168-
txs
169-
|> Enum.filter(&(&1.type == :credit))
170-
|> Enum.reduce_while(:ok, fn credit, :ok ->
171-
case Payments.fetch_active_account(credit.user_id) do
172-
{:ok, _account} ->
173-
case %{credit_id: credit.id}
174-
|> Payments.Jobs.ExecutePendingTransfer.new()
175-
|> Oban.insert() do
176-
{:ok, _job} -> {:cont, :ok}
177-
error -> {:halt, error}
178-
end
179-
180-
{:error, :no_active_account} ->
181-
case %{credit_id: credit.id}
182-
|> Bounties.Jobs.PromptPayoutConnect.new()
183-
|> Oban.insert() do
184-
{:ok, _job} -> {:cont, :ok}
185-
error -> {:halt, error}
186-
end
187-
end
188-
end)
189-
190-
with txs when txs != [] <- txs,
191-
:ok <- activities_result,
192-
:ok <- jobs_result do
151+
if status == :succeeded do
152+
bounty_ids = txs |> Enum.map(& &1.bounty_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
153+
tip_ids = txs |> Enum.map(& &1.tip_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
154+
contract_ids = txs |> Enum.map(& &1.contract_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
155+
claim_ids = txs |> Enum.map(& &1.claim_id) |> Enum.reject(&is_nil/1) |> Enum.uniq()
156+
157+
Repo.update_all(from(b in Bounty, where: b.id in ^bounty_ids), set: [status: :paid])
158+
Repo.update_all(from(t in Tip, where: t.id in ^tip_ids), set: [status: :paid])
159+
Repo.update_all(from(c in Contract, where: c.id in ^contract_ids), set: [status: :paid])
160+
# TODO: add and use a new "paid" status for claims
161+
Repo.update_all(from(c in Claim, where: c.id in ^claim_ids), set: [status: :approved])
162+
163+
activities_result =
164+
txs
165+
|> Enum.filter(&(&1.type == :credit))
166+
|> Enum.reduce_while(:ok, fn tx, :ok ->
167+
case Repo.insert_activity(tx, %{type: :transaction_succeeded, notify_users: [tx.user_id]}) do
168+
{:ok, _} -> {:cont, :ok}
169+
error -> {:halt, error}
170+
end
171+
end)
172+
173+
jobs_result =
174+
txs
175+
|> Enum.filter(&(&1.type == :credit))
176+
|> Enum.reduce_while(:ok, fn credit, :ok ->
177+
case Payments.fetch_active_account(credit.user_id) do
178+
{:ok, _account} ->
179+
case %{credit_id: credit.id}
180+
|> Payments.Jobs.ExecutePendingTransfer.new()
181+
|> Oban.insert() do
182+
{:ok, _job} -> {:cont, :ok}
183+
error -> {:halt, error}
184+
end
185+
186+
{:error, :no_active_account} ->
187+
case %{credit_id: credit.id}
188+
|> Bounties.Jobs.PromptPayoutConnect.new()
189+
|> Oban.insert() do
190+
{:ok, _job} -> {:cont, :ok}
191+
error -> {:halt, error}
192+
end
193+
end
194+
end)
195+
196+
with txs when txs != [] <- txs,
197+
:ok <- activities_result,
198+
:ok <- jobs_result do
199+
Payments.broadcast()
200+
{:ok, nil}
201+
else
202+
{:error, reason} ->
203+
Logger.error("Failed to update transactions: #{inspect(reason)}")
204+
{:error, :failed_to_update_transactions}
205+
206+
_error ->
207+
Logger.error("Failed to update transactions")
208+
{:error, :failed_to_update_transactions}
209+
end
210+
else
193211
Payments.broadcast()
194212
{:ok, nil}
195-
else
196-
{:error, reason} ->
197-
Logger.error("Failed to update transactions: #{inspect(reason)}")
198-
{:error, :failed_to_update_transactions}
199-
200-
_error ->
201-
Logger.error("Failed to update transactions")
202-
{:error, :failed_to_update_transactions}
203213
end
204214
end)
205215
end

lib/algora_web/live/contract_live.ex

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -674,11 +674,13 @@ defmodule AlgoraWeb.ContractLive do
674674

675675
defp assign_transactions(socket) do
676676
transactions =
677-
Payments.list_transactions(
677+
[
678678
user_id: socket.assigns.bounty.owner.id,
679-
status: :succeeded,
679+
status: [:succeeded, :requires_capture],
680680
bounty_id: socket.assigns.bounty.id
681-
)
681+
]
682+
|> Payments.list_transactions()
683+
|> Enum.filter(&(&1.type == :charge or &1.status == :succeeded))
682684

683685
balance = calculate_balance(transactions)
684686
volume = calculate_volume(transactions)
@@ -690,7 +692,9 @@ defmodule AlgoraWeb.ContractLive do
690692
end
691693

692694
defp calculate_balance(transactions) do
693-
Enum.reduce(transactions, Money.new!(0, :USD), fn transaction, acc ->
695+
transactions
696+
|> Enum.filter(&(&1.status == :succeeded))
697+
|> Enum.reduce(Money.new!(0, :USD), fn transaction, acc ->
694698
case transaction.type do
695699
type when type in [:charge, :deposit, :credit] ->
696700
Money.add!(acc, transaction.net_amount)
@@ -705,7 +709,9 @@ defmodule AlgoraWeb.ContractLive do
705709
end
706710

707711
defp calculate_volume(transactions) do
708-
Enum.reduce(transactions, Money.new!(0, :USD), fn transaction, acc ->
712+
transactions
713+
|> Enum.filter(&(&1.status == :succeeded))
714+
|> Enum.reduce(Money.new!(0, :USD), fn transaction, acc ->
709715
case transaction.type do
710716
type when type in [:charge, :credit] -> Money.add!(acc, transaction.net_amount)
711717
_ -> acc
@@ -720,7 +726,9 @@ defmodule AlgoraWeb.ContractLive do
720726
end
721727
end
722728

723-
defp description(%{type: :charge}), do: "Escrowed"
729+
defp description(%{type: :charge, status: :requires_capture}), do: "Authorized"
730+
731+
defp description(%{type: :charge, status: :succeeded}), do: "Escrowed"
724732

725733
defp description(%{type: :debit}), do: "Released"
726734

lib/algora_web/live/org/nav.ex

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ defmodule AlgoraWeb.Org.Nav do
129129
:hourly -> Money.mult!(data.hourly_rate, data.hours_per_week)
130130
end
131131

132-
dbg(data.hourly_rate)
133-
dbg(data.hours_per_week)
134-
135132
bounty_res =
136133
Bounties.create_bounty(
137134
%{

lib/algora_web/live/org/transactions_live.ex

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,13 @@ defmodule AlgoraWeb.Org.TransactionsLive do
137137

138138
defp assign_transactions(socket) do
139139
transactions =
140-
Payments.list_transactions(
140+
[
141141
user_id: socket.assigns.current_org.id,
142142
# TODO: also list transactions that are "processing"
143-
status: :succeeded
144-
)
143+
status: [:succeeded, :requires_capture]
144+
]
145+
|> Payments.list_transactions()
146+
|> Enum.filter(&(&1.type == :charge or &1.status == :succeeded))
145147

146148
balance = calculate_balance(transactions)
147149
volume = calculate_volume(transactions)
@@ -153,7 +155,9 @@ defmodule AlgoraWeb.Org.TransactionsLive do
153155
end
154156

155157
defp calculate_balance(transactions) do
156-
Enum.reduce(transactions, Money.new!(0, :USD), fn transaction, acc ->
158+
transactions
159+
|> Enum.filter(&(&1.status == :succeeded))
160+
|> Enum.reduce(Money.new!(0, :USD), fn transaction, acc ->
157161
case transaction.type do
158162
type when type in [:charge, :deposit, :credit] ->
159163
Money.add!(acc, transaction.net_amount)
@@ -484,6 +488,8 @@ defmodule AlgoraWeb.Org.TransactionsLive do
484488
end
485489
end
486490

491+
defp description(%{type: :charge, status: :requires_capture}), do: "Authorization"
492+
487493
defp description(%{type: type, tip_id: tip_id}) when type in [:debit, :credit] and not is_nil(tip_id), do: "Tip payment"
488494

489495
defp description(%{type: type, contract_id: contract_id}) when type in [:debit, :credit] and not is_nil(contract_id),

0 commit comments

Comments
 (0)