mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-13 06:32:40 +01:00
Merge pull request #6924 from beganovich/browser-pay
Google Pay, Apple Pay & Microsoft Pay using Stripe
This commit is contained in:
commit
5e4e65f9a0
@ -71,7 +71,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
public $payment_method;
|
||||
|
||||
/* PaymentHash */
|
||||
public $payment_hash;
|
||||
public PaymentHash $payment_hash;
|
||||
|
||||
/* Array of payment methods */
|
||||
public static $methods = [];
|
||||
|
215
app/PaymentDrivers/Stripe/BrowserPay.php
Normal file
215
app/PaymentDrivers/Stripe/BrowserPay.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers\Stripe;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Stripe\ApplePayDomain;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
use Stripe\PaymentIntent;
|
||||
|
||||
class BrowserPay implements MethodInterface
|
||||
{
|
||||
protected StripePaymentDriver $stripe;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
{
|
||||
$this->stripe = $stripe;
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$this->ensureApplePayDomainIsValidated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization page for browser pay.
|
||||
*
|
||||
* @param array $data
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeView(array $data): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the authorization for browser pay.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse(Request $request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
|
||||
public function paymentView(array $data): View
|
||||
{
|
||||
$payment_intent_data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'currency' => $this->stripe->client->getCurrencyCode(),
|
||||
'customer' => $this->stripe->findOrCreateCustomer(),
|
||||
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
|
||||
];
|
||||
|
||||
$data['gateway'] = $this->stripe;
|
||||
$data['pi_client_secret'] = $this->stripe->createPaymentIntent($payment_intent_data)->client_secret;
|
||||
|
||||
$data['payment_request_data'] = [
|
||||
'country' => $this->stripe->client->country->iso_3166_2,
|
||||
'currency' => strtolower(
|
||||
$this->stripe->client->getCurrencyCode()
|
||||
),
|
||||
'total' => [
|
||||
'label' => $payment_intent_data['description'],
|
||||
'amount' => $payment_intent_data['amount'],
|
||||
],
|
||||
'requestPayerName' => true,
|
||||
'requestPayerEmail' => true
|
||||
];
|
||||
|
||||
return render('gateways.stripe.browser_pay.pay', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle payment response for browser pay.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return RedirectResponse|App\PaymentDrivers\Stripe\never
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$gateway_response = json_decode($request->gateway_response);
|
||||
|
||||
$this->stripe->payment_hash
|
||||
->withData('gateway_response', $gateway_response)
|
||||
->withData('payment_intent', PaymentIntent::retrieve($gateway_response->id, $this->stripe->stripe_connect_auth));
|
||||
|
||||
if ($gateway_response->status === 'succeeded') {
|
||||
return $this->processSuccessfulPayment();
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful payment for browser pay.
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
protected function processSuccessfulPayment()
|
||||
{
|
||||
$gateway_response = $this->stripe->payment_hash->data->gateway_response;
|
||||
$payment_intent = $this->stripe->payment_hash->data->payment_intent;
|
||||
|
||||
$this->stripe->logSuccessfulGatewayResponse(['response' => $gateway_response, 'data' => $this->stripe->payment_hash], SystemLog::TYPE_STRIPE);
|
||||
|
||||
$payment_method = $this->stripe->getStripePaymentMethod($gateway_response->payment_method);
|
||||
|
||||
$data = [
|
||||
'payment_method' => $gateway_response->payment_method,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($payment_method->card->brand)),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($gateway_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => optional($payment_intent->charges->data[0])->id,
|
||||
'gateway_type_id' => GatewayType::APPLE_PAY,
|
||||
];
|
||||
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['amount' => $data['amount']]);
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
$payment = $this->stripe->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $gateway_response, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->stripe->client,
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle unsuccessful payment for browser pay.
|
||||
*
|
||||
* @return never
|
||||
*/
|
||||
protected function processUnsuccessfulPayment()
|
||||
{
|
||||
$server_response = $this->stripe->payment_hash->data->gateway_response;
|
||||
|
||||
$this->stripe->sendFailureMail($server_response->cancellation_reason);
|
||||
|
||||
$message = [
|
||||
'server_response' => $server_response,
|
||||
'data' => $this->stripe->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_STRIPE,
|
||||
$this->stripe->client,
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed('Failed to process the payment.', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure Apple Pay domain is verified.
|
||||
*
|
||||
* @return void
|
||||
* @throws ApiErrorException
|
||||
*/
|
||||
protected function ensureApplePayDomainIsValidated()
|
||||
{
|
||||
$config = $this->stripe->company_gateway->getConfig();
|
||||
|
||||
if (property_exists($config, 'apple_pay_domain_id')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = config('ninja.app_url');
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = isset($this->stripe_driver->company_gateway->company->portal_domain)
|
||||
? $this->stripe_driver->company_gateway->company->portal_domain
|
||||
: $this->stripe_driver->company_gateway->company->domain();
|
||||
}
|
||||
|
||||
$response = ApplePayDomain::create([
|
||||
'domain_name' => $domain,
|
||||
]);
|
||||
|
||||
$config->apple_pay_domain_id = $response->id;
|
||||
|
||||
$this->stripe->company_gateway->setConfig($config);
|
||||
|
||||
$this->stripe->company_gateway->save();
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ use App\PaymentDrivers\Stripe\EPS;
|
||||
use App\PaymentDrivers\Stripe\Bancontact;
|
||||
use App\PaymentDrivers\Stripe\BECS;
|
||||
use App\PaymentDrivers\Stripe\ACSS;
|
||||
use App\PaymentDrivers\Stripe\BrowserPay;
|
||||
use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -82,7 +83,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
GatewayType::BANK_TRANSFER => ACH::class,
|
||||
GatewayType::ALIPAY => Alipay::class,
|
||||
GatewayType::SOFORT => SOFORT::class,
|
||||
GatewayType::APPLE_PAY => ApplePay::class,
|
||||
GatewayType::APPLE_PAY => BrowserPay::class,
|
||||
GatewayType::SEPA => SEPA::class,
|
||||
GatewayType::PRZELEWY24 => PRZELEWY24::class,
|
||||
GatewayType::GIROPAY => GIROPAY::class,
|
||||
@ -139,7 +140,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
{
|
||||
$types = [
|
||||
// GatewayType::CRYPTO,
|
||||
GatewayType::CREDIT_CARD
|
||||
GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
if ($this->client
|
||||
@ -218,6 +219,14 @@ class StripePaymentDriver extends BaseDriver
|
||||
&& in_array($this->client->country->iso_3166_3, ["CAN", "USA"]))
|
||||
$types[] = GatewayType::ACSS;
|
||||
|
||||
if (
|
||||
$this->client
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->country->iso_3166_2, ['AE', 'AT', 'AU', 'BE', 'BG', 'BR', 'CA', 'CH', 'CI', 'CR', 'CY', 'CZ', 'DE', 'DK', 'DO', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'GT', 'HK', 'HU', 'ID', 'IE', 'IN', 'IT', 'JP', 'LI', 'LT', 'LU', 'LV', 'MT', 'MX', 'MY', 'NL', 'NO', 'NZ', 'PE', 'PH', 'PL', 'PT', 'RO', 'SE', 'SG', 'SI', 'SK', 'SN', 'TH', 'TT', 'US', 'UY'])
|
||||
) {
|
||||
$types[] = GatewayType::APPLE_PAY;
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
2
public/css/card-js.min.css
vendored
2
public/css/card-js.min.css
vendored
@ -1 +1 @@
|
||||
.card-js input.card-number{padding-right:48px}.card-js .card-number-wrapper .card-type-icon{height:23px;width:32px;position:absolute;display:block;right:8px;top:7px;background:url(https://cardjs.co.uk/img/cards.png) 0 23px no-repeat;pointer-events:none;opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.card-js .card-number-wrapper .show{opacity:1}.card-js .card-number-wrapper .card-type-icon.visa{background-position:0 0}.card-js .card-number-wrapper .card-type-icon.master-card{background-position:-32px 0}.card-js .card-number-wrapper .card-type-icon.american-express{background-position:-64px 0}.card-js .card-number-wrapper .card-type-icon.discover{background-position:-96px 0}.card-js .card-number-wrapper .card-type-icon.diners{background-position:-128px 0}.card-js .card-number-wrapper .card-type-icon.jcb{background-position:-160px 0}.card-js .cvc-container{width:50%;float:right}.card-js .cvc-wrapper{box-sizing:border-box;margin-left:5px}.card-js .cvc-wrapper .cvc{display:block;width:100%}.card-js .expiry-container{width:50%;float:left}.card-js .expiry-wrapper{box-sizing:border-box;margin-right:5px}.card-js .expiry-wrapper .expiry{display:block;width:100%}.card-js .expiry-wrapper .expiry-month{border-top-right-radius:0;border-bottom-right-radius:0;padding-left:30px}.card-js .expiry-wrapper .expiry-year{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}.card-js .expiry-wrapper .expiry-month,.card-js .expiry-wrapper .expiry-year{display:inline-block}.card-js .expiry-wrapper .expiry{padding-left:38px}.card-js .icon{position:absolute;display:block;width:24px;height:17px;left:8px;top:10px;pointer-events:none}.card-js .icon.right{right:8px;left:auto}.card-js .icon.popup{cursor:pointer;pointer-events:auto}.card-js .icon .svg{fill:#888}.card-js .icon.popup .svg{fill:#aaa!important}.card-js .card-number-wrapper,.card-js .name-wrapper{margin-bottom:15px;width:100%}.card-js .card-number-wrapper,.card-js .cvc-wrapper,.card-js .expiry-wrapper,.card-js .name-wrapper{-webkit-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-moz-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-ms-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-o-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);position:relative}.card-js .card-number-wrapper,.card-js .cvc-container,.card-js .expiry-container,.card-js .name-wrapper{display:inline-block}.card-js::after{content:' ';display:table;clear:both}.card-js input,.card-js select{color:#676767;font-size:15px;font-weight:300;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;height:36px;border:1px solid #d9d9d9;border-radius:4px;box-shadow:none;background-color:#FDFDFD;box-sizing:border-box;padding:0;-webkit-transition:border-color .15s linear,box-shadow .15s linear;-moz-transition:border-color .15s linear,box-shadow .15s linear;-ms-transition:border-color .15s linear,box-shadow .15s linear;-o-transition:border-color .15s linear,box-shadow .15s linear;transition:border-color .15s linear,box-shadow .15s linear}.card-js select{-moz-appearance:none;text-indent:.01px;text-overflow:''}.card-js input[disabled],.card-js select[disabled]{background-color:#eee;color:#555}.card-js select option[hidden]{color:#ABA9A9}.card-js input:focus,.card-js select:focus{background-color:#fff;outline:0;border-color:#66afe9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.card-js input[readonly=readonly]:not([disabled]),.card-js input[readonly]:not([disabled]){background-color:#fff;cursor:pointer}.card-js .has-error input,.card-js .has-error input:focus{border-color:#F64B2F;box-shadow:none}.card-js input.card-number,.card-js input.cvc,.card-js input.name{padding-left:38px;width:100%}.card-js.stripe .icon .svg{fill:#559A28}
|
||||
.card-js input.card-number{padding-right:48px}.card-js .card-number-wrapper .card-type-icon{height:23px;width:32px;position:absolute;display:block;right:8px;top:7px;background:url(https://cardjs.co.uk/img/cards.png) 0 23px no-repeat;pointer-events:none;opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-ms-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.card-js .card-number-wrapper .show{opacity:1}.card-js .card-number-wrapper .card-type-icon.visa{background-position:0 0}.card-js .card-number-wrapper .card-type-icon.master-card{background-position:-32px 0}.card-js .card-number-wrapper .card-type-icon.american-express{background-position:-64px 0}.card-js .card-number-wrapper .card-type-icon.discover{background-position:-96px 0}.card-js .card-number-wrapper .card-type-icon.diners{background-position:-128px 0}.card-js .card-number-wrapper .card-type-icon.jcb{background-position:-160px 0}.card-js .cvc-container{width:50%;float:right}.card-js .cvc-wrapper{box-sizing:border-box;margin-left:5px}.card-js .cvc-wrapper .cvc{display:block;width:100%}.card-js .expiry-container{width:50%;float:left}.card-js .expiry-wrapper{box-sizing:border-box;margin-right:5px}.card-js .expiry-wrapper .expiry{display:block;width:100%}.card-js .expiry-wrapper .expiry-month{border-top-right-radius:0;border-bottom-right-radius:0;padding-left:30px}.card-js .expiry-wrapper .expiry-year{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}.card-js .expiry-wrapper .expiry-month,.card-js .expiry-wrapper .expiry-year{display:inline-block}.card-js .expiry-wrapper .expiry{padding-left:38px}.card-js .icon{position:absolute;display:block;width:24px;height:17px;left:8px;top:10px;pointer-events:none}.card-js .icon.right{right:8px;left:auto}.card-js .icon.popup{cursor:pointer;pointer-events:auto}.card-js .icon .svg{fill:#888}.card-js .icon.popup .svg{fill:#aaa!important}.card-js .card-number-wrapper,.card-js .name-wrapper{margin-bottom:15px;width:100%}.card-js .card-number-wrapper,.card-js .cvc-wrapper,.card-js .expiry-wrapper,.card-js .name-wrapper{-webkit-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-moz-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-ms-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);-o-box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);box-shadow:0 1px 0 rgba(255,255,255,.7),inset 0 1px 0 rgba(255,255,255,.7);position:relative}.card-js .card-number-wrapper,.card-js .cvc-container,.card-js .expiry-container,.card-js .name-wrapper{display:inline-block}.card-js::after{content:' ';display:table;clear:both}.card-js input,.card-js select{color:#676767;font-size:15px;font-weight:300;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;height:36px;border:1px solid #d9d9d9;border-radius:4px;box-shadow:none;background-color:#fdfdfd;box-sizing:border-box;padding:0;-webkit-transition:border-color .15s linear,box-shadow .15s linear;-moz-transition:border-color .15s linear,box-shadow .15s linear;-ms-transition:border-color .15s linear,box-shadow .15s linear;-o-transition:border-color .15s linear,box-shadow .15s linear;transition:border-color .15s linear,box-shadow .15s linear}.card-js select{-moz-appearance:none;text-indent:.01px;text-overflow:''}.card-js input[disabled],.card-js select[disabled]{background-color:#eee;color:#555}.card-js select option[hidden]{color:#aba9a9}.card-js input:focus,.card-js select:focus{background-color:#fff;outline:0;border-color:#66afe9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.card-js input[readonly=readonly]:not([disabled]),.card-js input[readonly]:not([disabled]){background-color:#fff;cursor:pointer}.card-js .has-error input,.card-js .has-error input:focus{border-color:#f64b2f;box-shadow:none}.card-js input.card-number,.card-js input.cvc,.card-js input.name{padding-left:38px;width:100%}.card-js.stripe .icon .svg{fill:#559A28}
|
||||
|
2
public/js/clients/payments/stripe-browserpay.js
vendored
Normal file
2
public/js/clients/payments/stripe-browserpay.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see stripe-browserpay.js.LICENSE.txt */
|
||||
(()=>{function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function t(){var e;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),this.clientSecret=null===(e=document.querySelector("meta[name=stripe-pi-client-secret]"))||void 0===e?void 0:e.content}var n,r,o;return n=t,(r=[{key:"init",value:function(){var e,t,n={};return document.querySelector("meta[name=stripe-account-id]")&&(n.apiVersion="2020-08-27",n.stripeAccount=null===(t=document.querySelector("meta[name=stripe-account-id]"))||void 0===t?void 0:t.content),this.stripe=Stripe(null===(e=document.querySelector("meta[name=stripe-publishable-key]"))||void 0===e?void 0:e.content,n),this.elements=this.stripe.elements(),this}},{key:"createPaymentRequest",value:function(){return this.paymentRequest=this.stripe.paymentRequest(JSON.parse(document.querySelector("meta[name=payment-request-data").content)),this}},{key:"createPaymentRequestButton",value:function(){this.paymentRequestButton=this.elements.create("paymentRequestButton",{paymentRequest:this.paymentRequest})}},{key:"handlePaymentRequestEvents",value:function(e,t){document.querySelector("#errors").hidden=!0,this.paymentRequest.on("paymentmethod",(function(n){e.confirmCardPayment(t,{payment_method:n.paymentMethod.id},{handleActions:!1}).then((function(r){r.error?(n.complete("fail"),document.querySelector("#errors").innerText=r.error.message,document.querySelector("#errors").hidden=!1):(n.complete("success"),"requires_action"===r.paymentIntent.status?e.confirmCardPayment(t).then((function(e){e.error?(n.complete("fail"),document.querySelector("#errors").innerText=e.error.message,document.querySelector("#errors").hidden=!1):(document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.paymentIntent),document.getElementById("server-response").submit())})):(document.querySelector('input[name="gateway_response"]').value=JSON.stringify(r.paymentIntent),document.getElementById("server-response").submit()))}))}))}},{key:"handle",value:function(){var e=this;this.init().createPaymentRequest().createPaymentRequestButton(),this.paymentRequest.canMakePayment().then((function(t){var n;if(t)return e.paymentRequestButton.mount("#payment-request-button");document.querySelector("#errors").innerHTML=JSON.parse(null===(n=document.querySelector("meta[name=no-available-methods]"))||void 0===n?void 0:n.content),document.querySelector("#errors").hidden=!1})),this.handlePaymentRequestEvents(this.stripe,this.clientSecret)}}])&&e(n.prototype,r),o&&e(n,o),t}())).handle()})();
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
@ -36,6 +36,7 @@
|
||||
"/js/clients/payments/stripe-eps.js": "/js/clients/payments/stripe-eps.js?id=1ed972f879869de66c8a",
|
||||
"/js/clients/payments/stripe-ideal.js": "/js/clients/payments/stripe-ideal.js?id=73ce56676f9252b0cecf",
|
||||
"/js/clients/payments/stripe-przelewy24.js": "/js/clients/payments/stripe-przelewy24.js?id=f3a14f78bec8209c30ba",
|
||||
"/js/clients/payments/stripe-browserpay.js": "/js/clients/payments/stripe-browserpay.js?id=71e49866d66a6d85b88a",
|
||||
"/css/app.css": "/css/app.css?id=6d7f6103a3a7738d363b",
|
||||
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
|
||||
}
|
||||
|
145
resources/js/clients/payments/stripe-browserpay.js
vendored
Normal file
145
resources/js/clients/payments/stripe-browserpay.js
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
class StripeBrowserPay {
|
||||
constructor() {
|
||||
this.clientSecret = document.querySelector(
|
||||
'meta[name=stripe-pi-client-secret]'
|
||||
)?.content;
|
||||
}
|
||||
|
||||
init() {
|
||||
let config = {};
|
||||
|
||||
if (document.querySelector('meta[name=stripe-account-id]')) {
|
||||
config.apiVersion = '2020-08-27';
|
||||
|
||||
config.stripeAccount = document.querySelector(
|
||||
'meta[name=stripe-account-id]'
|
||||
)?.content;
|
||||
}
|
||||
|
||||
this.stripe = Stripe(
|
||||
document.querySelector('meta[name=stripe-publishable-key]')
|
||||
?.content,
|
||||
config
|
||||
);
|
||||
|
||||
this.elements = this.stripe.elements();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
createPaymentRequest() {
|
||||
this.paymentRequest = this.stripe.paymentRequest(
|
||||
JSON.parse(
|
||||
document.querySelector('meta[name=payment-request-data').content
|
||||
)
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
createPaymentRequestButton() {
|
||||
this.paymentRequestButton = this.elements.create(
|
||||
'paymentRequestButton',
|
||||
{
|
||||
paymentRequest: this.paymentRequest,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handlePaymentRequestEvents(stripe, clientSecret) {
|
||||
document.querySelector('#errors').hidden = true;
|
||||
|
||||
this.paymentRequest.on('paymentmethod', function (ev) {
|
||||
stripe
|
||||
.confirmCardPayment(
|
||||
clientSecret,
|
||||
{ payment_method: ev.paymentMethod.id },
|
||||
{ handleActions: false }
|
||||
)
|
||||
.then(function (confirmResult) {
|
||||
if (confirmResult.error) {
|
||||
ev.complete('fail');
|
||||
|
||||
document.querySelector('#errors').innerText =
|
||||
confirmResult.error.message;
|
||||
|
||||
document.querySelector('#errors').hidden = false;
|
||||
} else {
|
||||
ev.complete('success');
|
||||
|
||||
if (
|
||||
confirmResult.paymentIntent.status ===
|
||||
'requires_action'
|
||||
) {
|
||||
stripe
|
||||
.confirmCardPayment(clientSecret)
|
||||
.then(function (result) {
|
||||
if (result.error) {
|
||||
ev.complete('fail');
|
||||
|
||||
document.querySelector(
|
||||
'#errors'
|
||||
).innerText = result.error.message;
|
||||
|
||||
document.querySelector(
|
||||
'#errors'
|
||||
).hidden = false;
|
||||
} else {
|
||||
document.querySelector(
|
||||
'input[name="gateway_response"]'
|
||||
).value = JSON.stringify(
|
||||
result.paymentIntent
|
||||
);
|
||||
|
||||
document
|
||||
.getElementById('server-response')
|
||||
.submit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
document.querySelector(
|
||||
'input[name="gateway_response"]'
|
||||
).value = JSON.stringify(
|
||||
confirmResult.paymentIntent
|
||||
);
|
||||
|
||||
document.getElementById('server-response').submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.init().createPaymentRequest().createPaymentRequestButton();
|
||||
|
||||
this.paymentRequest.canMakePayment().then((result) => {
|
||||
if (result) {
|
||||
return this.paymentRequestButton.mount(
|
||||
'#payment-request-button'
|
||||
);
|
||||
}
|
||||
|
||||
document.querySelector('#errors').innerHTML = JSON.parse(
|
||||
document.querySelector('meta[name=no-available-methods]')
|
||||
?.content
|
||||
);
|
||||
|
||||
document.querySelector('#errors').hidden = false;
|
||||
});
|
||||
|
||||
this.handlePaymentRequestEvents(this.stripe, this.clientSecret);
|
||||
}
|
||||
}
|
||||
|
||||
new StripeBrowserPay().handle();
|
@ -4336,6 +4336,8 @@ $LANG = array(
|
||||
'acss' => 'Pre-authorized debit payments',
|
||||
'invalid_amount' => 'Invalid amount. Number/Decimal values only.',
|
||||
'client_payment_failure_body' => 'Payment for Invoice :invoice for amount :amount failed.',
|
||||
'browser_pay' => 'Google Pay, Apple Pay, Microsoft Pay',
|
||||
'no_available_methods' => 'We can\'t find any credit cards on your device. <a href="https://invoiceninja.github.io/docs/payments#apple-pay-google-pay-microsoft-pay" target="_blank" class="underline">Read more about this.</a>'
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -0,0 +1,39 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.browser_pay'), 'card_title' => ctrans('texts.browser_pay')])
|
||||
|
||||
@section('gateway_head')
|
||||
@if($gateway->company_gateway->getConfigField('account_id'))
|
||||
<meta name="stripe-account-id" content="{{ $gateway->company_gateway->getConfigField('account_id') }}">
|
||||
<meta name="stripe-publishable-key" content="{{ config('ninja.ninja_stripe_publishable_key') }}">
|
||||
@else
|
||||
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
||||
@endif
|
||||
|
||||
<meta name="stripe-pi-client-secret" content="{{ $pi_client_secret }}">
|
||||
<meta name="no-available-methods" content="{{ json_encode(ctrans('texts.no_available_methods')) }}">
|
||||
<meta name="payment-request-data" content="{{ json_encode($payment_request_data) }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||
@csrf
|
||||
<input type="hidden" name="gateway_response">
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||
<input type="hidden" name="store_card">
|
||||
<input type="hidden" name="token">
|
||||
</form>
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div id="payment-request-button"></div>
|
||||
@endcomponent
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="{{ asset('js/clients/payments/stripe-browserpay.js') }}"></script>
|
||||
@endsection
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -146,6 +146,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
"resources/js/clients/payments/stripe-przelewy24.js",
|
||||
"public/js/clients/payments/stripe-przelewy24.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/payments/stripe-browserpay.js",
|
||||
"public/js/clients/payments/stripe-browserpay.js"
|
||||
)
|
||||
|
||||
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user