customer deposit done

dev
Aji Kamaludin 1 year ago
parent f9064ab4d3
commit 4235d7aa8f
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -29,10 +29,10 @@
- [x] Login Customer
- [x] Login Customer Gmail
- [x] Customer Edit Profile
- [ ] Register Refferal
- [ ] Customer Deposit Manual
- [ ] Customer Deposit Payment Gateway
- [x] Customer Deposit Manual
- [x] Customer Deposit Payment Gateway
- [ ] Customer Purchase Voucher
- [ ] Register Refferal
- [ ] Customer Share Buyed Voucher, via WA dll
- [ ] Customer View Coin History
- [ ] Verified Akun

@ -0,0 +1,155 @@
<?php
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\DepositHistory;
use App\Models\Setting;
use App\Services\GeneralService;
use App\Services\MidtransService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class DepositController extends Controller
{
public function index()
{
$histories = DepositHistory::where('customer_id', auth()->id())
->orderBy('updated_at', 'desc')
->orderBy('is_valid', 'desc');
return inertia('Home/Deposit/Index', [
'histories' => $histories->paginate(20)
]);
}
public function create()
{
return inertia('Home/Deposit/Topup', [
'payments' => GeneralService::getEnablePayment(),
]);
}
public function store(Request $request)
{
$request->validate([
'amount' => 'required|numeric|min:10000',
'payment' => [
'required',
Rule::in([Setting::PAYMENT_MANUAL, Setting::PAYMENT_MIDTRANS])
]
]);
DB::beginTransaction();
$deposit = DepositHistory::make([
'customer_id' => auth()->id(),
'debit' => $request->amount,
'description' => 'Top Up #' . Str::random(5),
'payment_channel' => $request->payment,
]);
if ($request->payment == Setting::PAYMENT_MANUAL) {
$deposit->is_valid = DepositHistory::STATUS_WAIT_UPLOAD;
$deposit->save();
}
if ($request->payment == Setting::PAYMENT_MIDTRANS) {
$deposit->is_valid = DepositHistory::STATUS_WAIT_PAYMENT;
$deposit->save();
$token = (new MidtransService($deposit, Setting::getByKey('MIDTRANS_SERVER_KEY')))->getSnapToken();
$deposit->update(['payment_token' => $token]);
}
DB::commit();
return redirect()->route('customer.deposit.show', ['deposit' => $deposit->id, 'direct' => 'true']);
}
public function show(Request $request, DepositHistory $deposit)
{
return inertia('Home/Deposit/Detail', [
'deposit' => $deposit,
'accounts' => Account::get(),
'midtrans_client_key' => Setting::getByKey('MIDTRANS_CLIENT_KEY'),
'is_production' => app()->isProduction(),
'direct' => $request->direct
]);
}
public function update(Request $request, DepositHistory $deposit)
{
$request->validate([
'account_id' => 'required|exists:accounts,id',
'image' => 'required|image',
]);
$file = $request->file('image');
$file->store('uploads', 'public');
$deposit->update([
'image_prove' => $file->hashName('uploads'),
'account_id' => $request->account_id,
'is_valid' => DepositHistory::STATUS_WAIT_APPROVE
]);
session()->flash('message', ['type' => 'success', 'message' => 'Upload berhasil, silahkan tunggu untuk approve']);;
}
public function midtrans_payment(Request $request, DepositHistory $deposit)
{
DB::beginTransaction();
$transaction_status = $request->result['transaction_status'];
if ($transaction_status == 'settlement' || $transaction_status == 'capture') {
$is_valid = DepositHistory::STATUS_VALID;
$deposit->update_customer_balance();
} elseif ($transaction_status == 'pending') {
$is_valid = DepositHistory::STATUS_WAIT_PAYMENT;
} else {
$is_valid = DepositHistory::STATUS_INVALID;
}
$deposit->update([
'is_valid' => $is_valid,
'payment_response' => json_encode($request->result),
'payment_type' => $request->result['payment_type'],
]);
DB::commit();
return redirect()->route('customer.deposit.show', ['deposit' => $deposit->id]);
}
public function mindtrans_notification(Request $request)
{
DB::beginTransaction();
$deposit = DepositHistory::where('id', $request->order_id)->first();
if ($deposit != null && $deposit->is_valid != DepositHistory::STATUS_VALID) {
$deposit->fill([
'payment_response' => json_encode($request->all()),
'payment_type' => $request->result['payment_type'],
]);
if ($request->transaction_status == 'settlement' || $request->transaction_status == 'capture') {
$deposit->fill(['payment_status' => DepositHistory::STATUS_VALID]);
$deposit->update_customer_balance();
} elseif ($request->transaction_status == 'pending') {
$deposit->fill(['payment_status' => DepositHistory::STATUS_WAIT_PAYMENT]);
} else {
$deposit->fill(['payment_status' => DepositHistory::STATUS_INVALID]);
}
$deposit->save();
}
DB::commit();
return response()->json([
'status' => 'ok',
'order' => $deposit,
]);
}
}

@ -27,6 +27,7 @@ class Customer extends Authenticatable
'google_id',
'deposit_balance',
'coin_balance',
'paylater_balance',
'identity_verified',
'identity_image',
'customer_level_id',

@ -2,13 +2,22 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Carbon;
class DepositHistory extends Model
{
const VALID = 0;
const STATUS_VALID = 0;
const STATUS_WAIT_UPLOAD = 1;
const STATUS_WAIT_APPROVE = 2;
const STATUS_WAIT_PAYMENT = 3;
const WAIT = 1;
const STATUS_INVALID = 4;
const INVALID = 2;
const STATUS_REJECT = 5;
protected $fillable = [
'debit',
@ -19,10 +28,75 @@ class DepositHistory extends Model
'related_id',
'is_valid',
'image_prove',
'account_id',
'payment_token',
'payment_status',
'payment_response',
'payment_channel',
'payment_type',
];
protected $appends = [
'status',
'format_human_created_at',
'format_created_at',
'amount',
'image_prove_url'
];
public function status(): Attribute
{
return Attribute::make(get: function () {
return [
self::STATUS_VALID => ['text' => 'Success', 'color' => 'bg-green-600'],
self::STATUS_WAIT_UPLOAD => ['text' => 'Upload bukti transfer', 'color' => 'bg-red-600'],
self::STATUS_WAIT_APPROVE => ['text' => 'Menunggu Approve', 'color' => 'bg-green-600'],
self::STATUS_WAIT_PAYMENT => ['text' => 'Menunggu Pembayaran', 'color' => 'bg-green-600'],
self::STATUS_INVALID => ['text' => 'Error', 'color' => 'bg-red-600'],
self::STATUS_REJECT => ['text' => 'Reject', 'color' => 'bg-red-600'],
][$this->is_valid];
});
}
public function formatHumanCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->format('d F Y');
});
}
public function formatCreatedAt(): Attribute
{
return Attribute::make(get: function () {
return Carbon::parse($this->created_at)->locale('id')->format('d M Y H:i:s');
});
}
public function amount(): Attribute
{
return Attribute::make(get: function () {
if ($this->credit == 0) {
return $this->debit;
}
return $this->credit;
});
}
public function imageProveUrl(): Attribute
{
return Attribute::make(get: function () {
return $this->image_prove == null ? '' : asset($this->image_prove);
});
}
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function update_customer_balance()
{
$customer = Customer::find($this->customer_id);
$customer->update(['deposit_balance' => $customer->deposit_balance + $this->debit]);
}
}

@ -0,0 +1,13 @@
<?php
namespace App\Models;
class PaylaterHistory extends Model
{
protected $fillable = [
'debit',
'credit',
'description',
'customer_id',
];
}

@ -12,6 +12,12 @@ class Setting extends Model
{
use HasFactory, SoftDeletes, HasUlids;
const PAYMENT_MANUAL = 'MANUAL';
const PAYMENT_MIDTRANS = 'MIDTRANS';
const PAYMENT_PAYLATER = 'PAYLATER';
protected $fillable = [
'key',
'value',
@ -32,4 +38,9 @@ class Setting extends Model
return '';
});
}
public static function getByKey($key)
{
return Setting::where('key', $key)->value('value');
}
}

@ -2,6 +2,8 @@
namespace App\Services;
use App\Models\Setting;
class GeneralService
{
public static function script_parser($script)
@ -50,4 +52,20 @@ class GeneralService
return $item;
}
public static function getEnablePayment()
{
$payment = [
['name' => Setting::PAYMENT_MANUAL, 'logo' => null, 'display_name' => 'Transfer Manual']
];
$midtrans_enable = Setting::getByKey('MIDTRANS_ENABLED');
if ($midtrans_enable == 1) {
$payment[] = ['name' => Setting::PAYMENT_MIDTRANS, 'logo' => Setting::getByKey('MIDTRANS_LOGO')];
}
// Paylater
return $payment;
}
}

@ -2,55 +2,46 @@
namespace App\Services;
use App\Models\DepositHistory;
use Midtrans\Config;
use Midtrans\Snap;
class MidtransService
{
protected $order;
protected $deposit;
public function __construct($order, $serverKey)
public function __construct(DepositHistory $deposit, $serverKey)
{
Config::$serverKey = $serverKey;
Config::$isProduction = app()->isProduction();
Config::$isSanitized = true;
Config::$is3ds = true;
$this->order = $order;
$this->deposit = $deposit;
}
public function getSnapToken()
{
$items = $this->order->items->map(function ($item) {
return [
'id' => $item->id,
'price' => $item->amount,
'quantity' => $item->quantity,
'name' => $item->item->order_detail,
];
});
if ($this->order->total_discount > 0) {
$items->add([
'id' => 'Discount',
'price' => -$this->order->total_discount,
'quantity' => 1,
'name' => 'DISCOUNT',
]);
}
$params = [
'transaction_details' => [
'order_id' => $this->order->order_code,
'gross_amount' => $this->order->total_amount,
'order_id' => $this->deposit->id,
'gross_amount' => $this->deposit->debit,
],
'item_details' => $items->toArray(),
'item_details' => [[
'id' => $this->deposit->id,
'price' => $this->deposit->debit,
'quantity' => 1,
'name' => $this->deposit->description,
]],
'customer_details' => [
'name' => $this->order->customer->name,
'email' => $this->order->customer->email,
'phone' => $this->order->customer->phone,
'address' => $this->order->customer->address,
'name' => $this->deposit->customer->fullname,
'email' => $this->deposit->customer->email,
'phone' => $this->deposit->customer->phone,
'address' => $this->deposit->customer->address,
],
'callbacks' => [
'finish' => route('customer.deposit.show', ['deposit' => $this->deposit->id])
]
];
$snapToken = Snap::getSnapToken($params);

@ -26,6 +26,7 @@ return new class extends Migration
$table->string('google_id')->nullable();
$table->decimal('deposit_balance', 20, 2)->default(0);
$table->decimal('coin_balance', 20, 2)->default(0);
$table->decimal('paylater_balance', 20, 2)->default(0);
$table->smallInteger('identity_verified')->nullable();
$table->string('identity_image')->nullable();
$table->ulid('customer_level_id')->nullable();

@ -18,14 +18,15 @@ return new class extends Migration
$table->decimal('credit', 20, 2)->default(0);
$table->text('description')->nullable();
$table->ulid('customer_id')->nullable();
$table->ulid('account_id')->nullable();
$table->string('related_type')->nullable();
$table->string('related_id')->nullable();
$table->smallInteger('is_valid')->default(0);
$table->string('image_prove')->nullable();
$table->string('payment_channel')->nullable();
$table->string('payment_token')->nullable();
$table->string('payment_status')->nullable();
$table->string('payment_response')->nullable();
$table->string('payment_channel')->nullable();
$table->string('payment_type')->nullable();
$table->timestamps();

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('paylater_histories', function (Blueprint $table) {
$table->ulid('id')->primary();
$table->decimal('debit', 20, 2)->default(0);
$table->decimal('credit', 20, 2)->default(0);
$table->text('description')->nullable();
$table->ulid('customer_id')->nullable();
$table->timestamps();
$table->softDeletes();
$table->ulid('created_by')->nullable();
$table->ulid('updated_by')->nullable();
$table->ulid('deleted_by')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('paylater_histories');
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

@ -24,38 +24,7 @@ export default [
active: 'dashboard',
permission: 'view-dashboard',
},
{
name: 'Customer',
show: true,
icon: HiUserCircle,
route: route('customer.index'),
active: 'customer.*',
permission: 'view-customer',
},
{
name: 'Customer Level',
show: true,
icon: HiUserCircle,
route: route('customer-level.index'),
active: 'customer-level.*',
permission: 'view-customer-level',
},
{
name: 'Bank Akun',
show: true,
icon: HiBanknotes,
route: route('account.index'),
active: 'account.*',
permission: 'view-account',
},
{
name: 'Setting',
show: true,
icon: HiCog,
route: route('setting.index'),
active: 'setting.*',
permission: 'view-setting',
},
{
name: 'Lokasi',
show: true,
@ -95,6 +64,30 @@ export default [
},
],
},
{
name: 'Customer',
show: true,
icon: HiUser,
items: [
{
name: 'Customer',
show: true,
icon: HiUserCircle,
route: route('customer.index'),
active: 'customer.*',
permission: 'view-customer',
},
{
name: 'Level',
show: true,
icon: HiUserCircle,
route: route('customer-level.index'),
active: 'customer-level.*',
permission: 'view-customer-level',
},
],
},
{
name: 'User',
show: true,
@ -118,4 +111,20 @@ export default [
},
],
},
{
name: 'Bank Akun',
show: true,
icon: HiBanknotes,
route: route('account.index'),
active: 'account.*',
permission: 'view-account',
},
{
name: 'Setting',
show: true,
icon: HiCog,
route: route('setting.index'),
active: 'setting.*',
permission: 'view-setting',
},
]

@ -0,0 +1,257 @@
import React, { useState, useEffect } from 'react'
import { Head, router, useForm, usePage } from '@inertiajs/react'
import { HiChevronLeft } from 'react-icons/hi2'
import CustomerLayout from '@/Layouts/CustomerLayout'
import { formatIDR } from '@/utils'
import FormFile from '@/Components/FormFile'
import { isEmpty } from 'lodash'
import Alert from '@/Components/Alert'
const PayButton = () => {
const {
props: { deposit, midtrans_client_key, is_production, direct, flash },
} = usePage()
const [loading, setLoading] = useState(false)
const handleResult = (result) => {
fetch(route('api.midtrans.payment', deposit), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ result }),
}).finally(() => {
router.get(route(route().current(), deposit))
})
}
const onClickPay = () => {
if (loading) {
return
}
setLoading(true)
window.snap.pay(deposit.payment_token, {
// Optional
onSuccess: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onPending: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
// Optional
onError: function (result) {
console.log(result)
handleResult(result)
setLoading(false)
},
onClose: function () {
setLoading(false)
},
})
}
useEffect(() => {
//change this to the script source you want to load, for example this is snap.js sandbox env
let midtransScriptUrl = 'https://app.sandbox.midtrans.com/snap/snap.js'
if (is_production) {
midtransScriptUrl = 'https://app.midtrans.com/snap/snap.js'
}
//change this according to your client-key
let scriptTag = document.createElement('script')
scriptTag.src = midtransScriptUrl
// optional if you want to set script attribute
// for example snap.js have data-client-key attribute
scriptTag.setAttribute('data-client-key', midtrans_client_key)
document.body.appendChild(scriptTag)
if (direct === 'true') {
setTimeout(() => {
onClickPay()
}, 1000)
}
return () => {
document.body.removeChild(scriptTag)
}
}, [])
return (
<div className="w-full px-5 py-10">
<Alert type={flash.message.type}>
<span className="font-semibold">{flash.message.message}</span>
</Alert>
<div
className="px-4 py-2 bg-blue-700 text-white rounded-full border hover:bg-white hover:text-black"
onClick={onClickPay}
>
Bayar
</div>
</div>
)
}
const FormUpload = () => {
const {
props: { accounts, deposit, flash },
} = usePage()
const [account, setAccount] = useState(null)
const { data, setData, errors, processing, post } = useForm({
account_id: '',
image: null,
image_url: deposit.image_prove_url,
})
const handleSelectAccount = (id) => {
if (id === '') {
setData('account_id', '')
setAccount(null)
}
const account = accounts.find((acc) => acc.id === id)
setData('account_id', account.id)
setAccount(account)
}
const handleSubmit = () => {
if (processing) {
return
}
post(route('customer.deposit.update', deposit))
}
return (
<div className="px-5 mt-4">
<div className="my-4">
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="mb-1 text-sm">Bank </div>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
onChange={(e) => handleSelectAccount(e.target.value)}
value={data.account_id}
name="account_id"
>
<option value=""></option>
{accounts.map((account) => (
<option value={account.id} key={account.id}>
{account.bank_name}
</option>
))}
</select>
</div>
{data.account_id !== '' && (
<>
<div className="my-5">
<div className="bg-red-200 text-red-600 p-3 border rounded-md border-red-700">
<div>Silahkan transfer nominal di atas ke</div>
<div>
Bank :{' '}
<span className="font-bold">
{account.bank_name}
</span>
</div>
<div>
Atas Nama :{' '}
<span className="font-bold">
{account.holder_name}
</span>
</div>
<div>
Nomor Rekening :{' '}
<span className="font-bold">
{account.account_number}
</span>
</div>
</div>
</div>
<FormFile
label={'Bukti Transfer'}
onChange={(e) => setData('image', e.target.files[0])}
error={errors.image}
preview={
isEmpty(data.image_url) == false && (
<img
src={`${data.image_url}`}
className="w-full h-52 mb-1"
alt="bukti transfer"
/>
)
}
/>
<div
className="mt-10 w-full px-4 py-2 border rounded-full bg-blue-600 text-white hover:bg-white hover:text-black"
onClick={() => handleSubmit()}
>
Upload
</div>
</>
)}
</div>
)
}
const ActionSection = ({ deposit }) => {
return (
<div className="w-full">
{deposit.payment_channel === 'MIDTRANS' ? (
<PayButton />
) : (
<FormUpload />
)}
</div>
)
}
export default function Detail({ deposit }) {
return (
<CustomerLayout>
<Head title="Top Up" />
<div className="flex flex-col min-h-[calc(95dvh)]">
<div
className="w-full px-5 py-5"
onClick={() => {
router.get(route('customer.deposit.index'))
}}
>
<HiChevronLeft className="font-bold h-5 w-5" />
</div>
{/* detail */}
<div className="flex flex-row justify-between items-center pb-5 border-b px-5">
<div>
<div className="font-semibold text-xl text-gray-400">
{deposit.description}
</div>
<div className="font-bold text-3xl">
Rp {formatIDR(deposit.amount)}
</div>
<div className="text-gray-400">
{deposit.format_created_at}
</div>
</div>
<div>
<div
className={`text-xs px-2 py-1 rounded-full border ${deposit.status.color} text-white`}
>
{deposit.status.text}
</div>
</div>
</div>
{/* action */}
{deposit.is_valid !== 0 && <ActionSection deposit={deposit} />}
</div>
</CustomerLayout>
)
}

@ -0,0 +1,104 @@
import React, { useState } from 'react'
import { Head, router } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
import { formatIDR } from '@/utils'
export default function Index({
auth: { user },
histories: { data, next_page_url },
}) {
const [deposites, setDeposites] = useState(data)
const handleNextPage = () => {
router.get(
next_page_url,
{},
{
replace: true,
preserveState: true,
only: ['histories'],
onSuccess: (res) => {
setDeposites(deposites.concat(res.props.histories.data))
},
}
)
}
return (
<CustomerLayout>
<Head title="Top Up" />
<div className="flex flex-col w-full min-h-[calc(90dvh)]">
<div className="w-full pt-10 px-5">
<div className="text-base">{user.fullname}</div>
</div>
<div className="flex flex-row justify-between items-center pb-10 border-b px-5">
<div>
<div className="font-semibold text-xl text-gray-400">
Saldo
</div>
<div className="font-bold text-3xl">
Rp {user.display_deposit}
</div>
</div>
<div>
<div
className="px-3 py-2 border rounded-full bg-blue-700 text-white hover:bg-transparent hover:text-black"
onClick={() =>
router.get(route('customer.deposit.topup'))
}
>
Top Up
</div>
</div>
</div>
<div className="w-full">
<div className="flex flex-col py-10 space-y-5 px-5">
{deposites.map((history) => (
<div
key={history.id}
className="flex flex-row pb-2 items-center justify-between border-b"
onClick={() =>
router.get(
route(
'customer.deposit.show',
history.id
)
)
}
>
<div className="flex flex-col">
<div className="font-bold">
{history.format_human_created_at}
</div>
<div className="font-thin">
{history.description}
</div>
</div>
<div className="flex flex-col items-end">
<div className="font-bold text-lg">
{formatIDR(history.amount)}
</div>
{+history.is_valid !== 0 && (
<div
className={`text-xs px-2 py-1 rounded-full border ${history.status.color} text-white`}
>
{history.status.text}
</div>
)}
</div>
</div>
))}
{next_page_url !== null && (
<div
onClick={handleNextPage}
className="w-full text-center px-2 py-1 border mt-5 hover:bg-blue-600 hover:text-white"
>
Load more
</div>
)}
</div>
</div>
</div>
</CustomerLayout>
)
}

@ -0,0 +1,118 @@
import React from 'react'
import { Head, router, useForm } from '@inertiajs/react'
import { HiChevronLeft } from 'react-icons/hi2'
import CustomerLayout from '@/Layouts/CustomerLayout'
import FormInput from '@/Components/FormInput'
import Alert from '@/Components/Alert'
import { formatIDR } from '@/utils'
export default function Topup({ payments }) {
const { data, setData, post, processing, errors, reset, clearErrors } =
useForm({
amount: '',
payment: '',
})
const amounts = [20000, 50000, 100000, 200000, 300000, 500000]
const isActiveAmount = (amount) => {
return `px-3 py-3 border shadow-sm rounded ${
+amount === +data.amount ? 'bg-blue-700 text-white' : ''
}`
}
const setAmount = (amount) => {
setData('amount', amount)
}
const isActivePayment = (payment) => {
return `p-2 border shadow rounded flex flex-row items-center space-x-5 h-14 ${
payment === data.payment ? 'bg-blue-600 text-white' : ''
}`
}
const handleSubmit = () => {
if (processing) {
return
}
post(route('customer.deposit.topup'))
}
return (
<CustomerLayout>
<Head title="Top Up" />
<div className="flex flex-col min-h-[calc(95dvh)]">
<div
className="w-full px-5 py-5"
onClick={() => {
router.get(route('customer.deposit.index'))
}}
>
<HiChevronLeft className="font-bold h-5 w-5" />
</div>
<div className="w-full px-5">
<div className="mb-2 font-bold">Pilih Nominal</div>
<div className="w-full grid grid-cols-3 gap-2 text-center">
{amounts.map((amount) => (
<div
key={amount}
className={isActiveAmount(amount)}
onClick={() => setAmount(amount)}
>
{formatIDR(amount)}
</div>
))}
</div>
</div>
<div className="flex flex-row items-center space-x-2 justify-between w-full px-5 py-5">
<div className="border-b flex-1"></div>
<div>ATAU</div>
<div className="border-b flex-1"></div>
</div>
<div className="w-full px-5">
<FormInput
placeholder="masukan nominal, minimal 10.000"
value={data.amount}
onChange={(e) => setData('amount', e.target.value)}
error={errors.amount}
/>
</div>
<div className="w-full px-5 mt-10 flex flex-col">
<div className="font-bold mb-2">Metode Pembayaran</div>
{errors.payment && (
<Alert type="error">Pilih metode pembayaran</Alert>
)}
<div className="mb-2" />
<div className="w-full flex flex-col space-y-2">
{payments.map((payment) => (
<div
key={payment.name}
className={isActivePayment(payment.name)}
onClick={() => setData('payment', payment.name)}
>
<input type="radio" />
{payment.logo === null ? (
<p>{payment.display_name}</p>
) : (
<img
src={payment.logo}
className="h-7 object-cover"
/>
)}
</div>
))}
</div>
</div>
</div>
<div className="fixed bottom-20 right-0 md:right-1/3 md:pl-10 max-w-md w-full">
<div
onClick={handleSubmit}
className="border bg-blue-700 text-white px-5 py-2 mx-5 rounded-full hover:text-black hover:bg-white"
>
Bayar
</div>
</div>
</CustomerLayout>
)
}

@ -0,0 +1,34 @@
import { router } from '@inertiajs/react'
import { HiOutlineCash } from 'react-icons/hi'
export default function BalanceBanner({ user }) {
return (
<div
className="flex flex-row px-5 pb-3 text-base bg-blue-600"
onClick={() => router.get(route('customer.deposit.index'))}
>
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">Rp {user.display_deposit}</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin {user.display_coin}</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
{/* <HiOutlineAwa /> */}
<div>Rewards</div>
</div>
<div className="font-bold">{user.level.name} Member</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
</div>
</div>
</div>
)
}

@ -91,7 +91,7 @@ export default function Index(props) {
return (
<CustomerLayout>
<Head title="Home" />
<div className="flex flex-col">
<div className="flex flex-col min-h-[calc(95dvh)]">
{user !== null ? <UserBanner user={user} /> : <GuestBanner />}
{/* banner */}

@ -1,5 +1,6 @@
import React from 'react'
import { HiOutlineCash, HiOutlineBell } from 'react-icons/hi'
import BalanceBanner from './BalanceBanner'
export default function UserBanner({ user }) {
return (
@ -25,34 +26,7 @@ export default function UserBanner({ user }) {
</div>
</div>
{/* saldo */}
<div className="flex flex-row px-5 pb-3 text-base bg-blue-600">
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">
Rp {user.display_deposit}
</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin {user.display_coin}</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
{/* <HiOutlineAwa /> */}
<div>Rewards</div>
</div>
<div className="font-bold">
{user.level.name} Member
</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
</div>
</div>
</div>
<BalanceBanner user={user} />
</div>
)
}

@ -10,6 +10,7 @@ import {
import { HiOutlineBell, HiOutlineCash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import ModalConfirm from '@/Components/ModalConfirm'
import BalanceBanner from '../Index/BalanceBanner'
export default function Index({ auth: { user } }) {
const confirmModal = useModalState()
@ -59,43 +60,26 @@ export default function Index({ auth: { user } }) {
</div>
</div>
{/* saldo */}
<div className="flex flex-row px-5 pb-3 text-base bg-blue-600">
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">
Rp {user.display_deposit}
</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin {user.display_coin}</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Rewards</div>
</div>
<div className="font-bold">
{user.level.name} Member
</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
</div>
</div>
</div>
<BalanceBanner user={user} />
</div>
<div className="p-4 flex flex-col">
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Upgrade Member</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div
className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100"
onClick={() =>
router.get(route('customer.deposit.index'))
}
>
<div>Deposit Saldo</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Coin</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Transaksi</div>
<HiChevronRight className="h-5 w-5" />

@ -2,6 +2,7 @@
use App\Http\Controllers\Api\RoleController;
use App\Http\Controllers\Api\LocationController;
use App\Http\Controllers\Customer\DepositController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@ -25,4 +26,5 @@ Route::get('/roles', [RoleController::class, 'index'])->name('api.role.index');
Route::get('/locations', [LocationController::class, 'index'])->name('api.location.index');
// midtrans
Route::post('mindtrans/notification', fn () => 'Ok!')->name('api.midtrans.notification');
Route::post('mindtrans/notification', [DepositController::class, 'mindtrans_notification'])->name('api.midtrans.notification');
Route::post('mindtrans/{deposit}/payment', [DepositController::class, 'midtrans_payment'])->name('api.midtrans.payment');

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Customer\AuthController;
use App\Http\Controllers\Customer\DepositController;
use App\Http\Controllers\Customer\HomeController;
use App\Http\Controllers\Customer\ProfileController;
use Illuminate\Support\Facades\Route;
@ -28,8 +29,16 @@ Route::middleware(['http_secure_aware', 'guard_should_customer', 'inertia.custom
Route::post('profile/update', [ProfileController::class, 'update']);
// logout
Route::post('logout', [AuthController::class, 'destroy'])->name('customer.logout');
// deposite
Route::get('deposit', [DepositController::class, 'index'])->name('customer.deposit.index');
Route::get('deposit/topup', [DepositController::class, 'create'])->name('customer.deposit.topup');
Route::post('deposit/topup', [DepositController::class, 'store']);
Route::get('deposit/trx/{deposit}', [DepositController::class, 'show'])->name('customer.deposit.show');
Route::post('deposit/trx/{deposit}', [DepositController::class, 'update'])->name('customer.deposit.update');
});
Route::middleware('guest:customer')->group(function () {
// login
Route::get('/login', [AuthController::class, 'login'])->name('customer.login');

Loading…
Cancel
Save