@@ -328,23 +328,21 @@ defmodule Algora.Payments do
328328 { :ok , PSP . transfer ( ) } | { :error , :not_found } | { :error , :duplicate_transfer_attempt }
329329 def execute_pending_transfer ( credit_id ) do
330330 with { :ok , credit } <- Repo . fetch_by ( Transaction , id: credit_id , type: :credit , status: :succeeded ) do
331- transfers =
332- Repo . all (
333- from ( t in Transaction ,
334- where: t . user_id == ^ credit . user_id ,
335- where: t . group_id == ^ credit . group_id ,
336- where: t . type == :transfer ,
337- where: t . status in [ :initialized , :processing , :succeeded ]
338- )
339- )
340-
341- amount_transferred = Enum . reduce ( transfers , Money . zero ( :USD ) , fn t , acc -> Money . add! ( acc , t . net_amount ) end )
342-
343- if Money . positive? ( amount_transferred ) do
344- Logger . error ( "Duplicate transfer attempt at transaction #{ credit_id } " )
345- { :error , :duplicate_transfer_attempt }
346- else
347- initialize_and_execute_transfer ( credit )
331+ case fetch_active_account ( credit . user_id ) do
332+ { :ok , account } ->
333+ with { :ok , transaction } <- fetch_or_create_transfer ( credit ) ,
334+ { :ok , transfer } <- execute_transfer ( transaction , account ) do
335+ broadcast ( )
336+ { :ok , transfer }
337+ else
338+ error ->
339+ Logger . error ( "Failed to execute transfer: #{ inspect ( error ) } " )
340+ error
341+ end
342+
343+ _ ->
344+ Logger . error ( "Attempted to execute transfer to inactive account" )
345+ { :error , :no_active_account }
348346 end
349347 end
350348 end
@@ -395,53 +393,43 @@ defmodule Algora.Payments do
395393 end
396394 end
397395
398- @ spec initialize_and_execute_transfer ( credit :: Transaction . t ( ) ) :: { :ok , PSP . transfer ( ) } | { :error , term ( ) }
399- defp initialize_and_execute_transfer ( % Transaction { } = credit ) do
400- case fetch_active_account ( credit . user_id ) do
401- { :ok , account } ->
402- with { :ok , transaction } <- initialize_transfer ( credit ) ,
403- { :ok , transfer } <- execute_transfer ( transaction , account ) do
404- broadcast ( )
405- { :ok , transfer }
406- else
407- error ->
408- Logger . error ( "Failed to execute transfer: #{ inspect ( error ) } " )
409- error
410- end
396+ def fetch_or_create_transfer ( % Transaction { } = credit ) do
397+ idempotency_key = "credit_#{ credit . id } "
411398
412- _ ->
413- Logger . error ( "Attempted to execute transfer to inactive account" )
414- { :error , :no_active_account }
399+ case Repo . get_by ( Transaction , idempotency_key: idempotency_key ) do
400+ nil ->
401+ % Transaction { }
402+ |> change ( % {
403+ id: Nanoid . generate ( ) ,
404+ provider: "stripe" ,
405+ type: :transfer ,
406+ status: :initialized ,
407+ tip_id: credit . tip_id ,
408+ bounty_id: credit . bounty_id ,
409+ contract_id: credit . contract_id ,
410+ claim_id: credit . claim_id ,
411+ user_id: credit . user_id ,
412+ gross_amount: credit . net_amount ,
413+ net_amount: credit . net_amount ,
414+ total_fee: Money . zero ( :USD ) ,
415+ group_id: credit . group_id ,
416+ idempotency_key: idempotency_key
417+ } )
418+ |> Algora.Validations . validate_positive ( :gross_amount )
419+ |> Algora.Validations . validate_positive ( :net_amount )
420+ |> unique_constraint ( :idempotency_key )
421+ |> foreign_key_constraint ( :user_id )
422+ |> foreign_key_constraint ( :tip_id )
423+ |> foreign_key_constraint ( :bounty_id )
424+ |> foreign_key_constraint ( :contract_id )
425+ |> foreign_key_constraint ( :claim_id )
426+ |> Repo . insert ( )
427+
428+ transfer ->
429+ { :ok , transfer }
415430 end
416431 end
417432
418- def initialize_transfer ( % Transaction { } = credit ) do
419- % Transaction { }
420- |> change ( % {
421- id: Nanoid . generate ( ) ,
422- provider: "stripe" ,
423- type: :transfer ,
424- status: :initialized ,
425- tip_id: credit . tip_id ,
426- bounty_id: credit . bounty_id ,
427- contract_id: credit . contract_id ,
428- claim_id: credit . claim_id ,
429- user_id: credit . user_id ,
430- gross_amount: credit . net_amount ,
431- net_amount: credit . net_amount ,
432- total_fee: Money . zero ( :USD ) ,
433- group_id: credit . group_id
434- } )
435- |> Algora.Validations . validate_positive ( :gross_amount )
436- |> Algora.Validations . validate_positive ( :net_amount )
437- |> foreign_key_constraint ( :user_id )
438- |> foreign_key_constraint ( :tip_id )
439- |> foreign_key_constraint ( :bounty_id )
440- |> foreign_key_constraint ( :contract_id )
441- |> foreign_key_constraint ( :claim_id )
442- |> Repo . insert ( )
443- end
444-
445433 def execute_transfer ( % Transaction { } = transaction , account ) do
446434 charge = Repo . get_by ( Transaction , type: :charge , status: :succeeded , group_id: transaction . group_id )
447435
@@ -455,7 +443,7 @@ defmodule Algora.Payments do
455443 |> Map . merge ( if transaction . group_id , do: % { transfer_group: transaction . group_id } , else: % { } )
456444 |> Map . merge ( if charge && charge . provider_id , do: % { source_transaction: charge . provider_id } , else: % { } )
457445
458- case PSP.Transfer . create ( transfer_params , % { idempotency_key: transaction . id } ) do
446+ case PSP.Transfer . create ( transfer_params , % { idempotency_key: transaction . idempotency_key } ) do
459447 { :ok , transfer } ->
460448 transaction
461449 |> change ( % {
0 commit comments