1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Payment responses and saving card details

This commit is contained in:
David Bomba 2019-09-25 14:03:28 +10:00
parent 89330e6e34
commit 5090c963d3
6 changed files with 131 additions and 26 deletions

View File

@ -124,13 +124,20 @@ class PaymentController extends Controller
'amount_with_fee' => $amount + $gateway->calcGatewayFee($amount),
'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id),
'payment_method_id' => $payment_method_id,
'hashed_ids' => explode(",",request()->input('hashed_ids')),
];
return $gateway->driver(auth()->user()->client)->processPayment($data);
return $gateway->driver(auth()->user()->client)->processPaymentView($data);
}
public function response(Request $request)
{
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
return $gateway->driver(auth()->user()->client)->processPaymentResponse($request);
}
}

View File

@ -82,6 +82,10 @@ class BasePaymentDriver
];
}
public function getCompanyGatewayId()
{
return $this->company_gateway->id;
}
/**
* Returns whether refunds are possible with the gateway
* @return boolean TRUE|FALSE
@ -111,10 +115,14 @@ class BasePaymentDriver
*/
public function refundPayment() {}
public function authorizeCreditCardView($data) {}
public function authorizeCreditCardView(array $data) {}
public function authorizeCreditCardResponse($request) {}
public function processPaymentView(array $data) {}
public function processPaymentResponse($request) {}
/************************************* Omnipay ******************************************
authorize($options) - authorize an amount on the customer's card
completeAuthorize($options) - handle return from off-site gateways after authorization

View File

@ -128,7 +128,7 @@ class StripePaymentDriver extends BasePaymentDriver
}
}
public function authorizeCreditCardView($data)
public function authorizeCreditCardView(array $data)
{
$intent['intent'] = $this->getSetupIntent();
@ -195,22 +195,24 @@ class StripePaymentDriver extends BasePaymentDriver
* @var amount_with_fee
* @var token
* @var payment_method_id
* @var hashed_ids
*
* @param array $data variables required to build payment page
* @return view Gateway and payment method specific view
*/
public function processPayment(array $data)
public function processPaymentView(array $data)
{
$payment_intent_data = [
'amount' => $data['amount_with_fee']*100,
'currency' => $this->client->getCurrencyCode(),
'customer' => $this->findOrCreateCustomer(),
'description' => $data['invoices']->pluck('id'),
'description' => $data['invoices']->pluck('id'), //todo more meaningful description here:
];
if($data['token'])
$payment_intent_data['payment_method'] = $data['token']->token;
else{
// $payment_intent_data['setup_future_usage'] = 'off_session';
$payment_intent_data['setup_future_usage'] = 'off_session';
// $payment_intent_data['save_payment_method'] = true;
// $payment_intent_data['confirm'] = true;
}
@ -218,12 +220,88 @@ class StripePaymentDriver extends BasePaymentDriver
$data['intent'] = $this->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this;
return view($this->viewForType($data['payment_method_id']), $data);
}
/**
* Payment Intent Reponse looks like this
+"id": "pi_1FMR7JKmol8YQE9DuC4zMeN3"
+"object": "payment_intent"
+"allowed_source_types": array:1 [
0 => "card"
]
+"amount": 2372484
+"canceled_at": null
+"cancellation_reason": null
+"capture_method": "automatic"
+"client_secret": "pi_1FMR7JKmol8YQE9DuC4zMeN3_secret_J3yseWJG6uV0MmsrAT1FlUklV"
+"confirmation_method": "automatic"
+"created": 1569381877
+"currency": "usd"
+"description": "[3]"
+"last_payment_error": null
+"livemode": false
+"next_action": null
+"next_source_action": null
+"payment_method": "pm_1FMR7ZKmol8YQE9DQWqPuyke"
+"payment_method_types": array:1 []
+"receipt_email": null
+"setup_future_usage": "off_session"
+"shipping": null
+"source": null
+"status": "succeeded"
*/
public function processPaymentResponse($request)
{
$server_response = json_decode($request->input('gateway_response'));
$payment_method = $server_response->payment_method;
$payment_status = $server_response->status;
$save_card = $request->input('store_card');
$gateway_type_id = $request->input('gateway_type_id');
$this->init()
$payment_intent = \Stripe\PaymentIntent::retrieve($server_response->id);
$customer = $payment_intent->customer;
if($save_card)
{
$this->init()
$stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method);
$stripe_payment_method_obj = $stripe_payment_method->jsonSerialize();
$stripe_payment_method->attach(['customer' => $customer]);
$payment_meta = new \stdClass;
if($stripe_payment_method_obj['type'] == 'card') {
$payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month'];
$payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year'];
$payment_meta->brand = $stripe_payment_method_obj['card']['brand'];
$payment_meta->last4 = $stripe_payment_method_obj['card']['last4'];
$payment_meta->type = $stripe_payment_method_obj['type'];
}
$cgt = new ClientGatewayToken;
$cgt->company_id = $this->client->company->id;
$cgt->client_id = $this->client->id;
$cgt->token = $payment_method;
$cgt->company_gateway_id = $this->company_gateway->id;
$cgt->gateway_type_id = $gateway_type_id;
$cgt->gateway_customer_reference = $customer;
$cgt->meta = $payment_meta;
$cgt->save();
if($is_default == 'true' || $this->client->gateway_tokens->count() == 1)
{
$this->client->gateway_tokens()->update(['is_default'=>0]);
$cgt->is_default = 1;
$cgt->save();
}
}
}
/**
* Creates a new String Payment Intent

View File

@ -2,17 +2,27 @@
@section('pay_now')
{!! Former::framework('TwitterBootstrap4'); !!}
{!! Former::horizontal_open()
->id('server_response')
->route('client.payments.response')
->method('POST'); !!}
{!! Former::hidden('gateway_response')->id('gateway_response') !!}
{!! Former::hidden('store_card')->id('store_card') !!}
{!! Former::hidden('hashed_ids')->value($hashed_ids) !!}
{!! Former::hidden('company_gateway_id')->value($payment_method_id) !!}
{!! Former::hidden('payment_method_id')->value($gateway->getCompanyGatewayId()) !!}
{!! Former::close() !!}
@if($token)
<div class="py-md-5 ninja stripe">
<div class="form-group">
<input class="form-control" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
</div>
<div class="form-group">
<div id="card-element"></div>
</div>
<div class="form-group">
<button id="card-button" data-secret="{{ $intent->client_secret }}">
Submit Payment
<button id="card-button" class="btn btn-primary pull-right" data-secret="{{ $intent->client_secret }}">
{{ ctrans('texts.pay_now') }} - {{ $token->meta->brand }} - {{ $token ->meta->last4}}
</button>
</div>
</div>
@ -29,8 +39,8 @@
</div>
<div class="form-check form-check-inline mr-1">
<input class="form-check-input" id="proxy_is_default" type="checkbox">
<label class="form-check-label" for="proxy_is_default">{{ ctrans('texts.save_as_default') }}</label>
<input class="form-check-input" id="token_billing_checkbox" type="checkbox">
<label class="form-check-label" for="token_billing_checkbox">{{ ctrans('texts.token_billing_checkbox') }}</label>
</div>
@ -38,7 +48,7 @@
<div class="form-group">
<button id="card-button" class="btn btn-primary pull-right" data-secret="{{ $intent->client_secret }}">
{{ ctrans('texts.pay_now') }}
{{ ctrans('texts.pay_now') }}
</button>
</div>
</div>
@ -53,9 +63,6 @@
var stripe = Stripe('{{ $gateway->getPublishableKey() }}');
var elements = stripe.elements();
var cardElement = elements.create('card');
cardElement.mount('#card-element');
var cardholderName = document.getElementById('cardholder-name');
var cardButton = document.getElementById('card-button');
@ -65,7 +72,7 @@
cardButton.addEventListener('click', function(ev) {
stripe.handleCardPayment(
clientSecret, {
payment_method: {{$token->token}},
payment_method: '{{$token->token}}',
}
).then(function(result) {
if (result.error) {
@ -82,6 +89,10 @@
});
});
@else
var cardElement = elements.create('card');
cardElement.mount('#card-element');
cardButton.addEventListener('click', function(ev) {
stripe.handleCardPayment(
clientSecret, cardElement, {
@ -103,7 +114,7 @@
}
});
});
@endif
$("#card-button").attr("disabled", true);
$('#cardholder-name').on('input',function(e){
@ -112,12 +123,13 @@
else
$("#card-button").attr("disabled", true);
});
@endif
function postResult(result)
{
$("#gateway_response").val(JSON.stringify(result.setupIntent));
$("#is_default").val($('#proxy_is_default').is(":checked"));
$("#gateway_response").val(JSON.stringify(result.paymentIntent));
$("#store_card").val($('#token_billing_checkbox').is(":checked"));
$("#card-button").attr("disabled", true);
$('#server_response').submit();
}

View File

@ -103,7 +103,6 @@ $(function() {
data: function(data) {
data.client_status = client_statuses;
data.filter = table_filter;
// data.search.value = table_filter;
}
},

View File

@ -25,6 +25,7 @@ Route::group(['middleware' => ['auth:contact'], 'prefix' => 'client', 'as' => 'c
Route::get('payments', 'ClientPortal\PaymentController@index')->name('payments.index');
Route::post('payments/process', 'ClientPortal\PaymentController@process')->name('payments.process');
Route::post('payments/process/response', 'ClientPortal\PaymentController@response')->name('payments.response');
Route::get('profile/{client_contact}/edit', 'ClientPortal\ProfileController@edit')->name('profile.edit');
Route::put('profile/{client_contact}/edit', 'ClientPortal\ProfileController@update')->name('profile.update');