mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-06-09 00:34:35 -04:00
Support Stripe 3D secure
This commit is contained in:
parent
72831b0cef
commit
ff064367d6
@ -11,6 +11,7 @@ use App\Models\Payment;
|
|||||||
use App\Models\PaymentMethod;
|
use App\Models\PaymentMethod;
|
||||||
use App\Models\Product;
|
use App\Models\Product;
|
||||||
use App\Ninja\Mailers\UserMailer;
|
use App\Ninja\Mailers\UserMailer;
|
||||||
|
use App\Ninja\PaymentDrivers\PaymentActionRequiredException;
|
||||||
use App\Ninja\Repositories\ClientRepository;
|
use App\Ninja\Repositories\ClientRepository;
|
||||||
use App\Ninja\Repositories\InvoiceRepository;
|
use App\Ninja\Repositories\InvoiceRepository;
|
||||||
use App\Services\InvoiceService;
|
use App\Services\InvoiceService;
|
||||||
@ -124,11 +125,18 @@ class OnlinePaymentController extends BaseController
|
|||||||
*
|
*
|
||||||
* @return \Illuminate\Http\RedirectResponse
|
* @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;
|
$invitation = $request->invitation;
|
||||||
|
|
||||||
if ($gatewayTypeAlias) {
|
if ($gatewayTypeAlias == GATEWAY_TYPE_TOKEN) {
|
||||||
|
$gatewayTypeId = $gatewayTypeAlias;
|
||||||
|
} elseif ($gatewayTypeAlias) {
|
||||||
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
$gatewayTypeId = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||||
} else {
|
} else {
|
||||||
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
$gatewayTypeId = Session::get($invitation->id . 'gateway_type');
|
||||||
@ -141,7 +149,16 @@ class OnlinePaymentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
if (request()->capture) {
|
||||||
return redirect('/client/dashboard')->withMessage(trans('texts.updated_payment_details'));
|
return redirect('/client/dashboard')->withMessage(trans('texts.updated_payment_details'));
|
||||||
@ -152,6 +169,8 @@ class OnlinePaymentController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $this->completePurchase($invitation);
|
return $this->completePurchase($invitation);
|
||||||
|
} catch (PaymentActionRequiredException $exception) {
|
||||||
|
return $paymentDriver->startStepTwo($exception->getData());
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
return $this->error($paymentDriver, $exception, true);
|
return $this->error($paymentDriver, $exception, true);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,11 @@ class CreateOnlinePaymentRequest extends Request
|
|||||||
$input['invitation'] = $invitation;
|
$input['invitation'] = $invitation;
|
||||||
|
|
||||||
if ($gatewayTypeAlias = request()->gateway_type) {
|
if ($gatewayTypeAlias = request()->gateway_type) {
|
||||||
|
if ($gatewayTypeAlias != GATEWAY_TYPE_TOKEN) {
|
||||||
$input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
$input['gateway_type'] = GatewayType::getIdFromAlias($gatewayTypeAlias);
|
||||||
|
} else {
|
||||||
|
$input['gateway_type'] = $gatewayTypeAlias;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$input['gateway_type'] = session($invitation->id . 'gateway_type');
|
$input['gateway_type'] = session($invitation->id . 'gateway_type');
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,12 @@ class BasePaymentDriver
|
|||||||
if (Session::has('error')) {
|
if (Session::has('error')) {
|
||||||
Session::reflash();
|
Session::reflash();
|
||||||
} else {
|
} else {
|
||||||
|
try {
|
||||||
$this->completeOnsitePurchase();
|
$this->completeOnsitePurchase();
|
||||||
|
} catch (PaymentActionRequiredException $exception) {
|
||||||
|
return $this->startStepTwo($exception->getData(), $sourceId);
|
||||||
|
}
|
||||||
|
|
||||||
if ($redirectUrl = session('redirect_url:' . $this->invitation->invitation_key)) {
|
if ($redirectUrl = session('redirect_url:' . $this->invitation->invitation_key)) {
|
||||||
$separator = strpos($redirectUrl, '?') === false ? '?' : '&';
|
$separator = strpos($redirectUrl, '?') === false ? '?' : '&';
|
||||||
|
|
||||||
@ -186,12 +191,59 @@ class BasePaymentDriver
|
|||||||
'account' => $this->account(),
|
'account' => $this->account(),
|
||||||
'sourceId' => $sourceId,
|
'sourceId' => $sourceId,
|
||||||
'tokenize' => $this->tokenize(),
|
'tokenize' => $this->tokenize(),
|
||||||
|
'driver' => $this,
|
||||||
'transactionToken' => $this->createTransactionToken(),
|
'transactionToken' => $this->createTransactionToken(),
|
||||||
];
|
];
|
||||||
|
|
||||||
return view($this->paymentView(), $data);
|
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
|
// check if a custom view exists for this provider
|
||||||
protected function paymentView()
|
protected function paymentView()
|
||||||
{
|
{
|
||||||
@ -267,10 +319,9 @@ class BasePaymentDriver
|
|||||||
return $this->gateway;
|
return $this->gateway;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function completeOnsitePurchase($input = false, $paymentMethod = false)
|
protected function prepareOnsitePurchase($input = false, $paymentMethod = false)
|
||||||
{
|
{
|
||||||
$this->input = $input && count($input) ? $input : false;
|
$this->input = $input && count($input) ? $input : false;
|
||||||
$gateway = $this->gateway();
|
|
||||||
|
|
||||||
if ($input) {
|
if ($input) {
|
||||||
$this->updateClient();
|
$this->updateClient();
|
||||||
@ -311,7 +362,32 @@ class BasePaymentDriver
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare and process payment
|
// 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
|
// TODO move to payment driver class
|
||||||
if ($this->isGateway(GATEWAY_SAGE_PAY_DIRECT) || $this->isGateway(GATEWAY_SAGE_PAY_SERVER)) {
|
if ($this->isGateway(GATEWAY_SAGE_PAY_DIRECT) || $this->isGateway(GATEWAY_SAGE_PAY_SERVER)) {
|
||||||
|
32
app/Ninja/PaymentDrivers/PaymentActionRequiredException.php
Normal file
32
app/Ninja/PaymentDrivers/PaymentActionRequiredException.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -9,12 +9,19 @@ use App\Models\GatewayType;
|
|||||||
use Cache;
|
use Cache;
|
||||||
use Exception;
|
use Exception;
|
||||||
use App\Models\PaymentType;
|
use App\Models\PaymentType;
|
||||||
|
use Stripe\PaymentIntent;
|
||||||
|
use Stripe\Stripe;
|
||||||
|
|
||||||
class StripePaymentDriver extends BasePaymentDriver
|
class StripePaymentDriver extends BasePaymentDriver
|
||||||
{
|
{
|
||||||
protected $customerReferenceParam = 'customerReference';
|
protected $customerReferenceParam = 'customerReference';
|
||||||
public $canRefundPayments = true;
|
public $canRefundPayments = true;
|
||||||
|
|
||||||
|
protected function prepareStripeAPI()
|
||||||
|
{
|
||||||
|
Stripe::setApiKey($this->accountGateway->getConfigField('apiKey'));
|
||||||
|
}
|
||||||
|
|
||||||
public function gatewayTypes()
|
public function gatewayTypes()
|
||||||
{
|
{
|
||||||
$types = [
|
$types = [
|
||||||
@ -61,6 +68,17 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
return $types;
|
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()
|
public function tokenize()
|
||||||
{
|
{
|
||||||
return $this->accountGateway->getPublishableKey();
|
return $this->accountGateway->getPublishableKey();
|
||||||
@ -134,14 +152,25 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
{
|
{
|
||||||
$data = parent::paymentDetails($paymentMethod);
|
$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) {
|
if ($paymentMethod) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stripe complains if the email field is set
|
if ( ! empty($this->input['paymentMethodID'])) {
|
||||||
unset($data['email']);
|
// We're using an existing payment method.
|
||||||
|
$data['payment_method'] = $this->input['paymentMethodID'];
|
||||||
if (! empty($this->input['sourceToken'])) {
|
unset($data['card']);
|
||||||
|
} else if ( ! empty($this->input['sourceToken'])) {
|
||||||
$data['token'] = $this->input['sourceToken'];
|
$data['token'] = $this->input['sourceToken'];
|
||||||
unset($data['card']);
|
unset($data['card']);
|
||||||
}
|
}
|
||||||
@ -155,26 +184,150 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
return $data;
|
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()
|
public function createToken()
|
||||||
{
|
{
|
||||||
$invoice = $this->invitation->invoice;
|
$invoice = $this->invitation->invoice;
|
||||||
$client = $invoice->client;
|
$client = $invoice->client;
|
||||||
|
|
||||||
$data = $this->paymentDetails();
|
$data = $this->paymentDetails();
|
||||||
$data['description'] = $client->getDisplayName();
|
|
||||||
|
|
||||||
// if a customer already exists link the token to it
|
if ( ! empty($data['payment_method']) || ! empty($data['payment_intent'])) {
|
||||||
if ($customer = $this->customer()) {
|
// Using the PaymentIntent API; we'll save the details later.
|
||||||
$data['customerReference'] = $customer->token;
|
return null;
|
||||||
// otherwise create a new customer
|
|
||||||
} else {
|
|
||||||
$response = $this->gateway()->createCustomer([
|
|
||||||
'description' => $client->getDisplayName(),
|
|
||||||
'email' => $this->contact()->email,
|
|
||||||
])->send();
|
|
||||||
$data['customerReference'] = $response->getCustomerReference();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data['description'] = $client->getDisplayName();
|
||||||
|
$data['customerReference'] = $this->getCustomerID();
|
||||||
|
|
||||||
if (! empty($data['plaidPublicToken'])) {
|
if (! empty($data['plaidPublicToken'])) {
|
||||||
$plaidResult = $this->getPlaidToken($data['plaidPublicToken'], $data['plaidAccountId']);
|
$plaidResult = $this->getPlaidToken($data['plaidPublicToken'], $data['plaidAccountId']);
|
||||||
unset($data['plaidPublicToken']);
|
unset($data['plaidPublicToken']);
|
||||||
@ -226,7 +379,12 @@ class StripePaymentDriver extends BasePaymentDriver
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! empty($source['id'])) {
|
||||||
$paymentMethod->source_reference = $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'];
|
$paymentMethod->last4 = $source['last4'];
|
||||||
|
|
||||||
// For older users the Stripe account may just have the customer token but not the card version
|
// For older users the Stripe account may just have the customer token but not the card version
|
||||||
|
@ -136,7 +136,7 @@ class PaymentService extends BaseService
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $paymentDriver->completeOnsitePurchase(false, $paymentMethod);
|
return $paymentDriver->completeOnsitePurchase(false, $paymentMethod, true);
|
||||||
} catch (Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
$subject = trans('texts.auto_bill_failed', ['invoice_number' => $invoice->invoice_number]);
|
$subject = trans('texts.auto_bill_failed', ['invoice_number' => $invoice->invoice_number]);
|
||||||
$message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $exception->getMessage());
|
$message = sprintf('%s: %s', ucwords($paymentDriver->providerName()), $exception->getMessage());
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
"predis/predis": "^1.1",
|
"predis/predis": "^1.1",
|
||||||
"roave/security-advisories": "dev-master",
|
"roave/security-advisories": "dev-master",
|
||||||
"simshaun/recurr": "dev-master",
|
"simshaun/recurr": "dev-master",
|
||||||
|
"stripe/stripe-php": "^6.40",
|
||||||
"symfony/css-selector": "~3.1",
|
"symfony/css-selector": "~3.1",
|
||||||
"turbo124/laravel-push-notification": "2.*",
|
"turbo124/laravel-push-notification": "2.*",
|
||||||
"webpatser/laravel-countries": "dev-master#75992ad",
|
"webpatser/laravel-countries": "dev-master#75992ad",
|
||||||
|
62
composer.lock
generated
62
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f1bdbf3d4e931ebd4abd3af608a16e7f",
|
"content-hash": "c2be867cfe90060339fcb51c3ffcad6c",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "abdala/omnipay-pagseguro",
|
"name": "abdala/omnipay-pagseguro",
|
||||||
@ -3032,12 +3032,12 @@
|
|||||||
"version": "v3.5.2",
|
"version": "v3.5.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/google/protobuf.git",
|
"url": "https://github.com/protocolbuffers/protobuf-php.git",
|
||||||
"reference": "b5fbb742af122b565925987e65c08957739976a7"
|
"reference": "b5fbb742af122b565925987e65c08957739976a7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"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",
|
"reference": "b5fbb742af122b565925987e65c08957739976a7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
@ -8999,6 +8999,62 @@
|
|||||||
],
|
],
|
||||||
"time": "2017-08-24T17:02:28+00:00"
|
"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",
|
"name": "swiftmailer/swiftmailer",
|
||||||
"version": "v5.4.9",
|
"version": "v5.4.9",
|
||||||
|
@ -154,11 +154,12 @@
|
|||||||
|
|
||||||
@if ($client)
|
@if ($client)
|
||||||
{{ Former::populate($client) }}
|
{{ Former::populate($client) }}
|
||||||
|
{{ Former::populateField('country_id', (string) $client->country_id) }}
|
||||||
{{ Former::populateField('first_name', $contact->first_name) }}
|
{{ Former::populateField('first_name', $contact->first_name) }}
|
||||||
{{ Former::populateField('last_name', $contact->last_name) }}
|
{{ Former::populateField('last_name', $contact->last_name) }}
|
||||||
{{ Former::populateField('email', $contact->email) }}
|
{{ Former::populateField('email', $contact->email) }}
|
||||||
@if (!$client->country_id && $client->account->country_id)
|
@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) }}
|
{{ Former::populateField('shipping_country_id', $client->account->country_id) }}
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@ -170,7 +171,7 @@
|
|||||||
{{ Former::populateField('city', 'New York') }}
|
{{ Former::populateField('city', 'New York') }}
|
||||||
{{ Former::populateField('state', 'NY') }}
|
{{ Former::populateField('state', 'NY') }}
|
||||||
{{ Former::populateField('postal_code', '10118') }}
|
{{ Former::populateField('postal_code', '10118') }}
|
||||||
{{ Former::populateField('country_id', 840) }}
|
{{ Former::populateField('country_id', (string) 840) }}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
$(function() {
|
||||||
@ -450,37 +451,57 @@
|
|||||||
var form = document.getElementById('payment-form');
|
var form = document.getElementById('payment-form');
|
||||||
form.addEventListener('submit', function(event) {
|
form.addEventListener('submit', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
name: document.getElementById('first_name').value + ' ' + document.getElementById('last_name').value
|
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
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (document.getElementById('postal_code')) {
|
@if(request()->capture)
|
||||||
options.address_zip = document.getElementById('postal_code').value;
|
stripe.handleCardSetup('{{$driver->getSetupIntent()->client_secret}}', cardNumber, {payment_method_data: options}).then(function (result) {
|
||||||
}
|
|
||||||
|
|
||||||
stripe.createToken(cardNumber, options).then(function(result) {
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
// Inform the user if there was an error.
|
// Inform the user if there was an error.
|
||||||
var errorElement = document.getElementById('card-errors');
|
var errorElement = document.getElementById('card-errors');
|
||||||
errorElement.textContent = result.error.message;
|
errorElement.textContent = result.error.message;
|
||||||
releaseSubmitButton();
|
releaseSubmitButton();
|
||||||
} else {
|
} else {
|
||||||
// Send the token to your server.
|
// Send the ID to your server.
|
||||||
stripeTokenHandler(result.token);
|
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
|
// Insert the token ID into the form so it gets submitted to the server
|
||||||
var form = document.getElementById('payment-form');
|
var form = document.getElementById('payment-form');
|
||||||
var hiddenInput = document.createElement('input');
|
var hiddenInput = document.createElement('input');
|
||||||
hiddenInput.setAttribute('type', 'hidden');
|
hiddenInput.setAttribute('type', 'hidden');
|
||||||
hiddenInput.setAttribute('name', 'sourceToken');
|
hiddenInput.setAttribute('name', 'paymentMethodID');
|
||||||
hiddenInput.setAttribute('value', token.id);
|
hiddenInput.setAttribute('value', paymentMethodID);
|
||||||
form.appendChild(hiddenInput);
|
form.appendChild(hiddenInput);
|
||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
|
30
resources/views/payments/stripe/step2.blade.php
Normal file
30
resources/views/payments/stripe/step2.blade.php
Normal 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
|
@ -37,7 +37,7 @@ Route::group(['middleware' => ['lookup:contact', 'auth:client']], function () {
|
|||||||
Route::get('view', 'HomeController@viewLogo');
|
Route::get('view', 'HomeController@viewLogo');
|
||||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||||
Route::get('payment/{invitation_key}/{gateway_type?}/{source_id?}', 'OnlinePaymentController@showPayment');
|
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::get('complete_source/{invitation_key}/{gateway_type}', 'OnlinePaymentController@completeSource');
|
||||||
Route::match(['GET', 'POST'], 'complete/{invitation_key?}/{gateway_type?}', 'OnlinePaymentController@offsitePayment');
|
Route::match(['GET', 'POST'], 'complete/{invitation_key?}/{gateway_type?}', 'OnlinePaymentController@offsitePayment');
|
||||||
Route::get('bank/{routing_number}', 'OnlinePaymentController@getBankInfo');
|
Route::get('bank/{routing_number}', 'OnlinePaymentController@getBankInfo');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user