diff --git a/.php_cs b/.php_cs index 9dfc128f7605..33c6ee25511b 100644 --- a/.php_cs +++ b/.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'); diff --git a/app/Exceptions/PaymentFailed.php b/app/Exceptions/PaymentFailed.php index 06d3f9dda5cb..55762f03fd8a 100644 --- a/app/Exceptions/PaymentFailed.php +++ b/app/Exceptions/PaymentFailed.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(), ]); diff --git a/app/Http/Controllers/PaymentWebhookController.php b/app/Http/Controllers/PaymentWebhookController.php index e1884e7af149..b32afa1d4c11 100644 --- a/app/Http/Controllers/PaymentWebhookController.php +++ b/app/Http/Controllers/PaymentWebhookController.php @@ -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()); } } diff --git a/app/Http/Requests/Payments/PaymentWebhookRequest.php b/app/Http/Requests/Payments/PaymentWebhookRequest.php index 7dc483272a23..f4704559aac8 100644 --- a/app/Http/Requests/Payments/PaymentWebhookRequest.php +++ b/app/Http/Requests/Payments/PaymentWebhookRequest.php @@ -1,4 +1,5 @@ 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(); } } diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index aeffd153351c..f5b51eb52b09 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -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 { diff --git a/app/PaymentDrivers/CheckoutCom/Utilities.php b/app/PaymentDrivers/CheckoutCom/Utilities.php index 455bc838a895..14a5c66c603d 100644 --- a/app/PaymentDrivers/CheckoutCom/Utilities.php +++ b/app/PaymentDrivers/CheckoutCom/Utilities.php @@ -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')); } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index afe466d7b3a2..9c0ff224eb12 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -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); + } } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index bca47d23277e..ceb5eea43d96 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -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', diff --git a/routes/api.php b/routes/api.php index 8291c7185c75..81d65c713f87 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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');