Skip to content

Commit 9b52008

Browse files
committed
handle splits
1 parent ee945e2 commit 9b52008

File tree

4 files changed

+132
-91
lines changed

4 files changed

+132
-91
lines changed

lib/algora/bounties/bounties.ex

Lines changed: 119 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -256,15 +256,10 @@ defmodule Algora.Bounties do
256256
Repo.transact(fn ->
257257
with {:ok, tip} <- Repo.insert(changeset) do
258258
create_payment_session(
259-
%{
260-
creator: creator,
261-
owner: owner,
262-
recipient: recipient,
263-
amount: amount,
264-
description: "Tip payment for OSS contributions"
265-
},
259+
%{creator: creator, amount: amount, description: "Tip payment for OSS contributions"},
266260
ticket_ref: opts[:ticket_ref],
267-
tip_id: tip.id
261+
tip_id: tip.id,
262+
recipient: recipient
268263
)
269264
end
270265
end)
@@ -273,132 +268,124 @@ defmodule Algora.Bounties do
273268
@spec reward_bounty(
274269
%{
275270
creator: User.t(),
276-
owner: User.t(),
277-
recipient: User.t(),
278271
amount: Money.t(),
279272
bounty_id: String.t(),
280-
claim_id: String.t()
273+
claims: [Claim.t()]
281274
},
282275
opts :: [ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()}]
283276
) ::
284277
{:ok, String.t()} | {:error, atom()}
285-
def reward_bounty(
286-
%{creator: creator, owner: owner, recipient: recipient, amount: amount, bounty_id: bounty_id, claim_id: claim_id},
287-
opts \\ []
288-
) do
289-
# TODO: handle bounty splits
278+
def reward_bounty(%{creator: creator, amount: amount, bounty_id: bounty_id, claims: claims}, opts \\ []) do
290279
create_payment_session(
291-
%{
292-
creator: creator,
293-
owner: owner,
294-
recipient: recipient,
295-
amount: amount,
296-
description: "Bounty payment for OSS contributions"
297-
},
280+
%{creator: creator, amount: amount, description: "Bounty payment for OSS contributions"},
298281
ticket_ref: opts[:ticket_ref],
299282
bounty_id: bounty_id,
300-
claim_id: claim_id
283+
claims: claims
301284
)
302285
end
303286

304287
@spec create_payment_session(
305-
%{creator: User.t(), owner: User.t(), recipient: User.t(), amount: Money.t(), description: String.t()},
288+
%{creator: User.t(), amount: Money.t(), description: String.t()},
306289
opts :: [
307290
ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()},
308291
tip_id: String.t(),
309292
bounty_id: String.t(),
310-
claim_id: String.t()
293+
claims: [Claim.t()],
294+
recipient: User.t()
311295
]
312296
) ::
313297
{:ok, String.t()} | {:error, atom()}
314-
def create_payment_session(
315-
%{creator: creator, owner: owner, recipient: recipient, amount: amount, description: description},
316-
opts \\ []
317-
) do
298+
def create_payment_session(%{creator: creator, amount: amount, description: description}, opts \\ []) do
318299
ticket_ref = opts[:ticket_ref]
300+
recipient = opts[:recipient]
301+
claims = opts[:claims]
319302

320-
# Initialize transaction IDs
321-
charge_id = Nanoid.generate()
322-
debit_id = Nanoid.generate()
323-
credit_id = Nanoid.generate()
324303
tx_group_id = Nanoid.generate()
325304

326305
# Calculate fees
327306
currency = to_string(amount.currency)
328-
total_paid = Payments.get_total_paid(owner.id, recipient.id)
329-
platform_fee_pct = FeeTier.calculate_fee_percentage(total_paid)
307+
platform_fee_pct = FeeTier.calculate_fee_percentage(Money.zero(:USD))
330308
transaction_fee_pct = Payments.get_transaction_fee_pct()
331309

332310
platform_fee = Money.mult!(amount, platform_fee_pct)
333311
transaction_fee = Money.mult!(amount, transaction_fee_pct)
334312
total_fee = Money.add!(platform_fee, transaction_fee)
335313
gross_amount = Money.add!(amount, total_fee)
336314

337-
line_items = [
338-
%{
339-
price_data: %{
340-
unit_amount: MoneyUtils.to_minor_units(amount),
341-
currency: currency,
342-
product_data: %{
343-
name: "Payment to @#{recipient.provider_login}",
344-
description: if(ticket_ref, do: "#{ticket_ref[:owner]}/#{ticket_ref[:repo]}##{ticket_ref[:number]}"),
345-
images: [recipient.avatar_url]
315+
line_items =
316+
if recipient do
317+
[
318+
%{
319+
price_data: %{
320+
unit_amount: MoneyUtils.to_minor_units(amount),
321+
currency: currency,
322+
product_data: %{
323+
name: "Payment to @#{recipient.provider_login}",
324+
description: if(ticket_ref, do: "#{ticket_ref[:owner]}/#{ticket_ref[:repo]}##{ticket_ref[:number]}"),
325+
images: [recipient.avatar_url]
326+
}
327+
},
328+
quantity: 1
346329
}
347-
},
348-
quantity: 1
349-
},
350-
%{
351-
price_data: %{
352-
unit_amount: MoneyUtils.to_minor_units(Money.mult!(amount, platform_fee_pct)),
353-
currency: currency,
354-
product_data: %{name: "Algora platform fee (#{Util.format_pct(platform_fee_pct)})"}
355-
},
356-
quantity: 1
357-
},
358-
%{
359-
price_data: %{
360-
unit_amount: MoneyUtils.to_minor_units(Money.mult!(amount, transaction_fee_pct)),
361-
currency: currency,
362-
product_data: %{name: "Transaction fee (#{Util.format_pct(transaction_fee_pct)})"}
363-
},
364-
quantity: 1
365-
}
366-
]
330+
]
331+
else
332+
[]
333+
end ++
334+
Enum.map(claims, fn claim ->
335+
%{
336+
price_data: %{
337+
# TODO: ensure shares are normalized
338+
unit_amount: amount |> Money.mult!(claim.group_share) |> MoneyUtils.to_minor_units(),
339+
currency: currency,
340+
product_data: %{
341+
name: "Payment to @#{claim.user.provider_login}",
342+
description: if(ticket_ref, do: "#{ticket_ref[:owner]}/#{ticket_ref[:repo]}##{ticket_ref[:number]}"),
343+
images: [claim.user.avatar_url]
344+
}
345+
},
346+
quantity: 1
347+
}
348+
end) ++
349+
[
350+
%{
351+
price_data: %{
352+
unit_amount: MoneyUtils.to_minor_units(Money.mult!(amount, platform_fee_pct)),
353+
currency: currency,
354+
product_data: %{name: "Algora platform fee (#{Util.format_pct(platform_fee_pct)})"}
355+
},
356+
quantity: 1
357+
},
358+
%{
359+
price_data: %{
360+
unit_amount: MoneyUtils.to_minor_units(Money.mult!(amount, transaction_fee_pct)),
361+
currency: currency,
362+
product_data: %{name: "Transaction fee (#{Util.format_pct(transaction_fee_pct)})"}
363+
},
364+
quantity: 1
365+
}
366+
]
367367

368368
Repo.transact(fn ->
369369
with {:ok, _charge} <-
370370
initialize_charge(%{
371-
id: charge_id,
371+
id: Nanoid.generate(),
372372
tip_id: opts[:tip_id],
373373
bounty_id: opts[:bounty_id],
374-
claim_id: opts[:claim_id],
374+
claim_id: nil,
375375
user_id: creator.id,
376376
gross_amount: gross_amount,
377377
net_amount: amount,
378378
total_fee: total_fee,
379379
line_items: line_items,
380380
group_id: tx_group_id
381381
}),
382-
{:ok, _debit} <-
383-
initialize_debit(%{
384-
id: debit_id,
385-
tip_id: opts[:tip_id],
386-
bounty_id: opts[:bounty_id],
387-
claim_id: opts[:claim_id],
388-
amount: amount,
389-
user_id: creator.id,
390-
linked_transaction_id: credit_id,
391-
group_id: tx_group_id
392-
}),
393-
{:ok, _credit} <-
394-
initialize_credit(%{
395-
id: credit_id,
382+
{:ok, _transactions} <-
383+
create_transaction_pairs(%{
384+
claims: opts[:claims] || [],
396385
tip_id: opts[:tip_id],
397386
bounty_id: opts[:bounty_id],
398-
claim_id: opts[:claim_id],
399387
amount: amount,
400-
user_id: recipient.id,
401-
linked_transaction_id: debit_id,
388+
creator_id: creator.id,
402389
group_id: tx_group_id
403390
}),
404391
{:ok, session} <-
@@ -415,7 +402,6 @@ defmodule Algora.Bounties do
415402
id: id,
416403
tip_id: tip_id,
417404
bounty_id: bounty_id,
418-
claim_id: claim_id,
419405
user_id: user_id,
420406
gross_amount: gross_amount,
421407
net_amount: net_amount,
@@ -431,7 +417,6 @@ defmodule Algora.Bounties do
431417
status: :initialized,
432418
tip_id: tip_id,
433419
bounty_id: bounty_id,
434-
claim_id: claim_id,
435420
user_id: user_id,
436421
gross_amount: gross_amount,
437422
net_amount: net_amount,
@@ -643,4 +628,52 @@ defmodule Algora.Bounties do
643628
reviews_count: 4
644629
}
645630
end
631+
632+
# Helper function to create transaction pairs
633+
defp create_transaction_pairs(%{claims: claims} = params) when length(claims) > 0 do
634+
Enum.reduce_while(claims, {:ok, []}, fn claim, {:ok, acc} ->
635+
params
636+
|> Map.put(:claim_id, claim.id)
637+
|> Map.put(:recipient_id, claim.user.id)
638+
|> create_single_transaction_pair()
639+
|> case do
640+
{:ok, transactions} -> {:cont, {:ok, transactions ++ acc}}
641+
error -> {:halt, error}
642+
end
643+
end)
644+
end
645+
646+
defp create_transaction_pairs(params) do
647+
create_single_transaction_pair(params)
648+
end
649+
650+
defp create_single_transaction_pair(params) do
651+
debit_id = Nanoid.generate()
652+
credit_id = Nanoid.generate()
653+
654+
with {:ok, debit} <-
655+
initialize_debit(%{
656+
id: debit_id,
657+
tip_id: params.tip_id,
658+
bounty_id: params.bounty_id,
659+
claim_id: params.claim_id,
660+
amount: params.amount,
661+
user_id: params.creator_id,
662+
linked_transaction_id: credit_id,
663+
group_id: params.group_id
664+
}),
665+
{:ok, credit} <-
666+
initialize_credit(%{
667+
id: credit_id,
668+
tip_id: params.tip_id,
669+
bounty_id: params.bounty_id,
670+
claim_id: params.claim_id,
671+
amount: params.amount,
672+
user_id: params.recipient_id,
673+
linked_transaction_id: debit_id,
674+
group_id: params.group_id
675+
}) do
676+
{:ok, [debit, credit]}
677+
end
678+
end
646679
end

lib/algora_web/live/claim_live.ex

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,9 @@ defmodule AlgoraWeb.ClaimLive do
126126
%{
127127
# TODO: handle unauthenticated user
128128
creator: socket.assigns.current_user,
129-
owner: bounty.owner,
130-
recipient: claim.user,
131129
amount: bounty.amount,
132130
bounty_id: bounty.id,
133-
claim_id: claim.id
131+
claims: socket.assigns.claims
134132
},
135133
ticket_ref: %{
136134
owner: claim.target.repository.user.provider_login,

lib/algora_web/router.ex

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ defmodule AlgoraWeb.Router do
114114
live "/payment/success", Payment.SuccessLive, :index
115115
live "/payment/canceled", Payment.CanceledLive, :index
116116
live "/@/:handle", User.ProfileLive, :index
117+
live "/claims/:group_id", ClaimLive
117118
end
118119

119120
live "/orgs/new", Org.CreateLive
@@ -140,8 +141,6 @@ defmodule AlgoraWeb.Router do
140141
live "/trotw", TROTWLive
141142

142143
live "/open-source", OpenSourceLive, :index
143-
144-
live "/claims/:group_id", ClaimLive
145144
end
146145

147146
# Other scopes may use custom stacks.

priv/repo/seeds.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ richard =
7777
7878
display_name: "Richard Hendricks",
7979
handle: "richard",
80+
provider_login: "richard",
8081
bio: "CEO of Pied Piper. Creator of the middle-out compression algorithm.",
8182
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/richard.jpg",
8283
tech_stack: ["Python", "C++"],
@@ -94,6 +95,7 @@ dinesh =
9495
9596
display_name: "Dinesh Chugtai",
9697
handle: "dinesh",
98+
provider_login: "dinesh",
9799
bio: "Lead Frontend Engineer at Pied Piper. Java bad, Python good.",
98100
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/dinesh.png",
99101
tech_stack: ["Python", "JavaScript"],
@@ -111,6 +113,7 @@ gilfoyle =
111113
112114
display_name: "Bertram Gilfoyle",
113115
handle: "gilfoyle",
116+
provider_login: "gilfoyle",
114117
bio: "Systems Architect. Security. DevOps. Satanist.",
115118
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/gilfoyle.jpg",
116119
tech_stack: ["Python", "Rust", "Go", "Terraform"],
@@ -128,6 +131,7 @@ jared =
128131
129132
display_name: "Jared Dunn",
130133
handle: "jared",
134+
provider_login: "jared",
131135
bio: "COO of Pied Piper. Former Hooli executive. Excel wizard.",
132136
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/jared.png",
133137
tech_stack: ["Python", "SQL"],
@@ -145,6 +149,7 @@ carver =
145149
146150
display_name: "Kevin 'The Carver'",
147151
handle: "carver",
152+
provider_login: "carver",
148153
bio:
149154
"Cloud architecture specialist. If your infrastructure needs a teardown, I'm your guy. Known for my 'insane' cloud architectures and occasional server incidents.",
150155
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/carver.jpg",
@@ -321,6 +326,7 @@ big_head =
321326
322327
display_name: "Nelson Bighetti",
323328
handle: "bighead",
329+
provider_login: "bighead",
324330
bio: "Former Hooli executive. Accidental tech success. Stanford President.",
325331
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/bighead.jpg",
326332
tech_stack: ["Python", "JavaScript"],
@@ -339,6 +345,7 @@ jian_yang =
339345
340346
display_name: "Jian Yang",
341347
handle: "jianyang",
348+
provider_login: "jianyang",
342349
bio: "App developer. Creator of SeeFood and Smokation.",
343350
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/jianyang.jpg",
344351
tech_stack: ["Swift", "Python", "TensorFlow"],
@@ -357,6 +364,7 @@ john =
357364
358365
display_name: "John Stafford",
359366
handle: "john",
367+
provider_login: "john",
360368
bio: "Datacenter infrastructure expert. Rack space optimization specialist.",
361369
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/john.png",
362370
tech_stack: ["Perl", "Terraform", "C++", "C"],
@@ -375,6 +383,7 @@ aly =
375383
376384
display_name: "Aly Dutta",
377385
handle: "aly",
386+
provider_login: "aly",
378387
bio: "Former Hooli engineer. Expert in distributed systems and scalability.",
379388
avatar_url: "https://algora.io/asset/storage/v1/object/public/mock/aly.png",
380389
tech_stack: ["Java", "Kotlin", "Go"],
@@ -582,6 +591,8 @@ for {repo_name, issues} <- repos do
582591
end)
583592
end
584593
end
594+
595+
Logger.info("Claim [#{claim.status}]: #{AlgoraWeb.Endpoint.url()}/claims/#{claim.group_id}")
585596
end
586597
end
587598
end

0 commit comments

Comments
 (0)