how to work with laravel transaction rollback - php

I am trying to execute some queries which are dependant on each other, so what I want is if any error occurs then rollback all the queries inside transaction. what I've tried so far is
DB::transaction(function () use($user,$token,$request) {
$billing = User_billings::create([
'users_id' => $user->id,
'agent_id' => null,
'purpose' => 'Some Purpose',
'agent_token_id'=>$token->id,
'amount' => $request->amount,
'payment_method' => 'Agent Token',
'status' => 1
]);
if($billing){
$user_jorunal = User_journal::create([
'bill2_id' => $billing->id, //Intentionally made this error to test transaction, this should be 'bill_id'
'u_id' => $user->id,
'purpose' => 'Topup via Agent Token',
'debit' => $billing->amount,
'invoice_number' => time()
]);
if($user_jorunal){
if($this->agentTokenBalance($request->token_id) == 0){
$token->status=1;
$token->update();
}
return response()->json(['status'=>true,'message'=>'TopUp was Successful!']);
}
}
});
so when I execute this query It generates an error as SQLSTATE[HY000]: General error: 1364 Field 'bill_id' doesn't have a default value, but it also creates a row on user_billings table.
Can you please specify where I am wrong?
all of the above code is running fine, be sure that there is no logical error in query except the intentional one .
I am using laravel 5.7 in this project
PHP version is 7.2.19
following laravel documentation

Manually Using Transactions
use DB::beginTransaction(); to start transaction.
use DB::rollBack(); after each error.
use DB::commit(); when transaction confirmed. ;
laravel reference

Create a $status variable that will make sure that everything has been creeted in db. If any error occur, all db action will be rolled back.
Below, i have adapted your code with that logic.
$status = true;
try
{
DB::beginTransaction();
$billing = User_billings::create([
'users_id' => $user->id,
'agent_id' => null,
'purpose' => 'Some Purpose',
'agent_token_id' => $token->id,
'amount' => $request->amount,
'payment_method' => 'Agent Token',
'status' => 1,
]);
$user_jorunal = User_journal::create([
'bill2_id' => $billing->id, //Intentionally made this error to test transaction, this should be 'bill_id'
'u_id' => $user->id,
'purpose' => 'Topup via Agent Token',
'debit' => $billing->amount,
'invoice_number' => time(),
]);
$status = $status && $billing && $user_jorunal;
} catch (\Exception $e)
{
DB::rollBack();
//throw $e; //sometime you want to rollback AND throw the exception
}
//if previous DB action are OK
if ($status)
{
DB::commit();
if ($this->agentTokenBalance($request->token_id) == 0)
{
$token->status = 1;
$token->update();
}
return response()->json(['status' => true, 'message' => 'TopUp was Successful!']);
} else
{
DB::rollBack();
//return somme errors
}
Please note that MyIsam engine doesn't support transaction as explained here MyIsam engine transaction support.

Haven't used the DB::transaction with a callback method... but you can do the following
DB::beginTransaction();
$billing = new User_billings;
$billing->users_id = $user->id;
// rest of the assignments
$billing->save();
// Rest of your code... Inserts, Deletes, Updates...
DB::commit();
You don't need to implement the DB::rollBack() in this case, if anything fails between those two lines, the transaction won't commit.

Related

Laravel DB::select is breaking a future transaction

I have the following code :
DB::select($sql, $params);
DB::beginTransaction();
try {
DB::commit();
} catch (Exception $e) {
DB::rollback();
return [
'code' => 500,
'error' => true,
'message' => 'There was a problem saving your transaction.'
];
}
Which is causing the following error:
message: "There is no active transaction"
exception "PDOException"
There should be more instructions in the try statement but to show that none of them have an affect they have been removed. It seems to be that select is for some reason opening a transaction and not closing it. (The select is running an SP that returns two result sets but I only want one hence the use of select.) Everything works well without the select statement, and the select statement on it's own doesn't cause any errors and returns what I need. However, if I put the select statement in the try statement it causes an exception.
Thanks.
Does something like this work?
DB::beginTransaction();
DB::select($sql, $params);
try {
// your code that could fail
}
catch (Exception $e)
{
DB::rollback();
return [
'code' => 500,
'error' => true,
'message' => 'There was a problem saving your transaction.'
];
}
DB::commit();
return [
'code' => 200,
'error' => false,
'message' => 'Successful transaction.'
];

Laravel 7.0 Cashier - Stripe Payment Exception

I'm having problem on this Laravel Cashier Stripe payment. I need to combine the charge and new subscription as one so that when there is an IncompletePayment exceptions I can still get the stripe webhooks.
try{
$user->charge(1000, $creditCard->id, [
'description' => 'Premium Registration',
])
$user->newSubscription('premium_member', $recurring)
->create($creditCard->id);
}
} catch (IncompletePayment $e) {
$intent = \Stripe\PaymentIntent::retrieve($e->payment->id);
$intent->confirm([
'return_url' => url('api/payments-3d-success'),
]);
return response()->json([
'e' => $intent,
]);
}
Another way is to catch the exception and build like laravel way of handling Incomplete exceptions.
try{
$subscription = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [[
'price' => $recurring,
]],
'add_invoice_items' => [[
'price' => $oneTime,
]],
]);
}
//I need to catch the exception here from stripe and build like a laravel way like IncompletePayment exceptions
catch(Exception $e){
$intent = \Stripe\PaymentIntent::retrieve($e->payment->id);
$intent->confirm([
'return_url' => url('api/payments-3d-success'),
]);
return response()->json([
'e' => $intent,
]);
}
Please let me know how you handle this problem. Thanks
There is a way you can do this using Invoice Items: https://stripe.com/docs/billing/invoices/subscription#adding-upcoming-invoice-items

I cannot print the array that I am sending to the database - Laravel

I have the following function, but when I run it in Postman to see the result, it doesn't print any value to me, it doesn't even give me an error. The var_dump set if it detects them, but the array does not... I think there is something wrong in the method updateOrCreate , because when I print this variable with var_dump, I can't see anything in the console.
This is the function:
public function createBidRival(Request $request)
{
$response = array('code' => 400, 'error_msg' => []);
if (!$request->id_karatekas) array_push($response['error_msg'], 'id_karateka is required');
if (!$request->id_participant_bid_send ) array_push($response['error_msg'], 'id_participant_bid_send is required');
if (!$request->id_participant_bid_receive) array_push($response['error_msg'], ' id_participant_bid_receive is required');
if (!$request->bid_rival) array_push($response['error_msg'], 'bid rival is required');
if (!count($response['error_msg']) > 0) {
try {
var_dump($request->id_karatekas);
var_dump($request->id_participant_bid_send);
var_dump($request->id_participant_bid_receive);
var_dump($request->bid_rival);
$bidRival = new BidBetweenRivals();
$bidRival = BidBetweenRivals::updateOrCreate(
[
'id_participant_bid_send' => $request->id_participant_bid_send,
'id_participant_bid_receive' => $request->id_participant_bid_receive,
'id_karatekas' => $request->id_karatekas
],
[
'id_participant_bid_send' => $request->id_participant_bid_send,
'id_participant_bid_receive' => $request->id_participant_bid_receive,
'id_karatekas' => $request->id_karatekas,
'bid_rival' => $request->bid_rival
]
);
$bidBetweenRivals->save;
$response = array('code' => 200, 'bidBetweenRivals' => $bidRival, 'msg' => 'Bid created');
}catch(\Exception $exception) {
$response = array('code' => 500, 'error_msg' => $exception->getMessage());
}
}
}
Dump to see whether if (!count($response['error_msg']) > 0) true or not and also dump something in the catch block to see if exception is occurring or not.
You can also test by commenting out the updateOrCreate part to see if it is interfering.

DB Transaction Laravel not rolling back

I'm trying to use Laravel DB facade to rollback my database transactions if a certain condition is met. But my rollback is not working.
First I started my DB transaction with DB::beginTransaction();
Secondly, I wrote my queries to create the following :
Create a new payment batch
Create payment batch record
Create payment approval
Create payment approvers
Thirdly I then did some condition to know when to rollback or commit my DB transactions. But the rollback is not working. Example this block of condition was met in my code but never rollback
if(!$mail_status){
DB::rollBack();
return back()->with('error', 'Mail not send to approvers. Try again or contact system administrator.');
}
Below is the full block of code.
//Start transaction
DB::beginTransaction();
try{
//Run Queries
//Create a new payment batch
$batch = Batch::create([
'uuid' => uniqid(),
'batch_name' => $batch_config->batch_name,
'service_code' => Auth::user()->service_code,
'created_by' => Auth::user()->name,
]);
//Create parameters for new payment batch record
foreach($batch_record_config as $data){
$record[] = [
'batch_id' => $batch->batch_id,
'payee_name' => $data['payee_name'],
'bank_id' => $data['bank_id'],
'bank_name' => $data['bank_name'],
'account_type' => $data['account_type'],
'account_number' => $data['account_number'],
'amount' => $data['amount'],
'description' => $data['description'],
'year' => date("Y"),
'month' => date("F", mktime(0, 0, 0, date("n"), 10)),
'uuid' => uniqid(),
'service_code' => Auth::user()->service_code,
'created_at' => now(),
'created_by' => Auth::user()->name,
];
}
$batch_record_status = DB::table('batch_record')->insert($record);
//Generate new payment approval
$payment_approval = PaymentApproval::create([
'uuid' => uniqid(),
'batch_id' => $batch->batch_id,
'service_code' => Auth::user()->service_code,
'created_by' => Auth::user()->name,
'deleted' => 0,
]);
//Generate payment approvers for the payment approval
$payment_approvers = [];
foreach($approval_users as $data){
$payment_approvers[] = [
'uuid' => uniqid(),
'payment_approval_id' => $payment_approval->id,
'approval_user_id' => $data->id,
'approval_level_id' => $data->approval_level_id,
'batch_id' => $batch->batch_id,
'approval_position_value' => $data->approval_level->approval_position,
'approval_level_name' => $data->approval_level->approval_level,
'user_id' => $data->user_id,
'batch_name' => $batch->batch_name,
'service_code' => Auth::user()->service_code,
'approved' => 0,
'deleted' => 0,
'created_at' => now(),
'created_by' => Auth::user()->name,
];
}
$payment_approvers_status = DB::table('payment_approvers')->insert($payment_approvers);
// If DB transcation is successful.
if($batch && $batch_record_status && $payment_approval && $payment_approvers_status){
//get the next approvers
$next_approvers = $this->next_approvals($payment_approval->id);
if($next_approvers){
//send mail to the next approvers
$mail_status = $this->send_approvers_mail($next_approvers,$batch);
if(!$mail_status){
DB::rollBack();
return back()->with('error', 'Mail not send to approvers. Try again or contact system administrator.');
}else{
DB::commit();
return back()->with('success', 'Batch has being initialized');
}
}else{
DB::rollBack();
return back()->with('error', 'Approvers not found. Try again or contact system administrator.');
}
}else{
DB::rollBack();
return back()->with('error', 'Something went wrong. Try again.');
}
}catch(\Exception $e){
DB::rollBack();
dd($e);
return back()->with('error', 'Something went wrong... Contact system administrator. Thanks.');
}
I had a look at your and as others have rightly pointed you need to commit the transactions. Which i can't see in your code. There are basically two ways of using transactions which i am aware of.
1.
\DB::beginTransaction();
try {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
} catch (\Exception $e) {
$count = 0;
\DB::rollBack();
\Log::info(self::LOG_PREFIX . 'Some Msg ', [$e->__toString()]);
}
\DB::commit();
2.
DB::transaction(function () {
DB::table('users')->update(['votes' => 1]);
DB::table('posts')->delete();
});
If you're using second method you won't have to do any commit, this will be rolled back automatically on failure.
I persume the error is in this line of yours
$next_approvers = $this->next_approvals($payment_approval->id);
I couldn't find *next_approvals8 property defined in your code, Plus you're using both Laravel ORM and Raw Statements (PaymentApproval::create & DB::table('batch_record')->insert($record);)
Try sticking to 1 and see if this works.
You didn't mention that either your DB get commit or throw some error?.Check what are you getting in mail function response. I thin you got success in mail because your else condition is not execute any case.

Omnipay 3Dsecure redirect

I am using Omnipay to allow users to pay using Cardsave.
I have the following:
\Omnipay::setTestMode(true);
$transactionId = date('YmdHis').$booking->space->id.$booking->user->id;
$response = $gateway->purchase([
'amount' => $booking->price,
'currency' => 'GBP',
'card' => $card,
'transactionId' => $transactionId,
'cancelUrl' => \base_url('cardsave/cancel/'.$booking->id),
'returnUrl' => \base_url('cardsave/confirm/'.$booking->id)
])->send();
if ($response->isSuccessful()) {
$transactionReference = $response->getTransactionReference();
//save the transaction reference in case of refund
return ['status' => 'success', 'message' => 'Reservation process complete'];
} elseif ($response->isRedirect()) {
\Log::info('3DSecure redirect');
$booking->addAdditional(['3dsecure_transaction_id' => $transactionId]);
return [
'status' => 'redirect',
'form_html' => $response->getRedirectResponse()->getContent()
];
}
throw new PaymentException ($response->getMessage());
and my confirm url goes to the following method:
$transactionId = $booking->getAdditional('3dsecure_transaction_id');
$response = $gateway->completePurchase([
'amount' => $amount,
'transactionId' => $transactionId,
'currency' => 'GBP',
])->send();
if ($response->isSuccessful()) {
$transactionReference = $response->getTransactionReference();
return $this->finalise($booking, $transactionReference);
} else {
$this->cancel($booking);
}
But looking through the code for league/omnipay-cardsave, I see the following:
$md = $this->httpRequest->request->get('MD');
$paRes = $this->httpRequest->request->get('PaRes');
if (empty($md) || empty($paRes)) {
throw new InvalidResponseException;
}
So my question is (and I realise it is probably dumb, but I can't seem to grok this, for some reason), where is that request coming from, if I just instantiated the gateway?
I think I am doing this wrong.
EDIT:
I have discovered that the return call from the 3DSecure thing comes with the MD and PaRes values as POST parameters. This allows me to set them on the gateway. How do I do that? Is it done automatically when I instantiate the gateway?
I was right, the question was dumb.
After reading the code, and trying it out, I found out that the AbstractGateway uses Symfony's request class to automatically pickup POST variables, amongst which are in this case, 'MD' and 'PaRes'.
In fact, it says so in the CompletePurchase class:
$md = $this->httpRequest->request->get('MD');
$paRes = $this->httpRequest->request->get('PaRes');
httpRequest is setup in AbstractGateway.
Basically, it just works.

Categories