@@ -52,7 +52,9 @@ defmodule Algora.Bounties do
52
52
ticket: Ticket . t ( ) ,
53
53
visibility: Bounty . visibility ( ) ,
54
54
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
56
58
} ) ::
57
59
{ :ok , Bounty . t ( ) } | { :error , atom ( ) }
58
60
defp do_create_bounty ( % { creator: creator , owner: owner , amount: amount , ticket: ticket } = params ) do
@@ -64,7 +66,9 @@ defmodule Algora.Bounties do
64
66
creator_id: creator . id ,
65
67
visibility: params [ :visibility ] || owner . bounty_mode ,
66
68
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 ]
68
72
} )
69
73
70
74
changeset
@@ -112,7 +116,9 @@ defmodule Algora.Bounties do
112
116
command_source: :ticket | :comment ,
113
117
visibility: Bounty . visibility ( ) | nil ,
114
118
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
116
122
]
117
123
) ::
118
124
{ :ok , Bounty . t ( ) } | { :error , atom ( ) }
@@ -144,7 +150,9 @@ defmodule Algora.Bounties do
144
150
ticket: ticket ,
145
151
visibility: opts [ :visibility ] ,
146
152
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 ]
148
156
} )
149
157
150
158
:set ->
@@ -195,7 +203,9 @@ defmodule Algora.Bounties do
195
203
strategy: strategy ( ) ,
196
204
visibility: Bounty . visibility ( ) | nil ,
197
205
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
199
209
]
200
210
) ::
201
211
{ :ok , Bounty . t ( ) } | { :error , atom ( ) }
@@ -215,7 +225,9 @@ defmodule Algora.Bounties do
215
225
ticket: ticket ,
216
226
visibility: opts [ :visibility ] ,
217
227
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 ]
219
231
} ) ,
220
232
{ :ok , _job } <- notify_bounty ( % { owner: owner , bounty: bounty } ) do
221
233
broadcast ( )
@@ -769,7 +781,12 @@ defmodule Algora.Bounties do
769
781
bounty: Bounty . t ( ) ,
770
782
claims: [ Claim . t ( ) ]
771
783
} ,
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
+ ]
773
790
) ::
774
791
{ :ok , String . t ( ) } | { :error , atom ( ) }
775
792
def reward_bounty ( % { owner: owner , amount: amount , bounty: bounty , claims: claims } , opts \\ [ ] ) do
@@ -778,7 +795,37 @@ defmodule Algora.Bounties do
778
795
ticket_ref: opts [ :ticket_ref ] ,
779
796
bounty: bounty ,
780
797
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 ]
782
829
)
783
830
end
784
831
@@ -801,61 +848,81 @@ defmodule Algora.Bounties do
801
848
description = if ( ticket_ref , do: "#{ ticket_ref [ :repo ] } ##{ ticket_ref [ :number ] } " )
802
849
803
850
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
805
852
Decimal . div ( owner . fee_pct_prev , 100 )
806
853
else
807
854
Decimal . div ( owner . fee_pct , 100 )
808
855
end
809
856
810
857
transaction_fee_pct = Payments . get_transaction_fee_pct ( )
811
858
812
- payouts =
813
- if recipient do
859
+ case opts [ :bounty ] do
860
+ % { contract_type: :marketplace } ->
814
861
[
815
862
% LineItem {
816
863
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)" ,
819
866
image: recipient . avatar_url ,
820
867
type: :payout
821
868
}
822
869
]
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
835
870
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
849
907
end
850
908
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
+
851
915
@ spec create_payment_session (
852
916
% { owner: User . t ( ) , amount: Money . t ( ) , description: String . t ( ) } ,
853
917
opts :: [
854
918
ticket_ref: % { owner: String . t ( ) , repo: String . t ( ) , number: integer ( ) } ,
855
919
tip_id: String . t ( ) ,
856
920
bounty: Bounty . t ( ) ,
857
921
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 ( )
859
926
]
860
927
) ::
861
928
{ :ok , String . t ( ) } | { :error , atom ( ) }
@@ -870,6 +937,19 @@ defmodule Algora.Bounties do
870
937
bounty: opts [ :bounty ]
871
938
)
872
939
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
+
873
953
gross_amount = LineItem . gross_amount ( line_items )
874
954
875
955
bounty_id = if bounty = opts [ :bounty ] , do: bounty . id
@@ -899,10 +979,12 @@ defmodule Algora.Bounties do
899
979
group_id: tx_group_id
900
980
} ) ,
901
981
{ :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
906
988
{ :ok , session . url }
907
989
end
908
990
end )
@@ -1012,7 +1094,6 @@ defmodule Algora.Bounties do
1012
1094
} )
1013
1095
|> Algora.Validations . validate_positive ( :gross_amount )
1014
1096
|> Algora.Validations . validate_positive ( :net_amount )
1015
- |> Algora.Validations . validate_positive ( :total_fee )
1016
1097
|> foreign_key_constraint ( :user_id )
1017
1098
|> unique_constraint ( [ :idempotency_key ] )
1018
1099
|> Repo . insert ( )
0 commit comments