mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 21:22:58 +01:00
Merge pull request #4439 from beganovich/v5-checkout-3ds
(v5) Support for Checkout.com 3D Secure 2 API
This commit is contained in:
commit
4f2bf09c0c
1
.php_cs
1
.php_cs
@ -4,6 +4,7 @@ $finder = Symfony\Component\Finder\Finder::create()
|
||||
->notPath('vendor')
|
||||
->notPath('bootstrap')
|
||||
->notPath('storage')
|
||||
->notPath('node_modules')
|
||||
->in(__DIR__)
|
||||
->name('*.php')
|
||||
->notName('*.blade.php');
|
||||
|
@ -13,7 +13,14 @@ class PaymentFailed extends Exception
|
||||
|
||||
public function render($request)
|
||||
{
|
||||
return render('gateways.unsuccessful', [
|
||||
if (auth()->user()) {
|
||||
return render('gateways.unsuccessful', [
|
||||
'message' => $this->getMessage(),
|
||||
'code' => $this->getCode(),
|
||||
]);
|
||||
}
|
||||
|
||||
return response([
|
||||
'message' => $this->getMessage(),
|
||||
'code' => $this->getCode(),
|
||||
]);
|
||||
|
@ -13,8 +13,6 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Models\Payment;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class PaymentWebhookController extends Controller
|
||||
{
|
||||
@ -23,36 +21,10 @@ class PaymentWebhookController extends Controller
|
||||
$this->middleware('guest');
|
||||
}
|
||||
|
||||
public function __invoke(PaymentWebhookRequest $request, string $company_key = null, string $gateway_key = null)
|
||||
public function __invoke(PaymentWebhookRequest $request, string $gateway_key, string $company_key)
|
||||
{
|
||||
$transaction_reference = $this->getTransactionReference($request->all(), $request);
|
||||
|
||||
$payment = Payment::where('transaction_reference', $transaction_reference)->first();
|
||||
|
||||
if (is_null($payment)) {
|
||||
return response([
|
||||
'message' => 'Sorry, we couldn\'t find requested payment.',
|
||||
'status_code' => 404,
|
||||
], 404); /* Record event, throw an exception.. */
|
||||
}
|
||||
|
||||
return $request
|
||||
->companyGateway()
|
||||
->driver($payment->client)
|
||||
->setPaymentMethod($payment->gateway_type_id)
|
||||
->processWebhookRequest($request->all(), $request->company(), $request->companyGateway(), $payment);
|
||||
}
|
||||
|
||||
public function getTransactionReference(array $data, PaymentWebhookRequest $request)
|
||||
{
|
||||
$flatten = Arr::dot($data);
|
||||
|
||||
if (isset($flatten['data.object.id'])) {
|
||||
return $flatten['data.object.id']; // stripe.com
|
||||
}
|
||||
|
||||
if ($request->has('cko-session-id')) {
|
||||
// checkout.com
|
||||
}
|
||||
return $request->getCompanyGateway()
|
||||
->driver($request->getClient())
|
||||
->processWebhookRequest($request, $request->getPayment());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -9,12 +10,14 @@
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Http\Requests\Payments;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
|
||||
class PaymentWebhookRequest extends Request
|
||||
{
|
||||
@ -30,28 +33,61 @@ class PaymentWebhookRequest extends Request
|
||||
];
|
||||
}
|
||||
|
||||
public function company()
|
||||
/**
|
||||
* Resolve company gateway.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return null|\App\Models\CompanyGateway
|
||||
*/
|
||||
public function getCompanyGateway(): ?CompanyGateway
|
||||
{
|
||||
if (! $this->company_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Company::query()
|
||||
->where('company_key', $this->company_key)
|
||||
->firstOrFail();
|
||||
return CompanyGateway::where('gateway_key', $this->gateway_key)->firstOrFail();
|
||||
}
|
||||
|
||||
public function companyGateway()
|
||||
/**
|
||||
* Resolve payment hash.
|
||||
*
|
||||
* @param string $hash
|
||||
* @return null|\App\Http\Requests\Payments\PaymentHash
|
||||
*/
|
||||
public function getPaymentHash(): ?PaymentHash
|
||||
{
|
||||
if (! $this->gateway_key || ! $this->company_key) {
|
||||
return false;
|
||||
if ($this->query('hash')) {
|
||||
return PaymentHash::where('hash', $this->query('hash'))->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
||||
$company = $this->company();
|
||||
/**
|
||||
* Resolve possible payment in the request.
|
||||
*
|
||||
* @return null|\App\Models\Payment
|
||||
*/
|
||||
public function getPayment(): ?Payment
|
||||
{
|
||||
$hash = $this->getPaymentHash();
|
||||
|
||||
return CompanyGateway::query()
|
||||
->where('gateway_key', $this->gateway_key)
|
||||
->where('company_id', $company->id)
|
||||
->firstOrFail();
|
||||
return $hash->payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve client from payment hash.
|
||||
*
|
||||
* @return null|\App\Models\Client
|
||||
*/
|
||||
public function getClient(): ?Client
|
||||
{
|
||||
$hash = $this->getPaymentHash();
|
||||
|
||||
return Client::find($hash->data->client_id)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve company from company_key parameter.
|
||||
*
|
||||
* @return null|\App\Models\Company
|
||||
*/
|
||||
public function getCompany(): ?Company
|
||||
{
|
||||
return Company::where('company_key', $this->company_key)->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ class CreditCard
|
||||
'currency' => $request->currency,
|
||||
'payment_hash' => $request->payment_hash,
|
||||
'reference' => $request->payment_hash,
|
||||
'client_id' => $this->checkout->client->id,
|
||||
];
|
||||
|
||||
$state = array_merge($state, $request->all());
|
||||
@ -121,8 +122,17 @@ class CreditCard
|
||||
$payment->amount = $this->checkout->payment_hash->data->value;
|
||||
$payment->reference = $this->checkout->payment_hash->data->reference;
|
||||
|
||||
$this->checkout->payment_hash->data = array_merge((array) $this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]);
|
||||
$this->checkout->payment_hash->save();
|
||||
|
||||
if ($this->checkout->client->currency()->code === 'EUR') {
|
||||
$payment->{'3ds'} = ['enabled' => true];
|
||||
|
||||
$payment->{'success_url'} = route('payment_webhook', [
|
||||
'gateway_key' => $this->checkout->company_gateway->gateway_key,
|
||||
'company_key' => $this->checkout->client->company->company_key,
|
||||
'hash' => $this->checkout->payment_hash->hash,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -29,6 +29,11 @@ trait Utilities
|
||||
return $this->company_gateway->getConfigField('publicApiKey');
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return static::class == 'App\PaymentDrivers\CheckoutComPaymentDriver' ? $this : $this->checkout;
|
||||
}
|
||||
|
||||
public function convertToCheckoutAmount($amount, $currency)
|
||||
{
|
||||
$cases = [
|
||||
@ -52,42 +57,42 @@ trait Utilities
|
||||
|
||||
private function processSuccessfulPayment(Payment $_payment)
|
||||
{
|
||||
if ($this->checkout->payment_hash->data->store_card) {
|
||||
if ($this->getParent()->payment_hash->data->store_card) {
|
||||
$this->storePaymentMethod($_payment);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'payment_method' => $_payment->source['id'],
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($_payment->source['scheme'])),
|
||||
'amount' => $this->checkout->payment_hash->data->raw_value,
|
||||
'amount' => $this->getParent()->payment_hash->data->raw_value,
|
||||
'transaction_reference' => $_payment->id,
|
||||
];
|
||||
|
||||
$payment = $this->checkout->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
$payment = $this->getParent()->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $_payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_CHECKOUT,
|
||||
$this->checkout->client
|
||||
$this->getParent()->client
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->checkout->encodePrimaryKey($payment->id)]);
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->getParent()->encodePrimaryKey($payment->id)]);
|
||||
}
|
||||
|
||||
public function processUnsuccessfulPayment(Payment $_payment)
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->checkout->client,
|
||||
$this->getParent()->client,
|
||||
$_payment,
|
||||
$this->checkout->client->company,
|
||||
$this->checkout->payment_hash->data->value
|
||||
$this->getParent()->client->company,
|
||||
$this->getParent()->payment_hash->data->value
|
||||
);
|
||||
|
||||
$message = [
|
||||
'server_response' => $_payment,
|
||||
'data' => $this->checkout->payment_hash->data,
|
||||
'data' => $this->getParent()->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
@ -95,7 +100,7 @@ trait Utilities
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_CHECKOUT,
|
||||
$this->checkout->client
|
||||
$this->getParent()->client
|
||||
);
|
||||
|
||||
throw new PaymentFailed($_payment->status, $_payment->http_code);
|
||||
@ -103,27 +108,10 @@ trait Utilities
|
||||
|
||||
private function processPendingPayment(Payment $_payment)
|
||||
{
|
||||
$data = [
|
||||
'payment_method' => $_payment->source->id,
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $this->checkout->payment_hash->data->value,
|
||||
'transaction_reference' => $_payment->id,
|
||||
];
|
||||
|
||||
$payment = $this->checkout->createPayment($data, \App\Models\Payment::STATUS_PENDING);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $_payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_CHECKOUT,
|
||||
$this->checkout->client
|
||||
);
|
||||
|
||||
try {
|
||||
return redirect($_payment->_links['redirect']['href']);
|
||||
} catch (Exception $e) {
|
||||
return $this->processInternallyFailedPayment($this->checkout, $e);
|
||||
return $this->processInternallyFailedPayment($this->getParent(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,10 +128,10 @@ trait Utilities
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
'token' => $response->source['id'],
|
||||
'payment_method_id' => $this->checkout->payment_hash->data->payment_method_id,
|
||||
'payment_method_id' => $this->getParent()->payment_hash->data->payment_method_id,
|
||||
];
|
||||
|
||||
return $this->checkout->storePaymentMethod($data);
|
||||
return $this->getParent()->storePaymentMethod($data);
|
||||
} catch (Exception $e) {
|
||||
session()->flash('message', ctrans('texts.payment_method_saving_failed'));
|
||||
}
|
||||
|
@ -12,7 +12,9 @@
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Company;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
@ -23,6 +25,7 @@ use App\Utils\Traits\SystemLogTrait;
|
||||
use Checkout\CheckoutApi;
|
||||
use Checkout\Library\Exceptions\CheckoutHttpException;
|
||||
use Checkout\Models\Payments\Refund;
|
||||
use Exception;
|
||||
|
||||
class CheckoutComPaymentDriver extends BaseDriver
|
||||
{
|
||||
@ -134,7 +137,7 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
|
||||
->with('missing_required_fields', $this->required_fields);
|
||||
}
|
||||
|
||||
|
||||
return $this->payment_method->authorizeResponse($data);
|
||||
}
|
||||
|
||||
@ -209,5 +212,24 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||
{
|
||||
$this->init();
|
||||
$this->setPaymentHash($request->getPaymentHash());
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments()->details($request->query('cko-session-id'));
|
||||
|
||||
if ($payment->approved) {
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
} else {
|
||||
return $this->processUnsuccessfulPayment($payment);
|
||||
}
|
||||
} catch (CheckoutHttpException | Exception $e) {
|
||||
return $this->processInternallyFailedPayment($this, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3233,7 +3233,7 @@ return [
|
||||
'test_pdf' => 'Test PDF',
|
||||
'status_cancelled' => 'Cancelled',
|
||||
|
||||
'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Save card" during payment process.',
|
||||
'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.',
|
||||
|
||||
'node_status' => 'Node status',
|
||||
'npm_status' => 'NPM status',
|
||||
|
@ -180,6 +180,6 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
Route::post('support/messages/send', 'Support\Messages\SendingController');
|
||||
});
|
||||
|
||||
Route::match(['get', 'post'], 'payment_webhook/{company_key?}/{gateway_key?}', 'PaymentWebhookController');
|
||||
Route::match(['get', 'post'], 'payment_webhook/{gateway_key}/{company_key}', 'PaymentWebhookController')->name('payment_webhook');
|
||||
|
||||
Route::fallback('BaseController@notFound');
|
||||
|
Loading…
Reference in New Issue
Block a user