mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Fixes for Stripe payments (#3542)
* Payment fixes: - Added new "process.js" inside of webpack.mix.js - BasePaymentDriver now accepts raw array, no explode - StripePaymentDriver now accepts raw array, no explode - Removed 'form-control' class from #card-element - New credit_card for processing payment * Production build of assets
This commit is contained in:
parent
1a0c20aa9d
commit
e93bdffc0b
@ -44,7 +44,7 @@ use Omnipay\Omnipay;
|
||||
class BasePaymentDriver
|
||||
{
|
||||
use SystemLogTrait;
|
||||
|
||||
|
||||
/* The company gateway instance*/
|
||||
protected $company_gateway;
|
||||
|
||||
@ -56,7 +56,7 @@ class BasePaymentDriver
|
||||
|
||||
/* Gateway capabilities */
|
||||
protected $refundable = false;
|
||||
|
||||
|
||||
/* Token billing */
|
||||
protected $token_billing = false;
|
||||
|
||||
@ -194,7 +194,7 @@ class BasePaymentDriver
|
||||
public function processPaymentView(array $data)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
}
|
||||
@ -251,7 +251,7 @@ class BasePaymentDriver
|
||||
/*
|
||||
$this->purchaseResponse = (array)$response->getData();*/
|
||||
}
|
||||
|
||||
|
||||
public function completePurchase($data)
|
||||
{
|
||||
$this->gateway();
|
||||
@ -269,14 +269,14 @@ class BasePaymentDriver
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->currency_id = $this->client->getSetting('currency_id');
|
||||
$payment->date = Carbon::now();
|
||||
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function attachInvoices(Payment $payment, $hashed_ids) : Payment
|
||||
{
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(explode(",", $hashed_ids)))
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($hashed_ids))
|
||||
->whereClientId($this->client->id)
|
||||
->get();
|
||||
|
||||
|
@ -264,7 +264,7 @@ class StripePaymentDriver extends BasePaymentDriver
|
||||
|
||||
$gateway_type_id = $request->input('payment_method_id');
|
||||
$hashed_ids = $request->input('hashed_ids');
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(explode(",", $hashed_ids)))
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys($hashed_ids))
|
||||
->whereClientId($this->client->id)
|
||||
->get();
|
||||
/**
|
||||
|
2
public/js/clients/payments/process.js
vendored
Normal file
2
public/js/clients/payments/process.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see process.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=6)}({6:function(e,t,n){e.exports=n("OXGg")},OXGg:function(e,t){function n(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 e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.key=t,this.usingToken=n}var t,r,o;return t=e,(r=[{key:"setupStripe",value:function(){return this.stripe=Stripe(this.key),this.elements=this.stripe.elements(),this}},{key:"createElement",value:function(){return this.cardElement=this.elements.create("card"),this}},{key:"mountCardElement",value:function(){return this.cardElement.mount("#card-element"),this}},{key:"completePaymentUsingToken",value:function(){var e=this,t=document.getElementById("pay-now-with-token");this.stripe.handleCardPayment(t.dataset.secret,{payment_method:t.dataset.token}).then((function(t){return t.error?e.handleFailure(t.error.message):e.handleSuccess(t)}))}},{key:"completePaymentWithoutToken",value:function(){var e=this,t=document.getElementById("pay-now"),n=document.getElementById("cardholder-name");this.stripe.handleCardPayment(t.dataset.secret,this.cardElement,{payment_method_data:{billing_details:{name:n.value}}}).then((function(t){return t.error?e.handleFailure(t.error.message):e.handleSuccess(t)}))}},{key:"handleSuccess",value:function(e){document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.paymentIntent);var t=document.querySelector('input[name="token-billing-checkbox"]');t&&(document.querySelector('input[name="store_card"]').value=t.checked),document.getElementById("server-response").submit()}},{key:"handleFailure",value:function(e){var t=document.getElementById("errors");t.textContent="",t.textContent=e,t.hidden=!1}},{key:"handle",value:function(){var e=this;this.setupStripe(),this.usingToken&&document.getElementById("pay-now-with-token").addEventListener("click",(function(){return e.completePaymentUsingToken()})),this.usingToken||(this.createElement().mountCardElement(),document.getElementById("pay-now").addEventListener("click",(function(){return e.completePaymentWithoutToken()})))}}])&&n(t.prototype,r),o&&n(t,o),e}())(document.querySelector('meta[name="stripe-publishable-key"]').content,document.querySelector('meta[name="using-token"]').content).handle()}});
|
9
public/js/clients/payments/process.js.LICENSE.txt
Normal file
9
public/js/clients/payments/process.js.LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
123
resources/js/clients/payments/process.js
vendored
Normal file
123
resources/js/clients/payments/process.js
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
class ProcessStripePayment {
|
||||
constructor(key, usingToken) {
|
||||
this.key = key;
|
||||
this.usingToken = usingToken;
|
||||
}
|
||||
|
||||
setupStripe() {
|
||||
this.stripe = Stripe(this.key);
|
||||
this.elements = this.stripe.elements();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
createElement() {
|
||||
this.cardElement = this.elements.create("card");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
mountCardElement() {
|
||||
this.cardElement.mount("#card-element");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
completePaymentUsingToken() {
|
||||
let payNowButton = document.getElementById("pay-now-with-token");
|
||||
|
||||
this.stripe
|
||||
.handleCardPayment(payNowButton.dataset.secret, {
|
||||
payment_method: payNowButton.dataset.token
|
||||
})
|
||||
.then(result => {
|
||||
if (result.error) {
|
||||
return this.handleFailure(result.error.message);
|
||||
}
|
||||
|
||||
return this.handleSuccess(result);
|
||||
});
|
||||
}
|
||||
|
||||
completePaymentWithoutToken() {
|
||||
let payNowButton = document.getElementById("pay-now");
|
||||
let cardHolderName = document.getElementById("cardholder-name");
|
||||
|
||||
this.stripe
|
||||
.handleCardPayment(payNowButton.dataset.secret, this.cardElement, {
|
||||
payment_method_data: {
|
||||
billing_details: { name: cardHolderName.value }
|
||||
}
|
||||
})
|
||||
.then(result => {
|
||||
if (result.error) {
|
||||
return this.handleFailure(result.error.message);
|
||||
}
|
||||
|
||||
return this.handleSuccess(result);
|
||||
});
|
||||
}
|
||||
|
||||
handleSuccess(result) {
|
||||
document.querySelector(
|
||||
'input[name="gateway_response"]'
|
||||
).value = JSON.stringify(result.paymentIntent);
|
||||
|
||||
let tokenBillingCheckbox = document.querySelector(
|
||||
'input[name="token-billing-checkbox"]'
|
||||
);
|
||||
|
||||
if (tokenBillingCheckbox) {
|
||||
document.querySelector('input[name="store_card"]').value =
|
||||
tokenBillingCheckbox.checked;
|
||||
}
|
||||
|
||||
document.getElementById("server-response").submit();
|
||||
}
|
||||
|
||||
handleFailure(message) {
|
||||
let errors = document.getElementById("errors");
|
||||
|
||||
errors.textContent = "";
|
||||
errors.textContent = message;
|
||||
errors.hidden = false;
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.setupStripe();
|
||||
|
||||
if (this.usingToken) {
|
||||
document
|
||||
.getElementById("pay-now-with-token")
|
||||
.addEventListener("click", () => {
|
||||
return this.completePaymentUsingToken();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.usingToken) {
|
||||
this.createElement().mountCardElement();
|
||||
|
||||
document.getElementById("pay-now").addEventListener("click", () => {
|
||||
return this.completePaymentWithoutToken();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const publishableKey = document.querySelector(
|
||||
'meta[name="stripe-publishable-key"]'
|
||||
).content;
|
||||
|
||||
const usingToken = document.querySelector('meta[name="using-token"]').content;
|
||||
|
||||
new ProcessStripePayment(publishableKey, usingToken).handle();
|
@ -44,7 +44,7 @@
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<div id="card-element" class="form-control"></div>
|
||||
<div id="card-element"></div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
|
@ -0,0 +1,106 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.pay_now'))
|
||||
|
||||
@push('head')
|
||||
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
||||
<meta name="using-token" content="{{ boolval($token) }}">
|
||||
@endpush
|
||||
|
||||
@section('header')
|
||||
Insert breadcrumbs..
|
||||
@endsection
|
||||
|
||||
@section('body')
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||
@csrf
|
||||
<input type="hidden" name="gateway_response">
|
||||
<input type="hidden" name="store_card">
|
||||
@foreach($invoices as $invoice)
|
||||
<input type="hidden" name="hashed_ids[]" value="{{ $invoice->hashed_id }}">
|
||||
@endforeach
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||
</form>
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||
{{ ctrans('texts.complete_your_payment') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<dl>
|
||||
@if($token)
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ strtoupper($token->meta->brand) }} - **** {{ $token->meta->last4 }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
data-secret="{{ $intent->client_secret }}"
|
||||
data-token="{{ $token->token }}"
|
||||
id="pay-now-with-token"
|
||||
class="button button-primary">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
<div
|
||||
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
||||
{{ ctrans('texts.name') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input class="input" id="cardholder-name" type="text"
|
||||
placeholder="{{ ctrans('texts.name') }}">
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<div id="card-element"></div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.token_billing_checkbox') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<input type="checkbox" class="form-check" name="token-billing-checkbox"/>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
id="pay-now"
|
||||
data-secret="{{ $intent->client_secret }}"
|
||||
class="button button-primary">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="{{ asset('js/clients/payments/process.js') }}"></script>
|
||||
@endpush
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -23,6 +23,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
.js(
|
||||
"resources/js/clients/quotes/approve.js",
|
||||
"public/js/clients/quotes/approve.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/payments/process.js",
|
||||
"public/js/clients/payments/process.js"
|
||||
);
|
||||
|
||||
mix.sass("resources/sass/app.scss", "public/css")
|
||||
|
Loading…
x
Reference in New Issue
Block a user