113 lines
4.8 KiB
PHP
113 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Billing\Gateways;
|
|
|
|
use App\Billing\Contracts\PaymentGateway;
|
|
use App\Models\BillingOrder;
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Support\Facades\Http;
|
|
use RuntimeException;
|
|
|
|
class DuitkuPaymentGateway implements PaymentGateway
|
|
{
|
|
public function paymentMethods(BillingOrder $order): array
|
|
{
|
|
[$merchant, $key] = $this->credentials();
|
|
$amount = (int) $order->total;
|
|
$dateTime = now('Asia/Jakarta')->format('Y-m-d H:i:s');
|
|
$response = $this->http()->post(
|
|
$this->baseUrl().'/webapi/api/merchant/paymentmethod/getpaymentmethod',
|
|
[
|
|
'merchantcode' => $merchant,
|
|
'amount' => $amount,
|
|
'datetime' => $dateTime,
|
|
'signature' => hash_hmac('sha256', $merchant.$amount.$dateTime, $key),
|
|
]
|
|
);
|
|
|
|
if (! $response->successful() || ! is_array($response->json('paymentFee'))) {
|
|
throw new RuntimeException('PAYMENT_METHODS_REJECTED: '.($response->json('responseMessage') ?: 'Daftar kanal pembayaran Duitku tidak tersedia.'));
|
|
}
|
|
|
|
return collect($response->json('paymentFee'))->map(fn (array $method): array => [
|
|
'code' => (string) $method['paymentMethod'],
|
|
'name' => (string) $method['paymentName'],
|
|
'fee' => (int) ($method['totalFee'] ?? 0),
|
|
])->values()->all();
|
|
}
|
|
|
|
public function createCheckout(BillingOrder $order): array
|
|
{
|
|
[$merchant, $key] = $this->credentials();
|
|
if (! $order->payment_method) {
|
|
throw new RuntimeException('PAYMENT_METHOD_REQUIRED: Pilih kanal pembayaran terlebih dahulu.');
|
|
}
|
|
$externalId = 'RNDM-'.$order->uuid;
|
|
$user = $order->tenant->users()->where('is_active', true)->oldest()->firstOrFail();
|
|
$amount = (int) $order->total;
|
|
$payload = [
|
|
'merchantCode' => $merchant, 'paymentAmount' => $amount, 'merchantOrderId' => $externalId,
|
|
'paymentMethod' => $order->payment_method,
|
|
'productDetails' => 'Lisensi bulanan RADIQ NDM '.config("billing.plans.{$order->plan_code}.name"),
|
|
'email' => $user->email, 'customerVaName' => $user->name,
|
|
'callbackUrl' => route('payments.webhook', ['gateway' => 'duitku']),
|
|
'returnUrl' => route('checkout.return', $order->uuid), 'expiryPeriod' => 1440,
|
|
'signature' => hash_hmac('sha256', $merchant.$externalId.$amount, $key),
|
|
];
|
|
$response = $this->http()->post($this->baseUrl().'/webapi/api/merchant/v2/inquiry', $payload);
|
|
if (! $response->successful() || ! $response->json('paymentUrl')) {
|
|
throw new RuntimeException('PAYMENT_GATEWAY_REJECTED: '.($response->json('Message') ?: $response->json('message') ?: 'Duitku tidak menghasilkan URL pembayaran.'));
|
|
}
|
|
|
|
return ['external_id' => $externalId, 'checkout_url' => $response->json('paymentUrl'), 'metadata' => ['reference' => $response->json('reference')]];
|
|
}
|
|
|
|
public function verifyWebhook(array $payload, array $headers = []): bool
|
|
{
|
|
$merchant = (string) ($payload['merchantCode'] ?? '');
|
|
$amount = (string) ($payload['amount'] ?? '');
|
|
$orderId = (string) ($payload['merchantOrderId'] ?? '');
|
|
$signature = (string) ($payload['signature'] ?? '');
|
|
$expected = hash_hmac('sha256', $merchant.$amount.$orderId, (string) config('billing.duitku.api_key'));
|
|
|
|
return $merchant !== '' && hash_equals((string) config('billing.duitku.merchant_code'), $merchant) && hash_equals($expected, $signature);
|
|
}
|
|
|
|
public function statusFromWebhook(array $payload): string
|
|
{
|
|
return ($payload['resultCode'] ?? null) === '00' ? 'paid' : 'failed';
|
|
}
|
|
|
|
public function externalIdFromWebhook(array $payload): ?string
|
|
{
|
|
return isset($payload['merchantOrderId']) ? (string) $payload['merchantOrderId'] : null;
|
|
}
|
|
|
|
/** @return array{string,string} */
|
|
private function credentials(): array
|
|
{
|
|
$merchant = (string) config('billing.duitku.merchant_code');
|
|
$key = (string) config('billing.duitku.api_key');
|
|
if ($merchant === '' || $key === '') {
|
|
throw new RuntimeException('PAYMENT_GATEWAY_NOT_CONFIGURED: Merchant Code atau API Key Duitku belum diisi.');
|
|
}
|
|
|
|
return [$merchant, $key];
|
|
}
|
|
|
|
private function baseUrl(): string
|
|
{
|
|
return config('billing.duitku.sandbox') ? 'https://sandbox.duitku.com' : 'https://passport.duitku.com';
|
|
}
|
|
|
|
private function http(): PendingRequest
|
|
{
|
|
$request = Http::asJson()->acceptJson()->timeout(20);
|
|
$caBundle = config('billing.duitku.ca_bundle');
|
|
|
|
return is_string($caBundle) && $caBundle !== ''
|
|
? $request->withOptions(['verify' => $caBundle])
|
|
: $request;
|
|
}
|
|
}
|