@@ -10,14 +10,14 @@ defmodule Algora.PaymentsTest do
1010 alias Algora.Payments.Transaction
1111 alias Algora.Repo
1212
13- describe "execute_pending_transfer/1" do
14- setup do
15- user = insert ( :user )
16- account = insert ( :account , user: user )
13+ setup do
14+ user = insert ( :user )
15+ account = insert ( :account , user: user )
1716
18- { :ok , user: user , account: account }
19- end
17+ { :ok , user: user , account: account }
18+ end
2019
20+ describe "execute_pending_transfer/1" do
2121 test "executes transfer when user has positive balance" , % { user: user , account: account } do
2222 credit =
2323 insert ( :transaction ,
@@ -106,6 +106,68 @@ defmodule Algora.PaymentsTest do
106106 transfer_tx = Repo . one ( from t in Transaction , where: t . type == :transfer )
107107 assert transfer_tx . status == :failed
108108 end
109+
110+ test "prevents duplicate transfers" , % { user: user } do
111+ credit_tx =
112+ insert ( :transaction ,
113+ type: :credit ,
114+ status: :succeeded ,
115+ net_amount: Money . new ( 1 , :USD ) ,
116+ group_id: Nanoid . generate ( ) ,
117+ user: user
118+ )
119+
120+ { :ok , transfer } = Payments . execute_pending_transfer ( credit_tx . id )
121+ { :ok , transfer_tx } = Repo . fetch_by ( Transaction , type: :transfer , provider_id: transfer . id )
122+
123+ assert Money . equal? ( transfer_tx . net_amount , Money . new ( 1 , :USD ) )
124+ assert transfer_tx . status == :succeeded
125+ assert transfer_tx . succeeded_at != nil
126+ assert transfer_tx . group_id == credit_tx . group_id
127+ assert transfer_tx . user_id == credit_tx . user_id
128+ assert transfer_tx . provider_id == transfer . id
129+
130+ { result , _log } = with_log ( fn -> Payments . execute_pending_transfer ( credit_tx . id ) end )
131+ assert { :error , :duplicate_transfer_attempt } = result
132+
133+ transfer_tx |> change ( status: :succeeded ) |> Repo . update! ( )
134+ { result , _log } = with_log ( fn -> Payments . execute_pending_transfer ( credit_tx . id ) end )
135+ assert { :error , :duplicate_transfer_attempt } = result
136+
137+ transfer_tx |> change ( status: :initialized ) |> Repo . update! ( )
138+ { result , _log } = with_log ( fn -> Payments . execute_pending_transfer ( credit_tx . id ) end )
139+ assert { :error , :duplicate_transfer_attempt } = result
140+
141+ transfer_tx |> change ( status: :processing ) |> Repo . update! ( )
142+ { result , _log } = with_log ( fn -> Payments . execute_pending_transfer ( credit_tx . id ) end )
143+ assert { :error , :duplicate_transfer_attempt } = result
144+ end
145+
146+ test "allows retrying failed/canceled transfers" , % { user: user , account: account } do
147+ credit_tx =
148+ insert ( :transaction ,
149+ type: :credit ,
150+ status: :succeeded ,
151+ net_amount: Money . new ( 1 , :USD ) ,
152+ group_id: Nanoid . generate ( ) ,
153+ user: user
154+ )
155+
156+ Account |> Repo . one! ( ) |> change ( % { provider_id: "acct_invalid" } ) |> Repo . update! ( )
157+ { result , _log } = with_log ( fn -> Payments . execute_pending_transfer ( credit_tx . id ) end )
158+ assert { :error , % Stripe.Error { code: :invalid_request_error } } = result
159+
160+ Account |> Repo . one! ( ) |> change ( % { provider_id: account . provider_id } ) |> Repo . update! ( )
161+ { :ok , transfer } = Payments . execute_pending_transfer ( credit_tx . id )
162+ { :ok , transfer_tx } = Repo . fetch_by ( Transaction , type: :transfer , status: :succeeded )
163+
164+ assert Money . equal? ( transfer_tx . net_amount , Money . new ( 1 , :USD ) )
165+ assert transfer_tx . status == :succeeded
166+ assert transfer_tx . succeeded_at != nil
167+ assert transfer_tx . group_id == credit_tx . group_id
168+ assert transfer_tx . user_id == credit_tx . user_id
169+ assert transfer_tx . provider_id == transfer . id
170+ end
109171 end
110172
111173 describe "enqueue_pending_transfers/1" do
@@ -191,4 +253,41 @@ defmodule Algora.PaymentsTest do
191253 refute_enqueued ( worker: ExecutePendingTransfer , args: % { credit_id: credit . id } )
192254 end
193255 end
256+
257+ describe "execute_transfer/2" do
258+ test "executes transfer idempotently with same transfer ID and transaction ID" , % { account: account } do
259+ credit_tx =
260+ insert ( :transaction ,
261+ type: :credit ,
262+ status: :succeeded ,
263+ net_amount: Money . new ( 1 , :USD ) ,
264+ group_id: Nanoid . generate ( )
265+ )
266+
267+ { :ok , transfer_tx } = Payments . initialize_transfer ( credit_tx )
268+
269+ { :ok , transfer1 } = Payments . execute_transfer ( transfer_tx , account )
270+ { :ok , transfer_tx1 } = Repo . fetch_by ( Transaction , type: :transfer )
271+
272+ assert Money . equal? ( transfer_tx1 . net_amount , Money . new ( 1 , :USD ) )
273+ assert transfer_tx1 . status == :succeeded
274+ assert transfer_tx1 . succeeded_at != nil
275+ assert transfer_tx1 . group_id == credit_tx . group_id
276+ assert transfer_tx1 . user_id == credit_tx . user_id
277+ assert transfer_tx1 . provider_id == transfer1 . id
278+
279+ { :ok , transfer2 } = Payments . execute_transfer ( transfer_tx , account )
280+ { :ok , transfer_tx2 } = Repo . fetch_by ( Transaction , type: :transfer )
281+
282+ assert Money . equal? ( transfer_tx2 . net_amount , Money . new ( 1 , :USD ) )
283+ assert transfer_tx2 . status == :succeeded
284+ assert transfer_tx2 . succeeded_at != nil
285+ assert transfer_tx2 . group_id == credit_tx . group_id
286+ assert transfer_tx2 . user_id == credit_tx . user_id
287+ assert transfer_tx2 . provider_id == transfer2 . id
288+
289+ assert transfer1 . id == transfer2 . id
290+ assert transfer_tx1 . id == transfer_tx2 . id
291+ end
292+ end
194293end
0 commit comments