1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 12:12:48 +01:00

Support Stripe 3D secure

This commit is contained in:
Joshua Dwire 2019-07-09 16:20:02 -04:00
parent 72831b0cef
commit ff064367d6
11 changed files with 480 additions and 83 deletions

View File

@ -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);
}

View File

@ -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');
}

View File

@ -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'));

View File

@ -0,0 +1,32 @@
<?php
namespace App\Ninja\PaymentDrivers;
use Throwable;
/**
* Thrown when Stripe requires further user intervention to process a charge.
* Allows the calling code to handle the exception by requesting further interaction from the user.
*
* Class StripeActionRequiredException
* @package App\Ninja\PaymentDrivers
*/
class PaymentActionRequiredException extends \Exception
{
protected $data;
public function __construct(
$data,
$message = "Direct user approval required.",
$code = 0,
Throwable $previous = null
) {
$this->data = $data;
parent::__construct($message, $code, $previous);
}
public function getData()
{
return $this->data;
}
}

View File

@ -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

View File

@ -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());

View File

@ -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",

62
composer.lock generated
View File

@ -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",

View File

@ -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) }}
<script>
$(function() {
@ -449,38 +450,58 @@
// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
event.preventDefault();
var options = {
billing_details: {
name: document.getElementById('first_name').value + ' ' + document.getElementById('last_name').value,
@if (!empty($accountGateway->show_address))
address: {
line1: $('#address1').val(),
line2: $('#address2').val(),
city: $('#city').val(),
state: $('#state').val(),
postal_code: document.getElementById('postal_code')?$('#postal_code').val():null,
country: $("#country_id option:selected").attr('data-iso_3166_2')
}
@endif
}
};
var options = {
name: document.getElementById('first_name').value + ' ' + document.getElementById('last_name').value
};
if (document.getElementById('postal_code')) {
options.address_zip = document.getElementById('postal_code').value;
}
stripe.createToken(cardNumber, options).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
releaseSubmitButton();
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
@if(request()->capture)
stripe.handleCardSetup('{{$driver->getSetupIntent()->client_secret}}', cardNumber, {payment_method_data: options}).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
releaseSubmitButton();
} else {
// Send the ID to your server.
stripePaymentMethodHandler(result.setupIntent.payment_method);
}
});
@else
stripe.createPaymentMethod('card', cardNumber, options).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
releaseSubmitButton();
} else {
// Send the ID to your server.
stripePaymentMethodHandler(result.paymentMethod.id);
}
});
@endif
});
function stripeTokenHandler(token) {
function stripePaymentMethodHandler(paymentMethodID) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'sourceToken');
hiddenInput.setAttribute('value', token.id);
hiddenInput.setAttribute('name', 'paymentMethodID');
hiddenInput.setAttribute('value', paymentMethodID);
form.appendChild(hiddenInput);
// Submit the form

View File

@ -0,0 +1,30 @@
@extends('payments.stripe.credit_card')
@section('head')
@parent
<script type="text/javascript">
// Create a Stripe client.
var stripe = Stripe('{{ $accountGateway->getPublishableKey() }}', {locale: "{{$client->language?$client->language->locale:$client->account->language->locale}}"});
stripe.handleCardAction("{{$step2_details['payment_intent']->client_secret}}"
).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'paymentIntentID');
hiddenInput.setAttribute('value', result.paymentIntent.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
});
</script>
@stop

View File

@ -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');