Skip to content

Commit e5fe7f1

Browse files
author
Just Chris
committed
feat: add manual payout request functionality
Add requestPayout method to InstantPayoutService for organizations to request manual payouts with validation for amount, minimum threshold, and pending payout checks. Also add hasPendingPayout method to CommissionableContract interface.
1 parent 8a5bb46 commit e5fe7f1

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

src/Contracts/CommissionableContract.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,9 @@ public function requestPayout(): ?Payout;
109109
* Determine if a payout can be requested.
110110
*/
111111
public function canRequestPayout(): bool;
112+
113+
/**
114+
* Check if this entity has a pending payout.
115+
*/
116+
public function hasPendingPayout(): bool;
112117
}

src/Services/InstantPayoutService.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)