diff --git a/app/Classes/ManualPaymentPlugin.php b/app/Classes/ManualPaymentPlugin.php index 34d855e3..0ee9c091 100644 --- a/app/Classes/ManualPaymentPlugin.php +++ b/app/Classes/ManualPaymentPlugin.php @@ -5,9 +5,12 @@ use App\Facades\Hook; use App\Forms\Form; use App\Models\Payment; +use Filament\Forms\Components\Grid; +use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\SpatieMediaLibraryFileUpload; use Filament\Forms\Get; use Filament\Notifications\Notification; +use Illuminate\Support\HtmlString; class ManualPaymentPlugin extends Plugin { @@ -24,31 +27,52 @@ public function boot() return false; }); - Hook::add('Forms::Form::components::paymentConfirmation', function ($hookName, array &$components, Form $form) { + Hook::add('Forms::Form::components::paymentForm', function ($hookName, array &$components, Form $form) { - $components[] = SpatieMediaLibraryFileUpload::make('payment_proof') - ->label('Payment Proof') + $components[] = Grid::make(1) ->visible(fn(Get $get) => $get('payment_method') == 'manual') - ->required() - ->downloadable() - ->collection('manual_payment_proof'); + ->schema([ + Placeholder::make('manual_payment_instructions') + ->label('Payment Instructions') + ->content(fn() => new HtmlString(app()->getCurrentScheduledConference()->getMeta('manual_payment_instructions'))) + ->visible(fn() => app()->getCurrentScheduledConference()->getMeta('manual_payment_instructions')), + SpatieMediaLibraryFileUpload::make('payment_proof') + ->label('Payment Proof') + ->required() + ->downloadable() + ->collection('manual_payment_proof') + ]); return false; }); + Hook::add('Forms::Form::components::submissionPayment', function ($hookName, array &$components, Form $form) { - Hook::add('Frontend::PaymentForm::submit', function($hookName, Payment $payment, array &$data, string &$requestUrl){ - - if($data['payment_method'] == 'manual'){ + $components[] = Grid::make(1) + ->visible(fn(Get $get) => $get('payment_method') == 'manual') + ->schema([ + SpatieMediaLibraryFileUpload::make('payment_proof') + ->label('Payment Proof') + ->required() + ->downloadable() + ->collection('manual_payment_proof') + ]) + ->disabled(); + + return false; + }); + + Hook::add('Frontend::Payment::handleRequestUrl', function ($hookName, Payment $payment, array &$data, string &$requestUrl) { + + if ($data['payment_method'] == 'manual') { Notification::make() ->title('Submit successfully') ->success() ->send(); } - - + + return false; }); - } } diff --git a/app/Frontend/ScheduledConference/Pages/ParticipantForm.php b/app/Frontend/ScheduledConference/Pages/ParticipantForm.php new file mode 100644 index 00000000..a52dfcf6 --- /dev/null +++ b/app/Frontend/ScheduledConference/Pages/ParticipantForm.php @@ -0,0 +1,186 @@ + | null + */ + public ?array $data = []; + + public function mount(PaymentFee $paymentFee) + { + if($paymentFee->type !== PaymentManager::TYPE_PARTICIPANT_FEE){ + abort('403', 'Invalid payment fee type'); + } + + $this->form->fill([ + + ]); + } + + public function getTitle(): string|Htmlable + { + return "Participant Registration"; + } + + public static function getLayout(): string + { + return static::$layout; + } + + /** + * @return array + */ + protected function getViewData(): array + { + return [ + + ]; + } + + public function form(Form $form): Form + { + $paymentManager = PaymentManager::get(); + + return $form + ->id('paymentForm') + ->statePath('data') + ->model(Participant::class) + ->schema([ + Placeholder::make('name') + ->content($this->paymentFee->name), + Placeholder::make('type') + ->content($this->paymentFee->getPaymentType()), + Placeholder::make('amount') + ->content($this->paymentFee->getFormattedFee()) + ->extraAttributes([ + 'style' => 'font-size:1rem;', + ]), + Placeholder::make('description') + ->content($this->paymentFee->getMeta('description')) + ->visible($this->paymentFee->getMeta('description') ?? false), + Grid::make() + ->schema([ + TextInput::make('given_name') + ->label(__('general.given_name')) + ->required(), + TextInput::make('family_name') + ->label(__('general.family_name')), + ]), + TextInput::make('public_name') + ->label(__('general.public_name')), + TextInput::make('email') + ->email() + ->label(__('general.email')) + ->required(), + ...$this->paymentFee->formItems->map(fn(PaymentFeeFormItem $item) => $item->getFormField())->toArray(), + Radio::make('payment_method') + ->required() + ->reactive() + ->options($paymentManager->getPaymentMethodOptions()) + ]); + } + + public function submitAction() + { + return Action::make('submitAction') + ->label('Submit') + ->submit('submit') + ->button(); + } + + public function submit() + { + $data = $this->form->getState(); + + try { + DB::beginTransaction(); + + $participant = new Participant(); + $participant->fill(Arr::only($data, ['given_name', 'family_name', 'email'])); + $participant->save(); + + $this->form->model($participant)->saveRelationships(); + + $paymentManager = PaymentManager::get(); + $payment = $paymentManager->queue($participant, $this->paymentFee, auth()->user(), PaymentManager::TYPE_PARTICIPANT_FEE, $this->paymentFee->name, $this->paymentFee->getMeta('description')); + $payment->payment_method = $data['payment_method']; + $payment->save(); + + if($meta = data_get($data, 'meta')){ + $payment->setManyMeta($meta); + } + + $requestUrl = $payment->getMeta('request_url'); + + Hook::call('Frontend::Payment::handleRequestUrl', [$payment, &$data, &$requestUrl]); + + DB::commit(); + + return redirect()->to($requestUrl); + } catch (\Throwable $th) { + DB::rollBack(); + throw $th; + } + } + + public static function routes(PageGroup $pageGroup): void + { + $slug = static::getSlug(); + Route::get("/{$slug}/{paymentFee}", static::class) + ->middleware(static::getRouteMiddleware($pageGroup)) + ->withoutMiddleware(static::getWithoutRouteMiddleware($pageGroup)) + ->name((string) str($slug)->replace('/', '.')); + } + + /** + * @return array + */ + public function getRenderHookScopes(): array + { + return [static::class]; + } + + /** + * @return array + */ + public function getExtraBodyAttributes(): array + { + return []; + } +} diff --git a/app/Frontend/ScheduledConference/Pages/ParticipantSuccessRegister.php b/app/Frontend/ScheduledConference/Pages/ParticipantSuccessRegister.php new file mode 100644 index 00000000..c9aee743 --- /dev/null +++ b/app/Frontend/ScheduledConference/Pages/ParticipantSuccessRegister.php @@ -0,0 +1,45 @@ + __('general.home'), + 'Participant Success Register', + ]; + } + + /** + * @return array + */ + protected function getViewData(): array + { + return [ + + ]; + } + + // public static function routes(PageGroup $pageGroup): void + // { + // $slug = static::getSlug(); + // Route::get("/{$slug}/{payment}", static::class) + // ->middleware(static::getRouteMiddleware($pageGroup)) + // ->withoutMiddleware(static::getWithoutRouteMiddleware($pageGroup)) + // ->name((string) str($slug)->replace('/', '.')); + // } +} diff --git a/app/Frontend/ScheduledConference/Pages/PaymentForm.php b/app/Frontend/ScheduledConference/Pages/PaymentForm.php index ade351b4..6bb86a8d 100644 --- a/app/Frontend/ScheduledConference/Pages/PaymentForm.php +++ b/app/Frontend/ScheduledConference/Pages/PaymentForm.php @@ -38,17 +38,16 @@ class PaymentForm extends Page implements HasForms, HasActions public function mount(Payment $payment) { - // dd($payment, $payment->getAllMeta()); + if($payment->isPaid()){ + abort('403', 'Payment fee already paid'); + } + if ($payment->isExpired()) { $payment->delete(); abort('403', 'Payment is expired'); } - if($payment->isPaid()){ - abort('403', 'Payment fee already paid'); - } - $this->form->fill([ ...$this->payment->attributesToArray(), 'meta' => $this->payment->getAllMeta()->toArray(), @@ -75,34 +74,18 @@ public function getBreadcrumbs(): array ]; } - /** - * @return array - */ - protected function getViewData(): array - { - $currentScheduledConference = app()->getCurrentScheduledConference(); - - return [ - 'heading' => "Pay", - 'paymentDetails' => $this->paymentMethod(), - 'payment' => $this->payment, - 'paymentInformation' => $currentScheduledConference->getMeta('payment_information'), - ]; - } - public function form(Form $form): Form { $paymentManager = PaymentManager::get(); return $form - ->id('paymentConfirmation') + ->id('paymentForm') ->statePath('data') ->model($this->payment) ->schema([ Shout::make('policy') - ->icon(fn() => null) - ->content(fn() => new HtmlString(app()->getCurrentScheduledConference()?->getMeta('submission_payment_policy'))) - ->visible(app()->getCurrentScheduledConference()?->getMeta('submission_payment_policy') ?? false), + ->content(fn() => new HtmlString(app()->getCurrentScheduledConference()?->getMeta('payment_policy'))) + ->visible(app()->getCurrentScheduledConference()?->getMeta('payment_policy') ?? false), Placeholder::make('title') ->content($this->payment->getMeta('title')), Placeholder::make('type') @@ -146,7 +129,7 @@ public function submit() $requestUrl = $this->payment->getMeta('request_url'); - Hook::call('Frontend::PaymentForm::submit', [$this->payment, &$data, &$requestUrl]); + Hook::call('Frontend::Payment::handleRequestUrl', [$this->payment, &$data, &$requestUrl]); return redirect()->to($requestUrl); } catch (\Throwable $th) { @@ -154,15 +137,6 @@ public function submit() } } - protected function paymentMethod() - { - $paymentMethods = []; - - Hook::call('Frontend::PaymentForm::getPaymentMethod', [$this->payment, &$paymentMethods]); - - return $paymentMethods; - } - public static function routes(PageGroup $pageGroup): void { $slug = static::getSlug(); diff --git a/app/Managers/PaymentManager.php b/app/Managers/PaymentManager.php index d1a5cb06..e2b6e840 100644 --- a/app/Managers/PaymentManager.php +++ b/app/Managers/PaymentManager.php @@ -3,6 +3,7 @@ namespace App\Managers; use App\Facades\Hook; +use App\Frontend\ScheduledConference\Pages\ParticipantSuccessRegister; use App\Interfaces\HasPayment; use App\Models\Payment; use App\Models\PaymentCompleted; @@ -18,7 +19,7 @@ class PaymentManager { public const TYPE_SUBMISSION_FEE = 1; - public const TYPE_ATTENDANCE_FEE = 2; + public const TYPE_PARTICIPANT_FEE = 2; public static function get(): PaymentManager { @@ -28,7 +29,7 @@ public static function get(): PaymentManager public function queue( Model & HasPayment $model, PaymentFee $paymentFee, - User $user, + ?User $user, int $type, string $title, ?string $description = null, @@ -38,7 +39,7 @@ public function queue( ) { $paymentQueue = new Payment([ - 'user_id' => $user->getKey(), + 'user_id' => $user?->getKey(), 'type' => $type, 'model_type' => $model::class, 'model_id' => $model->getKey(), @@ -52,13 +53,13 @@ public function queue( $requestUrl = match ($type) { self::TYPE_SUBMISSION_FEE => SubmissionResource::getUrl('view', ['record' => $model]), - self::TYPE_ATTENDANCE_FEE => "", //TODO add link for attendance - default => throw new Exception('Invalid payment type'), + self::TYPE_PARTICIPANT_FEE => route(ParticipantSuccessRegister::getRouteName('scheduledConference')), //TODO add link for attendance + default => url('/'), }; $paymentQueue->setManyMeta([ 'title' => $title, - 'user_id' => $user->getKey(), + 'user_id' => $user?->getKey(), 'request_url' => $requestUrl, 'description' => $description, ]); @@ -72,7 +73,7 @@ public function getPaymentTypeName(int $type) { return match ($type) { self::TYPE_SUBMISSION_FEE => "Submission Fee", - self::TYPE_ATTENDANCE_FEE => "Attendance Fee", + self::TYPE_PARTICIPANT_FEE => "Participant Fee", default => null, }; } diff --git a/app/Models/Participant.php b/app/Models/Participant.php new file mode 100644 index 00000000..2b223c21 --- /dev/null +++ b/app/Models/Participant.php @@ -0,0 +1,36 @@ +public_name ?? Str::squish($this->given_name.' '.$this->family_name); + }, + ); + } +} diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 1122b41d..f09990a0 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -60,6 +60,7 @@ public function fee(): BelongsTo public static function deleteExpired() { return self::query() + ->whereNull('paid_at') ->whereNotNull('expired_date') ->where('expired_date', '<', now()) ->delete(); @@ -67,10 +68,11 @@ public static function deleteExpired() public function scopePaid(Builder $query, $isPaid = true) { - $operator = $isPaid ? '<>' : 'IS NULL'; - $value = $isPaid ? '' : null; + if($isPaid){ + return $query->whereNotNull('paid_at'); + } - return $query->where('paid_at', $operator, $value); + return $query->whereNull('paid_at'); } public function scopeExpired(Builder $query, $isExpired = true) @@ -82,6 +84,10 @@ public function scopeExpired(Builder $query, $isExpired = true) public function isExpired(): bool { + if(!$this->paid_at){ + return false; + } + if (!$this->expired_at) { return false; } diff --git a/app/Models/PaymentFee.php b/app/Models/PaymentFee.php index 86e15f5f..5af2b8fc 100644 --- a/app/Models/PaymentFee.php +++ b/app/Models/PaymentFee.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Managers\PaymentManager; use App\Models\Concerns\BelongsToConference; use App\Models\Concerns\BelongsToScheduledConference; use Illuminate\Database\Eloquent\Builder; @@ -52,4 +53,18 @@ public function formItems() : HasMany return $this->hasMany(PaymentFeeFormItem::class); } + public function getPaymentType() + { + return PaymentManager::get()->getPaymentTypeName($this->type); + } + + public function getFormattedFee() + { + return money($this->amount, $this->currency, true)->formatWithoutZeroes(); + } + + public function payments(): HasMany + { + return $this->hasMany(Payment::class); + } } diff --git a/app/Panel/ScheduledConference/Livewire/ParticipantPaymentFeeTable.php b/app/Panel/ScheduledConference/Livewire/ParticipantPaymentFeeTable.php new file mode 100644 index 00000000..5543a553 --- /dev/null +++ b/app/Panel/ScheduledConference/Livewire/ParticipantPaymentFeeTable.php @@ -0,0 +1,285 @@ +type(PaymentManager::TYPE_PARTICIPANT_FEE) + ->with(['model.conference', 'user']); + } + + public function table(Table $table): Table + { + return $table + ->query($this->getTableQuery()) + ->columns([ + IndexColumn::make('No'), + TextColumn::make('model.full_name') + ->label('Name') + ->description(fn($record) => $record->model->email), + TextColumn::make('fee.name') + ->label("Participant Fee"), + TextColumn::make('amount') + ->getStateUsing(fn(Payment $record) => $record->amount ? money($record->amount, $record->currency, true)->formatWithoutZeroes() : 0), + TextColumn::make('paid_at') + ->date(), + ]) + ->headerActions([ + Action::make('mail') + ->label("Send Email") + ->icon('heroicon-o-envelope') + ->form(function (Form $form) { + return $form->schema([ + TextInput::make('subject') + ->label(__('general.subject')) + ->required(), + Select::make('participant_fee') + ->label('Participant Fee') + ->placeholder('All') + ->options( + PaymentFee::query() + ->type(PaymentManager::TYPE_PARTICIPANT_FEE) + ->pluck('name', 'id') + ), + Select::make('payment_status') + ->label('Payment Status') + ->placeholder('All') + ->options([ + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + ]), + TinyEditor::make('message') + ->label(__('general.message')) + ->minHeight(500) + ->required(), + ]); + }) + ->action(function (array $data, $action) { + Payment::query() + ->with(['model']) + ->type(PaymentManager::TYPE_PARTICIPANT_FEE) + ->when($data['participant_fee'], fn(Builder $query, $value) => $query->where('payment_fee_id', $value)) + ->when($data['payment_status'], fn(Builder $query, $value) => $query->paid($value === 'paid')) + ->get() + ->each(fn($payment) => Mail::to($payment->model->email, $payment->model->full_name)->send(new MailUser($data['subject'], $data['message']))); + + $action->success(); + + }) + ->successNotificationTitle('Sending Email in Background'), + ]) + ->filters([ + SelectFilter::make('payment_fee_id') + ->label("Payment Fee") + ->options( + PaymentFee::query() + ->type(PaymentManager::TYPE_PARTICIPANT_FEE) + ->pluck('name', 'id') + ), + SelectFilter::make('paid_at') + ->options([ + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + ]) + ->modifyQueryUsing(fn($query, $data) => $query->when($data['value'], fn($query, $value) => $query->paid($value === 'paid'))), + ]) + ->actions([ + ActionGroup::make([ + Action::make('payment') + ->icon('heroicon-o-banknotes') + ->modalWidth(MaxWidth::Large) + ->modalCancelActionLabel(__('general.close')) + ->mountUsing(function (Form $form, $record) { + $form->fill([ + ...$record->attributesToArray(), + 'meta' => $record->getAllMeta()->toArray(), + ]); + }) + ->visible(fn(Payment $record) => !$record->paid_at) + ->form(function (Form $form, Payment $record) { + return $form + ->id('payment') + ->model($record) + ->schema([ + Placeholder::make('type') + ->content($record->getPaymentType()), + Placeholder::make('amount') + ->content($record->getFormattedFee()) + ->extraAttributes([ + 'style' => 'font-size:1rem;', + ]), + DatePicker::make('paid_at'), + ]); + }) + ->action(fn(array $data, Payment $record) => $record->update([...$data, 'payment_method' => 'manual'])), + Action::make('detail') + ->icon('heroicon-o-eye') + ->mountUsing(function (Form $form, $record) { + $form->fill([ + ...$record->attributesToArray(), + 'meta' => $record->getAllMeta()->toArray(), + ]); + }) + ->modalWidth(MaxWidth::Large) + ->form(function (Form $form, Payment $record) { + return $form + ->id('paymentForm') + ->disabled() + ->model($record) + ->schema([ + Placeholder::make('title') + ->content($record->getMeta('title')), + Placeholder::make('type') + ->content($record->getPaymentType()), + Placeholder::make('amount') + ->content($record->getFormattedFee()) + ->extraAttributes([ + 'style' => 'font-size:1rem;', + ]), + Placeholder::make('paid_at') + ->visible($record->paid_at ? true : false) + ->content($record->paid_at?->format(Setting::get('format_date') . ' ' . Setting::get('format_time'))) + ->extraAttributes([ + 'style' => 'font-size:1rem;', + ]), + Placeholder::make('description') + ->content($record->getMeta('description')) + ->visible($record->getMeta('description') ?? false), + ...$record->fee?->formItems?->map(fn(PaymentFeeFormItem $item) => $item->getFormField())->toArray(), + Radio::make('payment_method') + ->required() + ->reactive() + ->options(PaymentManager::get()->getPaymentMethodOptions()) + ]); + }), + DeleteAction::make() + ->hidden(fn(Payment $record) => $record->paid_at) + ->using(function(Payment $record){ + $record->delete(); + + $record->model->delete(); + + return $record; + }), + ]), + + ]); + } + + public function form(Form $form): Form + { + return $form + ->schema([ + Grid::make() + ->schema([ + TextInput::make('name') + ->label(__('general.name')) + ->required() + ->unique( + ignorable: fn() => $form->getRecord(), + modifyRuleUsing: fn(Unique $rule) => $rule->where('scheduled_conference_id', app()->getCurrentScheduledConferenceId()), + ), + TextInput::make('limit') + ->label('Limit') + ->placeholder('Enter 0 for no limit') + ->numeric() + ->minValue(0) + ->required(), + ]), + Textarea::make('meta.description') + ->label(__('general.description')) + ->autosize(), + Grid::make() + ->schema([ + Select::make('currency') + ->label(__('general.currency')) + ->formatStateUsing(fn($state) => ($state !== null) ? ($state !== 'free' ? $state : null) : null) + ->options(fn() => Currency::query()->orderBy('code_numeric', 'asc')->get() + ->mapWithKeys(function (?Currency $value, int $key) { + $currencyCode = Str::upper($value->id); + $currencyName = $value->name; + + return [$value->id => "($currencyCode) $currencyName"]; + })) + ->searchable() + ->required(), + TextInput::make('amount') + ->label('Amount') + ->numeric() + ->required() + ->minValue(0), + ]), + Grid::make(2) + ->schema([ + DatePicker::make('opened_at') + ->label(__('general.opened_at')) + ->placeholder(__('general.select_type_opened_date')) + ->prefixIcon('heroicon-m-calendar-days') + ->before('closed_at'), + DatePicker::make('closed_at') + ->label(__('general.closed_at')) + ->placeholder(__('general.select_type_closed_date')) + ->prefixIcon('heroicon-m-calendar-days') + ->requiredWith('opened_at') + ->after('opened_at'), + ]), + Checkbox::make('is_active'), + ]); + } +} diff --git a/app/Panel/ScheduledConference/Livewire/PaymentFeeTable.php b/app/Panel/ScheduledConference/Livewire/PaymentFeeTable.php index a48b8a2d..646e7e52 100644 --- a/app/Panel/ScheduledConference/Livewire/PaymentFeeTable.php +++ b/app/Panel/ScheduledConference/Livewire/PaymentFeeTable.php @@ -3,7 +3,9 @@ namespace App\Panel\ScheduledConference\Livewire; use App\Facades\Setting; +use App\Frontend\ScheduledConference\Pages\ParticipantForm; use App\Infolists\Components\LivewireEntry; +use App\Managers\PaymentManager; use Livewire\Component; use Filament\Forms\Form; use App\Models\PaymentFee; @@ -35,6 +37,8 @@ use Filament\Tables\Actions\EditAction; use Filament\Tables\Columns\ToggleColumn; use Illuminate\Support\Facades\DB; +use Illuminate\Support\HtmlString; +use Illuminate\Support\Js; use Squire\Models\Currency; class PaymentFeeTable extends Component implements HasForms, HasTable @@ -53,7 +57,7 @@ public function render() public function getTableQuery(): Builder { return PaymentFee::query() - ->with(['formItems']) + ->with(['formItems', 'payments']) ->type($this->paymentType) ->ordered(); } @@ -101,8 +105,19 @@ public function table(Table $table): Table ]) ->actions([ ActionGroup::make([ + Action::make('copy_payment_link') + ->visible($this->paymentType === PaymentManager::TYPE_PARTICIPANT_FEE) + ->icon('heroicon-m-clipboard') + ->extraAttributes(fn ($record) => [ + 'x-data' => '', + 'x-on:click' => new HtmlString( + 'window.navigator.clipboard.writeText('. Js::from(route(ParticipantForm::getRouteName('scheduledConference'), ['paymentFee' => $record->getKey()])) .');' + ), + ]) + ->action(fn($action) => $action->success()) + ->successNotificationTitle('Link copied'), EditAction::make() - ->hidden(fn(PaymentFee $record) => $record->formItems->count()) + ->hidden(fn(PaymentFee $record) => $record->payments->count()) ->mutateRecordDataUsing(function (PaymentFee $record, array $data): array { $data['meta'] = $record->getAllMeta(); @@ -121,7 +136,7 @@ public function table(Table $table): Table }), Action::make('items') ->label('Form Items') - ->hidden(fn(PaymentFee $record) => $record->formItems->count()) + ->hidden(fn(PaymentFee $record) => $record->payments->count()) ->modalWidth(MaxWidth::TwoExtraLarge) ->icon('heroicon-m-list-bullet') ->modalCancelAction(false) @@ -170,7 +185,7 @@ public function table(Table $table): Table return $form->schema($record->formItems->map(fn($item) => $item->getFormField())->toArray()); }), DeleteAction::make() - ->hidden(fn(PaymentFee $record) => $record->formItems->count()), + ->hidden(fn(PaymentFee $record) => $record->payments->count()), ]), ]); } diff --git a/app/Panel/ScheduledConference/Livewire/Payment/PaymentSetting.php b/app/Panel/ScheduledConference/Livewire/PaymentSetting.php similarity index 67% rename from app/Panel/ScheduledConference/Livewire/Payment/PaymentSetting.php rename to app/Panel/ScheduledConference/Livewire/PaymentSetting.php index 322a3c59..d27e8acd 100644 --- a/app/Panel/ScheduledConference/Livewire/Payment/PaymentSetting.php +++ b/app/Panel/ScheduledConference/Livewire/PaymentSetting.php @@ -1,11 +1,12 @@ schema([ Section::make() ->schema([ - Toggle::make('meta.submission_payment') + Checkbox::make('meta.submission_payment') ->label(__('general.enable_submission_payment')), TinyEditor::make('meta.payment_policy') ->label(__('general.payment_policy')) - ->plugins('advlist autoresize codesample directionality emoticons fullscreen hr image imagetools link lists media table toc wordcount code') - ->toolbar('undo redo removeformat | formatselect fontsizeselect | bold italic | rtl ltr | alignjustify alignright aligncenter alignleft | numlist bullist | forecolor backcolor | blockquote table hr | image link code') - ->minHeight(450), - ]) - ->disabled(fn () => auth()->user()->cannot('RegistrationSetting:update')), + ]), Actions::make([ - Action::make('save_changes') - ->label(__('general.save_changes')) + Action::make('save') + ->label(__('general.save')) ->successNotificationTitle(__('general.saved')) ->failureNotificationTitle(__('general.data_could_not_saved')) ->action(function (Action $action) { $formData = $this->form->getState(); try { - ScheduledConferenceUpdateAction::run(app()->getCurrentScheduledConference(), $formData); - } catch (\Throwable $th) { - $action->failure(); throw $th; } $action->success(); - }) - ->authorize('RegistrationSetting:update'), + }), ]), ]) + ->disabled(fn () => !auth()->user()->can('update', app()->getCurrentScheduledConference())) ->statePath('formData'); } diff --git a/app/Panel/ScheduledConference/Livewire/SubmissionPaymentFeeTable.php b/app/Panel/ScheduledConference/Livewire/SubmissionPaymentFeeTable.php index 55a4adbc..e0d2b4ca 100644 --- a/app/Panel/ScheduledConference/Livewire/SubmissionPaymentFeeTable.php +++ b/app/Panel/ScheduledConference/Livewire/SubmissionPaymentFeeTable.php @@ -4,47 +4,32 @@ use App\Facades\Setting; use App\Managers\PaymentManager; -use App\Models\Enums\PaymentType; use App\Models\Payment; -use Filament\Tables; use Livewire\Component; use Filament\Forms\Form; -use App\Models\PaymentFee; use App\Models\PaymentFeeFormItem; +use App\Panel\ScheduledConference\Resources\SubmissionResource; use Filament\Tables\Table; use Illuminate\Support\Str; -use Filament\Facades\Filament; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Select; use Filament\Forms\Contracts\HasForms; use Filament\Forms\Components\Textarea; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Contracts\HasTable; -use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\Rules\Unique; use Filament\Forms\Components\TextInput; use Illuminate\Database\Eloquent\Builder; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Tables\Concerns\InteractsWithTable; -use Illuminate\Database\Eloquent\Relations\Relation; -use App\Panel\Conference\Resources\Conferences\AuthorRoleResource; -use App\Panel\ScheduledConference\Resources\SubmissionResource; use App\Tables\Columns\IndexColumn; -use Awcodes\Shout\Components\Shout; use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\DatePicker; -use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Radio; use Filament\Support\Enums\MaxWidth; use Filament\Tables\Actions\Action; use Filament\Tables\Actions\ActionGroup; -use Filament\Tables\Actions\CreateAction; -use Filament\Tables\Actions\DeleteAction; -use Filament\Tables\Actions\EditAction; -use Filament\Tables\Columns\TextColumn\TextColumnSize; -use Filament\Tables\Columns\ToggleColumn; -use Illuminate\Support\HtmlString; use Squire\Models\Currency; class SubmissionPaymentFeeTable extends Component implements HasForms, HasTable @@ -73,7 +58,7 @@ public function table(Table $table): Table IndexColumn::make('No'), TextColumn::make('title') ->color('primary') - // ->url(fn(Payment $record) => $record->model ? SubmissionResource::getUrl('view', ['record' => $record->model]) : null) + ->url(fn(Payment $record) => $record->model ? SubmissionResource::getUrl('view', ['record' => $record->model]) : null) ->getStateUsing(fn(Payment $record) => $record->model?->getMeta('title') ?? '-') ->wrap(), TextColumn::make('user.fullName') @@ -86,7 +71,6 @@ public function table(Table $table): Table ->orWhere('family_name', 'LIKE', "%{$search}%") ) ), - TextColumn::make('amount') ->getStateUsing(fn(Payment $record) => $record->amount ? money($record->amount, $record->currency, true)->formatWithoutZeroes() : 0), TextColumn::make('paid_at') @@ -95,6 +79,32 @@ public function table(Table $table): Table ]) ->actions([ ActionGroup::make([ + Action::make('payment') + ->modalWidth(MaxWidth::Large) + ->modalCancelActionLabel(__('general.close')) + ->mountUsing(function (Form $form, $record) { + $form->fill([ + ...$record->attributesToArray(), + 'meta' => $record->getAllMeta()->toArray(), + ]); + }) + ->visible(fn(Payment $record) => !$record->paid_at) + ->form(function (Form $form, Payment $record) { + return $form + ->id('payment') + ->model($record) + ->schema([ + Placeholder::make('type') + ->content($record->getPaymentType()), + Placeholder::make('amount') + ->content($record->getFormattedFee()) + ->extraAttributes([ + 'style' => 'font-size:1rem;', + ]), + DatePicker::make('paid_at'), + ]); + }) + ->action(fn(array $data, Payment $record) => $record->update([...$data, 'payment_method' => 'manual'])), Action::make('detail') ->mountUsing(function (Form $form, $record) { $form->fill([ @@ -102,11 +112,11 @@ public function table(Table $table): Table 'meta' => $record->getAllMeta()->toArray(), ]); }) + ->modalSubmitAction(false) ->modalWidth(MaxWidth::Large) ->form(function (Form $form, Payment $record) { - return $form - ->id('paymentConfirmation') + ->id('paymentForm') ->disabled() ->model($record) ->schema([ diff --git a/app/Panel/ScheduledConference/Livewire/SubmissionPaymentSetting.php b/app/Panel/ScheduledConference/Livewire/SubmissionPaymentSetting.php index 3c1a52d0..0a68c782 100644 --- a/app/Panel/ScheduledConference/Livewire/SubmissionPaymentSetting.php +++ b/app/Panel/ScheduledConference/Livewire/SubmissionPaymentSetting.php @@ -34,7 +34,7 @@ public function form(Form $form): Form ->schema([ Toggle::make('meta.submission_payment') ->label(__('general.enable_submission_payment')), - TinyEditor::make('meta.submission_payment_policy') + TinyEditor::make('meta.payment_policy') ->label(__('general.payment_policy')) ]), Actions::make([ diff --git a/app/Panel/ScheduledConference/Livewire/Submissions/Payment.php b/app/Panel/ScheduledConference/Livewire/Submissions/Payment.php deleted file mode 100644 index 64bc8be1..00000000 --- a/app/Panel/ScheduledConference/Livewire/Submissions/Payment.php +++ /dev/null @@ -1,357 +0,0 @@ - '$refresh', - ]; - - public function mount(Submission $submission) {} - - public function registrationPolicyAction() - { - return Action::make('registrationPolicyAction') - ->label(__('general.policy')) - ->modalHeading(__('general.registration_policy')) - ->icon('heroicon-o-book-open') - ->size('xs') - ->link() - ->infolist([ - TextEntry::make('registration_policy') - ->getStateUsing(fn () => app()->getCurrentScheduledConference()->getMeta('registration_policy')) - ->formatStateUsing(fn (string $state) => new HtmlString(<< - {$state} - - HTML)) - ->label('') - ->html(), - ]) - ->modalSubmitAction(false); - } - - public function decideRegsitrationAction() - { - return Action::make('decideRegsitrationAction') - ->label(__('general.decision')) - ->authorize('decideRegistration', $this->submission) - ->icon('heroicon-o-pencil-square') - ->color('primary') - ->size('xs') - ->modalHeading(__('general.paid_status_decision')) - ->modalWidth('2xl') - ->link() - ->mountUsing(function (Form $form) { - $registrationPayment = $this->submission->registration->registrationPayment; - $form->fill([ - 'registrationPayment' => [ - 'state' => $registrationPayment->state, - 'paid_at' => $registrationPayment->paid_at, - ], - ]); - }) - ->form([ - Grid::make(1) - ->schema([ - Select::make('registrationPayment.state') - ->label(__('general.state')) - ->options(RegistrationPaymentState::array()) - ->native(false) - ->required() - ->live(), - DatePicker::make('registrationPayment.paid_at') - ->label(__('general.paid_date')) - ->placeholder('Select registration paid date..') - ->prefixIcon('heroicon-m-calendar') - ->formatStateUsing(fn () => now()) - ->visible(fn (Get $get): bool => $get('registrationPayment.state') === RegistrationPaymentState::Paid->value) - ->required(), - ]), - ]) - ->action(function (Action $action, array $data) { - $registration = $this->submission->registration; - $formData = $data['registrationPayment']; - - if ($formData['state'] !== RegistrationPaymentState::Paid->value) { - $formData['type'] = null; - $formData['paid_at'] = null; - } else { - // manual payment because conference manager set it up - $formData['type'] = RegistrationPaymentType::Manual->value; - } - - try { - $registration->registrationPayment()->update(Arr::only($formData, ['state', 'paid_at', 'type'])); - - $registration->user->notify( - new RegistrationPaymentDecision( - registration: $registration, - state: $formData['state'], - ) - ); - } catch (\Throwable $th) { - throw $th; - } - - $action->successRedirectUrl( - SubmissionResource::getUrl('view', [ - 'record' => $this->submission->getKey(), - ]) - ); - - $action->success(); - }); - } - - public function deleteRegistrationAction() - { - return Action::make('deleteRegistrationAction') - ->label(__('general.delete')) - ->authorize('deleteRegistration', $this->submission) - ->icon('heroicon-o-x-mark') - ->color('danger') - ->size('xs') - ->link() - ->requiresConfirmation() - ->action(function (Action $action) { - - try { - $this->submission->registration->forceDelete(); - } catch (\Throwable $th) { - $action->failure(); - throw $th; - } - - $action->successRedirectUrl( - SubmissionResource::getUrl('view', [ - 'record' => $this->submission->getKey(), - ]) - ); - - $action->success(); - }); - } - - public function cancelRegistrationAction() - { - return Action::make('cancelRegistrationAction') - ->label(__('general.cancel')) - ->authorize('cancelRegistration', $this->submission) - ->icon('heroicon-o-x-mark') - ->tooltip(__('general.cancel_registration')) - ->color('danger') - ->size('xs') - ->link() - ->requiresConfirmation() - ->modalHeading(__('general.cancel_registration')) - ->action(function (Action $action) { - - try { - $this->submission->registration->forceDelete(); - } catch (\Throwable $th) { - $action->failure(); - throw $th; - } - - $action->successRedirectUrl( - SubmissionResource::getUrl('view', [ - 'record' => $this->submission->getKey(), - ]) - ); - - $action->success(); - }); - } - - public function declinePaymentAction() - { - return Action::make('declinePaymentAction') - ->label(__('general.decline_submission_payment')) - ->authorize('declinePayment', $this->submission) - ->icon('lineawesome-times-solid') - ->color('danger') - ->outlined() - ->requiresConfirmation() - ->modalDescription('Are you sure you want to decline the payment? Previous progress will return to payment (previous files and discussions will not be lost). ') - ->modalWidth('2xl') - ->mountUsing(function (Form $form) { - $mailTemplate = DefaultMailTemplate::where('mailable', DeclinePaymentMail::class)->first(); - $form->fill([ - 'email' => $this->submission->user->email, - 'subject' => $mailTemplate ? $mailTemplate->subject : '', - 'message' => $mailTemplate ? $mailTemplate->html_template : '', - ]); - }) - ->form([ - Fieldset::make('Notification') - ->columns(1) - ->schema([ - TextInput::make('email') - ->label(__('general.email')) - ->readOnly() - ->dehydrated(), - TextInput::make('subject') - ->label(__('general.subject')) - ->required(), - TinyEditor::make('message') - ->label(__('general.message')) - ->minHeight(300) - ->profile('email') - ->columnSpanFull(), - Checkbox::make('do-not-notify-author') - ->label(__('general.dont_send_notification_to_author')) - ->columnSpanFull(), - ]), - ]) - ->action(function (Action $action, array $data) { - $this->submission->state()->declinePayment(); - - if (! $data['do-not-notify-author']) { - try { - Mail::to($this->submission->user->email) - ->send( - (new DeclinePaymentMail($this->submission)) - ->subjectUsing($data['subject']) - ->contentUsing($data['message']) - ); - } catch (\Exception $e) { - $action->failureNotificationTitle(__('general.email_notification_was_not_delivered')); - $action->failure(); - } - } - - $action->successRedirectUrl( - SubmissionResource::getUrl('view', [ - 'record' => $this->submission->getKey(), - ]) - ); - - $action->success(); - }); - } - - public function approvePaymentAction() - { - return Action::make('approvePaymentAction') - ->label(__('general.approve_submission_payment')) - ->authorize('ApprovePayment', $this->submission) - ->icon('lineawesome-check-circle-solid') - ->color('primary') - ->modalWidth('2xl') - ->modalSubmitActionLabel(__('general.accept')) - ->mountUsing(function (Form $form) { - $mailTemplate = DefaultMailTemplate::where('mailable', ApprovePaymentMail::class)->first(); - $form->fill([ - 'email' => $this->submission->user->email, - 'subject' => $mailTemplate ? $mailTemplate->subject : '', - 'message' => $mailTemplate ? $mailTemplate->html_template : '', - ]); - }) - ->form([ - Fieldset::make('Notification') - ->label(__('general.notification')) - ->columns(1) - ->schema([ - TextInput::make('email') - ->label(__('general.email')) - ->readOnly() - ->dehydrated(), - TextInput::make('subject') - ->label(__('general.subject')) - ->required(), - TinyEditor::make('message') - ->label(__('general.message')) - ->minHeight(300) - ->profile('email') - ->columnSpanFull(), - Checkbox::make('do-not-notify-author') - ->label(__('general.dont_send_notification_to_author')) - ->columnSpanFull(), - ]), - ]) - ->action(function (Action $action, array $data) { - $this->submission->state()->approvePayment(); - - if (! $data['do-not-notify-author']) { - try { - Mail::to($this->submission->user->email) - ->send( - (new ApprovePaymentMail($this->submission)) - ->subjectUsing($data['subject']) - ->contentUsing($data['message']) - ); - } catch (\Exception $e) { - $action->failureNotificationTitle(__('general.email_notification_was_not_delivered')); - $action->failure(); - } - } - - $action->successRedirectUrl( - SubmissionResource::getUrl('view', [ - 'record' => $this->submission->getKey(), - ]) - ); - - $action->success(); - }) - ->disabled(fn () => ! $this->submission->registration); - } - - public function render() - { - $user = auth()->user(); - - return view('panel.scheduledConference.livewire.submissions.payment', [ - 'currentScheduledConference' => app()->getCurrentScheduledConference(), - 'submissionRegistration' => $this->submission->registration, - 'submissionRegistrant' => $this->submission->registration->user ?? null, - 'isSubmissionAuthor' => $this->submission->isParticipantAuthor($user), - 'isRegistrationOpen' => Timeline::isRegistrationOpen(), - 'submissionDecision' => ($user->hasAnyRole([UserRole::ConferenceManager, UserRole::Admin]) || $this->submission->isParticipantEditor($user)) && - in_array($this->submission->status, [ - SubmissionStatus::OnReview, - SubmissionStatus::PaymentDeclined, - SubmissionStatus::OnPresentation, - SubmissionStatus::Editing, - ]), - ]); - } -} diff --git a/app/Panel/ScheduledConference/Pages/Payments.php b/app/Panel/ScheduledConference/Pages/Payments.php index dd41b954..fc7a9d9e 100644 --- a/app/Panel/ScheduledConference/Pages/Payments.php +++ b/app/Panel/ScheduledConference/Pages/Payments.php @@ -7,9 +7,10 @@ use App\Infolists\Components\VerticalTabs as InfolistsVerticalTabs; use App\Managers\PaymentManager; use App\Models\Enums\PaymentType; +use App\Panel\ScheduledConference\Livewire\ParticipantPaymentFeeTable; use App\Panel\ScheduledConference\Livewire\Payment\ManualPaymentSetting; -use App\Panel\ScheduledConference\Livewire\Payment\PaymentSetting; use App\Panel\ScheduledConference\Livewire\PaymentFeeTable; +use App\Panel\ScheduledConference\Livewire\PaymentSetting; use App\Panel\ScheduledConference\Livewire\SubmissionPaymentFeeTable; use App\Panel\ScheduledConference\Livewire\SubmissionPaymentSetting; use Filament\Infolists\Infolist; @@ -69,16 +70,21 @@ public function infolist(Infolist $infolist): Infolist Tabs::make('Tabs') ->contained(false) ->tabs([ + Tabs\Tab::make('Settings') + ->schema([ + LivewireEntry::make('submission_payment_settings') + ->livewire(PaymentSetting::class), + ]), Tabs\Tab::make('Submission Payment') ->schema([ InfolistsVerticalTabs\Tabs::make() ->schema([ - InfolistsVerticalTabs\Tab::make('submission_payment_tab') - ->label("Settings") - ->schema([ - LivewireEntry::make('submission_payment_settings') - ->livewire(SubmissionPaymentSetting::class), - ]), + // InfolistsVerticalTabs\Tab::make('submission_payment_tab') + // ->label("Settings") + // ->schema([ + // LivewireEntry::make('submission_payment_settings') + // ->livewire(SubmissionPaymentSetting::class), + // ]), InfolistsVerticalTabs\Tab::make('submission_fee_tab') ->label("Fees") ->schema([ @@ -95,9 +101,24 @@ public function infolist(Infolist $infolist): Infolist ]), ]), - Tabs\Tab::make('Attendances Payment') + Tabs\Tab::make('Participant Payment') ->schema([ - // ... + InfolistsVerticalTabs\Tabs::make() + ->schema([ + InfolistsVerticalTabs\Tab::make('participant_fee_tab') + ->label("Fees") + ->schema([ + LivewireEntry::make('participant_payment_fees') + ->livewire(PaymentFeeTable::class, ['paymentType' => PaymentManager::TYPE_PARTICIPANT_FEE]), + ]), + InfolistsVerticalTabs\Tab::make('submission_fee_payments_tab') + ->label("Payments") + ->schema([ + LivewireEntry::make('participant_payment_fees') + ->livewire(ParticipantPaymentFeeTable::class), + + ]), + ]), ]), Tabs\Tab::make('Payment Method') ->schema([ diff --git a/app/Panel/ScheduledConference/Resources/SubmissionResource/Pages/ViewSubmission.php b/app/Panel/ScheduledConference/Resources/SubmissionResource/Pages/ViewSubmission.php index 17e987aa..604bb91f 100644 --- a/app/Panel/ScheduledConference/Resources/SubmissionResource/Pages/ViewSubmission.php +++ b/app/Panel/ScheduledConference/Resources/SubmissionResource/Pages/ViewSubmission.php @@ -107,30 +107,9 @@ protected function getHeaderActions(): array ->modalWidth(MaxWidth::Large) ->modalCancelActionLabel(__('general.close')) ->visible(fn() => $this->record->payment) - ->authorize(fn() => auth()->user()->can('actAsEditor', $this->record)) - ->mountUsing(function (Form $form) { - if (!$this->record->payment) return; - - $form->fill([ - ...$this->record->payment->attributesToArray(), - 'meta' => $this->record->payment->getAllMeta()->toArray(), - ]); - }) - ->form(function (Form $form) { - return $form - ->model($this->record->payment) - ->schema([ - Placeholder::make('type') - ->content($this->record?->payment->getPaymentType()), - Placeholder::make('amount') - ->content($this->record?->payment->getFormattedFee()) - ->extraAttributes([ - 'style' => 'font-size:1rem;', - ]), - DatePicker::make('paid_at'), - ]); - }) - ->action(fn(array $data) => $this->record->payment->update([...$data, 'payment_method' => 'manual'])), + ->authorize(fn() => $this->record->isParticipantAuthor(auth()->user())) + ->url(fn() => $this->record->payment->getPaymentUrl()) + ->hidden(fn() => $this->record->payment?->isPaid()), Action::make('charge_payment') ->label('Charge Payment') ->visible(fn() => !$this->record->payment && app()->getCurrentScheduledConference()->getMeta('submission_payment')) @@ -191,7 +170,6 @@ protected function getHeaderActions(): array ]), Textarea::make('description'), ]), - Checkbox::make('mark_payment_as_completed'), ]); }) ->successNotificationTitle('Payment fee sent to user') @@ -213,15 +191,7 @@ protected function getHeaderActions(): array $data['currency'], ); - if ($data['mark_payment_as_completed']) { - $paymentManager->fulfillQueued( - $paymentQueue, - 'Manual', - auth()->id() - ); - } else { - $this->record->user->notify(new PaymentRequired($paymentQueue)); - } + $this->record->user->notify(new PaymentRequired($paymentQueue)); $action->success(); }), diff --git a/database/migrations/2025_01_10_074242_create_payments_table.php b/database/migrations/2025_01_10_074242_create_payments_table.php index b766d6f8..430249a5 100644 --- a/database/migrations/2025_01_10_074242_create_payments_table.php +++ b/database/migrations/2025_01_10_074242_create_payments_table.php @@ -51,7 +51,7 @@ public function up(): void $table->foreignIdFor(User::class)->nullable()->constrained()->nullOnDelete(); $table->foreignIdFor(PaymentFee::class)->constrained(); $table->unsignedInteger('type'); - $table->morphs('model'); + $table->nullableMorphs('model'); $table->double('amount'); $table->string('currency'); $table->string('payment_method')->nullable(); diff --git a/database/migrations/2025_01_21_020555_create_participants_table.php b/database/migrations/2025_01_21_020555_create_participants_table.php new file mode 100644 index 00000000..e3885d3f --- /dev/null +++ b/database/migrations/2025_01_21_020555_create_participants_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignIdFor(Conference::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(ScheduledConference::class)->nullable()->constrained()->cascadeOnDelete(); + $table->string('given_name'); + $table->string('family_name')->nullable(); + $table->string('public_name')->nullable(); + $table->string('email'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('participants'); + } +}; diff --git a/lang/en/general.php b/lang/en/general.php index 250cf1b4..9a6b7848 100644 --- a/lang/en/general.php +++ b/lang/en/general.php @@ -398,6 +398,7 @@ 'submission_complete' => 'Submission complete', 'submission_payment' => 'Submission Payment', 'enable_submission_payment' => 'Enable Submission Payment', + 'enable_participant_payment' => "Enable Participant Payment", 'turn_off_to_disable_submission_payment' => 'Turn off to disable submission payment process.', 'submitted_submission' => 'You have submitted your submission, and an email has been sent to notify you. The manager will review your submission and send you another email once they are done.', 'submit_submission' => 'Submit Submission', diff --git a/resources/views/frontend/scheduledConference/pages/participant-form.blade.php b/resources/views/frontend/scheduledConference/pages/participant-form.blade.php new file mode 100644 index 00000000..b92b59bb --- /dev/null +++ b/resources/views/frontend/scheduledConference/pages/participant-form.blade.php @@ -0,0 +1,28 @@ +
+
+
+
+ +
+ +
+
+ {{ $this->getTitle()}} +
+ + + {{ $this->form }} + +
+ {{ $this->submitAction }} +
+
+
+
+
+ +
diff --git a/resources/views/frontend/scheduledConference/pages/participant-success-register.blade.php b/resources/views/frontend/scheduledConference/pages/participant-success-register.blade.php new file mode 100644 index 00000000..e9e26aa0 --- /dev/null +++ b/resources/views/frontend/scheduledConference/pages/participant-success-register.blade.php @@ -0,0 +1,15 @@ + +
+ +
+
+ {{--
+

{{ $this->getTitle() }}

+
+
--}} + +
+

Success register as participant.

+
+
+
\ No newline at end of file