Enables Stripe SCA payments

This commit is contained in:
Jeremy Quinton 2019-08-29 21:23:05 +02:00 committed by merdiano
parent a1c0b04ea4
commit 25181538e8
5 changed files with 255 additions and 96 deletions

View File

@ -16,7 +16,6 @@ use App\Models\QuestionAnswer;
use App\Models\ReservedTickets;
use App\Models\Ticket;
use App\Services\Order as OrderService;
use Services\PaymentGateway\Factory as PaymentGatewayFactory;
use Carbon\Carbon;
use Cookie;
use DB;
@ -181,17 +180,22 @@ class EventCheckoutController extends Controller
]);
}
$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',
]);
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;
}
$paymentGateway = $activeAccountPaymentGateway ? $activeAccountPaymentGateway->payment_gateway : false;
/*
* The 'ticket_order_{event_id}' session stores everything we need to complete the transaction.
*/
@ -275,6 +279,8 @@ 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([
@ -286,13 +292,6 @@ 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,7 +353,16 @@ 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;
switch ($payment_gateway->id) {
case config('attendize.payment_gateway_stripe_sca'):
\Stripe\Stripe::setApiKey($account_payment_gateway->config['apiKey']);
$intent = \Stripe\PaymentIntent::create([
'amount' => $order_total,
'currency' => $event->currency->code,
]);
break;
}
$secondsToExpire = Carbon::now()->diffInSeconds($order_session['expires']);
@ -365,83 +373,164 @@ 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,
'payment_failed' => $payment_failed
'secondsToExpire' => $secondsToExpire
];
if (!empty($intent)) {
$viewData['client_secret'] = $intent->client_secret;
}
return view('Public.ViewEvent.EventPagePayment', $viewData);
}
/**
* Create the order and start the payment for the order via Omnipay
*
* Create the order, update stats, fire off email jobs then redirect user
*
* @param Request $request
* @param $event_id
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
* @throws \Exception
* @return \Illuminate\Http\JsonResponse
*/
public function postCreateOrder(Request $request, $event_id)
{
$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);
//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']));
$ticket_order = session()->get('ticket_order_' . $event_id);
$event = Event::findOrFail($event_id);
$order_requires_payment = $ticket_order['order_requires_payment'];
$orderRequiresPayment = $ticket_order['order_requires_payment'];
if ($order_requires_payment && $request->get('pay_offline') && $event->enable_offline_payments) {
if ($orderRequiresPayment && $request->get('pay_offline') && $event->enable_offline_payments) {
return $this->completeOrder($event_id);
}
if (!$order_requires_payment) {
if (!$orderRequiresPayment) {
return $this->completeOrder($event_id);
}
try {
//more transation data being put in here.
$transaction_data = [];
if (config('attendize.enable_dummy_payment_gateway') == TRUE) {
$formData = config('attendize.fake_card_data');
$transaction_data = [
'card' => $formData
];
$order_service = new OrderService($ticket_order['order_total'], $ticket_order['total_booking_fee'], $event);
$order_service->calculateFinalCosts();
$gateway = Omnipay::create('Dummy');
$gateway->initialize();
$payment_gateway_config = $ticket_order['account_payment_gateway']->config + [
'testMode' => config('attendize.enable_test_payments')];
} else {
$payment_gateway_factory = new PaymentGatewayFactory();
$gateway = $payment_gateway_factory->create($ticket_order['payment_gateway']->name, $payment_gateway_config);
//certain payment gateways require an extra parameter here and there so this method takes care of that
//and sets certain options for the gateway that can be used when the transaction is started
$gateway->extractRequestParameters($request);
$gateway = Omnipay::create($ticket_order['payment_gateway']->name);
$gateway->initialize($ticket_order['account_payment_gateway']->config + [
'testMode' => config('attendize.enable_test_payments'),
]);
}
//generic data that is needed for most orders
$order_total = $order_service->getGrandTotal();
$order_email = $ticket_order['request_data'][0]['order_email'];
$orderService = new OrderService($ticket_order['order_total'], $ticket_order['total_booking_fee'], $event);
$orderService->calculateFinalCosts();
$response = $gateway->startTransaction($order_total, $order_email, $event);
$transaction_data += [
'amount' => $orderService->getGrandTotal(),
'currency' => $event->currency->code,
'description' => 'Order for customer: ' . $request->get('order_email'),
];
//TODO: class with an interface that builds the transaction data.
switch ($ticket_order['payment_gateway']->id) {
case config('attendize.payment_gateway_dummy'):
$token = uniqid();
$transaction_data += [
'token' => $token,
'receipt_email' => $request->get('order_email'),
'card' => $formData
];
break;
case config('attendize.payment_gateway_paypal'):
$transaction_data += [
'cancelUrl' => route('showEventCheckoutPaymentReturn', [
'event_id' => $event_id,
'is_payment_cancelled' => 1
]),
'returnUrl' => route('showEventCheckoutPaymentReturn', [
'event_id' => $event_id,
'is_payment_successful' => 1
]),
'brandName' => isset($ticket_order['account_payment_gateway']->config['brandingName'])
? $ticket_order['account_payment_gateway']->config['brandingName']
: $event->organiser->name
];
$transaction = $gateway->purchase($transaction_data);
$response = $transaction->send();
break;
case config('attendize.payment_gateway_stripe'):
$token = $request->get('stripeToken');
$transaction_data += [
'token' => $token,
'receipt_email' => $request->get('order_email'),
'returnUrl' => route('showEventCheckoutPaymentReturn', [
'event_id' => $event_id,
'is_payment_successful' => 1
]),
];
$transaction = $gateway->purchase($transaction_data);
$response = $transaction->send();
break;
case config('attendize.payment_gateway_stripe_sca'):
Log::error($ticket_order['payment_gateway']->name);
$paymentMethod = $request->get('paymentMethod');
$returnUrl = route('showEventCheckoutPaymentReturn', [
'event_id' => $event_id,
'is_payment_successful' => 1,
]);
$transaction_data += [
'paymentMethod' => $paymentMethod,
'receipt_email' => 'jeremyquinton@gmail.com', //$request->get('order_email'),
'returnUrl' => $returnUrl,
'confirm' => true,
];
Log::error(print_r($transaction_data, true));
$response = $gateway->authorize($transaction_data)->send();
break;
default:
Log::error('No payment gateway configured.');
return response()->json([
'status' => 'error',
'message' => 'No payment gateway configured.'
]);
break;
}
if ($response->isSuccessful()) {
session()->push('ticket_order_' . $event_id . '.transaction_id',
$response->getTransactionReference());
$additionalData = ($gateway->storeAdditionalData()) ? $gateway->getAdditionalData($response) : array();
session()->push('ticket_order_' . $event_id . '.transaction_data',
$gateway->getTransactionData() + $additionalData);
return $this->completeOrder($event_id);
} elseif ($response->isRedirect()) {
$additionalData = ($gateway->storeAdditionalData()) ? $gateway->getAdditionalData($response) : array();
session()->push('ticket_order_' . $event_id . '.transaction_data',
$gateway->getTransactionData() + $additionalData);
/*
* 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', $transaction_data);
Log::info("Redirect url: " . $response->getRedirectUrl());
$return = [
@ -479,38 +568,45 @@ class EventCheckoutController extends Controller
}
/**
* Handles the return when a payment is off site
* Attempt to complete a user's payment when they return from
* an off-site gateway
*
* @param Request $request
* @param $event_id
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
* @throws \Exception
*/
public function showEventCheckoutPaymentReturn(Request $request, $event_id)
{
if ($request->get('is_payment_cancelled') == '1') {
session()->flash('message', trans('Event.payment_cancelled'));
return response()->redirectToRoute('showEventCheckout', [
'event_id' => $event_id,
'is_payment_cancelled' => 1,
]);
}
$ticket_order = session()->get('ticket_order_' . $event_id);
$gateway = Omnipay::create($ticket_order['payment_gateway']->name);
$payment_gateway_config = $ticket_order['account_payment_gateway']->config + [
'testMode' => config('attendize.enable_test_payments')];
$gateway->initialize($ticket_order['account_payment_gateway']->config + [
'testMode' => config('attendize.enable_test_payments'),
]);
$payment_gateway_factory = new PaymentGatewayFactory();
$gateway = $payment_gateway_factory->create($ticket_order['payment_gateway']->name, $payment_gateway_config);
$gateway->extractRequestParameters($request);
$response = $gateway->completeTransaction($ticket_order['transaction_data'][0]);
$transaction = $gateway->completePurchase($ticket_order['transaction_data'][0]);
$response = $transaction->send();
if ($response->isSuccessful()) {
session()->push('ticket_order_' . $event_id . '.transaction_id', $response->getTransactionReference());
return $this->completeOrder($event_id, false);
} else {
session()->flash('message', $response->getMessage());
return response()->redirectToRoute('showEventPayment', [
return response()->redirectToRoute('showEventCheckout', [
'event_id' => $event_id,
'is_payment_failed' => 1,
]);
}
}
/**
@ -541,11 +637,6 @@ class EventCheckoutController extends Controller
if (isset($ticket_order['transaction_id'])) {
$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;
}
@ -808,3 +899,4 @@ class EventCheckoutController extends Controller
}
}

View File

@ -14,9 +14,6 @@ use HttpClient;
use Illuminate\Http\Request;
use Input;
use Mail;
use Services\PaymentGateway\Dummy;
use Services\PaymentGateway\Stripe;
use Services\PaymentGateway\StripeSCA;
use Validator;
use GuzzleHttp\Client;
@ -130,24 +127,20 @@ class ManageAccountController extends MyBaseController
$account = Account::find(Auth::user()->account_id);
$gateway_id = $request->get('payment_gateway');
$payment_gateway = PaymentGateway::where('id', '=', $gateway_id)->first();
$config = [];
switch ($payment_gateway->name) {
case Stripe::GATEWAY_NAME :
switch ($gateway_id) {
case config('attendize.payment_gateway_stripe') : //Stripe
$config = $request->get('stripe');
break;
case StripeSCA::GATEWAY_NAME :
case config('attendize.payment_gateway_paypal') : //PayPal
$config = $request->get('paypal');
break;
case config('attendize.payment_gateway_stripe_sca') : //Stripe SCA
$config = $request->get('stripe_sca');
break;
case Dummy::GATEWAY_NAME :
break;
}
PaymentGateway::query()->update(['default' => 0]);
$payment_gateway = PaymentGateway::where('id', '=', $gateway_id)->first();
$payment_gateway->default = 1;
$payment_gateway->save();

View File

@ -57,7 +57,6 @@
"symfony/css-selector": "~3.0",
"backpack/generators": "^1.2",
"laracasts/generators": "dev-master"
},
"autoload": {
"classmap": [

View File

@ -58,7 +58,7 @@
</div>
<div class="col-md-8 col-md-pull-4">
<div class="event_order_form">
{!! Form::open(['url' => route('postValidateOrder', ['event_id' => $event->id ]), 'class' => 'ajax payment-form']) !!}
{!! Form::open(['url' => route('postCreateOrder', ['event_id' => $event->id]), 'class' => ($order_requires_payment && @$payment_gateway->is_on_site) ? 'ajax payment-form' : 'ajax', 'data-stripe-pub-key' => isset($account_payment_gateway->config['publishableKey']) ? $account_payment_gateway->config['publishableKey'] : '']) !!}
{!! Form::hidden('event_id', $event->id) !!}
@ -143,6 +143,82 @@
</div>
</div>
<style>
.offline_payment_toggle {
padding: 20px 0;
}
</style>
@if($order_requires_payment)
<h3>@lang("Public_ViewEvent.payment_information")</h3>
@lang("Public_ViewEvent.below_payment_information_header")
@if($event->enable_offline_payments)
<div class="offline_payment_toggle">
<div class="custom-checkbox">
@if($payment_gateway === false)
{{-- Force offline payment if no gateway --}}
<input type="hidden" name="pay_offline" value="1">
<input id="pay_offline" type="checkbox" value="1" checked disabled>
@else
<input data-toggle="toggle" id="pay_offline" name="pay_offline" type="checkbox" value="1">
@endif
<label for="pay_offline">@lang("Public_ViewEvent.pay_using_offline_methods")</label>
</div>
</div>
<div class="offline_payment" style="display: none;">
<h5>@lang("Public_ViewEvent.offline_payment_instructions")</h5>
<div class="well">
{!! Markdown::parse($event->offline_payment_instructions) !!}
</div>
</div>
@endif
@if(@$payment_gateway->is_on_site)
<div class="online_payment">
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! Form::label('card-number', trans("Public_ViewEvent.card_number")) !!}
<input required="required" type="text" autocomplete="off" placeholder="**** **** **** ****" class="form-control card-number" size="20" data-stripe="number">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group">
{!! Form::label('card-expiry-month', trans("Public_ViewEvent.expiry_month")) !!}
{!! Form::selectRange('card-expiry-month',1,12,null, [
'class' => 'form-control card-expiry-month',
'data-stripe' => 'exp_month'
] ) !!}
</div>
</div>
<div class="col-xs-6">
<div class="form-group">
{!! Form::label('card-expiry-year', trans("Public_ViewEvent.expiry_year")) !!}
{!! Form::selectRange('card-expiry-year',date('Y'),date('Y')+10,null, [
'class' => 'form-control card-expiry-year',
'data-stripe' => 'exp_year'
] ) !!}</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
{!! Form::label('card-expiry-year', trans("Public_ViewEvent.cvc_number")) !!}
<input required="required" placeholder="***" class="form-control card-cvc" data-stripe="cvc">
</div>
</div>
</div>
</div>
@endif
@endif
@if($event->pre_order_display_message)
<div class="well well-small">
{!! nl2br(e($event->pre_order_display_message)) !!}

View File

@ -4,13 +4,6 @@
@lang("Public_ViewEvent.payment_information")
</h1>
</div>
@if($payment_failed)
<div class="row">
<div class="col-md-8 alert-danger" style="text-align: left; padding: 10px">
@lang("Order.payment_failed")
</div>
</div>
@endif
<div class="row">
<div class="col-md-12" style="text-align: center">
@lang("Public_ViewEvent.below_order_details_header")
@ -66,12 +59,18 @@
<div class="col-md-8 col-md-pull-4">
<div class="row">
{{ $payment_gateway->name }}
@if($order_requires_payment)
@include('Public.ViewEvent.Partials.OfflinePayments')
@endif
@if(View::exists($payment_gateway['checkout_blade_template']))
@include($payment_gateway['checkout_blade_template'])
@if($payment_gateway->name == 'Stripe')
@include('Public.ViewEvent.Partials.PaymentStripe')
@endif
@if($payment_gateway->name == 'Stripe\PaymentIntents')
@include('Public.ViewEvent.Partials.PaymentStripeSCA')
@endif
</div>