From d37064e393b3feacd836571f9ef773a447a7153f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 11 Jun 2020 15:13:35 +0200 Subject: [PATCH] wip --- .../ClientPortal/PaymentController.php | 1 - app/Models/SystemLog.php | 1 + app/PaymentDrivers/CheckoutCom/Utilities.php | 21 +++ .../CheckoutComPaymentDriver.php | 131 +++++++++++++++++- composer.json | 7 +- composer.lock | 94 ++++++++++--- public/js/clients/payments/checkout.com.js | 2 +- public/mix-manifest.json | 2 +- resources/js/clients/payments/checkout.com.js | 4 + .../gateways/checkout/credit_card.blade.php | 17 ++- .../gateways/stripe/credit_card.blade.php | 4 +- 11 files changed, 248 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index 0948aabb3d..2bb441275a 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -126,7 +126,6 @@ class PaymentController extends Controller public function response(Request $request) { - $gateway = CompanyGateway::find($request->input('company_gateway_id')); return $gateway diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index c10b9aa853..9f43d5bf42 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -35,6 +35,7 @@ class SystemLog extends Model const TYPE_STRIPE = 301; const TYPE_LEDGER = 302; const TYPE_FAILURE = 303; + const TYPE_CHECKOUT = 304; const TYPE_QUOTA_EXCEEDED = 400; const TYPE_UPSTREAM_FAILURE = 401; diff --git a/app/PaymentDrivers/CheckoutCom/Utilities.php b/app/PaymentDrivers/CheckoutCom/Utilities.php index 514884c7dd..e121c31b06 100644 --- a/app/PaymentDrivers/CheckoutCom/Utilities.php +++ b/app/PaymentDrivers/CheckoutCom/Utilities.php @@ -23,4 +23,25 @@ trait Utilities return $this->company_gateway->getConfig()->publicApiKey; } + + public function convertToCheckoutAmount($amount, $currency) + { + $cases = [ + 'option_1' => ['BIF', 'DJF', 'GNF', 'ISK', 'KMF', 'XAF', 'CLF', 'XPF', 'JPY', 'PYG', 'RWF', 'KRW', 'VUV', 'VND', 'XOF'], + 'option_2' => ['BHD', 'IQD', 'JOD', 'KWD', 'LYD', 'OMR', 'TND'], + ]; + + // https://docs.checkout.com/resources/calculating-the-value#Calculatingthevalue-Option1:Thefullvaluefullvalue + if (in_array($currency, $cases['option_1'])) { + return round($amount); + } + + // https://docs.checkout.com/resources/calculating-the-value#Calculatingthevalue-Option2:Thevaluedividedby1000valuediv1000 + if (in_array($currency, $cases['option_2'])) { + return round($amount * 1000); + } + + // https://docs.checkout.com/resources/calculating-the-value#Calculatingthevalue-Option3:Thevaluedividedby100valuediv100 + return round($amount * 100); + } } diff --git a/app/PaymentDrivers/CheckoutComPaymentDriver.php b/app/PaymentDrivers/CheckoutComPaymentDriver.php index 89d78b0364..45c7df97f4 100644 --- a/app/PaymentDrivers/CheckoutComPaymentDriver.php +++ b/app/PaymentDrivers/CheckoutComPaymentDriver.php @@ -12,9 +12,19 @@ namespace App\PaymentDrivers; +use App\Events\Payment\PaymentWasCreated; +use App\Jobs\Mail\PaymentFailureMailer; +use App\Jobs\Util\SystemLogger; use App\Models\GatewayType; +use App\Models\Payment as NinjaPaymentModel; +use App\Models\PaymentType; +use App\Models\SystemLog; use App\PaymentDrivers\CheckoutCom\Utilities; use App\Utils\Traits\SystemLogTrait; +use Checkout\CheckoutApi; +use Checkout\Library\Exceptions\CheckoutHttpException; +use Checkout\Models\Payments\Payment; +use Checkout\Models\Payments\TokenSource; class CheckoutComPaymentDriver extends BasePaymentDriver { @@ -22,7 +32,7 @@ class CheckoutComPaymentDriver extends BasePaymentDriver /* The company gateway instance*/ public $company_gateway; - + /* The Invitation */ protected $invitation; @@ -46,7 +56,9 @@ class CheckoutComPaymentDriver extends BasePaymentDriver public function init() { - // $this->gateway + $secret_key = $this->company_gateway->getConfig()->secretApiKey; + + $this->gateway = new CheckoutApi($secret_key); // @todo: 2nd (sandbox), 3rd (public) } public function viewForType($gateway_type_id) @@ -65,7 +77,8 @@ class CheckoutComPaymentDriver extends BasePaymentDriver $data['gateway'] = $this; $data['client'] = $this->client; $data['currency'] = $this->client->getCurrencyCode(); - $data['value'] = $data['amount_with_fee']; // Fix for currencies. + $data['value'] = $this->convertToCheckoutAmount($data['amount_with_fee'], $this->client->getCurrencyCode()); + $data['raw_value'] = $data['amount_with_fee']; $data['customer_email'] = $this->client->present()->email; return render($this->viewForType($data['payment_method_id']), $data); @@ -73,6 +86,116 @@ class CheckoutComPaymentDriver extends BasePaymentDriver public function processPaymentResponse($request) { - dd($request->all()); + $this->init(); + + $state = [ + 'server_response' => json_decode($request->gateway_response), + 'value' => $request->value, + 'raw_value' => $request->raw_value, + 'currency' => $request->currency, + ]; + + $state = array_merge($state, $request->all()); + + $method = new TokenSource($state['server_response']->cardToken); + + $payment = new Payment($method, $state['currency']); + $payment->amount = $state['value']; + // $payment->{"3ds"} = [ + // 'enabled' => true, + // ]; + + try { + $response = $this->gateway->payments()->request($payment); + $state['payment_response'] = $response; + + if ($response->status === 'Authorized') { + return $this->processSuccessfulPayment($state); + } + + if ($response->status === 'Pending') { + return $this->processPendingPayment($state); + } + + if ($response->status === 'Declined') { + return $this->processUnsuccessfulPayment($state); + } + } catch (CheckoutHttpException $e) { + return $this->processInternallyFailedPayment($e, $state); + } + } + + public function processSuccessfulPayment($state) + { + $state['charge_id'] = $state['payment_response']->id; + + if (isset($state['store_card'])) { + // .. + } + + $data = [ + 'payment_method' => $state['charge_id'], + 'payment_type' => PaymentType::CREDIT_CARD_OTHER, // @todo: needs proper status + 'amount' => $state['raw_value'], + ]; + + $payment = $this->createPayment($data, NinjaPaymentModel::STATUS_COMPLETED); + + $this->attachInvoices($payment, $state['hashed_ids']); + + $payment->service()->updateInvoicePayment(); + + event(new PaymentWasCreated($payment, $payment->company)); + + $logger_message = [ + 'server_response' => $state['payment_response'], + 'data' => $data + ]; + + SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_CHECKOUT, $this->client); + + return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); + } + + public function processPendingPayment($state) + { + + } + + public function processUnsuccessfulPayment($state) + { + dd($state); + } + + public function processInternallyFailedPayment($e, $state) + { + $message = json_decode($e->getBody()); + + PaymentFailureMailer::dispatch($this->client, $message->error_type, $this->client->company, $state['value']); + + $message = [ + 'server_response' => $state['server_response'], + 'data' => $message, + ]; + + SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_CHECKOUT, $this->client); + + throw new \Exception('Failed to process the payment.', 1); + } + + public function createPayment($data, $status = NinjaPaymentModel::STATUS_COMPLETED): NinjaPaymentModel + { + $payment = parent::createPayment($data, $status); + + $client_contact = $this->getContact(); + $client_contact_id = $client_contact ? $client_contact->id : null; + + $payment->amount = $data['amount']; + $payment->type_id = $data['payment_type']; + $payment->transaction_reference = $data['payment_method']; + $payment->client_contact_id = $client_contact_id; + $payment->save(); + + return $payment; } } diff --git a/composer.json b/composer.json index a2b3c4729f..ff0d8872e2 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "php": ">=7.3", "ext-json": "*", "asgrim/ofxparser": "^1.2", - "beganovich/omnipay-checkout": "dev-master", "checkout/checkout-sdk-php": "^1.0", "cleverit/ubl_invoice": "^1.3", "composer/composer": "^1.10", @@ -80,7 +79,8 @@ "App\\": "app/" }, "files": [ - "app/Libraries/OFX.php" ] + "app/Libraries/OFX.php" + ] }, "autoload-dev": { "psr-4": { @@ -89,8 +89,7 @@ }, "extra": { "laravel": { - "dont-discover": [ - ] + "dont-discover": [] } }, "scripts": { diff --git a/composer.lock b/composer.lock index f2b241ba58..25b39d3945 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "723f03b92457a3f3410083e2b42f96c3", + "content-hash": "92303801b323e8ec67d40803e6b4127f", "packages": [ { "name": "asgrim/ofxparser", @@ -147,30 +147,29 @@ "time": "2020-06-09T18:11:16+00:00" }, { - "name": "beganovich/omnipay-checkout", - "version": "dev-master", + "name": "checkout/checkout-sdk-php", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/beganovich/omnipay-checkout.git", - "reference": "a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21" + "url": "https://github.com/checkout/checkout-sdk-php.git", + "reference": "c83ecc54e549efde8ac53cf1bc9701e800d98b0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beganovich/omnipay-checkout/zipball/a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21", - "reference": "a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21", + "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/c83ecc54e549efde8ac53cf1bc9701e800d98b0b", + "reference": "c83ecc54e549efde8ac53cf1bc9701e800d98b0b", "shasum": "" }, "require": { - "omnipay/common": "^3", - "php": "^7.1" + "php": ">=5.4.0" }, "require-dev": { - "omnipay/tests": "^3" + "phpunit/phpunit": "^6" }, "type": "library", "autoload": { "psr-4": { - "Omnipay\\Checkout\\": "src" + "Checkout\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -179,18 +178,28 @@ ], "authors": [ { - "name": "Benjamin Beganović", - "email": "ben@invoiceninja.com", - "role": "Developer" + "name": "Checkout.com", + "email": "platforms@checkout.com", + "homepage": "https://github.com/checkout/checkout-sdk-php/graphs/contributors" } ], - "description": "Checkout driver for the Omnipay PHP payment processing library", - "homepage": "https://github.com/beganovich/omnipay-checkout", + "description": "Checkout.com SDK for PHP", + "homepage": "https://github.com/checkout/checkout-sdk-php", "keywords": [ - "beganovich", - "omnipay-checkout" + "CKO", + "GW3", + "Reboot", + "api", + "checkout", + "checkout.com", + "checkoutcom", + "gateway", + "library", + "payment", + "php", + "sdk" ], - "time": "2020-05-11T22:18:27+00:00" + "time": "2019-07-05T16:22:08+00:00" }, { "name": "cleverit/ubl_invoice", @@ -8653,6 +8662,52 @@ ], "time": "2020-05-05T10:53:32+00:00" }, + { + "name": "beganovich/omnipay-checkout", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/beganovich/omnipay-checkout.git", + "reference": "a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beganovich/omnipay-checkout/zipball/a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21", + "reference": "a4e78bc3ce6ea39ea2c2b7da3019b3966fc86a21", + "shasum": "" + }, + "require": { + "omnipay/common": "^3", + "php": "^7.1" + }, + "require-dev": { + "omnipay/tests": "^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Omnipay\\Checkout\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Beganović", + "email": "ben@invoiceninja.com", + "role": "Developer" + } + ], + "description": "Checkout driver for the Omnipay PHP payment processing library", + "homepage": "https://github.com/beganovich/omnipay-checkout", + "keywords": [ + "beganovich", + "omnipay-checkout" + ], + "time": "2020-05-11T22:18:27+00:00" + }, { "name": "beyondcode/laravel-dump-server", "version": "1.3.0", @@ -11005,7 +11060,6 @@ "aliases": [], "minimum-stability": "dev", "stability-flags": { - "beganovich/omnipay-checkout": 20, "webpatser/laravel-countries": 20 }, "prefer-stable": true, diff --git a/public/js/clients/payments/checkout.com.js b/public/js/clients/payments/checkout.com.js index 8117e89c43..bffb639fc0 100644 --- a/public/js/clients/payments/checkout.com.js +++ b/public/js/clients/payments/checkout.com.js @@ -1,2 +1,2 @@ /*! For license information please see checkout.com.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=8)}({8:function(e,t,n){e.exports=n("XYrq")},XYrq:function(e,t){window.CKOConfig={publicKey:document.querySelector('meta[name="public-key"]').content,customerEmail:document.querySelector('meta[name="customer-email"]').content,value:document.querySelector('meta[name="value"]').content,currency:document.querySelector('meta[name="currency"]').content,paymentMode:"cards",cardFormMode:"cardTokenisation",cardTokenised:function(e){document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.data),document.getElementById("server-response").submit()}}}}); \ No newline at end of file +!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=8)}({8:function(e,t,n){e.exports=n("XYrq")},XYrq:function(e,t){window.CKOConfig={publicKey:document.querySelector('meta[name="public-key"]').content,customerEmail:document.querySelector('meta[name="customer-email"]').content,value:document.querySelector('meta[name="value"]').content,currency:document.querySelector('meta[name="currency"]').content,paymentMode:"cards",cardFormMode:"cardTokenisation",cardTokenised:function(e){document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.data),document.querySelector('input[name="store_card"]').value=document.getElementById("store-card-checkbox").checked,document.getElementById("server-response").submit()}}}}); \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index da28049639..46f2984345 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -7,7 +7,7 @@ "/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=b0a1bd21a57e12cd491a", "/js/clients/payment_methods/authorize-stripe-card.js": "/js/clients/payment_methods/authorize-stripe-card.js?id=f4c45f0da9868d840799", "/js/clients/payments/alipay.js": "/js/clients/payments/alipay.js?id=04778cbf46488e8f6b5f", - "/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=c644b7555ae3c1a918ab", + "/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=ad0a824fb0f79cb8200b", "/js/clients/payments/process.js": "/js/clients/payments/process.js?id=be2b10b1b5e3a727f6e2", "/js/clients/payments/sofort.js": "/js/clients/payments/sofort.js?id=f9253aea74535bc46886", "/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=88ff76c9ca1c15fc011b", diff --git a/resources/js/clients/payments/checkout.com.js b/resources/js/clients/payments/checkout.com.js index 74e5512438..f1fe540445 100644 --- a/resources/js/clients/payments/checkout.com.js +++ b/resources/js/clients/payments/checkout.com.js @@ -20,6 +20,10 @@ window.CKOConfig = { document.querySelector( 'input[name="gateway_response"]' ).value = JSON.stringify(event.data); + + document.querySelector( + 'input[name="store_card"]' + ).value = document.getElementById('store-card-checkbox').checked; document.getElementById('server-response').submit(); }, diff --git a/resources/views/portal/ninja2020/gateways/checkout/credit_card.blade.php b/resources/views/portal/ninja2020/gateways/checkout/credit_card.blade.php index 50a3b7846e..ff6384a99f 100644 --- a/resources/views/portal/ninja2020/gateways/checkout/credit_card.blade.php +++ b/resources/views/portal/ninja2020/gateways/checkout/credit_card.blade.php @@ -20,6 +20,9 @@ @endforeach + + +
@@ -51,12 +54,20 @@ {{ App\Utils\Number::formatMoney($amount, $client) }}
-
+
+
+ {{ ctrans('texts.token_billing_checkbox') }} +
+
+ +
+
+
@if(app()->environment() == 'production') - + @else - + @endif
diff --git a/resources/views/portal/ninja2020/gateways/stripe/credit_card.blade.php b/resources/views/portal/ninja2020/gateways/stripe/credit_card.blade.php index 4ae470a663..8b8a5e1fd2 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/credit_card.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/credit_card.blade.php @@ -19,14 +19,14 @@
-
+

{{ ctrans('texts.pay_now') }}

-

+

{{ ctrans('texts.complete_your_payment') }}