Add optional owner gateway routing to Merchant.
Deploy Ladill Merchant / deploy (push) Successful in 44s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-21 19:47:13 +00:00
co-authored by Cursor
parent b737725be4
commit 5166cd8a64
9 changed files with 382 additions and 8 deletions
+101 -7
View File
@@ -10,6 +10,7 @@ use App\Services\Billing\BillingClient;
use App\Services\Billing\PaystackService;
use App\Services\Billing\SmsService;
use App\Services\Pay\PayClient;
use App\Services\Payments\MerchantGatewayService;
use App\Support\LadillLink;
use App\Support\Qr\QrTypeCatalog;
use RuntimeException;
@@ -22,6 +23,7 @@ class MerchantSaleService
private BillingClient $billing,
private SmsService $sms,
private KitchenPusher $kitchen,
private MerchantGatewayService $gateway,
) {}
/**
@@ -53,7 +55,23 @@ class MerchantSaleService
$customerEmail = trim((string) ($data['customer_email'] ?? ''));
$callbackUrl = LadillLink::path($qrCode->short_code, 'order/callback');
$payOrder = $this->pay->createCheckout([
$usesOwnerGateway = $this->gateway->shouldUseOwnerGateway($qrCode->user);
$payOrder = null;
if ($usesOwnerGateway) {
try {
$payOrder = $this->gateway->initialize(
$qrCode->user, (int) round($totalGhs * 100),
(string) ($qrCode->content()['currency'] ?? 'GHS'),
(string) config('pay.mini_checkout_email', 'pay@ladill.com'), $callbackUrl,
'MGP-'.strtoupper(\Illuminate\Support\Str::random(16)),
['qr_code_id' => $qrCode->id, 'short_code' => $qrCode->short_code, 'type' => 'order'],
);
} catch (\Throwable) {
$usesOwnerGateway = false;
}
}
if (! $usesOwnerGateway) {
$payOrder = $this->pay->createCheckout([
'merchant' => $qrCode->user->public_id,
'fee_tier' => 'sales',
'source_service' => 'merchant',
@@ -71,17 +89,18 @@ class MerchantSaleService
'qr_code_id' => $qrCode->id,
'short_code' => $qrCode->short_code,
],
]);
]);
}
$order = QrSaleOrder::create([
'pay_order_id' => $payOrder['id'] ?? null,
'pay_order_id' => $usesOwnerGateway ? null : ($payOrder['id'] ?? null),
'qr_code_id' => $qrCode->id,
'user_id' => $qrCode->user_id,
'customer_name' => trim((string) ($data['customer_name'] ?? '')),
'customer_email' => $customerEmail ?: null,
'customer_phone' => trim((string) ($data['customer_phone'] ?? '')) ?: null,
'items' => $items,
'amount_ghs' => round(($payOrder['amount_minor'] ?? 0) / 100, 2),
'amount_ghs' => $usesOwnerGateway ? $totalGhs : round(($payOrder['amount_minor'] ?? 0) / 100, 2),
'status' => QrSaleOrder::STATUS_PENDING,
'payment_reference' => $payOrder['reference'],
]);
@@ -154,7 +173,23 @@ class MerchantSaleService
}
$lineLabel = sprintf('%s — %s', $serviceName, $startsAt->format('D j M, g:i A'));
$payOrder = $this->pay->createCheckout([
$usesOwnerGateway = $this->gateway->shouldUseOwnerGateway($qrCode->user);
$payOrder = null;
if ($usesOwnerGateway) {
try {
$payOrder = $this->gateway->initialize(
$qrCode->user, (int) round($priceGhs * 100),
(string) ($content['currency'] ?? 'GHS'),
(string) config('pay.mini_checkout_email', 'pay@ladill.com'), $callbackUrl,
'MGP-'.strtoupper(\Illuminate\Support\Str::random(16)),
['qr_code_id' => $qrCode->id, 'qr_booking_id' => $booking->id, 'type' => 'booking'],
);
} catch (\Throwable) {
$usesOwnerGateway = false;
}
}
if (! $usesOwnerGateway) {
$payOrder = $this->pay->createCheckout([
'merchant' => $qrCode->user->public_id,
'fee_tier' => 'sales',
'source_service' => 'merchant',
@@ -171,10 +206,11 @@ class MerchantSaleService
'qr_booking_id' => $booking->id,
'type' => 'booking',
],
]);
]);
}
$booking->update([
'pay_order_id' => $payOrder['id'] ?? null,
'pay_order_id' => $usesOwnerGateway ? null : ($payOrder['id'] ?? null),
'payment_reference' => $payOrder['reference'],
]);
@@ -192,6 +228,9 @@ class MerchantSaleService
if (str_starts_with($reference, 'LP-')) {
return $this->completeLadillPayBooking($reference);
}
if (str_starts_with($reference, 'MGP-')) {
return $this->completeOwnerGatewayBooking($reference);
}
return $this->completeLegacyBooking($reference);
}
@@ -201,6 +240,9 @@ class MerchantSaleService
if (str_starts_with($reference, 'LP-')) {
return $this->completeLadillPay($reference);
}
if (str_starts_with($reference, 'MGP-')) {
return $this->completeOwnerGateway($reference);
}
return $this->completeLegacy($reference);
}
@@ -269,6 +311,32 @@ class MerchantSaleService
return $order;
}
private function completeOwnerGateway(string $reference): QrSaleOrder
{
$order = QrSaleOrder::where('payment_reference', $reference)
->where('status', QrSaleOrder::STATUS_PENDING)
->with('merchant')
->firstOrFail();
$result = $this->gateway->verify($order->merchant, $reference);
if (! $result['paid']) {
$order->update(['status' => QrSaleOrder::STATUS_FAILED]);
throw new RuntimeException('Payment was not successful.');
}
$paidMinor = (int) ($result['amount_minor'] ?: round($order->amount_ghs * 100));
$order->update([
'status' => QrSaleOrder::STATUS_PAID,
'paid_at' => now(),
'amount_ghs' => round($paidMinor / 100, 2),
'platform_fee_ghs' => 0,
'merchant_amount_ghs' => round($paidMinor / 100, 2),
'metadata' => array_merge((array) $order->metadata, ['merchant_gateway' => $result]),
]);
$this->notifyOrderPlaced($order->fresh(['qrCode', 'merchant']));
$this->kitchen->push($order->fresh(['qrCode', 'merchant']));
return $order;
}
private function completeLadillPayBooking(string $reference): QrBooking
{
$booking = QrBooking::where('payment_reference', $reference)
@@ -332,6 +400,32 @@ class MerchantSaleService
return $booking;
}
private function completeOwnerGatewayBooking(string $reference): QrBooking
{
$booking = QrBooking::where('payment_reference', $reference)
->where('status', QrBooking::STATUS_PENDING)
->with('merchant')
->firstOrFail();
$result = $this->gateway->verify($booking->merchant, $reference);
if (! $result['paid']) {
$booking->update(['status' => QrBooking::STATUS_FAILED]);
throw new RuntimeException('Payment was not successful.');
}
$paidMinor = (int) ($result['amount_minor'] ?: round($booking->amount_ghs * 100));
$booking->update([
'status' => QrBooking::STATUS_CONFIRMED,
'fulfillment_status' => QrBooking::FULFILLMENT_CONFIRMED,
'paid_at' => now(),
'amount_ghs' => round($paidMinor / 100, 2),
'platform_fee_ghs' => 0,
'merchant_amount_ghs' => round($paidMinor / 100, 2),
'metadata' => array_merge((array) $booking->metadata, ['merchant_gateway' => $result]),
]);
$this->notifyBookingConfirmed($booking->fresh(['qrCode']));
return $booking;
}
private function notifyBookingConfirmed(QrBooking $booking): void
{
if (! $booking->customer_phone) {