bavix / laravel-wallet

It's easy to work with a virtual wallet
https://bavix.github.io/laravel-wallet/
MIT License
1.12k stars 223 forks source link

Refunding Item with N - number of purchase #902

Closed Joemires closed 6 months ago

Joemires commented 6 months ago

Hello I have a concern, for instance a user purchased a certain item for N number of times If I want to effect a refund for a certain purchase, how can I go about this. I see the refund interface accept an item, but remember a user can purchase the item for N numbers

(bool) $user->paid($item); // bool(true)
(bool) $user->refund($item); // bool(true)

Is there a way to refund for a certain item transfer?

rez1dent3 commented 6 months ago

Hello. The method returns only one product.

(bool)$user->paid($item); // bool(true). First
(bool)$user->paid($item); // bool(true). Second

(bool)$user->refund($item); // bool(true). Only one item will be returned

To return N-items, you need to call N times.

Unfortunately, you cannot choose the exact purchase yourself. Perhaps someday such an opportunity will arise.

Joemires commented 6 months ago

Like I have a product called Apple And a user made a purchase of Apple 2 times with 2 transfers (1 & 2) What I'm asking is how do I refund the 2nd purchase?

rez1dent3 commented 6 months ago

What I'm asking is how do I refund the 2nd purchase?

Only one refund of the product will be made. There is no way to select a specific transfer.

If you call the refund method twice, both will be refunded.

rez1dent3 commented 6 months ago

That is:

// Once upon a time

(bool)$user->paid($item); // bool(true). First
(bool)$user->paid($item); // bool(true). Second

// Later...

$cart = app(Cart::class)->withItem($item, 2);
$user->wallet->refundCart($cart); // success 

// Later...

(bool)$user->safeRefund($item); // bool(false)
Joemires commented 6 months ago

From your explanation if I got it right, If I intend to refund only the second purchase made by a user on the item model


// Once upon a time
$transfer_1 = $user->pay($item);
$transfer_2 = $user->pay($item);

// Later...
$item = Item::first();
$cart = app(Cart::class)->withItem($item, 2); # 2 been the 2nd payment?
Joemires commented 6 months ago

Or I need to call

(bool) $user->paid($item); // bool(true). First
(bool) $user->paid($item); // bool(true). Second

Which loops to the exact purchase I want to refund before I can call the

(bool) $user->refund($item); // bool(true). Only one item will be returned
rez1dent3 commented 6 months ago

Not like that. The example with a shopping cart is a multi-refund, i.e. a refund for both items at once.

It is not possible to refund money for an item by index using a boxed solution. You will have to develop this solution yourself

Joemires commented 6 months ago

Alright thank you for this 👏🏾, I have created a workaround for this

I am currently making using of this package in a vehicle rental system, below is how I was able to make refunding possible for payments done after payment via $user->pay($item) transfer is a relation I had declared, I store it on every payment done to that order

<?php

namespace App\Models;

use Bavix\Wallet\Models\Transfer;

class Order extends Model
{
   ...
    public function refund()
    {
        $user = $this->user;
        $item = $this->vehicle;

        $status = [Transfer::STATUS_PAID];

        $cart = app(\Bavix\Wallet\Objects\Cart::class);
        $cart = $cart->withItem($item);

        $basketDto = $cart->getBasketDto();

        $eagerService = app(\Bavix\Wallet\Services\EagerLoaderServiceInterface::class);
        $eagerService->loadWalletsByBasket($user, $basketDto);

        $transfers = $this->transfers()
            ->with(['deposit', 'withdraw.wallet'])
            ->where('to_id', $item->wallet->getKey())
            ->whereIn('status', $status)
            ->get();

        $transferIds = $transfers->pluck('id')->toArray();

        $castService = app(\Bavix\Wallet\Services\CastServiceInterface::class);
        $prepareService = app(\Bavix\Wallet\Services\PrepareServiceInterface::class);
        $assistantService = app(\Bavix\Wallet\Services\AssistantServiceInterface::class);

        foreach($transfers as $transfer) {
            $objects[] = $prepareService->transferExtraLazy(
                $item,
                $castService->getWallet($item),
                $transfer->withdraw->wallet,
                $transfer->withdraw->wallet,
                Transfer::STATUS_TRANSFER,
                $transfer->deposit->amount,
                $assistantService->getMeta($basketDto, $item)
            );
        }

        assert($objects !== []);

        $transferService = app(\Bavix\Wallet\Services\TransferServiceInterface::class);

        $transferService->apply($objects);

        return $transferService->updateStatusByIds(Transfer::STATUS_REFUND, $transferIds);
    }
}

I can create a permanent and best approach for refunding an exact transfer for this package if you feel its okay

rez1dent3 commented 6 months ago

But you are not returning a specific transaction, but everything. Or is that what you need?

If you need to return everything, then you did it right. Just wrap it in AtomicServiceInterface to disable working with the wallet during data updating (race condition).

Joemires commented 6 months ago

Yes thank you for specifying the wrapping in AtomicServiceInterface I'm not returning just everything, I'm only returning all transfer pertaining to a certain order been that items can have multiple order with multiple transfer, the idea is to bound each transfer to a its respective order and refund for just that order

I wish there was a clearer way to explain 😐

Joemires commented 6 months ago

Thank you very much for the support tho, we can go on to closing the ticket 🫡