329 lines
11 KiB
PHP
329 lines
11 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\student;
|
||
|
||
use App\Events\PaymentCompleted;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\CartItem;
|
||
use App\Models\Payment_history;
|
||
use App\Models\VAPayment;
|
||
use App\Notifications\CoursePurchaseCreated;
|
||
use App\Models\Course;
|
||
use App\Models\Enrollment;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Session;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Str;
|
||
use Carbon\Carbon;
|
||
|
||
class VAPaymentController extends Controller
|
||
{
|
||
public function store(Request $request)
|
||
{
|
||
$request->validate([
|
||
'item_type' => 'required|string',
|
||
]);
|
||
|
||
$payment_details = Session::get('payment_details');
|
||
if (!$payment_details) {
|
||
return back()->with('error', 'Payment session not found.');
|
||
}
|
||
|
||
$item_id_arr = collect($payment_details['items'])->pluck('id')->toArray();
|
||
|
||
// BTN REQUIREMENT
|
||
$noTest = str_pad(random_int(0, 99999), 5, '0', STR_PAD_LEFT);
|
||
$nama = auth()->user()->name;
|
||
$tagihan = $payment_details['payable_amount'];
|
||
$expiredAt = Carbon::now()->addHour();
|
||
|
||
DB::beginTransaction();
|
||
|
||
try {
|
||
$response = $this->generateDummyVA(
|
||
$noTest,
|
||
$tagihan,
|
||
$nama,
|
||
$expiredAt->format('Y-m-d H:i:s')
|
||
);
|
||
|
||
$btnResponse = json_decode($response, true);
|
||
|
||
Log::info('BTN VA Response', $btnResponse ?? []);
|
||
|
||
$vaNumber = $btnResponse['BTNVirtualAccount'];
|
||
|
||
if (!$vaNumber) {
|
||
throw new \Exception('BTN VA failed: ' . $response);
|
||
}
|
||
|
||
$vaPayment = VAPayment::create([
|
||
'user_id' => auth()->id(),
|
||
'item_type' => $request->item_type,
|
||
'items' => json_encode($item_id_arr),
|
||
'tax' => $payment_details['tax'],
|
||
'total_amount' => $tagihan,
|
||
'coupon' => $payment_details['coupon'] ?? null,
|
||
'va_number' => $vaNumber,
|
||
'status' => 0,
|
||
'expired_at' => $expiredAt
|
||
]);
|
||
|
||
if ($request->item_type === 'course') {
|
||
// Hapus dari cart
|
||
CartItem::whereIn('course_id', $item_id_arr)
|
||
->where('user_id', auth()->id())
|
||
->delete();
|
||
$courses = Course::whereIn('id', $item_id_arr)->get();
|
||
|
||
auth()->user()->notify(
|
||
new CoursePurchaseCreated($vaPayment, $courses)
|
||
);
|
||
}
|
||
|
||
DB::commit();
|
||
|
||
return redirect()
|
||
->route('payment.va.show', $vaPayment->id)
|
||
->with('success', 'Virtual Account berhasil dibuat.');
|
||
|
||
} catch (\Throwable $e) {
|
||
DB::rollBack();
|
||
|
||
Log::error('VA Payment Error', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString()
|
||
]);
|
||
|
||
return back()->with('error', 'Gagal membuat Virtual Account: ' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
private function generateVAFast($notest, $tagihan, $nama, $tgl_terakhirbayar)
|
||
{
|
||
$curl = curl_init();
|
||
|
||
curl_setopt_array($curl, [
|
||
CURLOPT_URL => 'https://neosidata.unesa.ac.id/btn_v2/create',
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => [
|
||
'credential' => '$1$bCE4xLfB$U7t8ux0g4iflFaCpcLqaB.',
|
||
'noid' => $notest,
|
||
'nama' => $nama,
|
||
'tagihan' => $tagihan,
|
||
'flag' => 'F',
|
||
'expired_date' => $tgl_terakhirbayar,
|
||
'deskripsi' => 'Pembayaran Fast Track',
|
||
],
|
||
]);
|
||
|
||
$response = curl_exec($curl);
|
||
curl_close($curl);
|
||
|
||
return $response;
|
||
}
|
||
|
||
//untuk testing
|
||
private function generateDummyVA($notest, $tagihan, $nama, $tgl_terakhirbayar)
|
||
{
|
||
// Prefix BTN: 9422 + 5 digit random + 8 digit noTest (total 17 digit)
|
||
// Format: 9422 + XXXXX + 00000 + noTest (5 digit)
|
||
|
||
// Buat angka random 5 digit untuk middle part
|
||
$middle = str_pad(random_int(0, 99999), 5, '0', STR_PAD_LEFT);
|
||
|
||
// Pad noTest dengan leading zeros jika kurang dari 5 digit
|
||
$paddedNoTest = str_pad($notest, 5, '0', STR_PAD_LEFT);
|
||
|
||
// Generate VA number (17 digit)
|
||
$vaNumber = '9422' . $middle . '0' . $paddedNoTest; // 4 + 5 + 1 + 5 = 15 digit
|
||
// Tambah 2 digit random untuk genapin 17 digit
|
||
$vaNumber .= str_pad(random_int(0, 99), 2, '0', STR_PAD_LEFT);
|
||
|
||
// Format response seperti aslinya
|
||
$dummyResponse = [
|
||
'BTNVirtualAccount' => $vaNumber,
|
||
'status' => '00',
|
||
'message' => 'Success',
|
||
'data' => [
|
||
'va' => $vaNumber,
|
||
'nama' => $nama,
|
||
'tagihan' => $tagihan,
|
||
'expired_date' => $tgl_terakhirbayar,
|
||
'bank' => 'BTN',
|
||
'deskripsi' => 'Pembayaran Fast Track'
|
||
],
|
||
'timestamp' => now()->toISOString()
|
||
];
|
||
|
||
Log::info('DUMMY VA GENERATED', [
|
||
'va_number' => $vaNumber,
|
||
'notest' => $notest,
|
||
'padded_notest' => $paddedNoTest,
|
||
'total_digits' => strlen($vaNumber)
|
||
]);
|
||
|
||
return json_encode($dummyResponse);
|
||
}
|
||
|
||
public function show($id)
|
||
{
|
||
$vaPayment = VAPayment::where('id', $id)
|
||
->where('user_id', auth()->user()->id)
|
||
->firstOrFail();
|
||
|
||
return view('payment.va_show', compact('vaPayment'));
|
||
}
|
||
|
||
public function checkPaymentApi($id)
|
||
{
|
||
Log::info('CHECK PAYMENT STARTED');
|
||
|
||
$payment = VAPayment::where('id', $id)
|
||
->where('user_id', auth()->id())
|
||
->firstOrFail();
|
||
|
||
if ($payment->status == 1) {
|
||
return response()->json([
|
||
'status' => 1,
|
||
'message' => 'Already paid'
|
||
]);
|
||
}
|
||
|
||
if (now()->greaterThan($payment->expired_at)) {
|
||
$payment->update(['status' => 2]);
|
||
|
||
return response()->json([
|
||
'status' => 2,
|
||
'message' => 'Expired'
|
||
]);
|
||
}
|
||
|
||
try {
|
||
$fullVa = $payment->va_number;
|
||
$noidcek = substr($fullVa, 5);
|
||
|
||
$url = "https://neosidata.unesa.ac.id/api-va-narkoba/{$noidcek}/{$noidcek}";
|
||
|
||
$response = Http::timeout(15)
|
||
->withOptions(['verify' => false])
|
||
->get($url);
|
||
|
||
if (!$response->successful()) {
|
||
return response()->json([
|
||
'status' => 0,
|
||
'message' => 'BTN API error'
|
||
]);
|
||
}
|
||
|
||
$data = $response->json();
|
||
|
||
$isPaid = is_numeric($data['terbayar'] ?? 0)
|
||
&& floatval($data['terbayar']) >= floatval($data['tagihan'] ?? 0);
|
||
|
||
if (!$isPaid) {
|
||
return response()->json([
|
||
'status' => 0,
|
||
'message' => 'Unpaid'
|
||
]);
|
||
}
|
||
|
||
DB::beginTransaction();
|
||
|
||
// 1️⃣ Update VA Payment
|
||
$payment->update([
|
||
'status' => 1,
|
||
'paid_at' => now()
|
||
]);
|
||
|
||
// 2️⃣ Insert ke payment_history
|
||
if ($payment->item_type === 'course') {
|
||
$items = json_decode($payment->items);
|
||
|
||
foreach ($items as $courseId) {
|
||
$course = Course::findOrFail($courseId);
|
||
|
||
$amount = $course->discount_flag
|
||
? $course->discounted_price
|
||
: $course->price;
|
||
|
||
// Coupon
|
||
$discount = 0;
|
||
if ($payment->coupon) {
|
||
$coupon = Coupon::where('code', $payment->coupon)->first();
|
||
if ($coupon) {
|
||
$discount = $amount * ($coupon->discount / 100);
|
||
}
|
||
}
|
||
|
||
$finalAmount = $amount - $discount;
|
||
$tax = (get_settings('course_selling_tax') / 100) * $finalAmount;
|
||
|
||
$history = [
|
||
'invoice' => Str::random(20),
|
||
'user_id' => $payment->user_id,
|
||
'payment_type' => 'virtual_account',
|
||
'course_id' => $course->id,
|
||
'amount' => $finalAmount,
|
||
'tax' => $tax,
|
||
'coupon' => $payment->coupon,
|
||
];
|
||
|
||
// Revenue split
|
||
if (get_course_creator_id($course->id)->role === 'admin') {
|
||
$history['admin_revenue'] = $finalAmount;
|
||
} else {
|
||
$history['instructor_revenue'] =
|
||
$finalAmount * (get_settings('instructor_revenue') / 100);
|
||
$history['admin_revenue'] =
|
||
$finalAmount - $history['instructor_revenue'];
|
||
}
|
||
|
||
// Payment_history::insert($history);
|
||
|
||
// // 3️⃣ Enrollment
|
||
// Enrollment::insert([
|
||
// 'user_id' => $payment->user_id,
|
||
// 'course_id' => $course->id,
|
||
// 'enrollment_type' => 'paid',
|
||
// 'entry_date' => time(),
|
||
// 'expiry_date' => $course->expiry_period > 0
|
||
// ? strtotime('+' . ($course->expiry_period * 30) . ' days')
|
||
// : null,
|
||
// 'created_at' => now(),
|
||
// 'updated_at' => now(),
|
||
// ]);
|
||
}
|
||
}
|
||
|
||
DB::commit();
|
||
|
||
// 4️⃣ Event & Notification
|
||
event(new PaymentCompleted($payment));
|
||
|
||
return response()->json([
|
||
'status' => 1,
|
||
'message' => 'Payment successful'
|
||
]);
|
||
|
||
} catch (\Throwable $e) {
|
||
DB::rollBack();
|
||
|
||
Log::error('VA CHECK ERROR', [
|
||
'payment_id' => $payment->id,
|
||
'error' => $e->getMessage()
|
||
]);
|
||
|
||
return response()->json([
|
||
'status' => 0,
|
||
'message' => 'Check failed'
|
||
], 500);
|
||
}
|
||
}
|
||
|
||
|
||
}
|