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:
parent
72831b0cef
commit
ff064367d6
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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'));
|
||||
|
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 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
|
||||
|
@ -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());
|
||||
|
@ -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
62
composer.lock
generated
@ -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",
|
||||
|
@ -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
|
||||
|
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('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');
|
||||
|
Loading…
Reference in New Issue
Block a user