diff --git a/app/Http/Controllers/EventCheckoutController.php b/app/Http/Controllers/EventCheckoutController.php index 1a2006d2..7e08f05a 100644 --- a/app/Http/Controllers/EventCheckoutController.php +++ b/app/Http/Controllers/EventCheckoutController.php @@ -16,6 +16,7 @@ use App\Models\QuestionAnswer; use App\Models\ReservedTickets; use App\Models\Ticket; use App\Services\Order as OrderService; +use App\Services\PaymentGateway\Factory as PaymentGatewayFactory; use Carbon\Carbon; use Cookie; use DB; @@ -353,17 +354,6 @@ class EventCheckoutController extends Controller $orderService = new OrderService($order_session['order_total'], $order_session['total_booking_fee'], $event); $orderService->calculateFinalCosts(); - 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']); $viewData = ['event' => $event, @@ -376,19 +366,17 @@ class EventCheckoutController extends Controller 'secondsToExpire' => $secondsToExpire ]; - if (!empty($intent)) { - $viewData['client_secret'] = $intent->client_secret; - } - return view('Public.ViewEvent.EventPagePayment', $viewData); } /** - * Create the order, update stats, fire off email jobs then redirect user + * Create the order and start the payment for the order via Omnipay + * * * @param Request $request * @param $event_id - * @return \Illuminate\Http\JsonResponse + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse + * @throws \Exception */ public function postCreateOrder(Request $request, $event_id) { @@ -398,124 +386,36 @@ class EventCheckoutController extends Controller $ticket_order = session()->get('ticket_order_' . $event_id); $event = Event::findOrFail($event_id); - $orderRequiresPayment = $ticket_order['order_requires_payment']; + $order_requires_payment = $ticket_order['order_requires_payment']; - if ($orderRequiresPayment && $request->get('pay_offline') && $event->enable_offline_payments) { + if ($order_requires_payment && $request->get('pay_offline') && $event->enable_offline_payments) { return $this->completeOrder($event_id); } - if (!$orderRequiresPayment) { + if (!$order_requires_payment) { 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 - ]; - $gateway = Omnipay::create('Dummy'); - $gateway->initialize(); + $order_service = new OrderService($ticket_order['order_total'], $ticket_order['total_booking_fee'], $event); + $order_service->calculateFinalCosts(); - } else { + $payment_gateway_config = $ticket_order['account_payment_gateway']->config + [ + 'testMode' => config('attendize.enable_test_payments')]; - $gateway = Omnipay::create($ticket_order['payment_gateway']->name); - $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); + //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); - $orderService = new OrderService($ticket_order['order_total'], $ticket_order['total_booking_fee'], $event); - $orderService->calculateFinalCosts(); + //generic data that is needed for most orders + $order_total = $order_service->getGrandTotal(); + $order_email = $ticket_order['request_data'][0]['order_email']; - $transaction_data += [ - 'amount' => $orderService->getGrandTotal(), - 'currency' => $event->currency->code, - 'description' => 'Order for customer: ' . $request->get('order_email'), - ]; + $response = $gateway->startTransaction($order_total, $order_email, $event); - //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()) { @@ -526,11 +426,13 @@ class EventCheckoutController extends Controller } elseif ($response->isRedirect()) { - /* - * 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::error("reference : " . $response->getTransactionReference()); + + //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()); + Log::info("Redirect url: " . $response->getRedirectUrl()); $return = [ @@ -568,34 +470,26 @@ class EventCheckoutController extends Controller } /** - * Attempt to complete a user's payment when they return from - * an off-site gateway + * Handles the return when a payment is off site * * @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); - $gateway->initialize($ticket_order['account_payment_gateway']->config + [ - 'testMode' => config('attendize.enable_test_payments'), - ]); + $payment_gateway_config = $ticket_order['account_payment_gateway']->config + [ + 'testMode' => config('attendize.enable_test_payments')]; - $transaction = $gateway->completePurchase($ticket_order['transaction_data'][0]); + $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]); - $response = $transaction->send(); if ($response->isSuccessful()) { session()->push('ticket_order_' . $event_id . '.transaction_id', $response->getTransactionReference()); @@ -607,6 +501,7 @@ class EventCheckoutController extends Controller 'is_payment_failed' => 1, ]); } + } /** @@ -637,6 +532,7 @@ class EventCheckoutController extends Controller if (isset($ticket_order['transaction_id'])) { $order->transaction_id = $ticket_order['transaction_id'][0]; } + if ($ticket_order['order_requires_payment'] && !isset($request_data['pay_offline'])) { $order->payment_gateway_id = $ticket_order['payment_gateway']->id; } @@ -899,4 +795,3 @@ class EventCheckoutController extends Controller } } - diff --git a/app/Http/Controllers/ManageAccountController.php b/app/Http/Controllers/ManageAccountController.php index 66d64e00..20ec3be7 100644 --- a/app/Http/Controllers/ManageAccountController.php +++ b/app/Http/Controllers/ManageAccountController.php @@ -14,6 +14,9 @@ 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; @@ -127,20 +130,24 @@ class ManageAccountController extends MyBaseController $account = Account::find(Auth::user()->account_id); $gateway_id = $request->get('payment_gateway'); - switch ($gateway_id) { - case config('attendize.payment_gateway_stripe') : //Stripe + $payment_gateway = PaymentGateway::where('id', '=', $gateway_id)->first(); + + $config = []; + + switch ($payment_gateway->name) { + case Stripe::GATEWAY_NAME : $config = $request->get('stripe'); break; - case config('attendize.payment_gateway_paypal') : //PayPal - $config = $request->get('paypal'); - break; - case config('attendize.payment_gateway_stripe_sca') : //Stripe SCA + case StripeSCA::GATEWAY_NAME : $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(); diff --git a/app/Services/PaymentGateway/Dummy.php b/app/Services/PaymentGateway/Dummy.php new file mode 100644 index 00000000..09635bce --- /dev/null +++ b/app/Services/PaymentGateway/Dummy.php @@ -0,0 +1,52 @@ +gateway = $gateway; + $this->options = []; + } + + private function createTransactionData($order_total, $order_email, $event) + { + $token = uniqid(); + $this->transaction_data = [ + 'amount' => $order_total, + 'currency' => $event->currency->code, + 'description' => 'Order for customer: ' . $order_email, + 'card' => config('attendize.fake_card_data'), + 'token' => $token, + 'receipt_email' => $order_email + ]; + + return $this->transaction_data; + } + + public function startTransaction($order_total, $order_email, $event) + { + + $this->createTransactionData($order_total, $order_email, $event); + $transaction = $this->gateway->purchase($this->transaction_data); + $response = $transaction->send(); + + return $response; + } + + public function getTransactionData() { + return $this->transaction_data; + } + + public function extractRequestParameters($request) {} + + public function completeTransaction($transactionId) {} +} \ No newline at end of file diff --git a/app/Services/PaymentGateway/Factory.php b/app/Services/PaymentGateway/Factory.php new file mode 100644 index 00000000..cf4008f1 --- /dev/null +++ b/app/Services/PaymentGateway/Factory.php @@ -0,0 +1,67 @@ +initialize(); + + return new Dummy($gateway, $paymentGatewayConfig); + } + + case Stripe::GATEWAY_NAME : + { + + $gateway = Omnipay::create($name); + $gateway->initialize($paymentGatewayConfig); + + return new Stripe($gateway, $paymentGatewayConfig); + } + + case StripeSCA::GATEWAY_NAME : + { + + $gateway = Omnipay::create($name); + $gateway->initialize($paymentGatewayConfig); + + return new StripeSCA($gateway, $paymentGatewayConfig); + + } + + default : + { + throw New \Exception('Invalid gateway specified'); + } + } + } +} \ No newline at end of file diff --git a/app/Services/PaymentGateway/Stripe.php b/app/Services/PaymentGateway/Stripe.php new file mode 100644 index 00000000..d386d53a --- /dev/null +++ b/app/Services/PaymentGateway/Stripe.php @@ -0,0 +1,60 @@ +gateway = $gateway; + $this->options = []; + } + + private function createTransactionData($order_total, $order_email, $event) + { + $this->transaction_data = [ + 'amount' => $order_total, + 'currency' => $event->currency->code, + 'description' => 'Order for customer: ' . $order_email, + 'token' => $this->options['stripeToken'], + 'receipt_email' => $order_email + ]; + + return $this->transaction_data; + } + + public function startTransaction($order_total, $order_email, $event) + { + + $this->createTransactionData($order_total, $order_email, $event); + $transaction = $this->gateway->purchase($this->transaction_data); + $response = $transaction->send(); + + return $response; + } + + public function getTransactionData() { + return $this->transaction_data; + } + + public function extractRequestParameters($request) + { + foreach ($this->extra_params as $param) { + if (!empty($request->get($param))) { + $this->options[$param] = $request->get($param); + } + } + } + + public function completeTransaction($transactionId) {} + +} \ No newline at end of file diff --git a/app/Services/PaymentGateway/StripeSCA.php b/app/Services/PaymentGateway/StripeSCA.php new file mode 100644 index 00000000..f6a7e64e --- /dev/null +++ b/app/Services/PaymentGateway/StripeSCA.php @@ -0,0 +1,84 @@ +gateway = $gateway; + $this->options = []; + } + + private function createTransactionData($order_total, $order_email, $event) + { + + $returnUrl = route('showEventCheckoutPaymentReturn', [ + 'event_id' => $event->id, + 'is_payment_successful' => 1, + ]); + + $this->transaction_data = [ + 'amount' => $order_total, + 'currency' => $event->currency->code, + 'description' => 'Order for customer: ' . $order_email, + 'paymentMethod' => $this->options['paymentMethod'], + 'receipt_email' => $order_email, + 'returnUrl' => $returnUrl, + 'confirm' => true + ]; + + return $this->transaction_data; + } + + 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() { + return $this->transaction_data; + } + + public function extractRequestParameters($request) + { + foreach ($this->extra_params as $param) { + if (!empty($request->get($param))) { + $this->options[$param] = $request->get($param); + } + } + } + + public function completeTransaction($transactionId = '') { + + $intentData = array( + 'paymentIntentReference' => $this->options['payment_intent'], + ); + + $paymentIntent = $this->gateway->fetchPaymentIntent($intentData); + $response = $paymentIntent->send(); + if ($response->requiresConfirmation()) { + $response = $this->gateway->confirm($intentData)->send(); + } + + + return $response; + } + +} \ No newline at end of file diff --git a/composer.json b/composer.json index 9634cab0..ef99dd91 100755 --- a/composer.json +++ b/composer.json @@ -53,7 +53,8 @@ "database", "app/Http/Controllers", "app/Models", - "app/Attendize" + "app/Attendize", + "app/Services" ], "psr-4": { "App\\": "app/", diff --git a/database/migrations/2019_09_04_075835_add_default_gateways.php b/database/migrations/2019_09_04_075835_add_default_gateways.php new file mode 100644 index 00000000..2d7f6daf --- /dev/null +++ b/database/migrations/2019_09_04_075835_add_default_gateways.php @@ -0,0 +1,74 @@ +delete(); + + Schema::table('payment_gateways', function($table) { + $table->boolean('default')->default(0); + $table->string('admin_blade_template', 150)->default(''); + $table->string('checkout_blade_template', 150)->default(''); + }); + + DB::table('payment_gateways') + ->where('provider_name', 'Stripe') + ->update(['admin_blade_template' => 'ManageAccount.Partials.Stripe', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.PaymentStripe']); + + $dummyGateway = DB::table('payment_gateways')->where('name', '=', 'Dummy')->first(); + + if ($dummyGateway === null) { + // user doesn't exist + DB::table('payment_gateways')->insert( + array( + 'provider_name' => 'Dummy/Test Gateway', + 'provider_url' => 'none', + 'is_on_site' => 1, + 'can_refund' => 1, + 'name' => 'Dummy', + 'default' => 0, + 'admin_blade_template' => '', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.Dummy' + ) + ); + } + + $stripePaymentIntents = DB::table('payment_gateways')->where('name', '=', 'Stripe\PaymentIntents')->first(); + if ($stripePaymentIntents === null) { + DB::table('payment_gateways')->insert( + [ + 'provider_name' => 'Stripe SCA', + 'provider_url' => 'https://www.stripe.com', + 'is_on_site' => 0, + 'can_refund' => 1, + 'name' => 'Stripe\PaymentIntents', + 'default' => 0, + 'admin_blade_template' => 'ManageAccount.Partials.StripeSCA', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.PaymentStripeSCA' + ] + ); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} \ No newline at end of file diff --git a/database/seeds/PaymentGatewaySeeder.php b/database/seeds/PaymentGatewaySeeder.php index 1795872f..7d792922 100644 --- a/database/seeds/PaymentGatewaySeeder.php +++ b/database/seeds/PaymentGatewaySeeder.php @@ -11,27 +11,56 @@ class PaymentGatewaySeeder extends Seeder */ public function run() { - $payment_gateways = [ - [ - 'id' => 1, - 'name' => 'Stripe', - 'provider_name' => 'Stripe', - 'provider_url' => 'https://www.stripe.com', - 'is_on_site' => 1, - 'can_refund' => 1, - ], - [ - 'id' => 2, - 'name' => 'PayPal_Express', - 'provider_name' => 'PayPal Express', - 'provider_url' => 'https://www.paypal.com', - 'is_on_site' => 0, - 'can_refund' => 0 - ] - ]; + $dummyGateway = DB::table('payment_gateways')->where('name', '=', 'Dummy')->first(); - DB::table('payment_gateways')->insert($payment_gateways); + if ($dummyGateway === null) { + // user doesn't exist + DB::table('payment_gateways')->insert( + [ + 'provider_name' => 'Dummy/Test Gateway', + 'provider_url' => 'none', + 'is_on_site' => 1, + 'can_refund' => 1, + 'name' => 'Dummy', + 'default' => 0, + 'admin_blade_template' => '', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.Dummy' + ] + ); + } + + $stripe = DB::table('payment_gateways')->where('name', '=', 'Stripe')->first(); + if ($stripe === null) { + DB::table('payment_gateways')->insert( + [ + 'name' => 'Stripe', + 'provider_name' => 'Stripe', + 'provider_url' => 'https://www.stripe.com', + 'is_on_site' => 1, + 'can_refund' => 1, + 'default' => 0, + 'admin_blade_template' => 'ManageAccount.Partials.Stripe', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.PaymentStripe' + ] + ); + } + + $stripePaymentIntents = DB::table('payment_gateways')->where('name', '=', 'Stripe\PaymentIntents')->first(); + if ($stripePaymentIntents === null) { + DB::table('payment_gateways')->insert( + [ + 'provider_name' => 'Stripe SCA', + 'provider_url' => 'https://www.stripe.com', + 'is_on_site' => 0, + 'can_refund' => 1, + 'name' => 'Stripe\PaymentIntents', + 'default' => 0, + 'admin_blade_template' => 'ManageAccount.Partials.StripeSCA', + 'checkout_blade_template' => 'Public.ViewEvent.Partials.PaymentStripeSCA' + ] + ); + } } -} +} \ No newline at end of file diff --git a/resources/views/ManageAccount/Partials/PaymentGatewayOptions.blade.php b/resources/views/ManageAccount/Partials/PaymentGatewayOptions.blade.php index 94f0eff7..8c18e970 100644 --- a/resources/views/ManageAccount/Partials/PaymentGatewayOptions.blade.php +++ b/resources/views/ManageAccount/Partials/PaymentGatewayOptions.blade.php @@ -1,10 +1,10 @@ @@ -38,10 +36,9 @@ } }; - var cardElement = elements.create('card', {style: style}); + var cardElement = elements.create('card', {hidePostalCode: true, style: style}); cardElement.mount('#card-element'); - cardElement.addEventListener('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) {