1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00
This commit is contained in:
Benjamin Beganović 2020-06-11 15:13:35 +02:00
parent 049f30104e
commit d37064e393
11 changed files with 248 additions and 36 deletions

View File

@ -126,7 +126,6 @@ class PaymentController extends Controller
public function response(Request $request)
{
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
return $gateway

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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": {

94
composer.lock generated
View File

@ -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,

View File

@ -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()}}}});
!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()}}}});

View File

@ -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",

View File

@ -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();
},

View File

@ -20,6 +20,9 @@
@endforeach
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="value" value="{{ $value }}">
<input type="hidden" name="raw_value" value="{{ $raw_value }}">
<input type="hidden" name="currency" value="{{ $currency }}">
</form>
<div class="container mx-auto">
@ -51,12 +54,20 @@
<span class="font-bold">{{ App\Utils\Number::formatMoney($amount, $client) }}</span>
</dd>
</div>
<div class="bg-gray-50 px-4 py-5 flex justify-end">
<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.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" id="store-card-checkbox" class="form-checkbox">
</dd>
</div>
<div class="bg-white px-4 py-5 flex justify-end">
<form class="payment-form" method="POST" action="https://merchant.com/successUrl">
@if(app()->environment() == 'production')
<script async src="https://cdn.checkout.com/js/checkout.js"></script>
<script async src="https://cdn.checkout.com/js/checkout.js"></script>
@else
<script async src="https://cdn.checkout.com/sandbox/js/checkout.js"></script>
<script async src="https://cdn.checkout.com/sandbox/js/checkout.js"></script>
@endif
</form>
</div>

View File

@ -19,14 +19,14 @@
</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="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>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
{{ ctrans('texts.complete_your_payment') }}
</p>
</div>