Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use Livewire\Attributes\Computed;
use Lunar\Actions\Orders\RefundTransaction;
use Lunar\Admin\Filament\Resources\ProductResource\Pages\EditProduct;
use Lunar\Admin\Livewire\Components\TableComponent;
use Lunar\Admin\Support\Concerns\CallsHooks;
use Lunar\Admin\Support\Tables\Components\KeyValue;
use Lunar\Base\DataTransferObjects\RefundRequest;
use Lunar\Models\ProductVariant;
use Lunar\Models\Transaction;

Expand Down Expand Up @@ -185,8 +187,23 @@ function () {
])
->action(function ($data, BulkAction $action) {
$transaction = Transaction::findOrFail($data['transaction']);

$response = $transaction->refund(bcmul($data['amount'], $this->record->currency->factor), $data['notes']);
$selectedLines = $this->record->lines()
->whereIn('id', $this->selectedTableRecords)
->get();

$response = app(RefundTransaction::class)->execute(
new RefundRequest(
transaction: $transaction,
amount: (int) bcmul($data['amount'], $this->record->currency->factor),
notes: $data['notes'],
actorId: auth('staff')->id() ?: auth()->id(),
lineAllocations: $selectedLines->map(fn ($line) => [
'order_line_id' => $line->id,
'quantity' => $line->quantity,
'amount' => $line->total->value,
])->values()->all(),
)
);

if (! $response->success) {
$action->failureNotification(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Lunar\Actions\Orders\RefundTransaction;
use Lunar\Admin\Filament\Resources\CustomerResource;
use Lunar\Admin\Filament\Resources\OrderResource;
use Lunar\Admin\Filament\Resources\OrderResource\Concerns\DisplaysOrderAddresses;
Expand All @@ -42,6 +43,7 @@
use Lunar\Admin\Support\Infolists\Components\Livewire;
use Lunar\Admin\Support\Infolists\Components\Tags;
use Lunar\Admin\Support\Pages\BaseViewRecord;
use Lunar\Base\DataTransferObjects\RefundRequest;
use Lunar\Models\Order;
use Lunar\Models\Tag;
use Lunar\Models\Transaction;
Expand Down Expand Up @@ -426,7 +428,14 @@ function () {
->action(function ($data, $record, Action $action) {
$transaction = Transaction::findOrFail($data['transaction']);

$response = $transaction->refund(bcmul($data['amount'], $record->currency->factor), $data['notes']);
$response = app(RefundTransaction::class)->execute(
new RefundRequest(
transaction: $transaction,
amount: (int) bcmul($data['amount'], $record->currency->factor),
notes: $data['notes'],
actorId: auth('staff')->id() ?: auth()->id(),
)
);

if (! $response->success) {
$action->failureNotification(
Expand Down
109 changes: 109 additions & 0 deletions packages/core/src/Actions/Orders/RefundTransaction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Lunar\Actions\Orders;

use Lunar\Actions\AbstractAction;
use Lunar\Base\DataTransferObjects\PaymentRefund;
use Lunar\Base\DataTransferObjects\RefundRequest;
use Lunar\Base\RefundAuthorizationInterface;
use Lunar\Events\RefundCompleted;
use Lunar\Events\RefundFailed;
use Lunar\Events\RefundRequested;
use Lunar\Models\Transaction;
use Throwable;

class RefundTransaction extends AbstractAction
{
public function __construct(
protected RefundAuthorizationInterface $refundAuthorization,
) {
//
}

public function execute(RefundRequest $refundRequest): PaymentRefund
{
$authorization = $this->refundAuthorization->authorize($refundRequest);

if (! $authorization->authorized) {
$paymentRefund = new PaymentRefund(
success: false,
message: $authorization->message,
lineAllocations: $refundRequest->lineAllocations,
);

RefundFailed::dispatch(
refundRequest: $refundRequest,
paymentRefund: $paymentRefund,
message: $authorization->message,
meta: $authorization->meta,
);

return $paymentRefund;
}

RefundRequested::dispatch($refundRequest);

$existingRefundIds = $refundRequest->transaction->order
->refunds()
->pluck('id');

try {
$paymentRefund = $refundRequest->transaction->refund(
$refundRequest->amount,
$refundRequest->notes,
);
} catch (Throwable $e) {
$paymentRefund = new PaymentRefund(
success: false,
message: $e->getMessage(),
lineAllocations: $refundRequest->lineAllocations,
);

RefundFailed::dispatch(
refundRequest: $refundRequest,
paymentRefund: $paymentRefund,
message: $e->getMessage(),
meta: $authorization->meta,
);

return $paymentRefund;
}

$paymentRefund->lineAllocations ??= $refundRequest->lineAllocations;

if (! $paymentRefund->refundTransactionId) {
$refundTransaction = $refundRequest->transaction->order
->refunds()
->whereNotIn('id', $existingRefundIds)
->latest('id')
->first();

if ($refundTransaction) {
$this->hydratePaymentRefund($paymentRefund, $refundTransaction);
}
}

if (! $paymentRefund->success) {
RefundFailed::dispatch(
refundRequest: $refundRequest,
paymentRefund: $paymentRefund,
message: $paymentRefund->message,
meta: $authorization->meta,
);

return $paymentRefund;
}

RefundCompleted::dispatch($refundRequest, $paymentRefund);

return $paymentRefund;
}

protected function hydratePaymentRefund(PaymentRefund $paymentRefund, Transaction $refundTransaction): void
{
$paymentRefund->refundTransactionId ??= $refundTransaction->id;
$paymentRefund->reference ??= $refundTransaction->reference;
$paymentRefund->status ??= $refundTransaction->status;
$paymentRefund->meta ??= $refundTransaction->meta?->getArrayCopy() ?: null;
}
}
11 changes: 10 additions & 1 deletion packages/core/src/Base/DataTransferObjects/PaymentRefund.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@

class PaymentRefund
{
/**
* @param array<string, mixed>|null $meta
* @param array<int, array<string, mixed>>|null $lineAllocations
*/
public function __construct(
public bool $success = false,
public ?string $message = null
public ?string $message = null,
public ?int $refundTransactionId = null,
public ?string $reference = null,
public ?string $status = null,
public ?array $meta = null,
public ?array $lineAllocations = null,
) {
//
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Lunar\Base\DataTransferObjects;

class RefundAuthorizationResult
{
/**
* @param array<string, mixed> $meta
*/
public function __construct(
public bool $authorized = true,
public ?string $message = null,
public array $meta = [],
) {
//
}
}
23 changes: 23 additions & 0 deletions packages/core/src/Base/DataTransferObjects/RefundRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Lunar\Base\DataTransferObjects;

use Lunar\Models\Transaction;

class RefundRequest
{
/**
* @param array<string, mixed> $meta
* @param array<int, array<string, mixed>>|null $lineAllocations
*/
public function __construct(
public Transaction $transaction,
public int $amount,
public ?string $notes = null,
public ?int $actorId = null,
public array $meta = [],
public ?array $lineAllocations = null,
) {
//
}
}
44 changes: 44 additions & 0 deletions packages/core/src/Base/OrderLineCapabilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace Lunar\Base;

use Lunar\Models\OrderLine;

class OrderLineCapabilities implements OrderLineCapabilitiesInterface
{
public function isRefundable(OrderLine $orderLine): bool
{
return $orderLine->order->isPlaced()
&& in_array($orderLine->type, ['physical', 'digital'], true)
&& $orderLine->total->value > 0;
}

public function isCancellable(OrderLine $orderLine): bool
{
return $orderLine->order->isPlaced()
&& $orderLine->type === 'digital'
&& $orderLine->total->value > 0;
}

public function requiresPhysicalReturn(OrderLine $orderLine): bool
{
return $orderLine->order->isPlaced() && $orderLine->type === 'physical';
}

public function createsEntitlement(OrderLine $orderLine): bool
{
return $orderLine->type === 'digital';
}

public function supportsEndOfTerm(OrderLine $orderLine): bool
{
return false;
}

public function allowsAccountCredit(OrderLine $orderLine): bool
{
return $orderLine->order->isPlaced()
&& in_array($orderLine->type, ['physical', 'digital'], true)
&& $orderLine->total->value > 0;
}
}
20 changes: 20 additions & 0 deletions packages/core/src/Base/OrderLineCapabilitiesInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Lunar\Base;

use Lunar\Models\OrderLine;

interface OrderLineCapabilitiesInterface
{
public function isRefundable(OrderLine $orderLine): bool;

public function isCancellable(OrderLine $orderLine): bool;

public function requiresPhysicalReturn(OrderLine $orderLine): bool;

public function createsEntitlement(OrderLine $orderLine): bool;

public function supportsEndOfTerm(OrderLine $orderLine): bool;

public function allowsAccountCredit(OrderLine $orderLine): bool;
}
16 changes: 16 additions & 0 deletions packages/core/src/Base/RefundAuthorization.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Lunar\Base;

use Lunar\Base\DataTransferObjects\RefundAuthorizationResult;
use Lunar\Base\DataTransferObjects\RefundRequest;

class RefundAuthorization implements RefundAuthorizationInterface
{
public function authorize(RefundRequest $refundRequest): RefundAuthorizationResult
{
return new RefundAuthorizationResult(
authorized: true,
);
}
}
11 changes: 11 additions & 0 deletions packages/core/src/Base/RefundAuthorizationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Lunar\Base;

use Lunar\Base\DataTransferObjects\RefundAuthorizationResult;
use Lunar\Base\DataTransferObjects\RefundRequest;

interface RefundAuthorizationInterface
{
public function authorize(RefundRequest $refundRequest): RefundAuthorizationResult;
}
27 changes: 27 additions & 0 deletions packages/core/src/Events/OrderPaid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Lunar\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Lunar\Base\DataTransferObjects\PaymentAuthorize;
use Lunar\Models\Order;
use Lunar\Models\Transaction;

class OrderPaid
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* @param array<string, mixed> $meta
*/
public function __construct(
public Order $order,
public PaymentAuthorize $paymentAuthorize,
public ?Transaction $transaction = null,
public array $meta = [],
) {
//
}
}
21 changes: 21 additions & 0 deletions packages/core/src/Events/RefundCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Lunar\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Lunar\Base\DataTransferObjects\PaymentRefund;
use Lunar\Base\DataTransferObjects\RefundRequest;

class RefundCompleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public function __construct(
public RefundRequest $refundRequest,
public PaymentRefund $paymentRefund,
) {
//
}
}
Loading
Loading