nycorp/finance

to handle wallet

1.2.8 2025-04-16 00:52 UTC

This package is auto-updated.

Last update: 2025-05-13 14:07:54 UTC


README

Latest Version on Packagist Total Downloads

Features

  1. make deposit
  2. make withdrawal
  3. Set account threshold
  4. Listen to success transaction
  5. Custom payment provider

Installation (with Composer)

composer require nycorp/finance

Then

composer require nycorp/lite-api

Configuration

Publish migration file and config

php artisan vendor:publish --provider="NYCorp\Finance\FinanceServiceProvider"

Run migration

php artisan migrate

Usage

Add Finance Account Trait to any model

use FinanceAccountTrait;

(Optional) Set Threshold the min balance for an account

return User::first()->setThreshold(100) 

For deposit

return User::first()->deposit(DefaultPaymentProvider::getId(), 12, $description)

Get balance

return User::first()->balance

For withdrawal

return User::first()->withdrawal(DefaultPaymentProvider::getId(), 12, $description)

Customize the canWithdraw in the model (Optional)

public function canWithdraw(float $amount, bool $forceBalanceCalculation): bool
    {
        //EX : Set to true because the account is debited only when the service is consumed
        return true;
    }

Set model currency by adding this method in the model

public function getCurrency()
    {
        // Implement your logic to get currency here the default value is set in the finance config file
        return \NYCorp\Finance\Http\Core\ConfigReader::getDefaultCurrency();
    }

Check if user can make transaction if his finance account is not disabled

return Company::first()->canMakeTransaction() ? Company::first()->withdrawal(DefaultPaymentProvider::getId(), 12, $description) : 'Your account is disabled';

Check if user can make transaction if his finance account has enough balance base on threshold use true to force balance calculation

return Company::first()->canWithdraw(100,true) ? Company::first()->withdrawal(DefaultPaymentProvider::getId(), 12, $description) : 'Insufficient balance';

To listen to success transaction

php artisan make:listener SuccessFinanceTransactionListener --event=FinanceTransactionSuccessEvent
 /**
     * Handle the event.
     */
    public function handle(FinanceTransactionSuccessEvent $event): void
    {
        # In case you handle multiple model
        match (get_class($event->model)) {
            Model1::class => $this->handleModel1($event),
            Model1::class => $this->hanleModel2($event),
            default => static fn() => Log::warning("FinanceTransactionSuccessEvent Model not handle")
        };
    }

Custom Provider

use NYCorp\Finance\Http\Payment\PaymentProviderGateway;

class CustomPaymentProvider extends PaymentProviderGateway
{

    public static function getName(): string
    {
        return 'CustomProvider';
    }

    public function deposit(FinanceTransaction $transaction): PaymentProviderGateway
    {
        #Your custom logic here
        
        //use this url for callback
        $callbackUrl = self::depositNotificationUrl(self::getId());

        $response = Http::post('https://api-checkout/v2/payment', $formData);
        $this->successful = $response->successful();
        $this->message = $response->json('description');
        $this->response = new FinanceProviderGatewayResponse($transaction, $this->getWallet($transaction)->id, $response->body(), false, $response->json('data.payment_url'));
        return $this;
    }

    private function normalizeAmount($amount, string $id): int
    {
        return ceil($amount * Cache::get($id, 655));
    }

    public static function getId(): string
    {
        return 'MY_PROVIDER_ID';
    }

    public function withdrawal(FinanceTransaction $transaction): PaymentProviderGateway
    {
        #Your custom logic here
        
        //use this url for callback
        $callbackUrl = self::depositNotificationUrl(self::getId());

        $response = Http::post('https://api-checkout/v2/payment', $formData);
        $this->successful = $response->successful();
        $this->message = $response->json('description');
        $this->response = new FinanceProviderGatewayResponse($transaction, $this->getWallet($transaction)->id, $response->body(), false, $response->json('data.payment_url'));
        return $this;
    }

    public function onDepositSuccess(Request $request): PaymentProviderGateway
    {
        Log::debug("**Payment** | " . self::getId() . ": callback " . $request->cpm_trans_id, $request->all());

        if ($this->findTransaction($request, 'cpm_trans_id') === null) {
            return $this;
        }

        return $this;
    }

    public function onWithdrawalSuccess(Request $request): PaymentProviderGateway
    {
        return $this;
    }

    protected function findTransaction(Request $request, string $key): ?FinanceTransaction
    {
        $transactionId = Arr::get($request->all(), $key);
        $this->transaction = FinanceTransaction::find($transactionId);
        if (empty($this->transaction)) {
            $id = self::getId();
            Log::error("**Payment** | $id : order not found $transactionId");
            $this->message = "Order not found !";
            $this->successful = false;
            $this->response = new FinanceProviderGatewayResponse(null, null, $request->all());
        }
        return $this->transaction;
    }
}

Register provider in config

return [
    'default_payment_provider_id' => 'LOCAL_PROVIDER',
    'default_payment_provider_name' => env('APP_NAME')."'s Local Provider",
    'default_threshold' => 0, #minimum account balance applied to all model
    'default_currency' => 'USD',
    'refresh_account_ttl' => 60, #in minute
    'payment_providers' => [
        \NYCorp\Finance\Http\Payment\DefaultPaymentProvider::class,
        \NYCorp\Finance\Http\Payment\CustomPaymentProvider::class,
    ],

    'force_balance_check_min_amount' => 5000,
    'prefix' => 'finance',
    'middleware' => ['api'],


    'user_email_field' => "email",
    'finance_account_id_parameter' => "finance_account_id",
];

Response handle

$response = \Nycorp\LiteApi\Response\DefResponse::parse(User::first()->withdrawal(DefaultPaymentProvider::getId(), 12, $description));
$response->getBody(); // get the body of the response
$response->isSuccess(); // get the success state as boolean
$response->getMessage(); // get response message