Merge pull request #9905 from turbo124/v5-develop

v5.10.22
This commit is contained in:
David Bomba 2024-08-14 20:20:24 +10:00 committed by GitHub
commit ba0765ac6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 423 additions and 42 deletions

View File

@ -1 +1 @@
5.10.21
5.10.22

View File

@ -232,7 +232,7 @@ class BaseDriver extends AbstractPaymentDriver
*
* @param ClientGatewayToken $cgt The client gateway token object
* @param PaymentHash $payment_hash The Payment hash containing the payment meta data
* @return void The payment response
* @return ?Payment|bool The payment response
*/
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
@ -433,7 +433,7 @@ class BaseDriver extends AbstractPaymentDriver
$invoice_item->tax_name2 = $fees_and_limits->fee_tax_name2;
$invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3;
$invoice_item->tax_name3 = $fees_and_limits->fee_tax_name3;
$invoice_item->tax_id = \App\Models\Product::PRODUCT_TYPE_OVERRIDE_TAX;
$invoice_item->tax_id = (string)\App\Models\Product::PRODUCT_TYPE_OVERRIDE_TAX;
}
$invoice->line_items = $invoice_items;

View File

@ -17,6 +17,54 @@ use App\Models\Company;
class ForteCustomerFactory
{
public function convertToForte(Client $client): array
{
return [
"first_name" => $client->present()->first_name(),
"last_name" => $client->present()->last_name(),
"company_name" => $client->present()->name(),
"addresses" => [
[
"label" => "Billing Address",
"first_name" => $client->present()->first_name(),
"last_name" => $client->present()->last_name(),
"company_name" => $client->present()->name(),
"phone" => $client->present()->phone(),
"email" => $client->present()->email(),
"shipping_address_type" => "commercial",
"address_type" => "default_shipping",
"physical_address" => [
"street_line1" => $client->address2,
"street_line2" => $client->address1,
"locality" => $client->city,
"region" => $client->state,
"postal_code" => $client->postal_code
]
],
// [
// "label" => "Brown Billing",
// "first_name" => "Emmett",
// "last_name" => "Brown",
// "company_name" => "Brown Associates",
// "phone" => "444-444-4444",
// "email" => "e.brown@forte.net",
// "shipping_address_type" => "commercial",
// "address_type" => "default_billing",
// "physical_address" => [
// "street_line1" => "500 Delorean Dr",
// "street_line2" => "Suite 200",
// "locality" => "Hill Valley",
// "region" => "CA",
// "postal_code" => "95420"
// ]
// ]
]
];
}
public function convertToNinja(array $customer, Company $company): array
{
return

View File

@ -12,6 +12,7 @@
namespace App\PaymentDrivers\Forte;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
@ -59,22 +60,99 @@ class CreditCard
public function authorizeResponse($request)
{
$payment_meta = new \stdClass();
$payment_meta->exp_month = (string) $request->expire_month;
$payment_meta->exp_year = (string) $request->expire_year;
$payment_meta->brand = (string) $request->card_type;
$payment_meta->last4 = (string) $request->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$cst = $this->forte->findOrCreateCustomer();
$data = [
'payment_meta' => $payment_meta,
'token' => $request->one_time_token,
'payment_method_id' => $request->payment_method_id,
"label" => $request->card_holders_name." " .$request->card_type,
"notes" => $request->card_holders_name." " .$request->card_type,
"card" => [
"one_time_token" => $request->one_time_token,
"name_on_card" => $request->card_holders_name
],
];
$this->forte->storeGatewayToken($data);
$response = $this->forte->stubRequest()
->post("{$this->forte->baseUri()}/organizations/{$this->forte->getOrganisationId()}/locations/{$this->forte->getLocationId()}/customers/{$cst}/paymethods", $data);
if($response->successful()){
$token = $response->object();
$payment_meta = new \stdClass();
$payment_meta->exp_month = (string) $request->expire_month;
$payment_meta->exp_year = (string) $request->expire_year;
$payment_meta->brand = (string) $request->card_brand;
$payment_meta->last4 = (string) $request->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $token->paymethod_token,
'payment_method_id' => $request->payment_method_id,
];
$this->forte->storeGatewayToken($data, ['gateway_customer_reference' => $cst]);
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
}
$error = $response->object();
$message = [
'server_message' => $error->response->response_desc,
'server_response' => $response->json(),
'data' => $data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_FORTE,
$this->client,
$this->client->company,
);
throw new \App\Exceptions\PaymentFailed("Unable to store payment method: {$error->response->response_desc}", 400);
}
private function createPaymentToken($request)
{
$cst = $this->forte->findOrCreateCustomer();
$data = [
"label" => $this->forte->client->present()->name(),
"notes" => $this->forte->client->present()->name(),
"card" => [
"one_time_token" => $request->payment_token,
"name_on_card" => $this->forte->client->present()->first_name(). " ". $this->forte->client->present()->last_name()
],
];
$response = $this->forte->stubRequest()
->post("{$this->forte->baseUri()}/organizations/{$this->forte->getOrganisationId()}/locations/{$this->forte->getLocationId()}/customers/{$cst}/paymethods", $data);
if($response->successful()){
$token = $response->object();
$payment_meta = new \stdClass();
$payment_meta->exp_month = (string) $request->expire_month;
$payment_meta->exp_year = (string) $request->expire_year;
$payment_meta->brand = (string) $request->card_brand;
$payment_meta->last4 = (string) $request->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$data = [
'payment_meta' => $payment_meta,
'token' => $token->paymethod_token,
'payment_method_id' => $request->payment_method_id,
];
$this->forte->storeGatewayToken($data, ['gateway_customer_reference' => $cst]);
}
return redirect()->route('client.payment_methods.index')->withSuccess('Payment Method added.');
}
public function paymentView(array $data)
@ -88,7 +166,20 @@ class CreditCard
public function paymentResponse(PaymentResponseRequest $request)
{
$payment_hash = PaymentHash::where('hash', $request->input('payment_hash'))->firstOrFail();
if(strlen($request->token ?? '') > 3){
$cgt = \App\Models\ClientGatewayToken::find($this->decodePrimaryKey($request->token));
$payment = $this->forte->tokenBilling($cgt, $payment_hash);
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
}
$amount_with_fee = $payment_hash->data->total->amount_with_fee;
$invoice_totals = $payment_hash->data->total->invoice_totals;
$fee_total = null;
@ -126,8 +217,8 @@ class CreditCard
"authorization_amount":'.$amount_with_fee.',
"service_fee_amount":'.$fee_total.',
"billing_address":{
"first_name":"'.$this->forte->client->name.'",
"last_name":"'.$this->forte->client->name.'"
"first_name":"'.$this->forte->client->present()->first_name().'",
"last_name":"'.$this->forte->client->present()->last_name().'"
},
"card":{
"one_time_token":"'.$request->payment_token.'"
@ -146,6 +237,7 @@ class CreditCard
curl_close($curl);
$response = json_decode($response);
} catch (\Throwable $th) {
throw $th;
}
@ -187,7 +279,11 @@ class CreditCard
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED);
// return redirect('client/invoices')->withSuccess('Invoice paid.');
if($request->store_card) {
$this->createPaymentToken($request);
}
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
}

View File

@ -11,6 +11,7 @@
namespace App\PaymentDrivers;
use App\Exceptions\PaymentFailed;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Models\GatewayType;
@ -224,7 +225,7 @@ class FortePaymentDriver extends BaseDriver
return $forte_base_uri;
}
private function getOrganisationId(): string
public function getOrganisationId(): string
{
return $this->company_gateway->getConfigField('organizationId');
}
@ -247,12 +248,90 @@ class FortePaymentDriver extends BaseDriver
private function getClient(?string $email)
{
if(!$email)
return false;
return ClientContact::query()
->where('company_id', $this->company_gateway->company_id)
->where('email', $email)
->first();
}
public function tokenBilling(\App\Models\ClientGatewayToken $cgt, \App\Models\PaymentHash $payment_hash)
{
$amount_with_fee = $payment_hash->data->amount_with_fee;
$fee_total = $payment_hash->fee_total;
$data =
[
"action" => "sale",
"authorization_amount" => $amount_with_fee,
"paymethod_token" => $cgt->token,
"billing_address" => [
"first_name" => $this->client->present()->first_name(),
"last_name" => $this->client->present()->last_name()
],
];
if($fee_total > 0){
$data["service_fee_amount"] = $fee_total;
}
$response = $this->stubRequest()
->post("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}/transactions", $data);
$forte_response = $response->object();
if($response->successful()){
$data = [
'payment_method' => $cgt->gateway_type_id,
'payment_type' => $cgt->gateway_type_id == 2 ? \App\Models\PaymentType::ACH : \App\Models\PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $forte_response->transaction_id,
'gateway_type_id' => $cgt->gateway_type_id,
];
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
$message = [
'server_message' => $forte_response->response->response_desc,
'server_response' => $response->json(),
'data' => $data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_FORTE,
$this->client,
$this->client->company,
);
return $payment;
}
$message = [
'server_message' => $forte_response->response->response_desc,
'server_response' => $response->json(),
'data' => $data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_FORTE,
$this->client,
$this->client->company,
);
throw new PaymentFailed($forte_response->response->response_desc, 500);
}
public function getLocation()
{
@ -264,6 +343,8 @@ class FortePaymentDriver extends BaseDriver
return $response->json();
}
// return $response->body();
return false;
}
@ -297,6 +378,43 @@ class FortePaymentDriver extends BaseDriver
}
public function findOrCreateCustomer()
{
$client_gateway_token = \App\Models\ClientGatewayToken::query()
->where('client_id', $this->client->id)
->where('company_gateway_id', $this->company_gateway->id)
->whereNotLike('token', 'ott_%')
->first();
if($client_gateway_token){
return $client_gateway_token->gateway_customer_reference;
}
else {
$factory = new ForteCustomerFactory();
$data = $factory->convertToForte($this->client);
$response = $this->stubRequest()
->post("{$this->baseUri()}/organizations/{$this->getOrganisationId()}/locations/{$this->getLocationId()}/customers/", $data);
//create customer
if($response->successful()){
$customer = $response->object();
nlog($customer);
return $customer->customer_token;
}
nlog($response->body());
throw new PaymentFailed("Unable to create customer in Forte", 400);
//@todo add syslog here
}
}
public function importCustomers()
{

View File

@ -81,7 +81,7 @@ class AddGatewayFee extends AbstractService
$invoice_item->tax_name2 = $fees_and_limits->fee_tax_name2;
$invoice_item->tax_rate3 = $fees_and_limits->fee_tax_rate3;
$invoice_item->tax_name3 = $fees_and_limits->fee_tax_name3;
$invoice_item->tax_id = Product::PRODUCT_TYPE_OVERRIDE_TAX;
$invoice_item->tax_id = (string)Product::PRODUCT_TYPE_OVERRIDE_TAX;
}
$invoice_items = (array) $this->invoice->line_items;

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.10.21'),
'app_tag' => env('APP_TAG', '5.10.21'),
'app_version' => env('APP_VERSION', '5.10.22'),
'app_tag' => env('APP_TAG', '5.10.22'),
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

View File

@ -1,9 +0,0 @@
var a=Object.defineProperty;var d=(n,e,t)=>e in n?a(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var r=(n,e,t)=>(d(n,typeof e!="symbol"?e+"":e,t),t);/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/class o{constructor(e){r(this,"handleAuthorization",()=>{var e=$("#my-card"),t={api_login_id:this.apiLoginId,card_number:e.CardJs("cardNumber").replace(/[^\d]/g,""),expire_year:e.CardJs("expiryYear").replace(/[^\d]/g,""),expire_month:e.CardJs("expiryMonth").replace(/[^\d]/g,""),cvv:document.getElementById("cvv").value.replace(/[^\d]/g,"")};return document.getElementById("pay-now")&&(document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden")),forte.createToken(t).success(this.successResponseHandler).error(this.failedResponseHandler),!1});r(this,"successResponseHandler",e=>(document.getElementById("payment_token").value=e.onetime_token,document.getElementById("card_brand").value=e.card_type,document.getElementById("server_response").submit(),!1));r(this,"failedResponseHandler",e=>{var t='<div class="alert alert-failure mb-4"><ul><li>'+e.response_description+"</li></ul></div>";return document.getElementById("forte_errors").innerHTML=t,document.getElementById("pay-now").disabled=!1,document.querySelector("#pay-now > svg").classList.add("hidden"),document.querySelector("#pay-now > span").classList.remove("hidden"),!1});r(this,"handle",()=>{let e=document.getElementById("pay-now");return e&&e.addEventListener("click",t=>{this.handleAuthorization()}),this});this.apiLoginId=e,this.cardHolderName=document.getElementById("cardholder_name")}}const s=document.querySelector('meta[name="forte-api-login-id"]').content;new o(s).handle();

View File

@ -0,0 +1,9 @@
var r=Object.defineProperty;var l=(n,e,t)=>e in n?r(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var d=(n,e,t)=>(l(n,typeof e!="symbol"?e+"":e,t),t);/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/class c{constructor(e){d(this,"handleAuthorization",()=>{var e=$("#my-card"),t={api_login_id:this.apiLoginId,card_number:e.CardJs("cardNumber").replace(/[^\d]/g,""),expire_year:e.CardJs("expiryYear").replace(/[^\d]/g,""),expire_month:e.CardJs("expiryMonth").replace(/[^\d]/g,""),cvv:document.getElementById("cvv").value.replace(/[^\d]/g,"")};return document.getElementById("pay-now")&&(document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden")),forte.createToken(t).success(this.successResponseHandler).error(this.failedResponseHandler),!1});d(this,"successResponseHandler",e=>{document.getElementById("payment_token").value=e.onetime_token,document.getElementById("card_brand").value=e.card_type,document.getElementById("expire_year").value=e.expire_year,document.getElementById("expire_month").value=e.expire_month,document.getElementById("last_4").value=e.last_4;let t=document.querySelector("input[name=token-billing-checkbox]:checked");return t&&(document.getElementById("store_card").value=t.value),document.getElementById("server_response").submit(),!1});d(this,"failedResponseHandler",e=>{var t='<div class="alert alert-failure mb-4"><ul><li>'+e.response_description+"</li></ul></div>";return document.getElementById("forte_errors").innerHTML=t,document.getElementById("pay-now").disabled=!1,document.querySelector("#pay-now > svg").classList.add("hidden"),document.querySelector("#pay-now > span").classList.remove("hidden"),!1});d(this,"handle",()=>{Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(o=>o.addEventListener("click",a=>{document.getElementById("save-card--container").style.display="none",document.getElementById("forte--credit-card-container").style.display="none",document.getElementById("token").value=a.target.dataset.token}));let e=document.getElementById("toggle-payment-with-credit-card");e&&e.addEventListener("click",()=>{document.getElementById("save-card--container").style.display="grid",document.getElementById("forte--credit-card-container").style.display="flex",document.getElementById("token").value=null});let t=document.getElementById("pay-now");return t&&t.addEventListener("click",o=>{let a=document.getElementById("token");a.value?this.handlePayNowAction(a.value):this.handleAuthorization()}),this});this.apiLoginId=e,this.cardHolderName=document.getElementById("cardholder_name")}handlePayNowAction(e){document.getElementById("pay-now").disabled=!0,document.querySelector("#pay-now > svg").classList.remove("hidden"),document.querySelector("#pay-now > span").classList.add("hidden"),document.getElementById("token").value=e,document.getElementById("server_response").submit()}}const i=document.querySelector('meta[name="forte-api-login-id"]').content;new c(i).handle();

View File

@ -86,7 +86,7 @@
"src": "resources/js/clients/payments/forte-ach-payment.js"
},
"resources/js/clients/payments/forte-credit-card-payment.js": {
"file": "assets/forte-credit-card-payment-b605ccf2.js",
"file": "assets/forte-credit-card-payment-d2571506.js",
"isEntry": true,
"src": "resources/js/clients/payments/forte-credit-card-payment.js"
},

View File

@ -41,8 +41,18 @@ class ForteAuthorizeCard {
};
successResponseHandler = (response) => {
document.getElementById('payment_token').value = response.onetime_token;
document.getElementById('card_brand').value = response.card_type;
document.getElementById('card_brand').value = response.card_brand;
document.getElementById('expire_year').value = response.expire_year;
document.getElementById('expire_month').value = response.expire_month;
document.getElementById('last_4').value = response.last_4;
let storeCard = document.querySelector('input[name=token-billing-checkbox]:checked');
if (storeCard) {
document.getElementById("store_card").value = storeCard.value;
}
document.getElementById('server_response').submit();
@ -62,17 +72,94 @@ class ForteAuthorizeCard {
return false;
};
handlePayNowAction(token_hashed_id) {
document.getElementById('pay-now').disabled = true;
document.querySelector('#pay-now > svg').classList.remove('hidden');
document.querySelector('#pay-now > span').classList.add('hidden');
document.getElementById("token").value = token_hashed_id;
document.getElementById("server_response").submit();
}
handle = () => {
Array
.from(document.getElementsByClassName('toggle-payment-with-token'))
.forEach((element) => element.addEventListener('click', (e) => {
document
.getElementById('save-card--container').style.display = 'none';
document
.getElementById('forte--credit-card-container').style.display = 'none';
document
.getElementById('token').value = e.target.dataset.token;
}));
let payWithCreditCardToggle = document.getElementById('toggle-payment-with-credit-card');
if (payWithCreditCardToggle) {
payWithCreditCardToggle
.addEventListener('click', () => {
document
.getElementById('save-card--container').style.display = 'grid';
document
.getElementById('forte--credit-card-container').style.display = 'flex';
document
.getElementById('token').value = null;
});
}
let payNowButton = document.getElementById('pay-now');
if (payNowButton) {
payNowButton.addEventListener('click', (e) => {
this.handleAuthorization();
});
payNowButton
.addEventListener('click', (e) => {
let token = document.getElementById('token');
token.value
? this.handlePayNowAction(token.value)
: this.handleAuthorization();
});
}
return this;
};
}
}
const apiLoginId = document.querySelector(

View File

@ -12,6 +12,9 @@
<form action="{{ route('client.payments.response') }}" method="post" id="server_response">
@csrf
<input type="hidden" name="card_brand" id="card_brand">
<input type="hidden" name="expire_year" id="expire_year">
<input type="hidden" name="expire_month" id="expire_month">
<input type="hidden" name="last_4" id="last_4">
<input type="hidden" name="payment_token" id="payment_token">
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
@ -32,10 +35,39 @@
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => 'Pay with Credit Card'])
@include('portal.ninja2020.gateways.forte.includes.credit_card')
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
<ul class="list-none">
@if(count($tokens) > 0)
@foreach($tokens as $token)
<li class="py-2">
<label class="mr-4 cursor-pointer">
<input
type="radio"
data-token="{{ $token->hashed_id }}"
name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token"/>
<span class="ml-1 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
</label>
</li>
@endforeach
@endisset
<li class="py-2">
<label class="mr-4 cursor-pointer">
<input
type="radio"
id="toggle-payment-with-credit-card"
class="form-radio cursor-pointer"
name="payment-type"
checked/>
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
</label>
</li>
</ul>
@endcomponent
@include('portal.ninja2020.gateways.includes.save_card')
@include('portal.ninja2020.gateways.forte.includes.credit_card')
@include('portal.ninja2020.gateways.includes.pay_now')
@endsection

View File

@ -1,5 +1,5 @@
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
style="display: flex!important; justify-content: center!important;" id="authorize--credit-card-container">
style="display: flex!important; justify-content: center!important;" id="forte--credit-card-container">
<div class="card-js" id="my-card" data-capture-name="true">
<input class="name" id="cardholder_name" name="card_holders_name" placeholder="{{ ctrans('texts.name')}}">
<input class="card-number my-custom-class" id="card_number">

View File

@ -51,10 +51,10 @@
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
<ul class="list-none hover:list-disc">
<ul class="list-none">
@if(count($tokens) > 0)
@foreach($tokens as $token)
<li class="py-2 hover:text-blue hover:bg-blue-600">
<li class="py-2 cursor-pointer">
<label class="mr-4">
<input
type="radio"
@ -67,7 +67,7 @@
@endforeach
@endisset
<li class="py-2 hover:text-blue hover:bg-blue-600">
<li class="py-2 cursor-pointer">
<label>
<input
type="radio"