diff --git a/VERSION.txt b/VERSION.txt index 686411917aea..e0178fd19665 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.10.21 \ No newline at end of file +5.10.22 \ No newline at end of file diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index dd84001f872e..e7ea1efd83d4 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -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; diff --git a/app/PaymentDrivers/Factory/ForteCustomerFactory.php b/app/PaymentDrivers/Factory/ForteCustomerFactory.php index 613870fe42e2..e7c535221627 100644 --- a/app/PaymentDrivers/Factory/ForteCustomerFactory.php +++ b/app/PaymentDrivers/Factory/ForteCustomerFactory.php @@ -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 diff --git a/app/PaymentDrivers/Forte/CreditCard.php b/app/PaymentDrivers/Forte/CreditCard.php index 5a317f4ec8a7..16ceb030fdf0 100644 --- a/app/PaymentDrivers/Forte/CreditCard.php +++ b/app/PaymentDrivers/Forte/CreditCard.php @@ -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]); } diff --git a/app/PaymentDrivers/FortePaymentDriver.php b/app/PaymentDrivers/FortePaymentDriver.php index 902b4a62e46f..90efcfa0c18b 100644 --- a/app/PaymentDrivers/FortePaymentDriver.php +++ b/app/PaymentDrivers/FortePaymentDriver.php @@ -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() { diff --git a/app/Services/Invoice/AddGatewayFee.php b/app/Services/Invoice/AddGatewayFee.php index 73e7064fe265..fc1365e92337 100644 --- a/app/Services/Invoice/AddGatewayFee.php +++ b/app/Services/Invoice/AddGatewayFee.php @@ -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; diff --git a/config/ninja.php b/config/ninja.php index 3044b8fc662a..b646985bb879 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -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), diff --git a/public/build/assets/forte-credit-card-payment-b605ccf2.js b/public/build/assets/forte-credit-card-payment-b605ccf2.js deleted file mode 100644 index 563ebe666f15..000000000000 --- a/public/build/assets/forte-credit-card-payment-b605ccf2.js +++ /dev/null @@ -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='