Default Wallet
For simple projects when there is no need for multiple wallets.
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index f2002d21d..000000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: build docs - -on: - pull_request: - branches: [ master ] - -jobs: - build_pages: - permissions: - contents: write - uses: bavix/.github/.github/workflows/compile-assets.yml@0.3.0 - secrets: inherit diff --git a/docs/.vitepress/.gitignore b/docs/.vitepress/.gitignore index 6e25fa8f1..c670ee771 100644 --- a/docs/.vitepress/.gitignore +++ b/docs/.vitepress/.gitignore @@ -1 +1,2 @@ -cache/ \ No newline at end of file +cache/ +dist/ diff --git a/docs/.vitepress/dist/404.html b/docs/.vitepress/dist/404.html deleted file mode 100644 index 4312446c9..000000000 --- a/docs/.vitepress/dist/404.html +++ /dev/null @@ -1,24 +0,0 @@ - - -
- - -=0)c=r.activeElement;else{var f=i.tabbableGroups[0],p=f&&f.firstTabbableNode;c=p||h("fallbackFocus")}if(!c)throw new Error("Your focus-trap needs to have at least one focusable element");return c},v=function(){if(i.containerGroups=i.containers.map(function(c){var f=br(c,a.tabbableOptions),p=wr(c,a.tabbableOptions),C=f.length>0?f[0]:void 0,I=f.length>0?f[f.length-1]:void 0,M=p.find(function(m){return le(m)}),P=p.slice().reverse().find(function(m){return le(m)}),z=!!f.find(function(m){return se(m)>0});return{container:c,tabbableNodes:f,focusableNodes:p,posTabIndexesFound:z,firstTabbableNode:C,lastTabbableNode:I,firstDomTabbableNode:M,lastDomTabbableNode:P,nextTabbableNode:function(x){var $=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,K=f.indexOf(x);return K<0?$?p.slice(p.indexOf(x)+1).find(function(Q){return le(Q)}):p.slice(0,p.indexOf(x)).reverse().find(function(Q){return le(Q)}):f[K+($?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(c){return c.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(c){return c.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},y=function w(c){var f=c.activeElement;if(f)return f.shadowRoot&&f.shadowRoot.activeElement!==null?w(f.shadowRoot):f},b=function w(c){if(c!==!1&&c!==y(document)){if(!c||!c.focus){w(d());return}c.focus({preventScroll:!!a.preventScroll}),i.mostRecentlyFocusedNode=c,Ar(c)&&c.select()}},E=function(c){var f=h("setReturnFocus",c);return f||(f===!1?!1:c)},g=function(c){var f=c.target,p=c.event,C=c.isBackward,I=C===void 0?!1:C;f=f||Ae(p),v();var M=null;if(i.tabbableGroups.length>0){var P=l(f,p),z=P>=0?i.containerGroups[P]:void 0;if(P<0)I?M=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:M=i.tabbableGroups[0].firstTabbableNode;else if(I){var m=ft(i.tabbableGroups,function(B){var U=B.firstTabbableNode;return f===U});if(m<0&&(z.container===f||_e(f,a.tabbableOptions)&&!le(f,a.tabbableOptions)&&!z.nextTabbableNode(f,!1))&&(m=P),m>=0){var x=m===0?i.tabbableGroups.length-1:m-1,$=i.tabbableGroups[x];M=se(f)>=0?$.lastTabbableNode:$.lastDomTabbableNode}else ge(p)||(M=z.nextTabbableNode(f,!1))}else{var K=ft(i.tabbableGroups,function(B){var U=B.lastTabbableNode;return f===U});if(K<0&&(z.container===f||_e(f,a.tabbableOptions)&&!le(f,a.tabbableOptions)&&!z.nextTabbableNode(f))&&(K=P),K>=0){var Q=K===i.tabbableGroups.length-1?0:K+1,q=i.tabbableGroups[Q];M=se(f)>=0?q.firstTabbableNode:q.firstDomTabbableNode}else ge(p)||(M=z.nextTabbableNode(f))}}else M=h("fallbackFocus");return M},S=function(c){var f=Ae(c);if(!(l(f,c)>=0)){if(ye(a.clickOutsideDeactivates,c)){s.deactivate({returnFocus:a.returnFocusOnDeactivate});return}ye(a.allowOutsideClick,c)||c.preventDefault()}},T=function(c){var f=Ae(c),p=l(f,c)>=0;if(p||f instanceof Document)p&&(i.mostRecentlyFocusedNode=f);else{c.stopImmediatePropagation();var C,I=!0;if(i.mostRecentlyFocusedNode)if(se(i.mostRecentlyFocusedNode)>0){var M=l(i.mostRecentlyFocusedNode),P=i.containerGroups[M].tabbableNodes;if(P.length>0){var z=P.findIndex(function(m){return m===i.mostRecentlyFocusedNode});z>=0&&(a.isKeyForward(i.recentNavEvent)?z+1
j)for(;E<=B;)Le(u[E],b,S,!0),E++;else{const q=E,z=E,ee=new Map;for(E=z;E<=j;E++){const be=d[E]=R?Ge(d[E]):Ae(d[E]);be.key!=null&&ee.set(be.key,E)}let Q,ae=0;const Te=j-z+1;let ht=!1,zr=0;const St=new Array(Te);for(E=0;E {const{el:S,type:O,transition:x,children:R,shapeFlag:E}=u;if(E&6){tt(u.component.subTree,d,g,v);return}if(E&128){u.suspense.move(d,g,v);return}if(E&64){O.move(u,d,g,dt);return}if(O===_e){r(S,d,g);for(let B=0;B The recommended installation method is using Composer. In your project root just run: We need a simple model with the ability to work multi-wallets. Let's create wallets with currency: Find wallets and exchange from one to another. It's simple! Using uuid greatly reduces package performance. We recommend using int. Often there is a need to store an identifier in uuid/ulid. Since version 9.0 laravel-wallet supports string identifiers, you only need to perform the migration. To simplify the process, you can use a ready-made package. Attention! It will not work to use UUID instead of ID in wallet models; there is a special uuid field for this. The recommended installation method is using Composer. In your project root just run: Now you need to migrate! After migration, you can use the UUID in your models. You can find implementation examples in the package tests: https://github.com/bavix/laravel-wallet-uuid/tree/master/tests It's simple! The idea is based on the division into teams for creating wallets, transactions, etc. The creation of a wallet can be accelerated if the client "generates a wallet himself". Add the You receive requests to create a wallet on the backend, and you create them asynchronously. UUID4 is generated on the client side and the client already knows it. You will not be able to create two wallets with one uuid, because the column in the database is unique. The user no longer needs to wait for the creation of a wallet, it is enough to know the uuid. You get the most stable application. It's simple! Working with atomic wallet operations. Before you start working with atomicity, you need to study the "Race Condition" section and configure the lock. Sometimes it is necessary to apply actions to the user and the wallet atomically. For example, you want to raise an ad in the search and withdraw money from your wallet. You need an Atomic Service Interface. What's going on here? We block the wallet and raise the ad in the transaction (yes, atomic immediately starts the transaction - this is the main difference from LockServiceInterface). We raise the ad and deduct the amount from the wallet. If there are not enough funds to raise the ad, the error will complete the atomic operation and the transaction will roll back, and the lock on the wallet will be removed. There is also an opportunity to block a lot of wallets. The operation is expensive, it generates N requests to the lock service. Maybe I'll optimize it in the future, but that's not for sure. For example, we need to debit from two wallets at the same time. Then let's use the "blocks" method. In this case, we blocked both wallets and started the process of debiting funds. Debiting from both wallets will be considered a successful operation. If there are not enough funds on some wallet, the operation is canceled. It's simple! A common issue in the issue is about race conditions. If you have not yet imported the config into the project, then you need to do this. Previously, there was a vacuum package, but now it is a part of the core. You just need to configure the lock service and the cache service in the package configuration To enable the fight against race conditions, you need to select a provider that supports work with locks. I recommend There is a setting for storing the state of the wallet, I recommend choosing You need Redis is recommended but not required. You can choose whatever the framework offers you. It's simple! You definitely need to know the feature of transactions. The wallet is automatically blocked from the moment it is used until the end of the transaction. Therefore, it is necessary to use the wallet closer to the end of the transaction. Very important! Almost all wallet transactions are blocking. The point is that you need to minimize operations within transactions as much as possible. The longer the transaction, the longer the wallet lock. The maximum wallet blocking time is set in the configuration. The longer the transaction takes, the more likely it is to get a race for the wallet. It's simple! There are tasks when you urgently need to do something when the user's balance changes. A frequent case of transferring data via websockets to the front-end. Version 7.2 introduces an interface to which you can subscribe. This is done using standard Laravel methods. More information in the documentation. And then we create a listener. It's simple! Sometimes you want to modify the standard events of a package. This is done quite simply. Let's add broadcast support? We need to implement our event from the interface. The event is ready, but that's not all. Now you need to implement your assembler class, which will create an event inside the package. Next, go to the package settings (wallet.php). We change the event to a new one. Then everything is the same as with the standard events of the package. And then we create a listener. It's simple! The events are similar to the events for updating the balance, only for the creation of a wallet. A frequent case of transferring data via websockets to the front-end. Version 9.1 introduces an interface to which you can subscribe. This is done using standard Laravel methods. More information in the documentation. And then we create a listener. It's simple! The events are similar to the events for updating the balance, only for the creation of a wallet. A frequent case of transferring data via websockets to the front-end. Version 7.3 introduces an interface to which you can subscribe. This is done using standard Laravel methods. More information in the documentation. And then we create a listener. It's simple! A deposit is a sum of money which is part of the full price of something, and which you pay when you agree to buy it. In this case, the Deposit is the replenishment of the wallet. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. Find user: As the user uses The balance is zero, which is what we expected. Wow! Transfer in our system are two well-known Deposit and Withdraw operations that are performed in one transaction. The transfer takes place between wallets. Find user: As the user uses The transfer will be from the first user to the second. It's simple! Check the user's balance. The transfer will be from the first user to the second. It's simple! When there is enough money in the account, you can transfer/withdraw it or buy something in the system. Since the currency is virtual, you can buy any services on your website. For example, priority in search results. Find user: As the user uses The balance is not empty, so you can withdraw funds. It's simple! Forced withdrawal is necessary for those cases when the user has no funds. For example, a fine for spam. There can be two situations: Sometimes you need to convert the balance to some format. A small and simple helper has appeared that will simplify the process a little. It's simple! Sometimes situations arise when there is a need to make multiple changes to wallets. For example, we need to change the balance of many wallets at once. For example, the system administrator accrues a bonus for participating in some promotion. Previously, the code would look like this: The code is working and everything works correctly. But what happens under the hood? Nothing good. For 5 users it will look like this. Since the operations inside an atomic operation can depend on each other, we will not be able to combine insert queries into a batch. But there is an opportunity to reduce the number of update queries and improve application performance out of the blue. After small things, the situation will look like this. As you can see, things are getting better. Still, I would like to be able to tell the package that the changes are independent of each other. In this case, the package will be able to collapse all insert queries into a single query and insert in a batch. Here new api handles can help us: Let's use the API handle. And now look at the result and it is impressive. But it is worth noting that these are highly efficient api handles and they do not check the balance of the wallet before making changes. If you need it, then you have to do something like this. In version 10.x, it became possible to create transactions with a given uuid (generate on the client side). The main thing is to keep uniqueness. It's simple! If you need multiple transfers between wallets, you can use a high-performance handle. It is worth remembering that the pen does not check the balance of the wallet before transferring, you need to take care of this yourself. Previously, you would have written the following code: This would lead to the generation of a huge number of requests to the database and cache, because. the package does not know that the response from The package will optimize queries and execute them in a single transaction. I strongly advise against creating large packs, because. this can lead to a large increase in request queuing. In version 10.x, it became possible to create transactions&transfers with a given uuid (generate on the client side). The main thing is to keep uniqueness. It's simple! It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. The package is built on simple transactions: Consider an example: Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Proceed to purchase. When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem. To illustrate the N + 1 query problem, consider a Add the Now, let's retrieve all wallets and their users: This loop will execute 1 query to retrieve all of the users on the table, then another query for each user to retrieve the wallet. So, if we have 25 users, the code above would run 26 queries: 1 for the original user, and 25 additional queries to retrieve the wallet of each user. Thankfully, we can use eager loading to reduce this operation to just 2 queries. When querying, you may specify which relationships should be eager loaded using the with method: For this operation, only two queries will be executed. Add the Now we make transactions. You can get the float amount by accessing the Though this package is crafted to suit most of your needs by default, you can edit the configuration file to suit certain demands. Customize config/wallet.php: You can extend base Wallet model by creating a new class that extends App/Models/MyWallet.php: config/wallet.php: This same method above, can be used to extend the base You can change the default wallet decimal places, in wallet config file. This can be useful when working with fractional numbers. config/wallet.php: Please ask questions on the Github issues page. The recommended installation method is using Composer. In your project root just run: Ensure that you’ve set up your project to autoload Composer-installed packages. Sometimes it is useful... Publish the migrations with this artisan command: You can publish the config file with this artisan command: After installing the package, you can proceed to use it or configure it to suit your needs. Replace Replace Replace Replace Replace path Replace path Replace path Add the Add method Replace If you are using php 7.1, then version 4.0 is not available to you. You need to update php. Removed support for older versions of You must add the argument Your code on 3.x: Your code on 4.x: By updating the library from 4.x to 5.x you lose strong typing. This solution was necessary to support APM (Arbitrary Precision Mathematics). In your goods: Your code on 4.x: Your code on 5.x: In the exchange rate processing service: Your code on 4.x: Your code on 5.x: Go to Removing unnecessary code. Replace your math class ($mathClass) with Your code on 5.x: Your code on 6.x: You need to update to the latest version for all migrations to appear. Update The Then return your settings. The package configuration has changed globally and there is no point in describing each key 🔑 UUID for wallet The uuid field has been added to the wallet table, which is now actively used. If you have a highload, then I recommend that you add the field yourself and mark the migration (UpdateWalletsUuidTable) completed. If you have mysql, it is better to do this via pt-online-schema-change. If you have a small project and a small wallet base, then the migration will be applied automatically. That's it, you can use all 7.x functions to the fullest. The contract did not change globally, added more stringency and toned down the performance of the package. On a basket of 150 products, the acceleration is a whopping 24x. All changes can be found in the pull request. The kernel has changed globally, I would not recommend switching to version 7.0.0 at the very beginning, there may be bugs. I advise you should at least 7.0.1. Nothing needs to be done. Replace Cart methods now support fluent-dto. It is necessary to replace the old code with a new one, for example: The logic of storing transfers between accounts has changed. Previously, money could be credited to the user directly, but starting from version nine, all transactions go strictly between wallets. Thanks to this approach, finally, there will be full-fledged work with uuid identifiers in the project. To migrate to the correct structure, you need to run the command: If the command fails, then the command must be restarted. Continue until the command starts executing immediately (no bad entries left). The product has been divided into two interfaces: The old Product interface should be replaced with one of these. Replace You can create an unlimited number of wallets, but the Add the Find user: Create a new wallet. Is it possible to use the default wallet and multi-wallets at the same time? Yes. How to get the default wallet? It's simple! Often developers ask me about the Let's take a look at a livelier code example: It's simple! Transfer in our system are two well-known Deposit and Withdraw operations that are performed in one transaction. The transfer takes place between wallets. Prepare the model, add the Find user: Create new wallets for users. The transfer will be from the first user to the last. It's simple! Check the user's balance. The transfer will be from the first user to the second. It worked! Check the user's balance. We will execute the transfer, but without confirmation of the withdrawal of funds. It's simple! Buying goods one at a time is, of course, good. But it’s more convenient to buy in a pack, right? In laravel wallet you can buy a basket of goods at once. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user and check the balance. Let's start shopping. It's simple! Commissions are part of purchasing goods. The commission is debited from the buyer's account and is not credited for the product. This amount is lost along the way. The commission amount can always be found in the fee column. Add the The trait Add the Find the user and check the balance. Find the goods and check the cost. The user can buy a product, buy... Add interface Find the user and check the balance. Find the goods and check the cost. The user can buy a product, buy... Find the user and check the balance. Find the goods and check the cost. The user can buy a product, buy... It's simple! Gifts are a purchase with someone else's wallet attached. The peculiarity of gifts is that when the gift is returned, the money is returned to the person who bought it, and not to the person to whom it was given. This functionality is usually used to reward users by the administration. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user's and check the balance. One user wants to give a gift to another. Find the product. The first user buys the product and gives it. If the product uses the It's simple! Payments as part of e-commerce are an important part. This section is about payment for goods. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user and check the balance. Find the goods and check the cost. Purchase! It's simple! Payments as part of e-commerce are an important part. This section is about payment for goods. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user and check the balance. Find the goods and check the cost. The user can buy a product, buy... What happens if the user does not have the funds? The same as with the withdrawal. The question arises, how do you know that the product is purchased? To not write It's simple! The architecture of laravel wallet is designed in such a way that when purchasing goods, wallets are created and all money is credited to them, but it happens that this is not necessary. In this case, this functionality will help. You specify the wallet for depositing funds and the buyer will transfer money to this wallet when purchasing a product. This is convenient for marketplaces. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user and check the balance. Find the goods and check the cost. The user can buy a product, buy... It's simple! We already know how to buy, but what about returns? But they exist and you can use them. Add the The trait Add the Starting from version 9.x there are two product interfaces: An example with an unlimited number of products: Example with a limited number of products: I do not recommend using the limited interface when working with a shopping cart. If you are working with a shopping cart, then you should override the Find the user and check the balance. Find the goods and check the balance. Return of funds! It's simple! Sometimes you need to cancel a confirmed transaction. For example, money was received or debited by mistake. You can reset the confirmation of a specific transaction. Add the You can only cancel the transaction with the wallet you paid with. Created a transaction, and after resetting its confirmation. It's simple! There are situations when it is necessary to create a transaction without crediting to the wallet or debiting. Laravel-wallet has such a mode of unconfirmed transactions. You create a transaction without confirmation, and a little later you confirm it. Add the You can only confirm the transaction with the wallet you paid with. Sometimes you need to create an operation and confirm its field. That is what this trey does. It's simple! If you need the ability to have wallets have a credit limit, then this functionality is for you. The functionality does nothing, it only allows you not to use "force" for most of the operations within the credit limit. You should write the logic for collecting interest, notifications on debts, etc. By default, the credit limit is zero. An example of working with a credit limit: For multi-wallets when creating: It's simple! A deposit is a sum of money which is part of the full price of something, and which you pay when you agree to buy it. In this case, the Deposit is the replenishment of the wallet. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. Find user: As the user uses The balance is zero, which is what we expected. Put it on his 10 cents account. Wow! The balance is 10 cents, the money is credited. Everyone’s tasks are different and with the help of this functionality you can add exchange rates to your wallets. The wallet currency is set via meta. Example: Service for working with currencies you need to write yourself or use library. We will write a simple service. We will take the data from the array, and not from the database. The service you wrote must be registered, this is done in the file Create two wallets. We replenish the ruble wallet with 100 rubles. We will exchange rubles into dollars. Unfortunately, the world is not perfect. You will not get back your 100 rubles. Due to conversion and mathematical rounding, you lost 62 kopecks. You have 99 rubles 38 kopecks left. It's simple! There are situations when you create a lot of unconfirmed operations, and then abruptly confirm everything. In this case, the user's balance will not change. You must be forced to refresh the balance. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. Let's say the user's balance And he has unconfirmed transactions. Confirm all transactions. Refresh the balance. It's simple! Transfer in our system are two well-known Deposit and Withdraw operations that are performed in one transaction. The transfer takes place between wallets. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. Find user: As the user uses The transfer will be from the first user to the second. It's simple! Check the user's balance. The transfer will be from the first user to the second. It's simple! When there is enough money in the account, you can transfer/withdraw it or buy something in the system. Since the currency is virtual, you can buy any services on your website. For example, priority in search results. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. Find user: As the user uses The balance is not empty, so you can withdraw funds. It's simple! Forced withdrawal is necessary for those cases when the user has no funds. For example, a fine for spam. There can be two situations: The recommended installation method is using Composer. In your project root just run: Ensure that you’ve set up your project to autoload Composer-installed packages. When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem. To illustrate the N + 1 query problem, consider a Add the Now, let's retrieve all wallets and their users: This loop will execute 1 query to retrieve all of the users on the table, then another query for each user to retrieve the wallet. So, if we have 25 users, the code above would run 26 queries: 1 for the original user, and 25 additional queries to retrieve the wallet of each user. Thankfully, we can use eager loading to reduce this operation to just 2 queries. When querying, you may specify which relationships should be eager loaded using the with method: For this operation, only two queries will be executed. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet. It is necessary to expand the model that will have the wallet. This is done in two stages: Let's get started. The model is prepared to work with a wallet.Laravel Wallet Swap
Composer
composer req bavix/laravel-wallet-swap
User model
use Bavix\\Wallet\\Interfaces\\Wallet;
-use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Traits\\HasWallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet, HasWallets;
-}
Simple example
$usd = $user->createWallet([
- 'name' => 'My Dollars',
- 'slug' => 'usd',
- 'meta' => ['currency' => 'USD'],
-]);
-
-$rub = $user->createWallet([
- 'name' => 'My Ruble',
- 'slug' => 'rub',
- 'meta' => ['currency' => 'RUB'],
-]);
$rub = $user->getWallet('rub');
-$usd = $user->getWallet('usd');
-
-$usd->balance; // 200
-$rub->balance; // 0
-
-$usd->exchange($rub, 10);
-$usd->balance; // 190
-$rub->balance; // 622
Laravel Wallet UUID
Composer
composer req bavix/laravel-wallet-uuid
Asynchronous wallet creation
User Model
HasWallet
, HasWallets
trait's and Wallet
interface to model.use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet, HasWallets;
-}
Action Handler
use Illuminate\\Http\\Request;
-use Illuminate\\Http\\Response;
-use Illuminate\\Support\\Facades\\Response as ResponseFactory;
-...
-
-public function __invoke(User $user, Request $request): Response
-{
- $name = $request->get('wallet_name');
- $uuid = $request->get('wallet_uuid');
-
- $message = new CreateWalletCommandMessage($user, $name, $uuid);
- dispatch($message);
-
- return ResponseFactory::json([], 202);
-}
Command Handler
public function __invoke(CreateWalletCommandMessage $message): void
-{
- $user = $message->getUser();
- $user->createWallet([
- 'uuid' => $message->getWalletUuid(),
- 'name' => $message->getWalletName(),
- ]);
-}
Atomic Service
use Bavix\\Wallet\\Services\\AtomicServiceInterface;
-
-app(AtomicServiceInterface::class)->block($wallet, function () use ($wallet, $entity) {
- $entity->increaseSales(); // update entity set sort_at=NOW() where id=123;
- $wallet->withdraw(100);
-});
use Bavix\\Wallet\\Services\\AtomicServiceInterface;
-
-app(AtomicServiceInterface::class)->blocks([$wallet1, $wallet2], function () use ($wallet1, $wallet2) {
- $wallet1->withdraw(100);
- $wallet2->withdraw(100);
-});
Race Condition
php artisan vendor:publish --tag=laravel-wallet-config
wallet.php
. /**
- * A system for dealing with race conditions.
- */
- 'lock' => [
- 'driver' => 'array',
- 'seconds' => 1,
- ],
redis
.redis
here too. /**
- * Storage of the state of the balance of wallets.
- */
- 'cache' => ['driver' => 'array'],
redis-server
and php-redis
.Transaction
use Illuminate\\Support\\Facades\\DB;
-
-DB::beginTransaction();
-$wallet->balanceInt; // now the wallet is blocked
-doingMagic(); // running for a long time.
-DB::commit(); // here will unlock the wallet
Tracking balance changes
use Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface;
-
-protected $listen = [
- BalanceUpdatedEventInterface::class => [
- MyBalanceUpdatedListener::class,
- ],
-];
use Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface;
-
-class MyBalanceUpdatedListener
-{
- public function handle(BalanceUpdatedEventInterface $event): void
- {
- // And then the implementation...
- }
-}
Customizing events
use Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface;
-use Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast;
-
-final class MyUpdatedEvent implements BalanceUpdatedEventInterface, ShouldBroadcast
-{
- public function __construct(
- private \\Bavix\\Wallet\\Models\\Wallet $wallet,
- private DateTimeImmutable $updatedAt,
- ) {}
-
- public function getWalletId(): int { return $this->wallet->getKey(); }
- public function getWalletUuid(): string { return $this->wallet->uuid; }
- public function getBalance(): string { return $this->wallet->balanceInt; }
- public function getUpdatedAt(): DateTimeImmutable { return $this->updatedAt; }
-
- public function broadcastOn(): array
- {
- return $this->wallet->getAttributes();
- }
-}
use Bavix\\Wallet\\Internal\\Assembler\\BalanceUpdatedEventAssemblerInterface;
-
-class MyUpdatedEventAssembler implements BalanceUpdatedEventAssemblerInterface
-{
- public function create(\\Bavix\\Wallet\\Models\\Wallet $wallet) : \\Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface
- {
- return new MyUpdatedEvent($wallet, new DateTimeImmutable());
- }
-}
'assemblers' => [
- 'balance_updated_event' => MyUpdatedEventAssembler::class,
- ],
use Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface;
-
-protected $listen = [
- BalanceUpdatedEventInterface::class => [
- MyBalanceUpdatedListener::class,
- ],
-];
use Bavix\\Wallet\\Internal\\Events\\BalanceUpdatedEventInterface;
-
-class MyBalanceUpdatedListener
-{
- public function handle(BalanceUpdatedEventInterface $event): void
- {
- // And then the implementation...
- }
-}
Tracking the creation of wallet transactions
use Bavix\\Wallet\\Internal\\Events\\TransactionCreatedEventInterface;
-
-protected $listen = [
- TransactionCreatedEventInterface::class => [
- MyWalletTransactionCreatedListener::class,
- ],
-];
use Bavix\\Wallet\\Internal\\Events\\TransactionCreatedEventInterface;
-
-class MyWalletTransactionCreatedListener
-{
- public function handle(TransactionCreatedEventInterface $event): void
- {
- // And then the implementation...
- }
-}
Tracking the creation of wallets
use Bavix\\Wallet\\Internal\\Events\\WalletCreatedEventInterface;
-
-protected $listen = [
- WalletCreatedEventInterface::class => [
- MyWalletCreatedListener::class,
- ],
-];
use Bavix\\Wallet\\Internal\\Events\\WalletCreatedEventInterface;
-
-class MyWalletCreatedListener
-{
- public function handle(WalletCreatedEventInterface $event): void
- {
- // And then the implementation...
- }
-}
Deposit float
User Model
Wallet
interface;HasWalletFloat
trait;use Bavix\\Wallet\\Traits\\HasWalletFloat;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWalletFloat;
-}
Make a Deposit
$user = User::first();
HasWalletFloat
, he will have balance
property. Check the user's balance.$user->balance; // 0
-$user->balanceInt; // 0
-$user->balanceFloatNum; // 0
$user->depositFloat(10.1);
-$user->balance; // 1010
-$user->balanceInt; // 1010
-$user->balanceFloatNum; // 10.1
Transfer
User Model
Example contract
$transfer = $user1->transferFloat(
- $user2,
- 5.11,
- new Extra(
- deposit: [
- 'type' => 'extra-deposit',
- ],
- withdraw: new Option(
- [
- 'type' => 'extra-withdraw',
- ],
- false // confirmed
- ),
- extra: [
- 'msg' => 'hello world',
- ],
- )
-);
Make a Transfer
$first = User::first();
-$last = User::orderBy('id', 'desc')->first(); // last user
-$first->getKey() !== $last->getKey(); // true
HasWalletFloat
, he will have balance
property. Check the user's balance.$fist->balanceFloatNum; // 100.00
-$last->balanceFloatNum; // 0
$first->transferFloat($last, 5);
-$first->balanceFloatNum; // 95
-$last->balanceFloatNum; // 5
Force Transfer
$first->balanceFloatNum; // 100
-$last->balanceFloatNum; // 0
$first->forceTransferFloat($last, 500);
-$first->balanceFloatNum; // -400
-$last->balanceFloatNum; // 500
Withdraw
User Model
Make a Withdraw
$user = User::first();
HasWalletFloat
, he will have balance
property. Check the user's balance.$user->balance; // 10000
-$user->balanceInt; // 10000
-$user->balanceFloatNum; // 100.00
$user->withdrawFloat(10);
-$user->balance; // 9000
-$user->balanceInt; // 9000
-$user->balanceFloatNum; // 90.00
Force Withdraw
$user->balanceFloatNum; // 90.00
-$user->forceWithdrawFloat(101);
-$user->balanceFloatNum; // -11.00
And what will happen if the money is not enough?
`,18),n=[h];function l(p,r,k,d,o,c){return i(),a("div",null,n)}const u=s(t,[["render",l]]);export{g as __pageData,u as default};
diff --git a/docs/.vitepress/dist/assets/guide_fractional_withdraw.md.B7oZ_OTi.lean.js b/docs/.vitepress/dist/assets/guide_fractional_withdraw.md.B7oZ_OTi.lean.js
deleted file mode 100644
index cbd789c6c..000000000
--- a/docs/.vitepress/dist/assets/guide_fractional_withdraw.md.B7oZ_OTi.lean.js
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,c as a,o as i,a3 as e}from"./chunks/framework.OdeEVNy0.js";const g=JSON.parse('{"title":"Withdraw","description":"","frontmatter":{},"headers":[],"relativePath":"guide/fractional/withdraw.md","filePath":"guide/fractional/withdraw.md"}'),t={name:"guide/fractional/withdraw.md"},h=e("",18),n=[h];function l(p,r,k,d,o,c){return i(),a("div",null,n)}const u=s(t,[["render",l]]);export{g as __pageData,u as default};
diff --git a/docs/.vitepress/dist/assets/guide_helpers_formatter.md.D75UEgM0.js b/docs/.vitepress/dist/assets/guide_helpers_formatter.md.D75UEgM0.js
deleted file mode 100644
index e16927607..000000000
--- a/docs/.vitepress/dist/assets/guide_helpers_formatter.md.D75UEgM0.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import{_ as s,c as i,o as a,a3 as t}from"./chunks/framework.OdeEVNy0.js";const c=JSON.parse('{"title":"Formatter","description":"","frontmatter":{},"headers":[],"relativePath":"guide/helpers/formatter.md","filePath":"guide/helpers/formatter.md"}'),h={name:"guide/helpers/formatter.md"},e=t(`Bavix\\Wallet\\Exceptions\\BalanceIsEmpty
Bavix\\Wallet\\Exceptions\\InsufficientFunds
Formatter
To fractional numbers
app(FormatterServiceInterface::class)->floatValue('12345', 2); // 123.45
-app(FormatterServiceInterface::class)->floatValue('12345', 3); // 12.345
To whole numbers
app(FormatterServiceInterface::class)->intValue('12.345', 3); // 12345
-app(FormatterServiceInterface::class)->intValue('123.45', 2); // 12345
Batch Transactions
use Bavix\\Wallet\\Services\\AtomicServiceInterface;
-
-app(AtomicServiceInterface::class)->blocks($wallets, function () use ($amount, $wallets) {
- foreach ($wallets as $wallet) {
- $wallet->deposit($amount);
- }
-});
// For multiple transactions.
-interface TransactionQueryHandlerInterface
-{
- /**
- * @param non-empty-array<TransactionQuery> $objects
- * @return non-empty-array<string, Transaction>
- * @throws ExceptionInterface
- */
- public function apply(array $objects): array;
-}
-
-// For multiple transfers of funds.
-interface TransferQueryHandlerInterface
-{
- /**
- * @param non-empty-array<TransferQuery> $objects
- * @return non-empty-array<string, Transfer>
- * @throws ExceptionInterface
- */
- public function apply(array $objects): array;
-}
use Bavix\\Wallet\\External\\Api\\TransactionQuery;
-use Bavix\\Wallet\\External\\Api\\TransactionQueryHandlerInterface;
-
-app(TransactionQueryHandlerInterface::class)->apply(
- array_map(
- static fn (Wallet $wallet) => TransactionQuery::createDeposit($wallet, $amount, null),
- $wallets
- )
-);
use Bavix\\Wallet\\External\\Api\\TransactionQuery;
-use Bavix\\Wallet\\External\\Api\\TransactionQueryHandlerInterface;
-use Bavix\\Wallet\\Services\\AtomicServiceInterface;
-use Bavix\\Wallet\\Services\\ConsistencyServiceInterface;
-
-app(AtomicServiceInterface::class)->blocks($wallets, function () use ($wallets, $amount) {
- foreach ($wallets as $wallet) {
- app(ConsistencyServiceInterface::class)->checkPotential($wallet, $amount);
- }
-
- app(TransactionQueryHandlerInterface::class)->apply(
- array_map(
- static fn (Wallet $wallet) => TransactionQuery::createWithdraw($wallet, $amount, null),
- $wallets
- )
- );
-});
use Bavix\\Wallet\\External\\Api\\TransactionQuery;
-
-// int version
-TransactionQuery::createDeposit($wallet, $amount, null, uuid: '5f7820d1-1e82-4d03-9414-05d0c44da9a1');
-TransactionQuery::createWithdraw($wallet, $amount, null, uuid: '6e87dbf2-7be7-48c2-b688-f46ba4e25786');
-
-// float version
-TransactionFloatQuery::createDeposit($wallet, $amountFloat, null, uuid: '5f7820d1-1e82-4d03-9414-05d0c44da9a1');
-TransactionFloatQuery::createWithdraw($wallet, $amountFloat, null, uuid: '6e87dbf2-7be7-48c2-b688-f46ba4e25786');
Batch Transfers
use Bavix\\Wallet\\Services\\AtomicServiceInterface;
-
-app(AtomicServiceInterface::class)->block($from, function () use ($amount, $from, $wallets) {
- foreach ($wallets as $wallet) {
- $from->forceTransfer($wallet, $amount);
- }
-});
forceTransfer
is not used by you at all inside AtomicService. Now, you can report it:use Bavix\\Wallet\\External\\Api\\TransferQuery;
-use Bavix\\Wallet\\External\\Api\\TransferQueryHandlerInterface;
-
-app(TransferQueryHandlerInterface::class)->apply(
- array_map(
- static fn (Wallet $wallet) => new TransferQuery($from, $wallet, $amount, null),
- $wallets
- )
-);
use Bavix\\Wallet\\External\\Api\\TransferQuery;
-
-// int version
-new TransferQuery($from, $wallet, $amount, new \\Bavix\\Wallet\\External\\Dto\\Extra(
- deposit: new \\Bavix\\Wallet\\External\\Dto\\Option(
- null,
- uuid: '71cecafe-da10-464f-9e00-c80437bb4c3e', // deposit transaction
- ),
- withdraw: new \\Bavix\\Wallet\\External\\Dto\\Option(
- null,
- uuid: '3805730b-39a1-419d-8715-0b7cc3f1ffc2', // withdraw transaction
- ),
- uuid: 'f8becf81-3993-43d7-81f1-7a725c72e976', // transfer uuid
- extra: ['info' => 'fast deposit'], // metadata in the table transfers
-));
-
-// float version
-new TransferFloatQuery($from, $wallet, $amountFlaot, new \\Bavix\\Wallet\\External\\Dto\\Extra(
- deposit: new \\Bavix\\Wallet\\External\\Dto\\Option(
- null,
- uuid: '71cecafe-da10-464f-9e00-c80437bb4c3e', // deposit transaction
- ),
- withdraw: new \\Bavix\\Wallet\\External\\Dto\\Option(
- null,
- uuid: '3805730b-39a1-419d-8715-0b7cc3f1ffc2', // withdraw transaction
- ),
- uuid: 'f8becf81-3993-43d7-81f1-7a725c72e976', // transfer uuid
- extra: ['info' => 'fast deposit'], // metadata in the table transfers
-));
Basic Usage
Simple Wallet
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Simple wallet transactions
$user = User::first();
-$user->balance; // 0
-
-$user->deposit(10);
-$user->balance; // 10
-
-$user->withdraw(1);
-$user->balance; // 9
-
-$user->forceWithdraw(200, ['description' => 'payment of taxes']);
-$user->balance; // -191
Purchases
CanPay
trait and Customer
interface to your User
model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.$user = User::first();
-$user->balance; // 100
-
-$item = Item::first();
-$user->pay($item); // If you do not have enough money, throw an exception
-var_dump($user->balance); // 0
-
-if ($user->safePay($item)) {
- // try to buy again )
-}
-
-var_dump((bool)$user->paid($item)); // bool(true)
-
-var_dump($user->refund($item)); // bool(true)
-var_dump((bool)$user->paid($item)); // bool(false)
Eager Loading
Wallet
model that is related to User
:HasWallet
trait and Wallet
interface to model.use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet; // public function wallet(): MorphOne...
-}
$users = User::all();
-
-foreach ($users as $user) {
- // echo $user->wallet->balance;
- echo $user->balance; // Abbreviated notation
-}
$users = User::with('wallet')->all();
-
-foreach ($users as $user) {
- // echo $user->wallet->balance;
- echo $user->balance; // Abbreviated notation
-}
How to work with fractional numbers?
HasWalletFloat
trait and WalletFloat
interface to model.use Bavix\\Wallet\\Traits\\HasWalletFloat;
-use Bavix\\Wallet\\Interfaces\\WalletFloat;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet, WalletFloat
-{
- use HasWalletFloat;
-}
$user = User::first();
-$user->balance; // 100
-$user->balanceFloat; // 1.00
-
-$user->depositFloat(1.37);
-$user->balance; // 237
-$user->balanceFloat; // 2.37
amountFloat
attribute on the transaction model$transaction->amount; // 137
-$transaction->amountFloat; // 1.37
Configuration
Environment
Name Description Default WALLET_MATH_SCALE
Select mathematical precision 64 WALLET_CACHE_DRIVER
Cache for wallet balance array WALLET_CACHE_TTL
Cache TTL for wallet balance 24h WALLET_LOCK_DRIVER
Lock for wallets array WALLET_LOCK_TTL
Lock TTL for wallets 1s WALLET_TRANSACTION_TABLE_NAME
Transaction table name transactions WALLET_TRANSFER_TABLE_NAME
Transfer table name transfers WALLET_WALLET_TABLE_NAME
Wallet table name wallets WALLET_DEFAULT_WALLET_NAME
Default wallet name Default Wallet WALLET_DEFAULT_WALLET_SLUG
Default wallet slug default Configure default wallet
name
,slug
and meta
of default wallet.'default' => [
- 'name' => 'Ethereum',
- 'slug' => 'ETH',
- 'meta' => [],
-],
Extend base Wallet model
Bavix\\Wallet\\Models\\Wallet
and registering the new class in config/wallet.php
. Example MyWallet.php
use Bavix\\Wallet\\Models\\Wallet as WalletBase;
-
-class MyWallet extends WalletBase {
- public function helloWorld(): string { return "hello world"; }
-}
Register base Wallet model
'wallet' => [
- 'table' => 'wallets',
- 'model' => MyWallet::class,
- 'creating' => [],
- 'default' => [
- 'name' => 'Default Wallet',
- 'slug' => 'default',
- 'meta' => [],
- ],
-],
echo $user->wallet->helloWorld();
Transfer
and Transaction
models and registering the extended models in the configuration file.Changing wallet decimal places
/**
- * Base model 'wallet'.
- */
-'wallet' => [
- ....
- 'creating' => [
- 'decimal_places' => 18,
- ],
- ....
-],
Introduction
laravel-wallet
- Easy work with virtual wallet.Support
Installation
Composer
composer req bavix/laravel-wallet
Customize
Run Migrations
php artisan vendor:publish --tag=laravel-wallet-migrations
Configuration
php artisan vendor:publish --tag=laravel-wallet-config
Upgrade Guide
1.x.x → 2.x.x
::with('balance')
to ::with('wallet')
2.1.x → 2.2.x
CanBePaid
to CanPay
.CanBePaidFloat
to CanPayFloat
.2.2.x → 2.4.x
calculateBalance
to refreshBalance
2.4.x → 3.0.x
bavix.wallet::transaction
to Bavix\\Wallet\\Models\\Transaction::class
bavix.wallet::transfer
to Bavix\\Wallet\\Models\\Transfer::class
bavix.wallet::wallet
to Bavix\\Wallet\\Models\\Wallet::class
// old
-app('bavix.wallet::transaction');
-// new
-app(Bavix\\Wallet\\Models\\Transaction::class);
$quantity
parameter to the canBuy
method.// old
-public function canBuy(Customer $customer, bool $force = false): bool
-// new
-public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
getUniqueId
to Interface Product
class Item extends Model implements Product
-{
-
- // Your method
-
- public function getUniqueId(): string
- {
- return (string)$this->getKey();
- }
-
-}
3.0.x → 3.1.x
Taxing
to Taxable
.3.1.x → 4.0.x
laravel/cashier
. We support 7+.If you use payment for goods
Customer $customer
to the getAmountProduct
method of your model. public function getAmountProduct(): int
- {
- return $this->price;
- }
public function getAmountProduct(Customer $customer): int
- {
- return $this->price;
- }
4.0.x → 5.0.x
public function getAmountProduct(Customer $customer): int { ... }
-
- public function getFeePercent(): float { ... }
-
- public function getMinimalFee(): int { ... }
public function getAmountProduct(Customer $customer) { ... }
-
- public function getFeePercent() { ... }
-
- public function getMinimalFee() { ... }
protected function rate(Wallet $wallet): float { ... }
-
- public function convertTo(Wallet $wallet): float { ... }
protected function rate(Wallet $wallet) { ... }
-
- public function convertTo(Wallet $wallet) { ... }
5.x.x → 6.0.x
config/wallet.php
file (if you have it) and edit it.$bcLoaded = extension_loaded('bcmath');
-$mathClass = Math::class;
-switch (true) {
- case class_exists(BigDecimal::class):
- $mathClass = BrickMath::class;
- break;
- case $bcLoaded:
- $mathClass = BCMath::class;
- break;
-}
brick/math
. 'mathable' => $mathClass,
'mathable' => BrickMath::class,
6.x.x → 6.2.4
6.2.4 → 7.x.x
config/wallet.php
config/wallet.php
config has changed a lot, if you have it in your project, then replace it run.php artisan vendor:publish --tag=laravel-wallet-config --force
7.x.x → 8.0.x
8.0.x → 8.1.x
getAvailableBalance
to getAvailableBalanceAttribute
(method) or available_balance
(property).// old
-$cart = app(\\Bavix\\Wallet\\Objects\\Cart::class)
- ->addItems($products)
- ->addItem($product)
- ->setMeta(['hello' => 'world']);
-
-$cart->addItem($product);
-
-// new. fluent
-$cart = app(\\Bavix\\Wallet\\Objects\\Cart::class)
- ->withItems($products)
- ->withItem($product)
- ->withMeta(['hello' => 'world']);
-
-$cart = $cart->withItem($product);
8.1.x+ → 9.0.x
artisan bx:transfer:fix
ProductLimitedInterface
. Needed to create limited goods;ProductInterface
. Needed for an infinite number of products;Bavix\\Wallet\\Interfaces\\Product
to Bavix\\Wallet\\Interfaces\\ProductLimitedInterface
.9.x.x → 10.0.x
10.x.x → 11.0.x
`,84),h=[n];function l(p,k,d,r,o,c){return a(),i("div",null,h)}const y=s(t,[["render",l]]);export{E as __pageData,y as default};
diff --git a/docs/.vitepress/dist/assets/guide_introduction_upgrade.md.CWeACiVZ.lean.js b/docs/.vitepress/dist/assets/guide_introduction_upgrade.md.CWeACiVZ.lean.js
deleted file mode 100644
index 3bc4319b0..000000000
--- a/docs/.vitepress/dist/assets/guide_introduction_upgrade.md.CWeACiVZ.lean.js
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,c as i,o as a,a3 as e}from"./chunks/framework.OdeEVNy0.js";const E=JSON.parse('{"title":"Upgrade Guide","description":"","frontmatter":{},"headers":[],"relativePath":"guide/introduction/upgrade.md","filePath":"guide/introduction/upgrade.md"}'),t={name:"guide/introduction/upgrade.md"},n=e("",84),h=[n];function l(p,k,d,r,o,c){return a(),i("div",null,h)}const y=s(t,[["render",l]]);export{E as __pageData,y as default};
diff --git a/docs/.vitepress/dist/assets/guide_multi_new-wallet.md.DLsWaElB.js b/docs/.vitepress/dist/assets/guide_multi_new-wallet.md.DLsWaElB.js
deleted file mode 100644
index 2b771da99..000000000
--- a/docs/.vitepress/dist/assets/guide_multi_new-wallet.md.DLsWaElB.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import{_ as s,c as i,o as a,a3 as l}from"./chunks/framework.OdeEVNy0.js";const c=JSON.parse('{"title":"New Wallet","description":"","frontmatter":{},"headers":[],"relativePath":"guide/multi/new-wallet.md","filePath":"guide/multi/new-wallet.md"}'),t={name:"guide/multi/new-wallet.md"},e=l(`from_type
, to_type
in the transfers table have been physically removed. Make sure you don't use them;extra
column has been added to the transfers table. Don't forget to apply all new migrations;Bavix\\Wallet\\Interfaces\\Wallet
contract has been extended with the receivedTransfers method. If you overridden the implementation, then implement the new method;New Wallet
slug
for each wallet should be unique.User Model
HasWallets
trait's and Wallet
interface to model.use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallets;
-}
Create a wallet
$user = User::first();
$user->hasWallet('my-wallet'); // bool(false)
-$wallet = $user->createWallet([
- 'name' => 'New Wallet',
- 'slug' => 'my-wallet',
-]);
-
-$user->hasWallet('my-wallet'); // bool(true)
-
-$wallet->deposit(100);
-$wallet->balance; // 100
-$wallet->balanceFloatNum; // 1.00
How to get the right wallet?
$myWallet = $user->getWallet('my-wallet');
-$myWallet->balance; // 100
-$myWallet->balanceFloatNum; // 1.00
Default Wallet + MultiWallet
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet, HasWallets;
-}
$wallet = $user->wallet;
-$wallet->balance; // 10
-$wallet->balanceFloatNum; // 0.10
Transaction Filter
transactions
method. Yes, this method displays ALL transactions for the wallet owner. If you only need to filter one wallet at a time, now you can use the walletTransactions
method./** @var \\Bavix\\Wallet\\Models\\Wallet $wallet */
-
-$query = $wallet->walletTransactions();
$user->transactions()->count(); // 0
-
-// Multi wallets and default wallet can be used together
-// default wallet
-$user->deposit(100);
-$user->wallet->deposit(200);
-$user->wallet->withdraw(1);
-
-// usd
-$usd = $user->createWallet(['name' => 'USD']);
-$usd->deposit(100);
-
-// eur
-$eur = $user->createWallet(['name' => 'EUR']);
-$eur->deposit(100);
-
-$user->transactions()->count(); // 5
-$user->wallet->transactions()->count(); // 5
-$usd->transactions()->count(); // 5
-$eur->transactions()->count(); // 5
-// the transactions method returns data relative to the owner of the wallet, for all transactions
-
-$user->walletTransactions()->count(); // 3. we get the default wallet
-$user->wallet->walletTransactions()->count(); // 3
-$usd->walletTransactions()->count(); // 1
-$eur->walletTransactions()->count(); // 1
Transfer between wallets
User Model
HasWallets
trait's and Wallet
interface.use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallets;
-}
Make a Transfer
$first = User::first();
-$last = User::orderBy('id', 'desc')->first(); // last user
-$first->getKey() !== $last->getKey(); // true
$firstWallet = $first->createWallet(['name' => 'First User Wallet']);
-$lastWallet = $last->createWallet(['name' => 'Second User Wallet']);
-
-$firstWallet->deposit(100);
-$firstWallet->balance; // 100
-$lastWallet->balance; // 0
$firstWallet->transfer($lastWallet, 5);
-$firstWallet->balance; // 95
-$lastWallet->balance; // 5
Force Transfer
$firstWallet->balance; // 100
-$lastWallet->balance; // 0
$firstWallet->forceTransfer($lastWallet, 500);
-$firstWallet->balance; // -400
-$lastWallet->balance; // 500
Change the meta and confirmation
$firstWallet->balanceInt; // 1_000
-$secondWallet->balanceInt; // 0
use Bavix\\Wallet\\External\\Dto\\Extra;
-use Bavix\\Wallet\\External\\Dto\\Option;
-
-/** @var $firstWallet \\Bavix\\Wallet\\Interfaces\\Wallet */
-$transfer = $firstWallet->transfer($secondWallet, 500, new Extra(
- deposit: ['message' => 'Hello, secondWallet!'],
- withdraw: new Option(meta: ['something' => 'anything'], confirmed: false)
-));
-
-$transfer->withdraw->meta; // ['something' => 'anything']
-$transfer->withdraw->confirmed; // false
-
-$transfer->deposit->meta; // ['message' => 'Hello, secondWallet!']
-$transfer->deposit->confirmed; // true
-
-$firstWallet->balanceInt; // 1_000
-$secondWallet->balanceInt; // 500
Cart
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return round($this->price * 100);
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return round($this->price * 100);
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Fill the cart
$user = User::first();
-$user->balance; // 0
use Bavix\\Wallet\\Objects\\Cart;
-
-$list = [
- 'potato' => 3,
- 'carrot' => 10,
-];
-
-$products = Item::query()
- ->whereIn('slug', ['potato', 'carrot'])
- ->get();
-
-$cart = app(Cart::class);
-foreach ($products as $product) {
- $cart = $cart->withItem($product, quantity: $list[$product->slug]);
-}
-
-$cartTotal = $cart->getTotal($user); // 15127
-$user->deposit($cartTotal);
-$user->balanceInt; // 15127
-$user->balanceFloat; // 151.27
-
-$cart = $cart->withItem(current($products), pricePerItem: 500); // 15127+500
-$user->deposit(500);
-$user->balanceInt; // 15627
-$user->balanceFloat; // 156.27
-
-(bool)$user->payCart($cart); // true
-$user->balanceFloat; // 0
Commissions
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and ProductInterface
(or ProductLimitedInterface
) interface to Item model.use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\Taxable;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface, Taxable
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-
- public function getFeePercent()
- {
- return 0.03; // 3%
- }
-}
Tax process
$user = User::first();
-$user->balance; // 103
$item = Item::first();
-$item->getAmountProduct($user); // 100
$user->pay($item); // success, 100 (product) + 3 (fee) = 103
-$user->balance; // 0
Minimal Taxing
MinimalTaxable
(or MaximalTaxable
) in class Item
.use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\MinimalTaxable;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface, MinimalTaxable
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-
- public function getFeePercent()
- {
- return 0.03; // 3%
- }
-
- public function getMinimalFee()
- {
- return 5; // 3%, minimum 5
- }
-}
Successfully
$user = User::first();
-$user->balance; // 105
$item = Item::first();
-$item->getAmountProduct($user); // 100
$user->pay($item); // success, 100 (product) + 5 (minimal fee) = 105
-$user->balance; // 0
Failed
$user = User::first();
-$user->balance; // 103
$item = Item::first();
-$item->getAmountProduct($user); // 100
$user->safePay($item); // failed, 100 (product) + 5 (minimal fee) = 105
-$user->balance; // 103
Gift
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Santa Claus, give gifts
$first = User::first();
-$last = User::orderBy('id', 'desc')->first(); // last user
-$first->getKey() !== $last->getKey(); // true
-
-$first->balance; // 115
-$last->balance; // 0
$item = Item::first();
-$item->getAmountProduct($first); // 100
-$item->balance; // 0
Taxable
interface, then Santa will pay tax$first->gift($last, $item);
-(bool)$last->paid($item, true); // bool(true)
-$first->balance; // 15
-$last->balance; // 0
-$item->balance; // 100
Payment Free
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Pay Free
$user = User::first();
-$user->balance; // 100
$item = Item::first();
-$item->getAmountProduct($user); // 100
-$item->balance; // 0
$user->payFree($item);
-(bool)$user->paid($item); // bool(true)
-$user->balance; // 100
-$item->balance; // 0
Payment
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Proceed to purchase
$user = User::first();
-$user->balance; // 100
$item = Item::first();
-$item->getAmountProduct($user); // 100
$user->pay($item);
-$user->balance; // 0
$user->balance; // 0
-$user->pay($item);
-// throw an exception
(bool)$user->paid($item); // bool(true)
Safe Pay
try
and catch
use safePay
method.if ($user->safePay($item)) {
- // try to buy again )
-}
Payment. Customize receiving
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model. If we want to achieve multi wallets for a product, then we need to add HasWallets
.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet, HasWallets;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Traits\\HasWallets;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet, HasWallets;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Proceed to purchase
$user = User::first();
-$user->balance; // 100
$item = Item::first();
-$item->getAmountProduct($user); // 100
-
-$receiving = $item->createWallet([
- 'name' => 'Dollar',
- 'meta' => [
- 'currency' => 'USD',
- ],
-]);
$cart = app(Cart::class)
- ->withItem($item, receiving: $receiving)
-;
-
-$user->payCart($cart);
-$user->balance; // 0
-
-$receiving->balanceInt; // $100
Refund
User Model
CanPay
trait and Customer
interface to your User model.CanPay
already inherits HasWallet
, reuse will cause an error.use Bavix\\Wallet\\Traits\\CanPay;
-use Bavix\\Wallet\\Interfaces\\Customer;
-
-class User extends Model implements Customer
-{
- use CanPay;
-}
Item Model
HasWallet
trait and interface to Item
model.ProductInterface
);ProductLimitedInterface
);use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductInterface;
-
-class Item extends Model implements ProductInterface
-{
- use HasWallet;
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Customer;
-use Bavix\\Wallet\\Interfaces\\ProductLimitedInterface;
-
-class Item extends Model implements ProductLimitedInterface
-{
- use HasWallet;
-
- public function canBuy(Customer $customer, int $quantity = 1, bool $force = false): bool
- {
- /**
- * This is where you implement the constraint logic.
- *
- * If the service can be purchased once, then
- * return !$customer->paid($this);
- */
- return true;
- }
-
- public function getAmountProduct(Customer $customer): int|string
- {
- return 100;
- }
-
- public function getMetaProduct(): ?array
- {
- return [
- 'title' => $this->title,
- 'description' => 'Purchase of Product #' . $this->id,
- ];
- }
-}
PurchaseServiceInterface
interface. With it, you can check the availability of all products with one request, there will be no N-queries in the database.Make a refund
$user = User::first();
-$user->balance; // 0
$item = Item::first();
-$item->balance; // 100
(bool)$user->paid($item); // bool(true)
-(bool)$user->refund($item); // bool(true)
-$item->balance; // 0
-$user->balance; // 100
Cancel Transaction
User Model
CanConfirm
trait and Confirmable
interface to your User model.use Bavix\\Wallet\\Interfaces\\Confirmable;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-use Bavix\\Wallet\\Traits\\CanConfirm;
-use Bavix\\Wallet\\Traits\\HasWallet;
-
-class UserConfirm extends Model implements Wallet, Confirmable
-{
- use HasWallet, CanConfirm;
-}
To cancel
Example:
$user->balance; // 0
-$transaction = $user->deposit(100); // confirmed transaction
-$transaction->confirmed; // bool(true)
-$user->balance; // 100
-
-$user->resetConfirm($transaction); // bool(true)
-$transaction->confirmed; // bool(false)
-
-$user->balance; // 0
Confirm Transaction
User Model
CanConfirm
trait and Confirmable
interface to your User model.use Bavix\\Wallet\\Interfaces\\Confirmable;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-use Bavix\\Wallet\\Traits\\CanConfirm;
-use Bavix\\Wallet\\Traits\\HasWallet;
-
-class UserConfirm extends Model implements Wallet, Confirmable
-{
- use HasWallet, CanConfirm;
-}
To confirmation
Example:
$user->balance; // 0
-$transaction = $user->deposit(100, null, false); // not confirm
-$transaction->confirmed; // bool(false)
-$user->balance; // 0
-
-$user->confirm($transaction); // bool(true)
-$transaction->confirmed; // bool(true)
-
-$user->balance; // 100
Credit Limits
/**
- * @var \\Bavix\\Wallet\\Interfaces\\Customer $customer
- * @var \\Bavix\\Wallet\\Models\\Wallet $wallet
- * @var \\Bavix\\Wallet\\Interfaces\\ProductInterface $product
- */
-$wallet = $customer->wallet; // get default wallet
-$wallet->meta['credit'] = 10000; // credit limit
-$wallet->save(); // update credit limit
-
-$wallet->balanceInt; // 0
-$product->getAmountProduct($customer); // 500
-
-$wallet->pay($product); // success
-$wallet->balanceInt; // -500
/** @var \\Bavix\\Wallet\\Traits\\HasWallets $user */
-$wallet = $user->createWallet([
- 'name' => 'My Wallet',
- 'meta' => ['credit' => 500],
-]);
Deposit
User Model
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Make a Deposit
$user = User::first();
HasWallet
, he will have balance
property. Check the user's balance.$user->balance; // 0
-$user->balanceInt; // 0
$user->deposit(10);
-$user->balance; // 10
-$user->balanceInt; // 10
Exchange
$user->createWallet([
- 'name' => 'My USD Wallet',
- 'meta' => ['currency' => 'USD'],
-]);
Service for working with currency
use Bavix\\Wallet\\Internal\\Service\\MathServiceInterface;
-use Bavix\\Wallet\\Services\\ExchangeServiceInterface;
-
-class MyExchangeService implements ExchangeServiceInterface
-{
- private array $rates = [
- 'USD' => [
- 'RUB' => 67.61,
- ],
- ];
-
- private MathServiceInterface $mathService;
-
- public function __construct(MathServiceInterface $mathService)
- {
- $this->mathService = $mathService;
-
- foreach ($this->rates as $from => $rates) {
- foreach ($rates as $to => $rate) {
- if (empty($this->rates[$to][$from])) {
- $this->rates[$to][$from] = $this->mathService->div(1, $rate);
- }
- }
- }
- }
-
- /** @param float|int|string $amount */
- public function convertTo(string $fromCurrency, string $toCurrency, $amount): string
- {
- return $this->mathService->mul($amount, $this->rates[$fromCurrency][$toCurrency] ?? 1);
- }
-}
Service Registration
config/wallet.php
.return [
- // ...
- 'services' => [
- 'exchange' => MyExchangeService::class,
- // ...
- ],
- // ...
-];
Exchange process
$usd = $user->createWallet([
- 'name' => 'My Dollars',
- 'meta' => ['currency' => 'USD'],
-]);
-
-$rub = $user->createWallet([
- 'name' => 'My Ruble',
- 'meta' => ['currency' => 'RUB'],
-]);
$rub->deposit(10000);
$transfer = $rub->exchange($usd, 10000);
-$rub->balance; // 0
-$usd->balance; // 147, это $1.47
$transfer = $usd->exchange($rub, $usd->balance);
-$usd->balance; // 0
-$rub->balance; // 9938
To refresh the balance
User Model
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Get the current balance for your wallet
$user->id; // 5
-$user->balance; // 27
update transactions
-set confirmed=1
-where confirmed=0 and
- payable_type='App\\Models\\User' and
- payable_id=5;
--- 212 rows affected in 54 ms
$user->balance; // 27
-$user->wallet->refreshBalance();
-$user->balance; // 42
Transfer
User Model
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Example contract
$transfer = $user1->transfer(
- $user2,
- 511,
- new Extra(
- deposit: [
- 'type' => 'extra-deposit',
- ],
- withdraw: new Option(
- [
- 'type' => 'extra-withdraw',
- ],
- false // confirmed
- ),
- extra: [
- 'msg' => 'hello world',
- ],
- )
-);
Make a Transfer
$first = User::first();
-$last = User::orderBy('id', 'desc')->first(); // last user
-$first->getKey() !== $last->getKey(); // true
HasWallet
, he will have balance
property. Check the user's balance.$first->balance; // 100
-$last->balance; // 0
$first->transfer($last, 5);
-$first->balance; // 95
-$last->balance; // 5
Force Transfer
$first->balance; // 100
-$last->balance; // 0
$first->forceTransfer($last, 500);
-$first->balance; // -400
-$last->balance; // 500
Withdraw
User Model
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Make a Withdraw
$user = User::first();
HasWallet
, he will have balance
property. Check the user's balance.$user->balance; // 100
-$user->balanceInt; // 100
$user->withdraw(10);
-$user->balance; // 90
-$user->balanceInt; // 90
Force Withdraw
$user->balance; // 100
-$user->balanceInt; // 100
-$user->forceWithdraw(101);
-$user->balance; // -1
-$user->balanceInt; // -1
And what will happen if the money is not enough?
`,23),h=[n];function l(p,k,r,d,o,c){return a(),i("div",null,h)}const y=s(t,[["render",l]]);export{E as __pageData,y as default};
diff --git a/docs/.vitepress/dist/assets/guide_single_withdraw.md.DOQOK6kp.lean.js b/docs/.vitepress/dist/assets/guide_single_withdraw.md.DOQOK6kp.lean.js
deleted file mode 100644
index c134c27da..000000000
--- a/docs/.vitepress/dist/assets/guide_single_withdraw.md.DOQOK6kp.lean.js
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,c as i,o as a,a3 as e}from"./chunks/framework.OdeEVNy0.js";const E=JSON.parse('{"title":"Withdraw","description":"","frontmatter":{},"headers":[],"relativePath":"guide/single/withdraw.md","filePath":"guide/single/withdraw.md"}'),t={name:"guide/single/withdraw.md"},n=e("",23),h=[n];function l(p,k,r,d,o,c){return a(),i("div",null,h)}const y=s(t,[["render",l]]);export{E as __pageData,y as default};
diff --git a/docs/.vitepress/dist/assets/include_composer.md.Bahh6vzC.js b/docs/.vitepress/dist/assets/include_composer.md.Bahh6vzC.js
deleted file mode 100644
index 83acd1e24..000000000
--- a/docs/.vitepress/dist/assets/include_composer.md.Bahh6vzC.js
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as e,c as a,o as s,a3 as o}from"./chunks/framework.OdeEVNy0.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"_include/composer.md","filePath":"_include/composer.md"}'),t={name:"_include/composer.md"},r=o('Bavix\\Wallet\\Exceptions\\BalanceIsEmpty
Bavix\\Wallet\\Exceptions\\InsufficientFunds
Composer
composer req bavix/laravel-wallet
Eager Loading
Wallet
model that is related to User
:HasWallet
trait and Wallet
interface to model.use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet; // public function wallet(): MorphOne...
-}
$users = User::all();
-
-foreach ($users as $user) {
- // echo $user->wallet->balance;
- echo $user->balance; // Abbreviated notation
-}
$users = User::with('wallet')->all();
-
-foreach ($users as $user) {
- // echo $user->wallet->balance;
- echo $user->balance; // Abbreviated notation
-}
Wallet
interface;HasWallet
trait;use Bavix\\Wallet\\Traits\\HasWallet;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWallet;
-}
Wallet
interface;HasWalletFloat
trait;use Bavix\\Wallet\\Traits\\HasWalletFloat;
-use Bavix\\Wallet\\Interfaces\\Wallet;
-
-class User extends Model implements Wallet
-{
- use HasWalletFloat;
-}