nycorp / finance
to handle wallet
1.2.8
2025-04-16 00:52 UTC
Requires
- ext-json: *
Requires (Dev)
- dohone/payment: dev-master
- nycorp/lite-api: ^1.1
- orchestra/testbench: ^9.0
README
Features
- make deposit
- make withdrawal
- Set account threshold
- Listen to success transaction
- 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