From ff064367d6f64ce6c05249459ca8ea88c846c99a Mon Sep 17 00:00:00 2001 From: Joshua Dwire Date: Tue, 9 Jul 2019 16:20:02 -0400 Subject: [PATCH] Support Stripe 3D secure --- .../Controllers/OnlinePaymentController.php | 25 ++- .../Requests/CreateOnlinePaymentRequest.php | 6 +- .../PaymentDrivers/BasePaymentDriver.php | 138 ++++++++++--- .../PaymentActionRequiredException.php | 32 +++ .../PaymentDrivers/StripePaymentDriver.php | 192 ++++++++++++++++-- app/Services/PaymentService.php | 2 +- composer.json | 1 + composer.lock | 62 +++++- .../views/payments/credit_card.blade.php | 73 ++++--- .../views/payments/stripe/step2.blade.php | 30 +++ routes/web.php | 2 +- 11 files changed, 480 insertions(+), 83 deletions(-) create mode 100644 app/Ninja/PaymentDrivers/PaymentActionRequiredException.php create mode 100644 resources/views/payments/stripe/step2.blade.php diff --git a/app/Http/Controllers/OnlinePaymentController.php b/app/Http/Controllers/OnlinePaymentController.php index ca15dfc34404..ea7c08f147ac 100644 --- a/app/Http/Controllers/OnlinePaymentController.php +++ b/app/Http/Controllers/OnlinePaymentController.php @@ -11,6 +11,7 @@ use App\Models\Payment; use App\Models\PaymentMethod; use App\Models\Product; use App\Ninja\Mailers\UserMailer; +use App\Ninja\PaymentDrivers\PaymentActionRequiredException; use App\Ninja\Repositories\ClientRepository; use App\Ninja\Repositories\InvoiceRepository; use App\Services\InvoiceService; @@ -124,11 +125,18 @@ class OnlinePaymentController extends BaseController * * @return \Illuminate\Http\RedirectResponse */ - public function doPayment(CreateOnlinePaymentRequest $request, $invitationKey, $gatewayTypeAlias = false) + public function doPayment( + CreateOnlinePaymentRequest $request, + $invitationKey, + $gatewayTypeAlias = false, + $sourceId = false + ) { $invitation = $request->invitation; - if ($gatewayTypeAlias) { + if ($gatewayTypeAlias == GATEWAY_TYPE_TOKEN) { + $gatewayTypeId = $gatewayTypeAlias; + } elseif ($gatewayTypeAlias) { $gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias); } else { $gatewayTypeId = Session::get($invitation->id . 'gateway_type'); @@ -141,7 +149,16 @@ class OnlinePaymentController extends BaseController } try { - $paymentDriver->completeOnsitePurchase($request->all()); + // Load the payment method to charge. + // Currently only hit for saved cards that still require 3D secure verification. + $paymentMethod = null; + if ($sourceId) { + $paymentMethod = PaymentMethod::clientId($invitation->invoice->client_id) + ->wherePublicId($sourceId) + ->firstOrFail(); + } + + $paymentDriver->completeOnsitePurchase($request->all(), $paymentMethod); if (request()->capture) { return redirect('/client/dashboard')->withMessage(trans('texts.updated_payment_details')); @@ -152,6 +169,8 @@ class OnlinePaymentController extends BaseController } return $this->completePurchase($invitation); + } catch (PaymentActionRequiredException $exception) { + return $paymentDriver->startStepTwo($exception->getData()); } catch (Exception $exception) { return $this->error($paymentDriver, $exception, true); } diff --git a/app/Http/Requests/CreateOnlinePaymentRequest.php b/app/Http/Requests/CreateOnlinePaymentRequest.php index 387f25c0f84c..9149a30c551b 100644 --- a/app/Http/Requests/CreateOnlinePaymentRequest.php +++ b/app/Http/Requests/CreateOnlinePaymentRequest.php @@ -42,7 +42,11 @@ class CreateOnlinePaymentRequest extends Request $input['invitation'] = $invitation; if ($gatewayTypeAlias = request()->gateway_type) { - $input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias); + if ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) { + $input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias); + } else { + $input['gateway_type'] = $gatewayTypeAlias; + } } else { $input['gateway_type'] = session($invitation->id . 'gateway_type'); } diff --git a/app/Ninja/PaymentDrivers/BasePaymentDriver.php b/app/Ninja/PaymentDrivers/BasePaymentDriver.php index bb36eb1ad01a..cbd0132448af 100644 --- a/app/Ninja/PaymentDrivers/BasePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/BasePaymentDriver.php @@ -150,7 +150,12 @@ class BasePaymentDriver if (Session::has('error')) { Session::reflash(); } else { - $this->completeOnsitePurchase(); + try { + $this->completeOnsitePurchase(); + } catch (PaymentActionRequiredException $exception) { + return $this->startStepTwo($exception->getData(), $sourceId); + } + if ($redirectUrl = session('redirect_url:' . $this->invitation->invitation_key)) { $separator = strpos($redirectUrl, '?') === false ? '?' : '&'; @@ -169,29 +174,76 @@ class BasePaymentDriver } $data = [ - 'details' => ! empty($input['details']) ? json_decode($input['details']) : false, - 'accountGateway' => $this->accountGateway, + 'details' => ! empty($input['details']) ? json_decode($input['details']) : false, + 'accountGateway' => $this->accountGateway, 'acceptedCreditCardTypes' => $this->accountGateway->getCreditcardTypes(), - 'gateway' => $gateway, - 'showBreadcrumbs' => false, - 'url' => $url, - 'amount' => $this->invoice()->getRequestedAmount(), - 'invoiceNumber' => $this->invoice()->invoice_number, - 'client' => $this->client(), - 'contact' => $this->invitation->contact, - 'invitation' => $this->invitation, - 'gatewayType' => $this->gatewayType, - 'currencyId' => $this->client()->getCurrencyId(), - 'currencyCode' => $this->client()->getCurrencyCode(), - 'account' => $this->account(), - 'sourceId' => $sourceId, - 'tokenize' => $this->tokenize(), - 'transactionToken' => $this->createTransactionToken(), + 'gateway' => $gateway, + 'showBreadcrumbs' => false, + 'url' => $url, + 'amount' => $this->invoice()->getRequestedAmount(), + 'invoiceNumber' => $this->invoice()->invoice_number, + 'client' => $this->client(), + 'contact' => $this->invitation->contact, + 'invitation' => $this->invitation, + 'gatewayType' => $this->gatewayType, + 'currencyId' => $this->client()->getCurrencyId(), + 'currencyCode' => $this->client()->getCurrencyCode(), + 'account' => $this->account(), + 'sourceId' => $sourceId, + 'tokenize' => $this->tokenize(), + 'driver' => $this, + 'transactionToken' => $this->createTransactionToken(), ]; return view($this->paymentView(), $data); } + public function startStepTwo($data = null, $sourceId = false) + { + $url = 'payment/' . $this->invitation->invitation_key; + + if ($sourceId) { + $url .= '/token/' . $sourceId; + } + + if (request()->capture) { + $url .= '?capture=true'; + } + + $data = [ + 'step2_details' => $data, + 'url' => $url, + 'showBreadcrumbs' => false, + 'accountGateway' => $this->accountGateway, + 'gateway' => $this->accountGateway->gateway, + 'acceptedCreditCardTypes' => $this->accountGateway->getCreditcardTypes(), + 'amount' => $this->invoice()->getRequestedAmount(), + 'invoiceNumber' => $this->invoice()->invoice_number, + 'client' => $this->client(), + 'contact' => $this->invitation->contact, + 'invitation' => $this->invitation, + 'gatewayType' => $this->gatewayType, + 'currencyId' => $this->client()->getCurrencyId(), + 'currencyCode' => $this->client()->getCurrencyCode(), + 'account' => $this->account(), + 'tokenize' => $this->tokenize(), + 'transactionToken' => $this->createTransactionToken(), + ]; + + return view($this->stepTwoView(), $data); + } + + protected function stepTwoView() + { + $file = sprintf('%s/views/payments/%s/step2.blade.php', resource_path(), $this->providerName()); + + if (file_exists($file)) { + return sprintf('payments.%s/step2', $this->providerName()); + } else { + return 'payments.step2'; + } + } + // check if a custom view exists for this provider protected function paymentView() { @@ -267,10 +319,9 @@ class BasePaymentDriver return $this->gateway; } - public function completeOnsitePurchase($input = false, $paymentMethod = false) + protected function prepareOnsitePurchase($input = false, $paymentMethod = false) { $this->input = $input && count($input) ? $input : false; - $gateway = $this->gateway(); if ($input) { $this->updateClient(); @@ -278,16 +329,16 @@ class BasePaymentDriver // load or create token if ($this->isGatewayType(GATEWAY_TYPE_TOKEN)) { - if (! $paymentMethod) { + if ( ! $paymentMethod) { $paymentMethod = PaymentMethod::clientId($this->client()->id) - ->wherePublicId($this->sourceId) - ->firstOrFail(); + ->wherePublicId($this->sourceId) + ->firstOrFail(); } $invoicRepo = app('App\Ninja\Repositories\InvoiceRepository'); $invoicRepo->setGatewayFee($this->invoice(), $paymentMethod->payment_type->gateway_type_id); - if (! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) { + if ( ! $this->meetsGatewayTypeLimits($paymentMethod->payment_type->gateway_type_id)) { // The customer must have hacked the URL Session::flash('error', trans('texts.limits_not_met')); @@ -298,7 +349,7 @@ class BasePaymentDriver $paymentMethod = $this->createToken(); } - if (! $this->meetsGatewayTypeLimits($this->gatewayType)) { + if ( ! $this->meetsGatewayTypeLimits($this->gatewayType)) { // The customer must have hacked the URL Session::flash('error', trans('texts.limits_not_met')); @@ -311,7 +362,32 @@ class BasePaymentDriver } // prepare and process payment - $data = $this->paymentDetails($paymentMethod); + return $this->paymentDetails($paymentMethod); + } + + /** + * @param bool $input + * @param bool $paymentMethod + * @param bool $offSession True if this payment is being made automatically rather than manually initiated by the user. + * + * @return bool|mixed + * @throws PaymentActionRequiredException When further interaction is required from the user. + */ + public function completeOnsitePurchase($input = false, $paymentMethod = false, $offSession = false) + { + $data = $this->prepareOnsitePurchase($input, $paymentMethod); + + if ( ! $data) { + // No payment method to charge against yet; probably a 2-step or capture-only transaction. + return null; + } + + return $this->doOmnipayOnsitePurchase($data, $paymentMethod); + } + + protected function doOmnipayOnsitePurchase($data, $paymentMethod = false) + { + $gateway = $this->gateway(); // TODO move to payment driver class if ($this->isGateway(GATEWAY_SAGE_PAY_DIRECT) || $this->isGateway(GATEWAY_SAGE_PAY_SERVER)) { @@ -321,14 +397,14 @@ class BasePaymentDriver } else { $items = null; } - $response = $gateway->purchase($data) - ->setItems($items) - ->send(); - $this->purchaseResponse = (array) $response->getData(); + $response = $gateway->purchase($data) + ->setItems($items) + ->send(); + $this->purchaseResponse = (array)$response->getData(); // parse the transaction reference if ($this->transactionReferenceParam) { - if (! empty($this->purchaseResponse[$this->transactionReferenceParam])) { + if ( ! empty($this->purchaseResponse[$this->transactionReferenceParam])) { $ref = $this->purchaseResponse[$this->transactionReferenceParam]; } else { throw new Exception($response->getMessage() ?: trans('texts.payment_error')); diff --git a/app/Ninja/PaymentDrivers/PaymentActionRequiredException.php b/app/Ninja/PaymentDrivers/PaymentActionRequiredException.php new file mode 100644 index 000000000000..5b9e3f993d3b --- /dev/null +++ b/app/Ninja/PaymentDrivers/PaymentActionRequiredException.php @@ -0,0 +1,32 @@ +data = $data; + parent::__construct($message, $code, $previous); + } + + public function getData() + { + return $this->data; + } +} diff --git a/app/Ninja/PaymentDrivers/StripePaymentDriver.php b/app/Ninja/PaymentDrivers/StripePaymentDriver.php index 6e1cb5a38e67..ad7553632e9b 100644 --- a/app/Ninja/PaymentDrivers/StripePaymentDriver.php +++ b/app/Ninja/PaymentDrivers/StripePaymentDriver.php @@ -9,12 +9,19 @@ use App\Models\GatewayType; use Cache; use Exception; use App\Models\PaymentType; +use Stripe\PaymentIntent; +use Stripe\Stripe; class StripePaymentDriver extends BasePaymentDriver { protected $customerReferenceParam = 'customerReference'; public $canRefundPayments = true; + protected function prepareStripeAPI() + { + Stripe::setApiKey($this->accountGateway->getConfigField('apiKey')); + } + public function gatewayTypes() { $types = [ @@ -61,6 +68,17 @@ class StripePaymentDriver extends BasePaymentDriver return $types; } + /** + * Returns a setup intent that allows the user to enter card details without initiating a transaction. + * + * @return \Stripe\SetupIntent + */ + public function getSetupIntent() + { + $this->prepareStripeAPI(); + return \Stripe\SetupIntent::create(); + } + public function tokenize() { return $this->accountGateway->getPublishableKey(); @@ -134,14 +152,25 @@ class StripePaymentDriver extends BasePaymentDriver { $data = parent::paymentDetails($paymentMethod); + // Stripe complains if the email field is set + unset($data['email']); + + if ( ! empty($this->input['paymentIntentID'])) { + // If we're completing a previously initiated payment intent, use that ID first. + $data['payment_intent'] = $this->input['paymentIntentID']; + unset($data['card']); + return $data; + } + if ($paymentMethod) { return $data; } - // Stripe complains if the email field is set - unset($data['email']); - - if (! empty($this->input['sourceToken'])) { + if ( ! empty($this->input['paymentMethodID'])) { + // We're using an existing payment method. + $data['payment_method'] = $this->input['paymentMethodID']; + unset($data['card']); + } else if ( ! empty($this->input['sourceToken'])) { $data['token'] = $this->input['sourceToken']; unset($data['card']); } @@ -155,26 +184,150 @@ class StripePaymentDriver extends BasePaymentDriver return $data; } + /** + * @param bool $input + * @param bool $paymentMethod + * @param bool $offSession True if this payment is being made automatically rather than manually initiated by the user. + * + * @return bool|mixed + * @throws PaymentActionRequiredException When further interaction is required from the user. + */ + public function completeOnsitePurchase($input = false, $paymentMethod = false, $offSession = false) + { + $data = $this->prepareOnsitePurchase($input, $paymentMethod); + + if ( ! $data && request()->capture) { + // We only want to save the payment details, not actually charge the card. + $real_data = $this->paymentDetails($paymentMethod); + + if ( ! empty($real_data['payment_method'])) { + // Attach the payment method to the existing customer. + $this->prepareStripeAPI(); + $payment_method = \Stripe\PaymentMethod::retrieve($real_data['payment_method']); + $payment_method = $payment_method->attach(['customer' => $this->getCustomerID()]); + $this->tokenResponse = $payment_method; + parent::createToken(); + return $payment_method; + } + } + + if ( ! $data) { + // No payment method to charge against yet; probably a 2-step or capture-only transaction. + return null; + } + + if ( ! empty($data['payment_method']) || ! empty($data['payment_intent']) || ! empty($data['token'])) { + // Need to use Stripe's new Payment Intent API. + $this->prepareStripeAPI(); + + if ( ! empty($data['payment_intent'])) { + // Find the existing payment intent. + $intent = PaymentIntent::retrieve($data['payment_intent']); + + if ( ! $intent->amount == $data['amount'] * 100) { + // Make sure that the provided payment intent matches the invoice amount. + throw new Exception('Incorrect PaymentIntent amount.'); + } + $intent->confirm(); + } elseif ( ! empty($data['token']) || ! empty($data['payment_method'])) { + $params = [ + 'amount' => $data['amount'] * 100, + 'currency' => $data['currency'], + 'confirmation_method' => 'manual', + 'confirm' => true, + ]; + + if ($offSession) { + $params['off_session'] = true; + } + + if ( ! empty($data['description'])) { + $params['description'] = $data['description']; + } + + if ( ! empty($data['payment_method'])) { + $params['payment_method'] = $data['payment_method']; + + if ($this->shouldCreateToken()) { + // Tell Stripe to save the payment method for future usage. + $params['setup_future_usage'] = 'off_session'; + $params['save_payment_method'] = true; + $params['customer'] = $this->getCustomerID(); + } + } elseif ( ! empty($data['token'])) { + // Use a stored payment method. + $params['payment_method'] = $data['token']; + $params['customer'] = $this->getCustomerID(); + } + + $intent = PaymentIntent::create($params); + } + + if (empty($intent)) { + throw new \Exception('PaymentIntent not found.'); + } elseif (($intent->status == 'requires_source_action' || $intent->status == 'requires_action') && + $intent->next_action->type == 'use_stripe_sdk') { + // Throw an exception that can either be logged or be handled by getting further interaction from the user. + throw new PaymentActionRequiredException(['payment_intent' => $intent]); + } else if ($intent->status == 'succeeded') { + $ref = ! empty($intent->charges->data) ? $intent->charges->data[0]->id : null; + $payment = $this->createPayment($ref, $paymentMethod); + + if ($this->invitation->invoice->account->isNinjaAccount()) { + Session::flash('trackEventCategory', '/account'); + Session::flash('trackEventAction', '/buy_pro_plan'); + Session::flash('trackEventAmount', $payment->amount); + } + + if ($intent->setup_future_usage == 'off_session') { + // Save the payment method ID. + $payment_method = \Stripe\PaymentMethod::retrieve($intent->payment_method); + $this->tokenResponse = $payment_method; + parent::createToken(); + } + + return $payment; + } else { + throw new Exception('Invalid PaymentIntent status: ' . $intent->status); + } + } else { + return $this->doOmnipayOnsitePurchase($data, $paymentMethod); + } + } + + public function getCustomerID() + { + // if a customer already exists link the token to it + if ($customer = $this->customer()) { + return $customer->token; + } else { + // otherwise create a new czustomer + $invoice = $this->invitation->invoice; + $client = $invoice->client; + + $response = $this->gateway()->createCustomer([ + 'description' => $client->getDisplayName(), + 'email' => $this->contact()->email, + ])->send(); + return $response->getCustomerReference(); + } + } + public function createToken() { $invoice = $this->invitation->invoice; - $client = $invoice->client; + $client = $invoice->client; $data = $this->paymentDetails(); - $data['description'] = $client->getDisplayName(); - // if a customer already exists link the token to it - if ($customer = $this->customer()) { - $data['customerReference'] = $customer->token; - // otherwise create a new customer - } else { - $response = $this->gateway()->createCustomer([ - 'description' => $client->getDisplayName(), - 'email' => $this->contact()->email, - ])->send(); - $data['customerReference'] = $response->getCustomerReference(); + if ( ! empty($data['payment_method']) || ! empty($data['payment_intent'])) { + // Using the PaymentIntent API; we'll save the details later. + return null; } + $data['description'] = $client->getDisplayName(); + $data['customerReference'] = $this->getCustomerID(); + if (! empty($data['plaidPublicToken'])) { $plaidResult = $this->getPlaidToken($data['plaidPublicToken'], $data['plaidAccountId']); unset($data['plaidPublicToken']); @@ -226,7 +379,12 @@ class StripePaymentDriver extends BasePaymentDriver return false; } - $paymentMethod->source_reference = $source['id']; + if ( ! empty($source['id'])) { + $paymentMethod->source_reference = $source['id']; + } elseif ( ! empty($data['id'])) { + // Find an ID on the payment method instead of the card. + $paymentMethod->source_reference = $data['id']; + } $paymentMethod->last4 = $source['last4']; // For older users the Stripe account may just have the customer token but not the card version diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 62fb1bcd88e6..2ee4b4de41d1 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -136,7 +136,7 @@ class PaymentService extends BaseService } try { - return $paymentDriver->completeOnsitePurchase(false, $paymentMethod); + return $paymentDriver->completeOnsitePurchase(false, $paymentMethod, true); } catch (Exception $exception) { $subject = trans('texts.auto_bill_failed', ['invoice_number' => $invoice->invoice_number]); $message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $exception->getMessage()); diff --git a/composer.json b/composer.json index b17081a62d73..2b2996f52fc3 100644 --- a/composer.json +++ b/composer.json @@ -62,6 +62,7 @@ "predis/predis": "^1.1", "roave/security-advisories": "dev-master", "simshaun/recurr": "dev-master", + "stripe/stripe-php": "^6.40", "symfony/css-selector": "~3.1", "turbo124/laravel-push-notification": "2.*", "webpatser/laravel-countries": "dev-master#75992ad", diff --git a/composer.lock b/composer.lock index 3694f263aad5..8b9ad4617ea6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f1bdbf3d4e931ebd4abd3af608a16e7f", + "content-hash": "c2be867cfe90060339fcb51c3ffcad6c", "packages": [ { "name": "abdala/omnipay-pagseguro", @@ -3032,12 +3032,12 @@ "version": "v3.5.2", "source": { "type": "git", - "url": "https://github.com/google/protobuf.git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", "reference": "b5fbb742af122b565925987e65c08957739976a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/google/protobuf/zipball/b5fbb742af122b565925987e65c08957739976a7", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b5fbb742af122b565925987e65c08957739976a7", "reference": "b5fbb742af122b565925987e65c08957739976a7", "shasum": "" }, @@ -8999,6 +8999,62 @@ ], "time": "2017-08-24T17:02:28+00:00" }, + { + "name": "stripe/stripe-php", + "version": "v6.40.0", + "source": { + "type": "git", + "url": "https://github.com/stripe/stripe-php.git", + "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9c22ffab790ef4dae0f371929de50e8b53c9ec8d", + "reference": "9c22ffab790ef4dae0f371929de50e8b53c9ec8d", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "1.*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": "~2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Stripe\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stripe and contributors", + "homepage": "https://github.com/stripe/stripe-php/contributors" + } + ], + "description": "Stripe PHP Library", + "homepage": "https://stripe.com/", + "keywords": [ + "api", + "payment processing", + "stripe" + ], + "time": "2019-06-27T23:24:51+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v5.4.9", diff --git a/resources/views/payments/credit_card.blade.php b/resources/views/payments/credit_card.blade.php index 149d8a3d4ff8..f3216543a3a6 100644 --- a/resources/views/payments/credit_card.blade.php +++ b/resources/views/payments/credit_card.blade.php @@ -154,11 +154,12 @@ @if ($client) {{ Former::populate($client) }} + {{ Former::populateField('country_id', (string) $client->country_id) }} {{ Former::populateField('first_name', $contact->first_name) }} {{ Former::populateField('last_name', $contact->last_name) }} {{ Former::populateField('email', $contact->email) }} @if (!$client->country_id && $client->account->country_id) - {{ Former::populateField('country_id', $client->account->country_id) }} + {{ Former::populateField('country_id', (string) $client->account->country_id) }} {{ Former::populateField('shipping_country_id', $client->account->country_id) }} @endif @endif @@ -170,7 +171,7 @@ {{ Former::populateField('city', 'New York') }} {{ Former::populateField('state', 'NY') }} {{ Former::populateField('postal_code', '10118') }} - {{ Former::populateField('country_id', 840) }} + {{ Former::populateField('country_id', (string) 840) }} +@stop diff --git a/routes/web.php b/routes/web.php index ca45a1bf01ff..db780da164d6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -37,7 +37,7 @@ Route::group(['middleware' => ['lookup:contact', 'auth:client']], function () { Route::get('view', 'HomeController@viewLogo'); Route::get('approve/{invitation_key}', 'QuoteController@approve'); Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment'); - Route::post('payment/{invitation_key}/{gateway_type?}', 'OnlinePaymentController@doPayment'); + Route::post('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@doPayment'); Route::get('complete_source/{invitation_key}/{gateway_type}', 'OnlinePaymentController@completeSource'); Route::match(['GET', 'POST'], 'complete/{invitation_key?}/{gateway_type?}', 'OnlinePaymentController@offsitePayment'); Route::get('bank/{routing_number}', 'OnlinePaymentController@getBankInfo');