Problems with the same user session on simultaneous requests - php

I was assigned to maintain the project that sells game item codes via the web app and deliver the codes via the text message to customer's mobiles. There is a problem that I still don't know how to solve it.
It simply occurs when the user purchases the product. They just click to puchase once but the app seems to process the purchasing stage for multiple times. So the users get a lot of items by paying for just one item.
Normally, the users have to add funds vi credit card to their accounts in order to have the sufficient balance to buy the product. So, this stage is conditional. Here is the brief code
<?php
public function actionPurchase() {
$user = Users::findOne(['mobile' => Yii::$app->session->get('mobile')]);
$product_price = Yii::$app->session->get('product_price');
$product_code = ProductStock::getCodefromPrice($product_price);
// Save the transaction log
$transaction = new Transactions();
$transaction->product_code = $product_code;
$transaction->amount = $product_price;
$transaction->user_id = $user->id;
$transaction->credit_before = $user->balance;
$transaction->created = date('Y-m-d H:i:s');
$transaction->save();
if ($user->balance >= $product_price) {
// Redeem the product from the API
$raw = ProductStock::redeem($product_code);
// Decode the JSON response
$response = json_decode($resp);
if ($response->status == 'Success') {
// Adjust the user balance
$new_credit = $user->balance - $product_price;
$user->balance = $new_credit;
$user->update();
// Send the SMS
Utilities::sendSMS($user->mobile, $response->item_pin);
return $this->redirect(['success']);
}
}
}
?>
As you can see that the I also record the transaction with user credit before and after the purchase. So if the purchase is success. I reduce the user balance from the user table and record the credit after to be the ducucted one.
Please note that the users are stored in the MySQL table along with their current balance. So, here is how the table looks like
| id | mobile | balance | created |
| 1 | 0811234567 | 300 | 2017-01-01 00:00:00 |
And the transaction table looks like this
| id | user_id | product_code | amount | credit_before | credit_after | created |
| 119687 | 1 | estr1203 | 100 | 300 | 200 | 2017-01-01 13:15:02 |
Hence, if the users successfully purchase the product they will have their balance deducted at the end.
But this is the result from the problem I am current encountering
| id | user_id | product_code | amount | credit_before | credit_after | created |
| 119691 | 1 | estr1203 | 100 | 200 | 100 | 2017-01-01 13:15:03 |
| 119690 | 1 | estr1203 | 100 | 200 | 100 | 2017-01-01 13:15:03 |
| 119689 | 1 | estr1203 | 100 | 300 | 200 | 2017-01-01 13:15:02 |
| 119688 | 1 | estr1203 | 100 | 300 | 200 | 2017-01-01 13:15:02 |
| 119687 | 1 | estr1203 | 100 | 300 | 200 | 2017-01-01 13:15:02 |
Please see the log creation time, they are the same. Normally, if the users click to purchase the product multiple times it will process the transaction multiple times too. So their balance should get deducted in sequence like 300 to 200 and the next time is 200 to 100 until they run out of balance and can no longer make a purhase. For the example above the correct transaction record should be
| id | user_id | product_code | amount | credit_before | credit_after | created |
| 119689 | 1 | estr1203 | 100 | 100 | 0 | 2017-01-01 13:15:02 |
| 119688 | 1 | estr1203 | 100 | 200 | 100 | 2017-01-01 13:15:02 |
| 119687 | 1 | estr1203 | 100 | 300 | 200 | 2017-01-01 13:15:02 |
But this looks like the app treats these simultaneous requests as the same one because it is the same user and all access the the if condition to check whether the user has sufficient balance at the same time. So the multiple items are sent to the user and they just click to purchase once.
Anyone can give me good ideas of how to handle such a problem? I don't know how to solve it by now and have been working on it for already couple days. Could it be a problem from the gap when the app call the API for the item code to be sent to the user and when the simultaneous requests from the same user come. The user's balance is not yet deducted until the product is successfully returned from the API. So the balance checking condition returns true. I got no idea on it. Becaause when I test it, it works properly.
Thank you in advance.

Related

Using Multidimensional array in Laravel 8 Blade

I am trying to get a status count of some processes and tasks just like a to-do list tasks count.
My current database look like this SQL Database Structure
+----+---------+------------+-------------+---------+------------+---------------------+---------------------+
| id | user_id | list_id | name | records | status | created_at | updated_at |
+----+---------+------------+-------------+---------+------------+---------------------+---------------------+
| 1 | 1 | 8Kwvf8ikcV | 10 mails | 19 | On-Hold | 2021-03-11 07:56:17 | 2021-03-11 07:56:17 |
| 2 | 1 | a0pJRv4Zfc | temp_emails | 884 | On-Hold | 2021-03-11 08:02:13 | 2021-03-11 08:02:13 |
| 3 | 1 | xrgZZkrLFA | 10 mails | 19 | Processing | 2021-03-11 08:06:37 | 2021-03-11 08:06:37 |
| 4 | 1 | lDOX8p2sgU | 10 mails | 19 | Completed | 2021-03-11 08:53:51 | 2021-03-11 08:53:51 |
+----+---------+------------+-------------+---------+------------+---------------------+---------------------+
I am using
return $listStats = ListName::select([
DB::raw("status"),
DB::raw("COUNT(status) as count")
])->where('user_id', Auth::id())
->groupBy('status')
->get();
Query to get the database and its count.
Current laravel out put is showing as
[{"status":"Completed","count":1},{"status":"On-Hold","count":2},{"status":"Processing","count":1}]
My question is how can I get the count of completed status without using foreach loop?
Do I need to change my query? or can I simply use $listStats['Completed']['count'] ?
A foreach will be run at some point unless you use a join in the query to give an order and force a result with all possible status.
One easy solution would be
$StatCount[ //make sure to list all possible statuses here
"Completed" => 0,
"Processing" => 0,
"On-Hold" => 0,
];
foreach ($listStats as $listStat) {
$StatCount[$listStat->status] = $listStat->count
}
Now you can access your status counts like this $statCount['Completed']
Counting is a pretty lightweight job and it can be done on the fly easily. Instead of creating an extra query to count the records, collect the records and count them in Blade. It's a better idea because you will fetch the tasks anyway and no need to query them twice.
// In controller
$user = User::where('id', Auth::id())->with(['tasks' => function($query) {
$query->groupBy('status')
}])->get();
return view('viewName')->with(['user', $user]);
// In Blade
$user->tasks->Completed->count();

Calculate Available Balance from Wallet system excluding Expired Credits

In credits table I have
(id, user_id, process, amount, date_add, date_exp, date_redeemed, remark)
SELECT * FROM credits WHERE user_id = 2;
+----+---------+---------+--------+------------+------------+---------------+----------+
| id | user_id | process | amount | date_add | date_exp | date_redeemed | remark |
+----+---------+---------+--------+------------+------------+---------------+----------+
| 22 | 2 | Add | 200.00 | 2018-01-01 | 2019-01-01 | | Credit1 |
| 23 | 2 | Add | 200.00 | 2018-03-31 | 2019-03-31 | | Credit2 |
| 24 | 2 | Deduct | 200.00 | | | 2018-04-28 | Redeemed |
| 25 | 2 | Add | 200.00 | 2018-07-11 | 2018-10-11 | | Campaign |
| 26 | 2 | Deduct | 50.00 | | | 2018-08-30 | Redeemed |
| 27 | 2 | Add | 200.00 | 2018-10-01 | 2019-09-30 | | Credit3 |
| 28 | 2 | Deduct | 198.55 | | | 2018-10-20 | Redeemed |
+----+---------+---------+--------+------------+------------+---------------+----------+
The following query I wrote will only calculate the balance, but I don't know whether the credit is expired and used before expired.
SELECT
u.id,
email,
CONCAT(first_name, ' ', last_name) AS name,
type,
(CASE
WHEN (SUM(amount) IS NULL) THEN 0.00
ELSE CASE
WHEN
(SUM(CASE
WHEN process = 'Add' THEN amount
END) - SUM(CASE
WHEN process = 'Deduct' THEN amount
END)) IS NULL
THEN
SUM(CASE
WHEN process = 'Add' THEN amount
END)
ELSE SUM(CASE
WHEN process = 'Add' THEN amount
END) - SUM(CASE
WHEN process = 'Deduct' THEN amount
END)
END
END) AS balance
FROM
users u
LEFT JOIN
credits c ON u.id = c.user_id
GROUP BY u.id;
Or I am doing it in the wrong way? Maybe I should have done the calculation in my backend instead of SQL?
EDIT 1:
I want to calculate the balance of every user's e-wallet, but the credit will expire,
IF it was expired AND not redeemed then exclude from the balance
ELSE IF used before expired AND redeem amount < expire amount
THEN (balance - (expire amount - redeem amount))
ELSE IF used before expired AND redeem amount > expire amount
THEN the usable balance will be deducted as expire amount is not enough to deduct redeemed amount
EDIT 2:
The query above will output 351.45, my expected output is 201.45. Which will not calculate the redemption on 2018-08-30 as the redeem amount is lower than the expired amount
EDIT 3:
User table:
+----+------------+-----------+----------+----------------+----------+
| id | first_name | last_name | type | email | password |
+----+------------+-----------+----------+----------------+----------+
| 2 | Test | Oyster | Employee | test#gmail.com | NULL |
+----+------------+-----------+----------+----------------+----------+
My output:
+----+----------------+-------------+----------+---------+
| id | email | name | type | balance |
+----+----------------+-------------+----------+---------+
| 2 | test#gmail.com | Test Oyster | Employee | 351.45 |
+----+----------------+-------------+----------+---------+
Expected output:
total (200+200+200) 600
redeemed amount 448.55 (200+50+198.55)
Remaining balance is 151.45
+----+----------------+-------------+----------+---------+
| id | email | name | type | balance |
+----+----------------+-------------+----------+---------+
| 2 | test#gmail.com | Test Oyster | Employee | 151.45 |
+----+----------------+-------------+----------+---------+
There are basic structural problems with your current table. So I would propose some alterations to the table structure and subsequently application code. Table structures for Wallet systems can be very detailed; but I would suggest minimum possible changes here. I am not suggesting that it would be ideal way; but it should work. Initially, I will layout some of the problems with the current approach.
Problems:
What if there are multiple Credits available which are not yet expired ?
Out of these available Credits, some may actually have been utilized already, but not yet expired. How do we ignore them for available balance ?
Also, some may have been partially utilized. How do we account for partial utilization ?
There may be a scenario where a redemption amount spans across multiple unexpired credits. Some may get partially utilized; while some may get fully utilized.
General Practice:
We generally follow FIFO (First In First Out) approach, to give maximum benefit to the customer. So the older credits (which has higher chance of getting expired without utilization) gets utilized first.
In order to follow FIFO, we will have to effectively use a Looping technique in the query/application code every-time, in order to compute basic things, such as, "Available Wallet Balance", "Expired and Underutilized Credit", etc. Writing a query for this will be cumbersome and possibly inefficient at bigger scales
Solution:
We can add one more column amount_redeemed in your current table. It basically represent the amount which has already been redeemed against a specific credit.
ALTER TABLE credits ADD COLUMN amount_redeemed DECIMAL (8,2);
So, a filled table would look something like below:
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| id | user_id | process | amount | amount_redeemed | date_add | date_exp | date_redeemed | remark |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| 22 | 2 | Add | 200.00 | 200.00 | 2018-01-01 | 2019-01-01 | | Credit1 |
| 23 | 2 | Add | 200.00 | 200.00 | 2018-03-31 | 2019-03-31 | | Credit2 |
| 24 | 2 | Deduct | 200.00 | | | | 2018-04-28 | Redeemed |
| 25 | 2 | Add | 200.00 | 0.00 | 2018-07-11 | 2018-10-11 | | Campaign |
| 26 | 2 | Deduct | 50.00 | | | | 2018-08-30 | Redeemed |
| 27 | 2 | Add | 200.00 | 48.55 | 2018-10-01 | 2019-09-30 | | Credit3 |
| 28 | 2 | Deduct | 198.55 | | | | 2018-10-20 | Redeemed |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
Notice that the amount_redeemed against Credit of id = 25 is 0.00, using FIFO approach. It got a chance for redemption on 2018-10-20, but by that time, it has expired already (date_exp = 2018-10-11)
So, now once we have this setup, you can do the following things in your application code:
Populate amount_redeemed value in existing rows in the table:
This will be a one time activity. For this, formulating a single query would be difficult (that is why we are here in the first place). So I would suggest you to do it one time in your application code (eg: PHP), using Loops and FIFO approach. Look at Point 3 below, to get an idea of how to do it in application code.
Get Current Available Balance:
Query for this becomes trivial now, as we just need to calculate Sum of amount - amount_redeemed for all Add process, which are not yet expired.
SELECT SUM(amount - amount_redeemed) AS total_available_credit
FROM credits
WHERE process = 'Add' AND
date_exp > CURDATE() AND
user_id = 2
Update amount_redeemed at the time of redemption:
In this, you can firstly get all available Credits, which has amount available for redemption, and not yet expired.
SELECT id, (amount - amount_redeemed) AS available_credit
FROM credits
WHERE process = 'Add' AND
date_exp > CURDATE() AND
user_id = 2 AND
amount - amount_redeemed > 0
ORDER BY id
Now, we can loop over the above query results, and utilize the amount accordingly
// PHP code example
// amount to redeem
$amount_to_redeem = 100;
// Map storing amount_redeemed against id
$amount_redeemed_map = array();
foreach ($rows as $row) {
// Calculate the amount that can be used against a specific credit
// It will be the minimum of available credit and amount left to redeem
$amount_redeemed = min($row['available_credit'], $amount_to_redeem);
// Populate the map
$amount_redeemed_map[$row['id']] = $amount_redeemed;
// Adjust the amount_to_redeem
$amount_to_redeem -= $amount_redeemed;
// If no more amount_to_redeem, we can finish the loop
if ($amount_to_redeem == 0) {
break;
} elseif ($amount_to_redeem < 0) {
// This should never happen, still if it happens, throw error
throw new Exception ("Something wrong with logic!");
exit();
}
// if we are here, that means some more amount left to redeem
}
Now, you can use two Update queries. First one would update amount_redeemed value against all the Credit id(s). Second one would Insert the Deduct row using the sum of all individual amount_redeemed value.
SELECT `id`, `email`, `NAME`, `type`,
(
( SELECT SUM(amount) FROM credit_table AS ct1 WHERE u.id = ct1.id AND process = 'ADD' AND date_exp > CURDATE()) -
( SELECT SUM(amount) FROM credit_table AS ct2 WHERE u.id = ct2.id AND process = 'Deduct' )
) AS balance
FROM
`user_table` AS u
WHERE
id = 2;
Hope it works as you wish

How to create model for Codeigniter?

I Have 4 tables on my db
Table "User"
id | user
-----------
55 | Jhon
56 | Krish
57 | Boss
Table "Payout"
id | Payout_Date
------------------
1 | 31.10.2015
2 | 24.10.2015
3 | 17.10.2015
Table "Earning"
Userid | Date | Earning
------------------------------------
55 | 31.10.2015 | 5$
56 | 31.10.2015 | 1$
57 | 31.10.2015 | 3$
55 | 30.10.2015 | 5$
56 | 30.10.2015 | 12$
57 | 30.10.2015 | 0$
55 | 29.10.2015 | 5$
56 | 29.10.2015 | 4$
When I add a new Payout date (Payout_Date),
I want to find who earn >10$ in their Last payment date between new payout out date, If not pay before that user, his Last Payment_Date is No(get his all earning).
Then add that results to table "Payment" with new Payout_Date to Payment_Date. Their Last Payment_Date is From_Date.
Table "Payment"
id | Userid | From_Date | Payment_Date | Earning | Status
------------------------------------------------------------------
1 | 55 | 24.10.2015 | 31.10.2015 | 12$ | 0
2 | 56 | 24.10.2015 | 31.10.2015 | 17$ | 0
How to write that on Codeigniter Controller and Model?
You need to break the problem down into pieces: find current payout date, find last time user paid, add earnings since then, filter if > $10 .
Here is a sample of what a view may look like (I did not actually run this so there could be syntax errors)
SELECT Earning.Userid, sum(Earning.Earning) earning_since_last
FROM
/* Find current payout Date */
(SELECT MAX(Payout_Date) current_payout_date FROM PAYOUT) as curr_payout,
/* find last time user paid */
(SELECT Userid, MAX(Payout_Date) last_payout_date
FROM PAYOUT
GROUP BY Userid) last_payout,
Earning
WHERE Earning.date <= curr_payout.current_payout_date
and last_payout.Userid = Earning.Userid
and earning.date > last_payout.last_payout_date
GROUP BY Earning.Userid
HAVING SUM(Earning.Earning) >= 10

How to get the value in field by new line and update into the column in table with same id in php mysql?

i want to ask how to get the value of the column and update into another column with same id.. this data came from email piping and i want to separate the value of the body and update into the expense , net and gross field..
i hope you can help me guys.. thank you
+------+---------+--------------------+---------+----------+-------+----------+
| id | subject | body | expense | net | gross | email |
+------+---------+--------------------+---------+----------+-------+----------+
| 1 | Sales | Expense = 2000 | 0 | 0 | 0 | ex#ex.com|
| | | netIncome = 5000 | | | | |
| | | Gross = 10000 | | | | |
+------+---------+--------------------+---------+----------+-------+----------+
Assuming that you have one \n for the line break and having the same pattern for the body, lets say we have the following
mysql> select * from test \G
*************************** 1. row ***************************
id: 1
body: Expense = 2000
netIncome = 5000
Gross = 10000
Now to get each amount we can use substring_index something as
mysql> select
substring_index(substring_index(body,'=',-3),'\n',1) as expense,
substring_index(substring_index(body,'=',-2),'\n',1) as netincome,
substring_index(body,'=',-1) as gross from test;
+---------+-----------+--------+
| expense | netincome | gross |
+---------+-----------+--------+
| 2000 | 5000 | 10000 |
+---------+-----------+--------+
So for the update it could be as
update your_table
set
expense = substring_index(substring_index(body,'=',-3),'\n',1),
net = substring_index(substring_index(body,'=',-2),'\n',1),
gross = substring_index(body,'=',-1) ;

PHP / MySQL: synchronize two fields in the same table

I need to synchronize two different booking calendars, beacause both calendars book the same room (or the same event).
So, if a client book a day (and hours) in calendar_01, this value (booked day and hours) will be automatically updated in calendars_02 (and vice versa).
It's important to update (and rewrite the new value) in order of the last time (most recent booking) without a continuous loop.
MySql DB
I'm using a plugin for this and in "calendars" database there is a table called "days", in this table I can see this:
+--------------+-------------+------------+------+----------------------------------------------------------+
| unique_key | calendar_id | day | year | data |
+--------------+-------------+------------+------+----------------------------------------------------------+
| 1_2014-08-20 | 1 | 2014-08-20 | 2014 | available h10-12; booked h12-14; in pending h14-16; |
| 2_2014-08-20 | 2 | 2014-08-20 | 2014 | available h 10 - 12; available h12-14; available h14-16; |
| 1_2014-08-21 | 1 | 2014-08-21 | 2014 | available h10-12; available-14; available h14-16; |
| 2_2014-08-21 | 2 | 2014-08-21 | 2014 | booked h10-12; booked h12-14; in pending h14-16; |
+--------------+-------------+------------+------+----------------------------------------------------------+
Simplification: column "data" contains the values (TEXT type) that record every rebooking, so:
+--------------+-------------+------------+------+--------------+
| unique_key | calendar_id | day | year | data |
+--------------+-------------+------------+------+--------------+
| 1_2014-08-20 | 1 | 2014-08-20 | 2014 | text value A |
| 2_2014-08-20 | 2 | 2014-08-20 | 2014 | text value B |
| 1_2014-08-21 | 1 | 2014-08-21 | 2014 | text value C |
| 2_2014-08-21 | 2 | 2014-08-21 | 2014 | text value D |
+--------------+-------------+------------+------+--------------+
I need to update the values of the same column "data", like this:
+--------------+-------------+------------+------+--------------+
| unique_key | calendar_id | day | year | data |
+--------------+-------------+------------+------+--------------+
| 1_2014-08-20 | 1 | 2014-08-20 | 2014 | text value A |
| 2_2014-08-20 | 2 | 2014-08-20 | 2014 | text value A |
| 1_2014-08-21 | 1 | 2014-08-21 | 2014 | text value D |
| 2_2014-08-21 | 2 | 2014-08-21 | 2014 | text value D |
| 1_2014-08-22 | 1 | 2014-08-22 | 2014 | text value X |
| 2_2014-08-22 | 2 | 2014-08-22 | 2014 | text value X |
| 1_2014-08-23 | 1 | 2014-08-23 | 2014 | text value Y |
| 2_2014-08-23 | 2 | 2014-08-23 | 2014 | text value Y |
+--------------+-------------+------------+------+--------------+
Many thanks in advance for any help!
I would strongly advice against syncing/Data Replication. You'd need to run a deamon 24/7, the risk of running into issues is much higher, it's also less eficient since It has to keep checking for new data in both tables which also means a delay for people to see their new bookings on the site. And it not so easy to debug when something does go wrong with the deamon.
The following solution is much easier to debug, more efficient. I would suggest you write abstract CRUD code for the data: Create, Read, Update and Delete. Create and Update are probably the ones you're most interested in, what you would do is something like this:
<?php
function create($id, $data)
{
$id = mysql_real_escape_string($id);
$data = mysql_real_escape_string($data['data']);
mysqL_query("INSERT INTO calendars (unique_key,data) VALUES('".$id."','".$data."')");
mysqL_query("INSERT INTO days (unique_key,data) VALUES('".$id."','".$data."')");
}
function update($id, $data)
{
$id = mysql_real_escape_string($id);
$data = mysql_real_escape_string($data['data']);
mysqL_query("UPDATE calendars SET data = '".$data."' WHERE unique_key = '".$id."'");
mysqL_query("UPDATE days SET data = '".$data."' WHERE unique_key = '".$id."'");
}
create('1_2014-08-20', array(
'data' => 'data here'
));
update('1_2014-08-20', array(
'data' => 'data here'
));
This is as simple as passing data into it. If you ever modify the SQL structure you can create a new abstraction set of functions/classes that follows the new database structure and it's as easy as swapping out an include.

Categories