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->generateVAFast( $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); } } }