@@ -25,6 +25,100 @@ public function __construct(
2525 private CommissionCalculator $ calculator ,
2626 ) {}
2727
28+ /**
29+ * Request a manual payout for an organization.
30+ */
31+ public function requestPayout (
32+ Model &CommissionableContract $ organization ,
33+ ?int $ amount = null ,
34+ ?PayoutMethodEnum $ method = null ,
35+ ): Payout {
36+ $ available = $ organization ->pendingPayoutAmount ();
37+ $ amount ??= $ available ;
38+
39+ // Validate amount
40+ if ($ amount <= 0 ) {
41+ throw PayoutFailedException::insufficientFunds (available: $ available , requested: $ amount );
42+ }
43+
44+ if ($ amount > $ available ) {
45+ throw PayoutFailedException::insufficientFunds (available: $ available , requested: $ amount );
46+ }
47+
48+ // Check minimum payout amount
49+ /** @var int $minPayout */
50+ $ minPayout = config ('billing.marketplace.commission.min_payout_amount ' , 1000 );
51+
52+ if ($ amount < $ minPayout ) {
53+ throw PayoutFailedException::belowMinimum (amount: $ amount , minimum: $ minPayout );
54+ }
55+
56+ // Check for pending payout
57+ if ($ organization ->hasPendingPayout ()) {
58+ throw new PayoutFailedException ('A payout is already pending for this organization ' );
59+ }
60+
61+ // Validate payout details
62+ $ phone = $ organization ->getPayoutPhone ();
63+ $ name = $ organization ->getPayoutName ();
64+
65+ if ($ phone === null || $ phone === '' ) {
66+ throw PayoutFailedException::missingPayoutDetails ();
67+ }
68+
69+ /** @var string $currency */
70+ $ currency = config ('billing.marketplace.commission.currency ' , 'XOF ' );
71+
72+ $ payoutData = new PayoutData (
73+ amount: $ amount ,
74+ currency: $ currency ,
75+ method: $ method ?? PayoutMethodEnum::MobileMoney,
76+ recipientPhone: $ phone ,
77+ recipientName: $ name ,
78+ periodStart: new DateTimeImmutable ('now ' ),
79+ periodEnd: new DateTimeImmutable ('now ' ),
80+ metadata: [
81+ 'manual_request ' => true ,
82+ 'requested_at ' => (new DateTimeImmutable ('now ' ))->format ('Y-m-d H:i:s ' ),
83+ ],
84+ );
85+
86+ // Create the payout record
87+ $ payout = $ organization ->createPayout ($ payoutData , [
88+ 'provider ' => config ('billing.default ' ),
89+ ]);
90+
91+ // Execute via provider if it supports payouts
92+ /** @var string $providerName */
93+ $ providerName = config ('billing.default ' );
94+ $ provider = $ this ->billing ->provider ($ providerName );
95+
96+ if ($ provider instanceof PayoutProvider) {
97+ try {
98+ $ result = $ provider ->createPayout ($ payoutData );
99+
100+ if (isset ($ result ['payout_id ' ])) {
101+ $ payout ->markAsProcessing ($ result ['payout_id ' ]);
102+ }
103+
104+ if (isset ($ result ['status ' ]) && $ result ['status ' ] === 'completed ' ) {
105+ $ payout ->markAsCompleted ();
106+ event (new PayoutCompleted ($ payout ));
107+ }
108+ } catch (Throwable $ throwable ) {
109+ $ payout ->markAsFailed ($ throwable ->getMessage ());
110+ event (new PayoutFailed ($ payout , $ throwable ->getMessage ()));
111+
112+ throw new PayoutFailedException (
113+ message: 'Payout request failed: ' .$ throwable ->getMessage (),
114+ previous: $ throwable ,
115+ );
116+ }
117+ }
118+
119+ return $ payout ->fresh () ?? $ payout ;
120+ }
121+
28122 /**
29123 * Process an instant payout for a completed transaction.
30124 */
0 commit comments