Files
Verde-Web/app/Services/Payment/PayMongoDriver.php

126 lines
4.5 KiB
PHP

<?php
namespace App\Services\Payment;
use App\Models\Payment;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* PayMongo driver scaffold. Wires the SDK calls but is gated on
* PAYMONGO_SECRET_KEY being set. When the key is missing we treat the
* driver as misconfigured and return failure — the caller can then
* fall back to ManualPaymentDriver.
*/
class PayMongoDriver implements PaymentDriver
{
public function __construct(
private readonly string $secretKey,
private readonly string $webhookSecret,
private readonly string $endpoint = 'https://api.paymongo.com/v1',
private readonly string $successUrl = 'https://verde.local/payment/success',
private readonly string $cancelUrl = 'https://verde.local/payment/cancel',
) {}
public function initiate(Payment $payment): InitiateResult
{
if (empty($this->secretKey)) {
return InitiateResult::failure('paymongo_not_configured');
}
try {
$response = Http::withBasicAuth($this->secretKey, '')
->timeout(15)
->post($this->endpoint.'/checkout_sessions', [
'data' => [
'attributes' => [
'line_items' => [[
'name' => $payment->purpose,
'amount' => $payment->amount_centavos,
'currency' => $payment->currency,
'quantity' => 1,
]],
'payment_method_types' => ['gcash', 'paymaya', 'card'],
'success_url' => $this->successUrl.'?p='.$payment->uuid,
'cancel_url' => $this->cancelUrl.'?p='.$payment->uuid,
'metadata' => ['payment_uuid' => $payment->uuid],
],
],
]);
} catch (ConnectionException $e) {
Log::warning('PayMongo connection failed', ['error' => $e->getMessage()]);
return InitiateResult::failure('connection_failed');
}
if (! $response->successful()) {
Log::warning('PayMongo error', ['status' => $response->status(), 'body' => $response->body()]);
return InitiateResult::failure('provider_error_'.$response->status());
}
$data = $response->json('data') ?? [];
$sessionId = $data['id'] ?? null;
$checkoutUrl = $data['attributes']['checkout_url'] ?? null;
$payment->forceFill([
'provider' => Payment::PROVIDER_PAYMONGO,
'provider_payment_id' => $sessionId,
'provider_data' => $data,
'status' => Payment::STATUS_PROCESSING,
])->save();
return InitiateResult::success($checkoutUrl, $sessionId);
}
public function verifyWebhook(string $rawBody, string $signature): bool
{
if (empty($this->webhookSecret)) {
return false;
}
// PayMongo webhook header format: t=timestamp,te=signature,li=...
$parts = [];
foreach (explode(',', $signature) as $kv) {
[$k, $v] = array_pad(explode('=', $kv, 2), 2, '');
$parts[$k] = $v;
}
$timestamp = $parts['t'] ?? '';
$signed = $parts['te'] ?? '';
$expected = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $this->webhookSecret);
return hash_equals($expected, $signed);
}
public function applyWebhook(array $payload): ?Payment
{
$eventType = $payload['data']['attributes']['type'] ?? null;
$paymentUuid = $payload['data']['attributes']['data']['attributes']['metadata']['payment_uuid'] ?? null;
if (! $paymentUuid) {
return null;
}
$payment = Payment::where('uuid', $paymentUuid)->first();
if (! $payment) {
return null;
}
if (in_array($eventType, ['checkout_session.payment.paid', 'payment.paid'], true)) {
$payment->forceFill([
'status' => Payment::STATUS_PAID,
'paid_at' => now(),
'provider_data' => $payload,
])->save();
} elseif (in_array($eventType, ['payment.failed'], true)) {
$payment->forceFill([
'status' => Payment::STATUS_FAILED,
'provider_data' => $payload,
])->save();
}
return $payment->fresh();
}
}