type !== QrCode::TYPE_PAYMENT) { throw new RuntimeException('This QR is not a payment code.'); } $amountGhs = round((float) ($data['amount'] ?? 0), 2); if ($amountGhs <= 0) { throw new RuntimeException('Enter an amount greater than zero.'); } $qrCode->loadMissing('user.qrSetting'); $merchant = $qrCode->user; $email = trim((string) ($merchant?->qrSetting?->notify_email ?: $merchant?->email)); if ($email === '' || ! str_contains($email, '@')) { throw new RuntimeException('This payment QR is not ready yet — the vendor must add an email in Ladill Mini settings.'); } $amountMinor = (int) round($amountGhs * 100); $reference = 'MINP-'.strtoupper(Str::random(16)); $payRef = 'MIN-'.strtoupper(Str::random(16)); $payment = MiniPayment::create([ 'qr_code_id' => $qrCode->id, 'user_id' => $qrCode->user_id, 'reference' => $reference, 'amount_minor' => $amountMinor, 'currency' => $qrCode->content()['currency'] ?? 'GHS', 'payer_name' => null, 'payer_email' => null, 'payer_phone' => null, 'payer_note' => null, 'status' => MiniPayment::STATUS_PENDING, 'payment_reference' => $payRef, ]); $paystackData = $this->paystack->initializeTransaction([ 'email' => $email, 'amount' => $amountMinor, 'currency' => $payment->currency, 'reference' => $payRef, 'callback_url' => route('qr.public.payment.callback', ['shortCode' => $qrCode->short_code]), 'metadata' => [ 'mini_payment_id' => $payment->id, 'qr_code_id' => $qrCode->id, 'merchant_id' => $qrCode->user_id, ], ]); return [ 'payment' => $payment, 'checkout_url' => 'https://checkout.paystack.com/'.($paystackData['access_code'] ?? ''), ]; } public function complete(string $paymentReference): MiniPayment { $payment = MiniPayment::where('payment_reference', $paymentReference) ->where('status', MiniPayment::STATUS_PENDING) ->firstOrFail(); $data = $this->paystack->verifyTransaction($paymentReference); if (($data['status'] ?? '') !== 'success') { $payment->update(['status' => MiniPayment::STATUS_FAILED]); throw new RuntimeException('Payment was not successful.'); } $paidMinor = (int) ($data['amount'] ?? $payment->amount_minor); $platformFeeMinor = (int) round($paidMinor * MiniPayment::PLATFORM_FEE_RATE); $merchantMinor = $paidMinor - $platformFeeMinor; $payment->update([ 'status' => MiniPayment::STATUS_PAID, 'amount_minor' => $paidMinor, 'platform_fee_minor' => $platformFeeMinor, 'merchant_amount_minor' => $merchantMinor, 'paid_at' => now(), 'metadata' => array_merge((array) $payment->metadata, ['paystack' => $data]), ]); $merchant = $payment->merchant; $businessName = $payment->qrCode?->content()['business_name'] ?? $payment->qrCode?->label ?? 'Payment QR'; $this->billing->credit( $merchant->public_id, $merchantMinor, 'mini', 'pay', $paymentReference, $payment->id, sprintf('Payment via %s', $businessName), ); if ($payment->payer_phone) { $this->sms->send( $payment->payer_phone, sprintf( 'Payment of %s %s to %s confirmed. Ref: %s', $payment->currency, number_format($paidMinor / 100, 2), $businessName, $payment->reference ) ); } return $payment->fresh(['qrCode', 'merchant']); } }