@@ -52,7 +52,9 @@ defmodule Algora.Bounties do
5252 ticket: Ticket . t ( ) ,
5353 visibility: Bounty . visibility ( ) ,
5454 shared_with: [ String . t ( ) ] ,
55- hours_per_week: integer ( ) | nil
55+ hours_per_week: integer ( ) | nil ,
56+ hourly_rate: Money . t ( ) | nil ,
57+ contract_type: Bounty . contract_type ( ) | nil
5658 } ) ::
5759 { :ok , Bounty . t ( ) } | { :error , atom ( ) }
5860 defp do_create_bounty ( % { creator: creator , owner: owner , amount: amount , ticket: ticket } = params ) do
@@ -64,7 +66,9 @@ defmodule Algora.Bounties do
6466 creator_id: creator . id ,
6567 visibility: params [ :visibility ] || owner . bounty_mode ,
6668 shared_with: params [ :shared_with ] || [ ] ,
67- hours_per_week: params [ :hours_per_week ]
69+ hours_per_week: params [ :hours_per_week ] ,
70+ hourly_rate: params [ :hourly_rate ] ,
71+ contract_type: params [ :contract_type ]
6872 } )
6973
7074 changeset
@@ -112,7 +116,9 @@ defmodule Algora.Bounties do
112116 command_source: :ticket | :comment ,
113117 visibility: Bounty . visibility ( ) | nil ,
114118 shared_with: [ String . t ( ) ] | nil ,
115- hours_per_week: integer ( ) | nil
119+ hourly_rate: Money . t ( ) | nil ,
120+ hours_per_week: integer ( ) | nil ,
121+ contract_type: Bounty . contract_type ( ) | nil
116122 ]
117123 ) ::
118124 { :ok , Bounty . t ( ) } | { :error , atom ( ) }
@@ -144,7 +150,9 @@ defmodule Algora.Bounties do
144150 ticket: ticket ,
145151 visibility: opts [ :visibility ] ,
146152 shared_with: shared_with ,
147- hours_per_week: opts [ :hours_per_week ]
153+ hourly_rate: opts [ :hourly_rate ] ,
154+ hours_per_week: opts [ :hours_per_week ] ,
155+ contract_type: opts [ :contract_type ]
148156 } )
149157
150158 :set ->
@@ -195,7 +203,9 @@ defmodule Algora.Bounties do
195203 strategy: strategy ( ) ,
196204 visibility: Bounty . visibility ( ) | nil ,
197205 shared_with: [ String . t ( ) ] | nil ,
198- hours_per_week: integer ( ) | nil
206+ hours_per_week: integer ( ) | nil ,
207+ hourly_rate: Money . t ( ) | nil ,
208+ contract_type: Bounty . contract_type ( ) | nil
199209 ]
200210 ) ::
201211 { :ok , Bounty . t ( ) } | { :error , atom ( ) }
@@ -215,7 +225,9 @@ defmodule Algora.Bounties do
215225 ticket: ticket ,
216226 visibility: opts [ :visibility ] ,
217227 shared_with: shared_with ,
218- hours_per_week: opts [ :hours_per_week ]
228+ hours_per_week: opts [ :hours_per_week ] ,
229+ hourly_rate: opts [ :hourly_rate ] ,
230+ contract_type: opts [ :contract_type ]
219231 } ) ,
220232 { :ok , _job } <- notify_bounty ( % { owner: owner , bounty: bounty } ) do
221233 broadcast ( )
@@ -769,7 +781,12 @@ defmodule Algora.Bounties do
769781 bounty: Bounty . t ( ) ,
770782 claims: [ Claim . t ( ) ]
771783 } ,
772- opts :: [ ticket_ref: % { owner: String . t ( ) , repo: String . t ( ) , number: integer ( ) } , recipient: User . t ( ) ]
784+ opts :: [
785+ ticket_ref: % { owner: String . t ( ) , repo: String . t ( ) , number: integer ( ) } ,
786+ recipient: User . t ( ) ,
787+ success_url: String . t ( ) ,
788+ cancel_url: String . t ( )
789+ ]
773790 ) ::
774791 { :ok , String . t ( ) } | { :error , atom ( ) }
775792 def reward_bounty ( % { owner: owner , amount: amount , bounty: bounty , claims: claims } , opts \\ [ ] ) do
@@ -778,7 +795,37 @@ defmodule Algora.Bounties do
778795 ticket_ref: opts [ :ticket_ref ] ,
779796 bounty: bounty ,
780797 claims: claims ,
781- recipient: opts [ :recipient ]
798+ recipient: opts [ :recipient ] ,
799+ success_url: opts [ :success_url ] ,
800+ cancel_url: opts [ :cancel_url ]
801+ )
802+ end
803+
804+ @ spec authorize_payment (
805+ % {
806+ owner: User . t ( ) ,
807+ amount: Money . t ( ) ,
808+ bounty: Bounty . t ( ) ,
809+ claims: [ Claim . t ( ) ]
810+ } ,
811+ opts :: [
812+ ticket_ref: % { owner: String . t ( ) , repo: String . t ( ) , number: integer ( ) } ,
813+ recipient: User . t ( ) ,
814+ success_url: String . t ( ) ,
815+ cancel_url: String . t ( )
816+ ]
817+ ) ::
818+ { :ok , String . t ( ) } | { :error , atom ( ) }
819+ def authorize_payment ( % { owner: owner , amount: amount , bounty: bounty , claims: claims } , opts \\ [ ] ) do
820+ create_payment_session (
821+ % { owner: owner , amount: amount , description: "Bounty payment for OSS contributions" } ,
822+ ticket_ref: opts [ :ticket_ref ] ,
823+ bounty: bounty ,
824+ claims: claims ,
825+ recipient: opts [ :recipient ] ,
826+ capture_method: :manual ,
827+ success_url: opts [ :success_url ] ,
828+ cancel_url: opts [ :cancel_url ]
782829 )
783830 end
784831
@@ -801,61 +848,81 @@ defmodule Algora.Bounties do
801848 description = if ( ticket_ref , do: "#{ ticket_ref [ :repo ] } ##{ ticket_ref [ :number ] } " )
802849
803850 platform_fee_pct =
804- if bounty && Date . before? ( bounty . inserted_at , ~D[ 2025-04-16] ) do
851+ if bounty && Date . before? ( bounty . inserted_at , ~D[ 2025-04-16] ) && is_nil ( bounty . contract_type ) do
805852 Decimal . div ( owner . fee_pct_prev , 100 )
806853 else
807854 Decimal . div ( owner . fee_pct , 100 )
808855 end
809856
810857 transaction_fee_pct = Payments . get_transaction_fee_pct ( )
811858
812- payouts =
813- if recipient do
859+ case opts [ :bounty ] do
860+ % { contract_type: :marketplace } ->
814861 [
815862 % LineItem {
816863 amount: amount ,
817- title: "Payment to @#{ recipient . provider_login } " ,
818- description: description ,
864+ title: "Contract payment - @#{ recipient . provider_login } " ,
865+ description: "(includes all platform and payment processing fees)" ,
819866 image: recipient . avatar_url ,
820867 type: :payout
821868 }
822869 ]
823- else
824- Enum . map ( claims , fn claim ->
825- % LineItem {
826- # TODO: ensure shares are normalized
827- amount: Money . mult! ( amount , claim . group_share ) ,
828- title: "Payment to @#{ claim . user . provider_login } " ,
829- description: description ,
830- image: claim . user . avatar_url ,
831- type: :payout
832- }
833- end )
834- end
835870
836- payouts ++
837- [
838- % LineItem {
839- amount: Money . mult! ( amount , platform_fee_pct ) ,
840- title: "Algora platform fee (#{ Util . format_pct ( platform_fee_pct ) } )" ,
841- type: :fee
842- } ,
843- % LineItem {
844- amount: Money . mult! ( amount , transaction_fee_pct ) ,
845- title: "Transaction fee (#{ Util . format_pct ( transaction_fee_pct ) } )" ,
846- type: :fee
847- }
848- ]
871+ _ ->
872+ if recipient do
873+ [
874+ % LineItem {
875+ amount: amount ,
876+ title: "Payment to @#{ recipient . provider_login } " ,
877+ description: description ,
878+ image: recipient . avatar_url ,
879+ type: :payout
880+ }
881+ ]
882+ else
883+ Enum . map ( claims , fn claim ->
884+ % LineItem {
885+ # TODO: ensure shares are normalized
886+ amount: Money . mult! ( amount , claim . group_share ) ,
887+ title: "Payment to @#{ claim . user . provider_login } " ,
888+ description: description ,
889+ image: claim . user . avatar_url ,
890+ type: :payout
891+ }
892+ end )
893+ end ++
894+ [
895+ % LineItem {
896+ amount: Money . mult! ( amount , platform_fee_pct ) ,
897+ title: "Algora platform fee (#{ Util . format_pct ( platform_fee_pct ) } )" ,
898+ type: :fee
899+ } ,
900+ % LineItem {
901+ amount: Money . mult! ( amount , transaction_fee_pct ) ,
902+ title: "Transaction fee (#{ Util . format_pct ( transaction_fee_pct ) } )" ,
903+ type: :fee
904+ }
905+ ]
906+ end
849907 end
850908
909+ def calculate_contract_amount ( amount ) , do: Money . mult! ( amount , Decimal . new ( "1.13" ) )
910+
911+ def final_contract_amount ( :marketplace , amount ) , do: amount
912+
913+ def final_contract_amount ( :bring_your_own , amount ) , do: calculate_contract_amount ( amount )
914+
851915 @ spec create_payment_session (
852916 % { owner: User . t ( ) , amount: Money . t ( ) , description: String . t ( ) } ,
853917 opts :: [
854918 ticket_ref: % { owner: String . t ( ) , repo: String . t ( ) , number: integer ( ) } ,
855919 tip_id: String . t ( ) ,
856920 bounty: Bounty . t ( ) ,
857921 claims: [ Claim . t ( ) ] ,
858- recipient: User . t ( )
922+ recipient: User . t ( ) ,
923+ capture_method: :automatic | :automatic_async | :manual ,
924+ success_url: String . t ( ) ,
925+ cancel_url: String . t ( )
859926 ]
860927 ) ::
861928 { :ok , String . t ( ) } | { :error , atom ( ) }
@@ -870,6 +937,19 @@ defmodule Algora.Bounties do
870937 bounty: opts [ :bounty ]
871938 )
872939
940+ payment_intent_data = % {
941+ description: description ,
942+ metadata: % { "version" => Payments . metadata_version ( ) , "group_id" => tx_group_id }
943+ }
944+
945+ { payment_intent_data , session_opts } =
946+ if capture_method = opts [ :capture_method ] do
947+ { Map . put ( payment_intent_data , :capture_method , capture_method ) ,
948+ [ success_url: opts [ :success_url ] , cancel_url: opts [ :cancel_url ] ] }
949+ else
950+ { payment_intent_data , [ ] }
951+ end
952+
873953 gross_amount = LineItem . gross_amount ( line_items )
874954
875955 bounty_id = if bounty = opts [ :bounty ] , do: bounty . id
@@ -899,10 +979,12 @@ defmodule Algora.Bounties do
899979 group_id: tx_group_id
900980 } ) ,
901981 { :ok , session } <-
902- Payments . create_stripe_session ( owner , Enum . map ( line_items , & LineItem . to_stripe / 1 ) , % {
903- description: description ,
904- metadata: % { "version" => Payments . metadata_version ( ) , "group_id" => tx_group_id }
905- } ) do
982+ Payments . create_stripe_session (
983+ owner ,
984+ Enum . map ( line_items , & LineItem . to_stripe / 1 ) ,
985+ payment_intent_data ,
986+ session_opts
987+ ) do
906988 { :ok , session . url }
907989 end
908990 end )
@@ -1012,7 +1094,6 @@ defmodule Algora.Bounties do
10121094 } )
10131095 |> Algora.Validations . validate_positive ( :gross_amount )
10141096 |> Algora.Validations . validate_positive ( :net_amount )
1015- |> Algora.Validations . validate_positive ( :total_fee )
10161097 |> foreign_key_constraint ( :user_id )
10171098 |> unique_constraint ( [ :idempotency_key ] )
10181099 |> Repo . insert ( )
0 commit comments