From 0c1f4c86194f1b6d7cd8d47b59ff8e4e84b9d1ea Mon Sep 17 00:00:00 2001 From: Jeremy Quinton Date: Mon, 9 Sep 2019 16:42:01 +0200 Subject: [PATCH] Update Refund journeys 1. Updated the refund journeys so that they use the payment Gateway Factory. 2. Improved request data going in and out of the session. 3. Show error message when Stripe SCA journey fails. 4. Update migration. --- .../Controllers/EventCheckoutController.php | 59 +++++++++++-------- .../Controllers/EventOrdersController.php | 31 +++++----- app/Services/PaymentGateway/Dummy.php | 27 +++++++++ app/Services/PaymentGateway/Stripe.php | 36 ++++++++++- app/Services/PaymentGateway/StripeSCA.php | 51 +++++++++++++--- ...2019_09_04_075835_add_default_gateways.php | 4 ++ resources/lang/en/Order.php | 2 + .../ManageEvent/Modals/ManageOrder.blade.php | 6 ++ .../Partials/EventPaymentSection.blade.php | 7 +++ 9 files changed, 172 insertions(+), 51 deletions(-) diff --git a/app/Http/Controllers/EventCheckoutController.php b/app/Http/Controllers/EventCheckoutController.php index 7e08f05a..f6031828 100644 --- a/app/Http/Controllers/EventCheckoutController.php +++ b/app/Http/Controllers/EventCheckoutController.php @@ -181,22 +181,17 @@ class EventCheckoutController extends Controller ]); } - if (config('attendize.enable_dummy_payment_gateway') == TRUE) { - $activeAccountPaymentGateway = new AccountPaymentGateway(); - $activeAccountPaymentGateway->fill(['payment_gateway_id' => config('attendize.payment_gateway_dummy')]); - $paymentGateway = $activeAccountPaymentGateway; - } else { - $activeAccountPaymentGateway = $event->account->getGateway($event->account->payment_gateway_id); - //if no payment gateway configured and no offline pay, don't go to the next step and show user error - if (empty($activeAccountPaymentGateway) && !$event->enable_offline_payments) { - return response()->json([ - 'status' => 'error', - 'message' => 'No payment gateway configured', - ]); - } - $paymentGateway = $activeAccountPaymentGateway ? $activeAccountPaymentGateway->payment_gateway : false; + $activeAccountPaymentGateway = $event->account->getGateway($event->account->payment_gateway_id); + //if no payment gateway configured and no offline pay, don't go to the next step and show user error + if (empty($activeAccountPaymentGateway) && !$event->enable_offline_payments) { + return response()->json([ + 'status' => 'error', + 'message' => 'No payment gateway configured', + ]); } + $paymentGateway = $activeAccountPaymentGateway ? $activeAccountPaymentGateway->payment_gateway : false; + /* * The 'ticket_order_{event_id}' session stores everything we need to complete the transaction. */ @@ -280,8 +275,6 @@ class EventCheckoutController extends Controller public function postValidateOrder(Request $request, $event_id) { - session()->push('ticket_order_' . $event_id . '.request_data', $request->all()); - //If there's no session kill the request and redirect back to the event homepage. if (!session()->get('ticket_order_' . $event_id)) { return response()->json([ @@ -293,6 +286,13 @@ class EventCheckoutController extends Controller ]); } + $request_data = session()->get('ticket_order_' . $event_id . ".request_data"); + $request_data = (!empty($request_data[0])) ? array_merge($request_data[0], $request->all()) + : $request->all(); + + session()->remove('ticket_order_' . $event_id . '.request_data'); + session()->push('ticket_order_' . $event_id . '.request_data', $request_data); + $event = Event::findOrFail($event_id); $order = new Order(); $ticket_order = session()->get('ticket_order_' . $event_id); @@ -354,6 +354,8 @@ class EventCheckoutController extends Controller $orderService = new OrderService($order_session['order_total'], $order_session['total_booking_fee'], $event); $orderService->calculateFinalCosts(); + $payment_failed = $request->get('is_payment_failed') ? 1 : 0; + $secondsToExpire = Carbon::now()->diffInSeconds($order_session['expires']); $viewData = ['event' => $event, @@ -363,7 +365,8 @@ class EventCheckoutController extends Controller 'order_requires_payment' => (ceil($order_session['order_total']) == 0) ? false : true, 'account_payment_gateway' => $account_payment_gateway, 'payment_gateway' => $payment_gateway, - 'secondsToExpire' => $secondsToExpire + 'secondsToExpire' => $secondsToExpire, + 'payment_failed' => $payment_failed ]; return view('Public.ViewEvent.EventPagePayment', $viewData); @@ -380,10 +383,14 @@ class EventCheckoutController extends Controller */ public function postCreateOrder(Request $request, $event_id) { - //Add the request data to a session in case payment is required off-site - session()->push('ticket_order_' . $event_id . '.request_data', $request->except(['cardnumber', 'cvc'])); + $request_data = $ticket_order = session()->get('ticket_order_' . $event_id . ".request_data"); + $request_data = array_merge($request_data[0], $request->except(['cardnumber', 'cvc'])); + + session()->remove('ticket_order_' . $event_id . '.request_data'); + session()->push('ticket_order_' . $event_id . '.request_data', $request_data); $ticket_order = session()->get('ticket_order_' . $event_id); + $event = Event::findOrFail($event_id); $order_requires_payment = $ticket_order['order_requires_payment']; @@ -416,7 +423,6 @@ class EventCheckoutController extends Controller $response = $gateway->startTransaction($order_total, $order_email, $event); - if ($response->isSuccessful()) { session()->push('ticket_order_' . $event_id . '.transaction_id', @@ -426,12 +432,11 @@ class EventCheckoutController extends Controller } elseif ($response->isRedirect()) { - Log::error("reference : " . $response->getTransactionReference()); + $additionalData = ($gateway->storeAdditionalData()) ? $gateway->getAdditionalData($response) : array(); - //As we're going off-site for payment we need to store some data in a session so it's available - //when we return + session()->push('ticket_order_' . $event_id . '.transaction_data', + $gateway->getTransactionData() + $additionalData); - session()->push('ticket_order_' . $event_id . '.transaction_data', $gateway->getTransactionData()); Log::info("Redirect url: " . $response->getRedirectUrl()); @@ -496,7 +501,7 @@ class EventCheckoutController extends Controller return $this->completeOrder($event_id, false); } else { session()->flash('message', $response->getMessage()); - return response()->redirectToRoute('showEventCheckout', [ + return response()->redirectToRoute('showEventPayment', [ 'event_id' => $event_id, 'is_payment_failed' => 1, ]); @@ -533,6 +538,10 @@ class EventCheckoutController extends Controller $order->transaction_id = $ticket_order['transaction_id'][0]; } + if (isset($ticket_order['transaction_data'][0]['payment_intent'])) { + $order->payment_intent = $ticket_order['transaction_data'][0]['payment_intent']; + } + if ($ticket_order['order_requires_payment'] && !isset($request_data['pay_offline'])) { $order->payment_gateway_id = $ticket_order['payment_gateway']->id; } diff --git a/app/Http/Controllers/EventOrdersController.php b/app/Http/Controllers/EventOrdersController.php index 37a882c9..c5be49fc 100644 --- a/app/Http/Controllers/EventOrdersController.php +++ b/app/Http/Controllers/EventOrdersController.php @@ -8,7 +8,9 @@ use App\Models\Attendee; use App\Models\Event; use App\Models\EventStats; use App\Models\Order; +use App\Models\PaymentGateway; use App\Services\Order as OrderService; +use App\Services\PaymentGateway\Factory as PaymentGatewayFactory; use DB; use Excel; use Illuminate\Http\Request; @@ -196,11 +198,10 @@ class EventOrdersController extends MyBaseController /** - * Cancels an order - * * @param Request $request * @param $order_id - * @return mixed + * @return \Illuminate\Http\JsonResponse + * @throws \Exception */ public function postCancelOrder(Request $request, $order_id) { @@ -244,24 +245,23 @@ class EventOrdersController extends MyBaseController $order->event->currency))]); } if (!$error_message) { - try { - $gateway = Omnipay::create($order->payment_gateway->name); - $gateway->initialize($order->account->getGateway($order->payment_gateway->id)->config); + try { + + $payment_gateway_config = $order->account->getGateway($order->payment_gateway->id)->config + [ + 'testMode' => config('attendize.enable_test_payments')]; + + $payment_gateway_factory = new PaymentGatewayFactory(); + $gateway = $payment_gateway_factory->create($order->payment_gateway->name, $payment_gateway_config); if ($refund_type === 'full') { /* Full refund */ $refund_amount = $order->organiser_amount - $order->amount_refunded; } - $request = $gateway->refund([ - 'transactionReference' => $order->transaction_id, - 'amount' => $refund_amount, - 'refundApplicationFee' => floatval($order->booking_fee) > 0 ? true : false, - ]); + $refund_application_fee = floatval($order->booking_fee) > 0 ? true : false; + $response = $gateway->refundTransaction($order, $refund_amount, $refund_application_fee); - $response = $request->send(); - - if ($response->isSuccessful()) { + if ($response['successful']) { /* Update the event sales volume*/ $order->event->decrement('sales_volume', $refund_amount); $order->amount_refunded = round(($order->amount_refunded + $refund_amount), 2); @@ -274,10 +274,11 @@ class EventOrdersController extends MyBaseController $order->order_status_id = config('attendize.order_partially_refunded'); } } else { - $error_message = $response->getMessage(); + $error_message = $response['error_message']; } $order->save(); + } catch (\Exeption $e) { Log::error($e); $error_message = trans("Controllers.refund_exception"); diff --git a/app/Services/PaymentGateway/Dummy.php b/app/Services/PaymentGateway/Dummy.php index 09635bce..78f324f8 100644 --- a/app/Services/PaymentGateway/Dummy.php +++ b/app/Services/PaymentGateway/Dummy.php @@ -49,4 +49,31 @@ class Dummy public function extractRequestParameters($request) {} public function completeTransaction($transactionId) {} + + public function getAdditionalData() {} + + public function storeAdditionalData() { + return false; + } + + public function refundTransaction($order, $refund_amount, $refund_application_fee) { + + $request = $this->gateway->refund([ + 'transactionReference' => $order->transaction_id, + 'amount' => $refund_amount, + 'refundApplicationFee' => $refund_application_fee + ]); + + $response = $request->send(); + + if ($response->isSuccessful()) { + $refundResponse['successful'] = true; + } else { + $refundResponse['successful'] = false; + $refundResponse['error_message'] = $response->getMessage(); + } + + return $refundResponse; + } + } \ No newline at end of file diff --git a/app/Services/PaymentGateway/Stripe.php b/app/Services/PaymentGateway/Stripe.php index d386d53a..15b2ea4c 100644 --- a/app/Services/PaymentGateway/Stripe.php +++ b/app/Services/PaymentGateway/Stripe.php @@ -42,7 +42,8 @@ class Stripe return $response; } - public function getTransactionData() { + public function getTransactionData() + { return $this->transaction_data; } @@ -55,6 +56,37 @@ class Stripe } } - public function completeTransaction($transactionId) {} + public function completeTransaction($transactionId) + { + } + public function getAdditionalData() + { + } + + public function storeAdditionalData() + { + return false; + } + + public function refundTransaction($order, $refund_amount, $refund_application_fee) + { + + $request = $this->gateway->refund([ + 'transactionReference' => $order->transaction_id, + 'amount' => $refund_amount, + 'refundApplicationFee' => $refund_application_fee + ]); + + $response = $request->send(); + + if ($response->isSuccessful()) { + $refundResponse['successful'] = true; + } else { + $refundResponse['successful'] = false; + $refundResponse['error_message'] = $response->getMessage(); + } + + return $refundResponse; + } } \ No newline at end of file diff --git a/app/Services/PaymentGateway/StripeSCA.php b/app/Services/PaymentGateway/StripeSCA.php index f6a7e64e..090ec9fe 100644 --- a/app/Services/PaymentGateway/StripeSCA.php +++ b/app/Services/PaymentGateway/StripeSCA.php @@ -2,7 +2,6 @@ namespace Services\PaymentGateway; - use Illuminate\Support\Facades\Log; class StripeSCA @@ -14,7 +13,7 @@ class StripeSCA private $gateway; - private $extra_params = ['paymentMethod','payment_intent']; + private $extra_params = ['paymentMethod', 'payment_intent']; public function __construct($gateway) { @@ -45,14 +44,14 @@ class StripeSCA public function startTransaction($order_total, $order_email, $event) { - $this->createTransactionData($order_total, $order_email, $event); $response = $this->gateway->authorize($this->transaction_data)->send(); return $response; } - public function getTransactionData() { + public function getTransactionData() + { return $this->transaction_data; } @@ -65,11 +64,12 @@ class StripeSCA } } - public function completeTransaction($transactionId = '') { + public function completeTransaction($transactionId = '') + { - $intentData = array( + $intentData = [ 'paymentIntentReference' => $this->options['payment_intent'], - ); + ]; $paymentIntent = $this->gateway->fetchPaymentIntent($intentData); $response = $paymentIntent->send(); @@ -77,8 +77,41 @@ class StripeSCA $response = $this->gateway->confirm($intentData)->send(); } - return $response; } - + + public function getAdditionalData($response) + { + + $additionalData['payment_intent'] = $response->getPaymentIntentReference(); + return $additionalData; + } + + public function storeAdditionalData() + { + return true; + } + + public function refundTransaction($order, $refund_amount, $refund_application_fee) + { + + $request = $this->gateway->cancel([ + 'transactionReference' => $order->transaction_id, + 'amount' => $refund_amount, + 'refundApplicationFee' => $refund_application_fee, + 'paymentIntentReference' => $order->payment_intent + ]); + + $response = $request->send(); + + if ($response->isCancelled()) { + $refundResponse['successful'] = true; + } else { + $refundResponse['successful'] = false; + $refundResponse['error_message'] = $response->getMessage(); + } + + return $refundResponse; + } + } \ No newline at end of file diff --git a/database/migrations/2019_09_04_075835_add_default_gateways.php b/database/migrations/2019_09_04_075835_add_default_gateways.php index 2d7f6daf..be455ae1 100644 --- a/database/migrations/2019_09_04_075835_add_default_gateways.php +++ b/database/migrations/2019_09_04_075835_add_default_gateways.php @@ -22,6 +22,10 @@ class AddDefaultGateways extends Migration $table->string('checkout_blade_template', 150)->default(''); }); + Schema::table('orders', function($table) { + $table->string('payment_intent', 150)->default(''); + }); + DB::table('payment_gateways') ->where('provider_name', 'Stripe') ->update(['admin_blade_template' => 'ManageAccount.Partials.Stripe', diff --git a/resources/lang/en/Order.php b/resources/lang/en/Order.php index 2577ddbc..39cc28bc 100644 --- a/resources/lang/en/Order.php +++ b/resources/lang/en/Order.php @@ -31,6 +31,8 @@ return array ( 'order_ref' => 'Reference', 'organiser_booking_fees' => 'Organiser Booking Fees', 'payment_gateway' => 'Payment Gateway', + 'payment_intent' => 'Payment Intent', + 'payment_failed' => 'Payment failed please try enter your payment details again.', 'price' => 'Price', 'purchase_date' => 'Purchase Date', 'quantity' => 'Quantity', diff --git a/resources/views/ManageEvent/Modals/ManageOrder.blade.php b/resources/views/ManageEvent/Modals/ManageOrder.blade.php index 8309d5d8..e4212741 100644 --- a/resources/views/ManageEvent/Modals/ManageOrder.blade.php +++ b/resources/views/ManageEvent/Modals/ManageOrder.blade.php @@ -70,6 +70,12 @@ @endif + @if($order->payment_intent) +
+ @lang("Order.payment_intent")
{{$order->payment_intent}} +
+ @endif + @if ($order->is_business)
@lang("Public_ViewEvent.business_name")
diff --git a/resources/views/Public/ViewEvent/Partials/EventPaymentSection.blade.php b/resources/views/Public/ViewEvent/Partials/EventPaymentSection.blade.php index a63b9b3a..417e11ae 100644 --- a/resources/views/Public/ViewEvent/Partials/EventPaymentSection.blade.php +++ b/resources/views/Public/ViewEvent/Partials/EventPaymentSection.blade.php @@ -4,6 +4,13 @@ @lang("Public_ViewEvent.payment_information")
+ @if($payment_failed) +
+
+ @lang("Order.payment_failed") +
+
+ @endif
@lang("Public_ViewEvent.below_order_details_header")