Merge remote-tracking branch 'upstream/v2' into v2-2805-client-signup

This commit is contained in:
Benjamin Beganović 2020-06-18 15:57:05 +02:00
commit 22aa182250
212 changed files with 140466 additions and 106590 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"plugins": ["@babel/plugin-proposal-class-properties"]
}

View File

@ -43,6 +43,7 @@ MAIL_FROM_ADDRESS='user@example.com'
MAIL_FROM_NAME='Self Hosted User'
POSTMARK_API_TOKEN=
REQUIRE_HTTPS=true
GOOGLE_MAPS_API_KEY=
API_SECRET=superdoopersecrethere

1
.gitignore vendored
View File

@ -26,6 +26,5 @@ local_version.txt
# Ignore local migrations
storage/migrations
nbproject
/composer.lock
.php_cs.cache

View File

@ -1,5 +1,6 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
"trailingComma": "es5",
"arrowParens": "always"
}

View File

@ -26,7 +26,7 @@ npm i
npm run production
```
Please Note: Your APP_KEY in the .env file is used to encrypt data, if you loose this you will not be able to run the application.
Please Note: Your APP_KEY in the .env file is used to encrypt data, if you lose this you will not be able to run the application.
Run if you want to load sample data, remember to configure .env
```

View File

@ -299,7 +299,7 @@ class CheckData extends Command
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
if($ledger && $invoice_balance != $client->balance)
if($ledger && number_format($invoice_balance, 4) != number_format($client->balance, 4))
{
$wrong_balances++;
$this->logMessage($client->present()->name . " - " . $client->id . " - balances do not match {$invoice_balance} - {$client->balance} - {$ledger->balance}");

View File

@ -25,6 +25,7 @@ use App\Models\PaymentType;
use App\Models\Product;
use App\Models\User;
use App\Repositories\InvoiceRepository;
use App\Utils\Traits\GeneratesCounter;
use App\Utils\Traits\MakesHash;
use Carbon\Carbon;
use Faker\Factory;
@ -35,7 +36,7 @@ use Illuminate\Support\Str;
class CreateTestData extends Command
{
use MakesHash;
use MakesHash, GeneratesCounter;
/**
* @var string
*/
@ -233,7 +234,7 @@ class CreateTestData extends Command
$this->createClient($company, $user);
}
for($x=0; $x<$this->count; $x++)
for($x=0; $x<$this->count*100; $x++)
{
$client = $company->clients->random();
@ -314,7 +315,7 @@ class CreateTestData extends Command
]);
factory(\App\Models\Product::class, 50)->create([
factory(\App\Models\Product::class, 15000)->create([
'user_id' => $user->id,
'company_id' => $company->id,
]);
@ -324,7 +325,7 @@ class CreateTestData extends Command
$this->info('Creating '.$this->count. ' clients');
for ($x=0; $x<$this->count; $x++) {
for ($x=0; $x<$this->count*1000; $x++) {
$z = $x+1;
$this->info("Creating client # ".$z);
@ -393,6 +394,10 @@ class CreateTestData extends Command
'client_id' => $client->id,
'company_id' => $company->id
]);
$client->id_number = $this->getNextClientNumber($client);
$client->save();
}
private function createExpense($client)

View File

@ -13,6 +13,7 @@ namespace App\Console;
use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\CheckDbStatus;
use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SendFailedEmails;
use App\Jobs\Util\UpdateExchangeRates;
@ -52,6 +53,7 @@ class Kernel extends ConsoleKernel
if(Ninja::isHosted()) {
$schedule->job(new AdjustEmailQuota())->daily();
$schedule->job(new SendFailedEmails())->daily();
$schedule->job(new CheckDbStatus())->everyFiveMinutes();
}
/* Run queue's in shared hosting with this*/
if (Ninja::isSelfHost()) {

View File

@ -33,7 +33,7 @@ class CompanySettings extends BaseSettings
public $enable_client_portal_dashboard = true; //implemented
public $signature_on_pdf = false;
public $document_email_attachment = false;
public $send_portal_password = false;
//public $send_portal_password = false;
public $portal_design_id = '1';
@ -113,7 +113,7 @@ class CompanySettings extends BaseSettings
public $invoice_terms = '';
public $quote_terms = '';
public $invoice_taxes = 0;
public $enabled_item_tax_rates = 0;
// public $enabled_item_tax_rates = 0;
public $invoice_design_id = 'VolejRejNm';
public $quote_design_id = 'VolejRejNm';
public $credit_design_id = 'VolejRejNm';
@ -274,7 +274,7 @@ class CompanySettings extends BaseSettings
'email_template_statement' => 'string',
'email_subject_statement' => 'string',
'signature_on_pdf' => 'bool',
'send_portal_password' => 'bool',
// 'send_portal_password' => 'bool',
'quote_footer' => 'string',
'page_size' => 'string',
'font_size' => 'int',
@ -344,7 +344,7 @@ class CompanySettings extends BaseSettings
'invoice_design_id' => 'string',
'invoice_fields' => 'string',
'invoice_taxes' => 'int',
'enabled_item_tax_rates' => 'int',
//'enabled_item_tax_rates' => 'int',
'invoice_footer' => 'string',
'invoice_labels' => 'string',
'invoice_terms' => 'string',

View File

@ -33,7 +33,7 @@ class FreeCompanySettings extends BaseSettings
public $custom_value3 = '';
public $custom_value4 = '';
public $date_format_id = '';
public $enabled_item_tax_rates = 0;
// public $enabled_item_tax_rates = 0;
public $expense_number_pattern = '';
public $expense_number_counter = 1;
public $inclusive_taxes = false;

View File

@ -63,7 +63,7 @@ class Bold extends AbstractDesign
</div>
<div class="col-span-5">
<div class="bg-teal-600 px-5 py-3 text-white">
<div class="w-80 flex flex-col text-white">
<div class="w-80 flex flex-col text-white flex-wrap">
$entity_details
</div>
</div>
@ -76,7 +76,7 @@ class Bold extends AbstractDesign
<thead class="text-left">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -84,7 +84,7 @@ class Bold extends AbstractDesign
<thead class="text-left">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -52,10 +52,10 @@ $custom_css
<div class="col-span-2 p-3">
$company_logo
</div>
<div class="col-span-2 p-3 flex flex-col">
<div class="col-span-2 p-3 flex flex-col flex-wrap">
$company_details
</div>
<div class="col-span-2 p-3 flex flex-col">
<div class="col-span-2 p-3 flex flex-col flex-wrap">
$company_address
</div>
</div>';
@ -64,11 +64,11 @@ $custom_css
public function body()
{
return '<div class="grid grid-cols-12 gap-1 mt-8">
<div class="col-span-7 p-3 flex flex-col">
<div class="col-span-7 p-3 flex flex-col flex-wrap">
$client_details
</div>
<div class="col-span-5 p-3 flex flex-col bg-orange-600 px-4 py-4 h-auto rounded-lg">
<div class="flex flex-col text-white">
<div class="flex flex-col text-white flex-wrap">
$entity_details
</div>
</div>
@ -78,7 +78,7 @@ $custom_css
<thead class="text-left">
$product_table_header
</thead>
<tbody class="bg-gray-200">
<tbody class="bg-gray-200 whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -86,7 +86,7 @@ $custom_css
<thead class="text-left">
$task_table_header
</thead>
<tbody class="bg-gray-200">
<tbody class="bg-gray-200 whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -48,10 +48,10 @@ class Clean extends AbstractDesign
<div class="h-14 w-14">$company_logo</div>
</div>
<div class="w-auto flex">
<div class="mr-10 text-gray-600 flex flex-col">
<div class="mr-10 text-gray-600 flex flex-col flex-wrap">
$company_details
</div>
<div class="ml-5 text-gray-600 flex flex-col">
<div class="ml-5 text-gray-600 flex flex-col flex-wrap">
$company_address
</div>
</div>
@ -68,13 +68,13 @@ class Clean extends AbstractDesign
<div class="ml-4 py-4">
<div class="flex">
<div class="w-40 flex flex-col">
<div class="w-40 flex flex-col flex-wrap">
$entity_labels
</div>
<div class="w-48 flex flex-col">
<div class="w-48 flex flex-col flex-wrap">
$entity_details
</div>
<div class="w-56 flex flex-col">
<div class="w-56 flex flex-col flex-wrap">
$client_details
</div>
</div>
@ -84,7 +84,7 @@ class Clean extends AbstractDesign
<thead class="text-left">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -92,7 +92,7 @@ class Clean extends AbstractDesign
<thead class="text-left">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -60,14 +60,14 @@ $custom_css
<div class="col-span-7">
<p class="text-4xl text-pink-700">#$entity_number</p>
</div>
<div class="col-span-5 flex flex-col">$entity_details</div>
<div class="col-span-5 flex flex-col flex-wrap">$entity_details</div>
</div>
<table class="w-full table-auto border-t-4 border-pink-700 bg-white mt-8">
<thead class="text-left rounded-lg">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -75,7 +75,7 @@ $custom_css
<thead class="text-left rounded-lg">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -107,7 +107,7 @@ class Designer
</div>'
;
$signature = '<div></div>'; /** @wip */
$signature = '<img class="h-40" src="$contact.signature" />';
$logo = '<div></div>';
if (!$this->entity->user->account->isPaid()) {

View File

@ -47,7 +47,7 @@ class Elegant extends AbstractDesign
<div class="col-span-8">
$company_logo
</div>
<div class="col-span-4 flex flex-col">
<div class="col-span-4 flex flex-col flex-wrap">
$entity_details
</div>
</div>
@ -57,13 +57,13 @@ class Elegant extends AbstractDesign
public function body()
{
return '<div class="grid grid-cols-12 gap-4 mt-8">
<div class="col-span-4 mr-6 flex flex-col pr-2 border-r border-dashed border-black">
<div class="col-span-4 mr-6 flex flex-col pr-2 border-r border-dashed border-black flex-wrap">
$client_details
</div>
<div class="col-span-4 flex flex-col mr-6">
<div class="col-span-4 flex flex-col mr-6 flex-wrap">
$company_details
</div>
<div class="col-span-4 flex flex-col">
<div class="col-span-4 flex flex-col flex-wrap">
$company_address
</div>
</div>
@ -71,7 +71,7 @@ class Elegant extends AbstractDesign
<thead class="text-left border-dashed border-b border-black">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -79,7 +79,7 @@ class Elegant extends AbstractDesign
<thead class="text-left border-dashed border-b border-black">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -48,15 +48,15 @@ $custom_css
<div class="w-1/2 border-l pl-4 border-black mr-4">
<p class="font-semibold uppercase text-yellow-600">From:</p>
<div class="flex">
<div class="flex flex-col mr-5">
<div class="flex flex-col mr-5 flex-wrap">
$company_details
</div>
<div class="flex flex-col">
<div class="flex flex-col flex-wrap">
$company_address
</div>
</div>
</div>
<div class="w-1/3 border-l pl-4 border-black flex flex-col">
<div class="w-1/3 border-l pl-4 border-black flex flex-col flex-wrap">
<p class="font-semibold uppercase text-yellow-600">To:</p>
$client_details
</div>
@ -90,7 +90,7 @@ $custom_css
<thead class="text-left">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -98,7 +98,7 @@ $custom_css
<thead class="text-left">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -45,10 +45,10 @@ $custom_css
<div class="col-span-2 p-3">
<h1 class="text-white font-bold text-3xl">$company.name</h1>
</div>
<div class="col-span-2 p-3 flex flex-col text-white">
<div class="col-span-2 p-3 flex flex-col text-white flex-wrap">
$company_details
</div>
<div class="col-span-2 p-3 flex flex-col text-white">
<div class="col-span-2 p-3 flex flex-col text-white flex-wrap">
$entity_details
</div>
</div>
@ -64,7 +64,7 @@ $custom_css
<div class="col-span-2 p-3">
$company_logo
</div>
<div class="col-span-3 p-3 flex flex-col">
<div class="col-span-3 p-3 flex flex-col flex-wrap">
$client_details
</div>
</div>
@ -74,7 +74,7 @@ $custom_css
<thead class="text-left text-white bg-gray-900 display: table-header-group;">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -82,7 +82,7 @@ $custom_css
<thead class="text-left text-white bg-gray-900 display: table-header-group;">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>
@ -141,10 +141,10 @@ $custom_css
return '
<div class="footer bg-orange-600 flex justify-between py-8 px-12" style="page-break-inside: avoid;">
<div class="grid grid-cols-12 gap-4">
<div class="col-start-4 col-span-4 p-3 flex flex-col text-white text-right">
<div class="col-start-4 col-span-4 p-3 flex flex-col text-white text-right flex-wrap">
$company_details
</div>
<div class="col-span-4 p-3 flex flex-col text-white text-right">
<div class="col-span-4 p-3 flex flex-col text-white text-right flex-wrap">
$company_address
</div>
</div>

View File

@ -53,7 +53,7 @@ $custom_css
$company_logo
</div>
<div class="col-span-5">
<div class="flex flex-col">
<div class="flex flex-col flex-wrap">
$entity_details
</div>
</div>
@ -67,13 +67,13 @@ $custom_css
<div class="flex flex-col">
<div class="flex">
<p class="uppercase text-orange-800">$to_label:</p>
<div class="flex flex-col ml-2">
<div class="flex flex-col ml-2 flex-wrap">
$client_details
</div>
</div>
<div class="flex mt-5">
<p class="uppercase text-orange-800">$from_label:</p>
<div class="flex flex-col ml-2">
<div class="flex flex-col ml-2 flex-wrap">
$company_details
</div>
</div>
@ -84,7 +84,7 @@ $custom_css
<thead class="text-left border-b-4 border-black">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -92,7 +92,7 @@ $custom_css
<thead class="text-left border-b-4 border-black">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -45,10 +45,10 @@ $custom_css
<div class="col-span-2 p-3">
$company_logo
</div>
<div class="col-span-2 p-3 flex flex-col">
<div class="col-span-2 p-3 flex flex-col flex-wrap">
$company_details
</div>
<div class="col-span-2 p-3 flex flex-col">
<div class="col-span-2 p-3 flex flex-col flex-wrap">
$entity_details
</div>
</div>';
@ -56,7 +56,7 @@ $custom_css
public function body()
{
return '<div class="flex flex-col mt-8">
return '<div class="flex flex-col mt-8 flex-wrap">
$client_details
</div>
<table class="w-full table-auto mt-8">

View File

@ -48,7 +48,7 @@ $custom_css
$company_logo
</div>
<div class="col-span-5 bg-teal-600 p-5 text-white">
<div class="flex flex-col">
<div class="flex flex-col flex-wrap">
$entity_details
</div>
</div>
@ -62,7 +62,7 @@ $custom_css
<div class="flex flex-col">
<p class="font-semibold text-teal-600 pl-4">$to_label:</p>
<div class="flex border-dashed border-t-4 border-b-4 border-teal-600 py-4 mt-4 pl-4">
<section class="flex flex-col">
<section class="flex flex-col flex-wrap">
$client_details
</section>
</div>
@ -72,7 +72,7 @@ $custom_css
<div class="flex flex-col">
<p class="font-semibold text-teal-600 pl-4">$from_label:</p>
<div class="flex border-dashed border-t-4 border-b-4 border-teal-600 py-4 mt-4 pl-4">
<section class="flex flex-col">
<section class="flex flex-col flex-wrap">
$company_details
</section>
</div>
@ -83,7 +83,7 @@ $custom_css
<thead class="text-left bg-teal-600 rounded-lg">
$product_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$product_table_body
</tbody>
</table>
@ -91,7 +91,7 @@ $custom_css
<thead class="text-left bg-teal-600 rounded-lg">
$task_table_header
</thead>
<tbody>
<tbody class="whitespace-pre-line">
$task_table_body
</tbody>
</table>

View File

@ -0,0 +1,11 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class GenericPaymentDriverFailure extends Exception
{
// ..
}

View File

@ -11,6 +11,7 @@
namespace App\Exceptions;
use App\Exceptions\GenericPaymentDriverFailure;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
@ -115,8 +116,15 @@ class Handler extends ExceptionHandler
return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422);
} elseif ($exception instanceof RelationNotFoundException && $request->expectsJson()) {
return response()->json(['message' => $exception->getMessage()], 400);
} elseif ($exception instanceof GenericPaymentDriverFailure && $request->expectsJson()) {
return response()->json(['message' => $exception->getMessage()], 400);
} elseif ($exception instanceof GenericPaymentDriverFailure) {
$data['message'] = $exception->getMessage();
dd($data);
// return view('errors.layout', $data);
}
return parent::render($request, $exception);
}

View File

@ -91,7 +91,7 @@ class InvoiceItemSum
}
private function sumLineItem()
{
{ //todo need to support quantities less than the precision amount
$this->setLineTotal($this->formatValue($this->item->cost, $this->currency->precision) * $this->formatValue($this->item->quantity, $this->currency->precision));
return $this;
}

View File

@ -265,8 +265,8 @@ class BaseController extends Controller
'company.payments.paymentables',
'company.quotes.invitations.contact',
'company.quotes.invitations.company',
'company.credits',
'company.payment_terms',
'company.credits.invitations.company',
'company.payment_terms.company',
//'company.credits.invitations.contact',
//'company.credits.invitations.company',
'company.vendors.contacts',
@ -291,7 +291,7 @@ class BaseController extends Controller
* Thresholds for displaying large account on first load
*/
if (request()->has('first_load') && request()->input('first_load') == 'true') {
if (auth()->user()->getCompany()->invoices->count() > 1000) {
if (auth()->user()->getCompany()->invoices->count() > 1000 || auth()->user()->getCompany()->products->count() > 1000 || auth()->user()->getCompany()->clients->count() > 1000) {
$data = $mini_load;
} else {
$data = $first_load;
@ -314,11 +314,10 @@ class BaseController extends Controller
public function flutterRoute()
{
// // Ensure all request are over HTTPS in production
// if (! request()->secure()) {
// return redirect()->secure(request()->path());
// }
if (config('ninja.require_https') && !request()->isSecure()) {
return redirect()->secure(request()->getRequestUri());
}
if ((bool)$this->checkAppSetup() !== false && Schema::hasTable('accounts') && $account = Account::all()->first()) {
$data = [];

View File

@ -52,6 +52,10 @@ class InvoiceController extends Controller
'invoice' => $invoice,
];
if ($request->query('mode') === 'fullscreen') {
return $this->render('invoices.show.fullscreen', $data);
}
return $this->render('invoices.show', $data);
}

View File

@ -14,6 +14,7 @@ namespace App\Http\Controllers\ClientPortal;
use App\Filters\PaymentFilters;
use App\Http\Controllers\Controller;
use App\Jobs\Invoice\InjectSignature;
use App\Models\CompanyGateway;
use App\Models\Invoice;
use App\Models\Payment;
@ -90,12 +91,18 @@ class PaymentController extends Controller
$invoices->map(function ($invoice) {
$invoice->balance = Number::formatMoney($invoice->balance, $invoice->client);
$invoice->due_date = $this->formatDate($invoice->due_date, $invoice->client->date_format());
return $invoice;
});
if ((bool) request()->signature) {
$invoices->each(function ($invoice) {
InjectSignature::dispatch($invoice, request()->signature);
});
}
$payment_methods = auth()->user()->client->getPaymentMethods($amount);
$gateway = CompanyGateway::find(request()->input('company_gateway_id'));
$payment_method_id = request()->input('payment_method_id');
// Place to calculate gateway fee.
@ -110,14 +117,19 @@ class PaymentController extends Controller
'hashed_ids' => request()->invoices,
];
return $gateway->driver(auth()->user()->client)->processPaymentView($data);
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod($payment_method_id)
->processPaymentView($data);
}
public function response(Request $request)
{
$gateway = CompanyGateway::find($request->input('company_gateway_id'));
return $gateway->driver(auth()->user()->client)->processPaymentResponse($request);
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod($request->input('payment_method_id'))
->processPaymentResponse($request);
}
}

View File

@ -15,6 +15,8 @@ use App\Events\Payment\Methods\MethodDeleted;
use App\Http\Controllers\Controller;
use App\Http\Requests\ClientPortal\CreatePaymentMethodRequest;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\PaymentDrivers\AuthorizePaymentDriver;
use App\Utils\Traits\MakesDates;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
@ -42,13 +44,8 @@ class PaymentMethodController extends Controller
{
$gateway = auth()->user()->client->getCreditCardGateway();
$data = [
'gateway' => $gateway,
'gateway_type_id' => 1,
'token' => false,
];
return $gateway->driver(auth()->user()->client)->authorizeView(GatewayType::CREDIT_CARD);
return $gateway->driver(auth()->user()->client)->authorizeCreditCardView($data);
}
/**
@ -60,8 +57,9 @@ class PaymentMethodController extends Controller
public function store(Request $request)
{
$gateway = auth()->user()->client->getCreditCardGateway();
return $gateway->driver(auth()->user()->client)->authorizeResponseView($request->all());
return $gateway->driver(auth()->user()->client)->authorizeCreditCardResponse($request);
}
/**
@ -100,6 +98,26 @@ class PaymentMethodController extends Controller
//
}
public function verify(ClientGatewayToken $payment_method)
{
$gateway = auth()->user()->client->getCreditCardGateway();
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod('App\\PaymentDrivers\\Stripe\\ACH')
->verificationView($payment_method);
}
public function processVerification(ClientGatewaytoken $payment_method)
{
$gateway = auth()->user()->client->getCreditCardGateway();
return $gateway
->driver(auth()->user()->client)
->setPaymentMethod('App\\PaymentDrivers\\Stripe\\ACH')
->processVerification($payment_method);
}
/**
* Remove the specified resource from storage.
*

View File

@ -37,9 +37,15 @@ class QuoteController extends Controller
*/
public function show(ShowQuoteRequest $request, Quote $quote)
{
return $this->render('quotes.show', [
$data = [
'quote' => $quote,
]);
];
if ($request->query('mode') === 'fullscreen') {
return $this->render('quotes.show.fullscreen', $data);
}
return $this->render('quotes.show', $data);
}
public function bulk(ProcessQuotesInBulkRequest $request)

View File

@ -11,6 +11,7 @@
namespace App\Http\Controllers;
use App\DataMapper\CompanySettings;
use App\DataMapper\DefaultSettings;
use App\Http\Requests\Company\CreateCompanyRequest;
use App\Http\Requests\Company\DestroyCompanyRequest;
@ -218,6 +219,7 @@ class CompanyController extends BaseController
'is_locked' => 0,
'permissions' => '',
'settings' => null,
'notifications' => CompanySettings::notificationDefaults(),
//'settings' => DefaultSettings::userSettings(),
]);
@ -464,11 +466,12 @@ class CompanyController extends BaseController
*/
public function destroy(DestroyCompanyRequest $request, Company $company)
{
$company_count = $company->account->companies->count();
$account = $company->account;
if ($company_count == 1) {
$company->company_users->each(function ($company_user) {
$company_user->user->forceDelete();
});
@ -480,11 +483,13 @@ class CompanyController extends BaseController
} else {
$company_id = $company->id;
$company->delete();
//If we are deleting the default companies, we'll need to make a new company the default.
if ($account->default_company_id == $company_id) {
$new_default_company = Company::whereAccountId($account->id)->first();
$account->default_company_id = $new_default_company->id;
$account->save();

View File

@ -424,4 +424,73 @@ class CompanyGatewayController extends BaseController
return response()->json([], 200);
}
/**
* Perform bulk actions on the list view
*
* @param BulkCompanyGatewayRequest $request
* @return \Illuminate\Http\Response
*
*
* @OA\Post(
* path="/api/v1/company_gateways/bulk",
* operationId="bulkCompanyGateways",
* tags={"company_gateways"},
* summary="Performs bulk actions on an array of company_gateways",
* description="",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\RequestBody(
* description="Array of company gateway IDs",
* required=true,
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* type="array",
* @OA\Items(
* type="integer",
* description="Array of hashed IDs to be bulk 'actioned",
* example="[0,1,2,3]",
* ),
* )
* )
* ),
* @OA\Response(
* response=200,
* description="The Company Gateways response",
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/CompanyGateway"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
*/
public function bulk()
{
$action = request()->input('action');
$ids = request()->input('ids');
$company_gateways = CompanyGateway::withTrashed()->find($this->transformKeys($ids));
$company_gateways->each(function ($company_gateway, $key) use ($action) {
if (auth()->user()->can('edit', $company_gateway)) {
$this->company_repo->{$action}($company_gateway);
}
});
return $this->listResponse(CompanyGateway::withTrashed()->whereIn('id', $this->transformKeys($ids)));
}
}

View File

@ -662,6 +662,13 @@ class InvoiceController extends BaseController
case 'download':
return response()->download(TempFile::path($invoice->pdf_file_path()), basename($invoice->pdf_file_path()));
break;
case 'restore':
$this->invoice_repo->restore($invoice);
if (!$bulk) {
return $this->listResponse($invoice);
}
break;
case 'archive':
$this->invoice_repo->archive($invoice);

View File

@ -66,7 +66,6 @@
* @OA\Property(property="invoice_terms", type="string", example="Invoice Terms are...", description="The default invoice terms"),
* @OA\Property(property="quote_terms", type="string", example="Quote Terms are...", description="The default quote terms"),
* @OA\Property(property="invoice_taxes", type="number", example="1", description="Taxes can be applied to the invoice"),
* @OA\Property(property="enabled_item_tax_rates", type="number", example="1", description="Taxes can be applied to the invoice items"),
* @OA\Property(property="invoice_design_id", type="string", example="1", description="The default design id (invoice, quote etc)"),
* @OA\Property(property="quote_design_id", type="string", example="1", description="The default design id (invoice, quote etc)"),
* @OA\Property(property="invoice_footer", type="string", example="1", description="The default invoice footer"),
@ -130,7 +129,6 @@
* @OA\Property(property="email_template_statement", type="string", example="template matter", description="____________"),
* @OA\Property(property="email_subject_statement", type="string", example="subject matter", description="____________"),
* @OA\Property(property="signature_on_pdf", type="boolean", example=false, description="____________"),
* @OA\Property(property="send_portal_password", type="boolean", example=false, description="____________"),
* @OA\Property(property="quote_footer", type="string", example="the quote footer", description="____________"),
* @OA\Property(property="email_subject_custom1", type="string", example="Custom Subject 1", description="____________"),
* @OA\Property(property="email_subject_custom2", type="string", example="Custom Subject 2", description="____________"),

View File

@ -12,7 +12,6 @@
namespace App\Http\Controllers;
use App\Utils\Ninja;
use Codedge\Updater\UpdaterManager;
use Composer\Factory;
use Composer\IO\NullIO;
use Composer\Installer;
@ -61,8 +60,10 @@ class SelfUpdateController extends BaseController
* )
*
*/
public function update(UpdaterManager $updater)
public function update()
{
define('STDIN',fopen("php://stdin","r"));
if (Ninja::isNinja()) {
return response()->json(['message' => 'Self update not available on this system.'], 403);
}

View File

@ -29,7 +29,8 @@ class Kernel extends HttpKernel
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\Cors::class,
\App\Http\Middleware\Cors::class,
];
/**
@ -54,6 +55,7 @@ class Kernel extends HttpKernel
'bindings',
'query_logging',
\App\Http\Middleware\StartupCheck::class,
# \App\Http\Middleware\Cors::class,
],
'contact' => [
'throttle:60,1',

View File

@ -17,7 +17,7 @@ class CreditsTable extends Component
public function render()
{
$query = Credit::query()
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', auth('contact')->user()->client->id)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);

View File

@ -30,6 +30,7 @@ class InvoicesTable extends Component
public function render()
{
$query = Invoice::query()
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
@ -48,7 +49,7 @@ class InvoicesTable extends Component
}
$query = $query
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', auth('contact')->user()->client->id)
->paginate($this->per_page);
return render('components.livewire.invoices-table', [

View File

@ -24,7 +24,6 @@ class PaymentMethodsTable extends Component
{
$query = ClientGatewayToken::query()
->with('gateway_type')
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', $this->client->id)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);

View File

@ -24,7 +24,7 @@ class PaymentsTable extends Component
{
$query = Payment::query()
->with('type', 'client')
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', auth('contact')->user()->client->id)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->paginate($this->per_page);

View File

@ -46,7 +46,7 @@ class QuotesTable extends Component
}
$query = $query
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', auth('contact')->user()->client->id)
->paginate($this->per_page);
return render('components.livewire.quotes-table', [

View File

@ -18,7 +18,7 @@ class RecurringInvoicesTable extends Component
$query = RecurringInvoice::query();
$query = $query
->where('company_id', auth('contact')->user()->company->id)
->where('client_id', auth('contact')->user()->client->id)
->whereIn('status_id', [RecurringInvoice::STATUS_PENDING, RecurringInvoice::STATUS_ACTIVE, RecurringInvoice::STATUS_COMPLETED])
->orderBy('status_id', 'asc')
->with('client')

View File

@ -37,6 +37,7 @@ class Cors
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'X-API-SECRET,X-API-TOKEN,X-API-PASSWORD,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range');
$response->headers->set('Access-Control-Expose-Headers', 'X-APP-VERSION');
$response->headers->set('X-APP-VERSION', config('ninja.app_version'));
$response->headers->set('X-API-VERSION', config('ninja.api_version'));

View File

@ -50,8 +50,8 @@ class QueryLogging
Log::info($request->method() . ' - ' . $request->url() . ": $count queries - " . $time);
// if($count > 50)
// Log::info($queries);
//if($count > 10)
// Log::info($queries);
}
}

View File

@ -12,7 +12,6 @@
namespace App\Http\Requests\Company;
use App\Http\Requests\Request;
use App\Models\Company;
class DestroyCompanyRequest extends Request
{

View File

@ -67,6 +67,10 @@ class StoreInvoiceRequest extends Request
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (isset($input['client_contacts'])) {
foreach ($input['client_contacts'] as $key => $contact) {
if (!array_key_exists('send_email', $contact) || !array_key_exists('id', $contact)) {

View File

@ -65,6 +65,10 @@ class UpdateInvoiceRequest extends Request
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (isset($input['invitations'])) {
foreach ($input['invitations'] as $key => $value) {
if (is_numeric($input['invitations'][$key]['id'])) {

View File

@ -35,9 +35,10 @@ class UpdatePaymentRequest extends Request
public function rules()
{
{//min:1 removed
return [
'invoices' => ['required','array','min:1',new PaymentAppliedValidAmount,new ValidCreditsPresentRule],
'invoices' => ['required','array',new PaymentAppliedValidAmount,new ValidCreditsPresentRule],
'invoices.*.invoice_id' => 'distinct',
'documents' => 'mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx',
];
}
@ -77,4 +78,11 @@ class UpdatePaymentRequest extends Request
}
$this->replace($input);
}
public function messages()
{
return [
'distinct' => 'Attemping duplicate payment on the same invoice Invoice',
];
}
}

View File

@ -49,6 +49,7 @@ class PortalComposer
$data['company'] = auth()->user()->company;
$data['client'] = auth()->user()->client;
$data['settings'] = auth()->user()->client->getMergedSettings();
$data['currencies'] = TranslationHelper::getCurrencies();
$data['multiple_contacts'] = ClientContact::where('email', auth('contact')->user()->email)->whereNotNull('email')->distinct('company_id')->get();
@ -63,9 +64,9 @@ class PortalComposer
$data[] = [ 'title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'file-text'];
$data[] = [ 'title' => ctrans('texts.recurring_invoices'), 'url' => 'client.recurring_invoices.index', 'icon' => 'file'];
$data[] = [ 'title' => ctrans('texts.payments'), 'url' => 'client.payments.index', 'icon' => 'credit-card'];
$data[] = [ 'title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
$data[] = [ 'title' => ctrans('texts.quotes'), 'url' => 'client.quotes.index', 'icon' => 'align-left'];
$data[] = [ 'title' => ctrans('texts.credits'), 'url' => 'client.credits.index', 'icon' => 'credit-card'];
$data[] = [ 'title' => ctrans('texts.payment_methods'), 'url' => 'client.payment_methods.index', 'icon' => 'shield'];
return $data;
}

View File

@ -50,14 +50,14 @@ class CreateCompanyPaymentTerms
{
$paymentTerms = [
['num_days' => 0, 'name' => 'Net 0', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 7, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 10, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 14, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 15, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 30, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 60, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 90, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id],
['num_days' => 0, 'name' => 'Net 0', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 7, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 10, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 14, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 15, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 30, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 60, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
['num_days' => 90, 'name' => '', 'company_id' => $this->company->id, 'user_id' => $this->user->id, 'created_at' => now(), 'updated_at' => now()],
];
PaymentTerm::insert($paymentTerms);

View File

@ -0,0 +1,56 @@
<?php
namespace App\Jobs\Invoice;
use App\Models\Invoice;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InjectSignature implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var App\Models\Invoice
*/
public $invoice;
/**
* @var string
*/
public $signature;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Invoice $invoice, string $signature)
{
$this->invoice = $invoice;
$this->signature = $signature;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$invitation = $this->invoice->invitations->whereNotNull('signature_base64')->first();
if (!$invitation) {
return;
}
$invitation->signature_base64 = $this->signature;
$invitation->save();
CreateInvoicePdf::dispatch($invitation);
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Jobs\Ninja;
use App\Helpers\Email\InvoiceEmail;
use App\Jobs\Invoice\EmailInvoice;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Models\SystemLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Turbo124\Beacon\Jobs\Database\MySQL\DbStatus;
class CheckDbStatus implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
DbStatus::dispatchNow('db-ninja-01', 'db.status.db-ninja-01');
DbStatus::dispatchNow('db-ninja-02', 'db.status.db-ninja-02');
}
}

View File

@ -37,7 +37,7 @@ class RefundCancelledAccount implements ShouldQueue
if(Ninja::isSelfHost() || $this->account->isFreeHostedClient())
return;
$plan_details = $account->getPlanDetails();
$plan_details = $this->account->getPlanDetails();
/* Trial user cancelling early.... */
if($plan_details['trial_active'])

View File

@ -43,7 +43,7 @@ class SubscriptionHandler implements ShouldQueue
if(!$this->entity->company || $this->entity->company->company_users->first()->is_migrating)
return true;
info("i got past the check");
//info("i got past the check");
$subscriptions = Subscription::where('company_id', $this->entity->company_id)
->where('event_id', $this->event_id)

View File

@ -47,6 +47,9 @@ class UploadAvatar implements ShouldQueue
$path = Storage::putFile('public/' . $this->directory, new File(sys_get_temp_dir().'/'.$tmp_file));
info($path);
info($tmp_file);
$url = Storage::url($path);
//return file path

View File

@ -48,6 +48,6 @@ class CreateInvoiceActivity implements ShouldQueue
$fields->company_id = $event->invoice->company_id;
$fields->activity_type_id = Activity::CREATE_INVOICE;
$this->activity_repo->save($fields, $event->invoice);
$this->activity_repo->save($fields, $event->invoice, $event->invoice->company->db);
}
}

View File

@ -35,6 +35,10 @@ class CreateInvoicePdf implements ShouldQueue
*/
public function handle($event)
{
PdfCreator::dispatch($event->invoice->invitations->first());
$event->invoice->invitations->each(function ($invitation) {
PdfCreator::dispatch($invitation);
});
}
}

View File

@ -50,7 +50,8 @@ class UpdateInvoiceActivity implements ShouldQueue
$fields->user_id = $event->invoice->user_id;
$fields->company_id = $event->invoice->company_id;
$fields->activity_type_id = Activity::UPDATE_INVOICE;
$fields->invoice_id = $event->invoice->id;
$this->activity_repo->save($fields, $event->invoice);
}
}

View File

@ -251,12 +251,12 @@ class Client extends BaseModel implements HasLocalePreference
* @param float $amount Adjustment amount
* @return Client
*/
public function processUnappliedPayment($amount) :Client
{
return $this->service()->updatePaidToDate($amount)
->adjustCreditBalance($amount)
->save();
}
// public function processUnappliedPayment($amount) :Client
// {
// return $this->service()->updatePaidToDate($amount)
// ->adjustCreditBalance($amount)
// ->save();
// }
/**
*
@ -507,4 +507,9 @@ class Client extends BaseModel implements HasLocalePreference
return $defaults;
}
public function payments()
{
return $this->hasMany(Payment::class);
}
}

View File

@ -29,6 +29,9 @@ class ClientGatewayToken extends BaseModel
'deleted_at' => 'timestamp',
];
protected $appends = [
'hashed_id',
];
public function getEntityType()
{
return ClientGatewayToken::class;

View File

@ -17,9 +17,12 @@ use App\Models\Gateway;
use App\Models\GatewayType;
use App\Utils\Number;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CompanyGateway extends BaseModel
{
use SoftDeletes;
protected $casts = [
'fees_and_limits' => 'object',
'updated_at' => 'timestamp',

View File

@ -70,6 +70,7 @@ class Credit extends BaseModel
protected $casts = [
'line_items' => 'object',
'backup' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',

View File

@ -25,5 +25,7 @@ class Currency extends StaticModel
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
//'precision' => 'string',
'precision' => 'integer',
];
}

View File

@ -19,6 +19,7 @@ use App\Helpers\Invoice\InvoiceSumInclusive;
use App\Jobs\Client\UpdateClientBalance;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Invoice\CreateInvoicePdf;
use App\Models\Backup;
use App\Models\CompanyLedger;
use App\Models\Currency;
use App\Models\Filterable;
@ -98,10 +99,12 @@ class Invoice extends BaseModel
'custom_surcharge_tax3',
'custom_surcharge_tax4',
'design_id',
'assigned_user_id',
];
protected $casts = [
'line_items' => 'object',
'backup' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
@ -208,7 +211,7 @@ class Invoice extends BaseModel
public function history()
{
$this->activities->with('backup');
return $this->hasManyThrough(Backup::class, Activity::class);
}
// public function credits()

View File

@ -201,7 +201,9 @@ class Payment extends BaseModel
public function refund(array $data) :Payment
{
return $this->processRefund($data);
return $this->service()->refundPayment($data);
//return $this->processRefund($data);
}
/**

View File

@ -25,4 +25,14 @@ class ClientContactPresenter extends EntityPresenter
{
return $this->entity->first_name . ' ' . $this->entity->last_name;
}
public function first_name()
{
return $this->entity->first_name ?: '';
}
public function last_name()
{
return $this->entity->last_name ?: '';
}
}

View File

@ -25,15 +25,20 @@ class ClientPresenter extends EntityPresenter
*/
public function name()
{
if($this->entity->name)
return $this->entity->name;
$contact = $this->entity->primary_contact->first();
$contact_name = 'No Contact Set';
if ($contact) {
if ($contact && (strlen($contact->first_name) >=1 || strlen($contact->last_name) >=1)) {
$contact_name = $contact->first_name. ' '. $contact->last_name;
}
elseif($contact && (strlen($contact->email)))
$contact_name = $contact->email;
return $this->entity->name ?: $contact_name;
return $contact_name;
}
public function primary_contact_name()

View File

@ -38,7 +38,7 @@ class CompanyPresenter extends EntityPresenter
$settings = $this->entity->settings;
}
return iconv_strlen($settings->company_logo > 0) ? $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
return (strlen($settings->company_logo) > 0) ? $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
}
public function address($settings = null)

View File

@ -76,6 +76,7 @@ class Quote extends BaseModel
'due_date' => 'date:Y-m-d',
'partial_due_date' => 'date:Y-m-d',
'line_items' => 'object',
'backup' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
@ -84,6 +85,7 @@ class Quote extends BaseModel
const STATUS_DRAFT = 1;
const STATUS_SENT = 2;
const STATUS_APPROVED = 3;
const STATUS_CONVERTED = 4;
const STATUS_EXPIRED = -1;
public function getEntityType()

View File

@ -101,6 +101,7 @@ class RecurringInvoice extends BaseModel
protected $casts = [
'settings' => 'object',
'line_items' => 'object',
'backup' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',

View File

@ -83,6 +83,7 @@ class RecurringQuote extends BaseModel
protected $casts = [
'line_items' => 'object',
'backup' => 'object',
'settings' => 'object',
'updated_at' => 'timestamp',
'created_at' => 'timestamp',

View File

@ -35,6 +35,8 @@ class SystemLog extends Model
const TYPE_STRIPE = 301;
const TYPE_LEDGER = 302;
const TYPE_FAILURE = 303;
const TYPE_CHECKOUT = 304;
const TYPE_AUTHORIZE = 305;
const TYPE_QUOTA_EXCEEDED = 400;
const TYPE_UPSTREAM_FAILURE = 401;

View File

@ -0,0 +1,25 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
abstract class AbstractPaymentDriver
{
abstract public function authorize($payment_method);
abstract public function purchase($amount, $return_client_response = false);
abstract public function refund($amount, $transaction_reference, $return_client_response = false);
abstract public function setPaymentMethod($payment_method_id);
}

View File

@ -0,0 +1,89 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use App\Exceptions\GenericPaymentDriverFailure;
use App\Models\Client;
use App\Models\GatewayType;
use App\PaymentDrivers\AuthorizePaymentDriver;
use net\authorize\api\constants\ANetEnvironment;
use net\authorize\api\contract\v1\CreateCustomerProfileRequest;
use net\authorize\api\contract\v1\CustomerAddressType;
use net\authorize\api\contract\v1\CustomerPaymentProfileType;
use net\authorize\api\contract\v1\CustomerProfileType;
use net\authorize\api\controller\CreateCustomerProfileController;
/**
* Class BaseDriver
* @package App\PaymentDrivers
*
*/
class AuthorizeCreateCustomer
{
public $authorize;
public $client;
public function __construct(AuthorizePaymentDriver $authorize, Client $client)
{
$this->authorize = $authorize;
$this->client = $client;
}
public function create($data = null)
{
error_reporting (E_ALL & ~E_DEPRECATED);
$this->authorize->init();
// Create the Bill To info for new payment type
$contact = $this->client->primary_contact()->first();
$refId = 'ref' . time();
// Create a new CustomerProfileType and add the payment profile object
$customerProfile = new CustomerProfileType();
$customerProfile->setDescription($this->client->present()->name());
$customerProfile->setMerchantCustomerId("M_" . time());
$customerProfile->setEmail($this->client->present()->email());
// Assemble the complete transaction request
$request = new CreateCustomerProfileRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId);
$request->setProfile($customerProfile);
// Create the controller and get the response
$controller = new CreateCustomerProfileController($request);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if (($response != null) && ($response->getMessages()->getResultCode() == "Ok")) {
return $response->getCustomerProfileId();
} else {
$errorMessages = $response->getMessages()->getMessage();
$message = "Unable to add customer to Authorize.net gateway";
if(is_array($errorMessages))
$message = $errorMessages[0]->getCode() . " " .$errorMessages[0]->getText();
throw new GenericPaymentDriverFailure($message);
}
}
}

View File

@ -0,0 +1,170 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\AuthorizePaymentDriver;
use App\PaymentDrivers\Authorize\AuthorizeCreateCustomer;
use App\PaymentDrivers\Authorize\AuthorizePaymentMethod;
use App\PaymentDrivers\Authorize\ChargePaymentProfile;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Carbon;
/**
* Class AuthorizeCreditCard
* @package App\PaymentDrivers\Authorize
*
*/
class AuthorizeCreditCard
{
use MakesHash;
public $authorize;
public function __construct(AuthorizePaymentDriver $authorize)
{
$this->authorize = $authorize;
}
public function processPaymentView($data)
{
$tokens = ClientGatewayToken::where('client_id', $this->authorize->client->id)
->where('company_gateway_id', $this->authorize->company_gateway->id)
->where('gateway_type_id', GatewayType::CREDIT_CARD)
->get();
$data['tokens'] = $tokens;
$data['gateway'] = $this->authorize->company_gateway;
$data['public_client_id'] = $this->authorize->init()->getPublicClientKey();
$data['api_login_id'] = $this->authorize->company_gateway->getConfigField('apiLoginId');
return render('gateways.authorize.credit_card_payment', $data);
}
public function processPaymentResponse($request)
{
if($request->token)
return $this->processTokenPayment($request);
$data = $request->all();
$authorise_create_customer = new AuthorizeCreateCustomer($this->authorize, $this->authorize->client);
$gateway_customer_reference = $authorise_create_customer->create($data);
info($gateway_customer_reference);
$authorise_payment_method = new AuthorizePaymentMethod($this->authorize);
$payment_profile = $authorise_payment_method->addPaymentMethodToClient($gateway_customer_reference, $data);
$payment_profile_id = $payment_profile->getPaymentProfile()->getCustomerPaymentProfileId();
info($request->input('store_card'));
if($request->has('store_card') && $request->input('store_card') === 'true'){
$authorise_payment_method->payment_method = GatewayType::CREDIT_CARD;
$client_gateway_token = $authorise_payment_method->createClientGatewayToken($payment_profile, $gateway_customer_reference);
}
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($gateway_customer_reference, $payment_profile_id, $data['amount_with_fee']);
return $this->handleResponse($data, $request);
}
private function processTokenPayment($request)
{
$client_gateway_token = ClientGatewayToken::find($this->decodePrimaryKey($request->token));
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($client_gateway_token->gateway_customer_reference, $client_gateway_token->token, $request->input('amount_with_fee'));
return $this->handleResponse($data, $request);
}
private function handleResponse($data, $request)
{
//info(print_r( $response->getTransactionResponse()->getMessages(),1));
$response = $data['response'];
if($response != null && $response->getMessages()->getResultCode() == "Ok")
return $this->processSuccessfulResponse($data, $request);
return $this->processFailedResponse($data, $request);
}
private function processSuccessfulResponse($data, $request)
{
$response = $data['response'];
//create a payment record and fire notifications and then return
$payment = PaymentFactory::create($this->authorize->client->company_id, $this->authorize->client->user_id);
$payment->client_id = $this->authorize->client->id;
$payment->company_gateway_id = $this->authorize->company_gateway->id;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->currency_id = $this->authorize->client->getSetting('currency_id');
$payment->date = Carbon::now();
$payment->transaction_reference = $response->getTransactionResponse()->getTransId();
$payment->amount = $request->input('amount_with_fee');
$payment->currency_id = $this->authorize->client->getSetting('currency_id');
$payment->client->getNextPaymentNumber($this->authorize->client);
$payment->save();
$this->authorize->attachInvoices($payment, $request->hashed_ids);
$payment->service()->updateInvoicePayment();
event(new PaymentWasCreated($payment, $payment->company));
$logger_message = [
'server_response' => $response->getTransactionResponse()->getTransId(),
'data' => $this->formatGatewayResponse($data, $request)
];
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client);
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
}
private function processFailedResponse($data, $request)
{ dd($data);
info(print_r($data,1));
}
private function formatGatewayResponse($data, $request)
{
$response = $data['response'];
return [
'transaction_reference' => $response->getTransactionResponse()->getTransId(),
'amount' => $request->input('amount'),
'auth_code' => $response->getTransactionResponse()->getAuthCode(),
'code' => $response->getTransactionResponse()->getMessages()[0]->getCode(),
'description' => $response->getTransactionResponse()->getMessages()[0]->getDescription(),
'invoices' => $request->hashed_ids,
];
}
}

View File

@ -0,0 +1,267 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use App\Exceptions\GenericPaymentDriverFailure;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\PaymentDrivers\AuthorizePaymentDriver;
use App\PaymentDrivers\Authorize\AuthorizeCreateCustomer;
use net\authorize\api\contract\v1\CreateCustomerPaymentProfileRequest;
use net\authorize\api\contract\v1\CreateCustomerProfileRequest;
use net\authorize\api\contract\v1\CustomerAddressType;
use net\authorize\api\contract\v1\CustomerPaymentProfileType;
use net\authorize\api\contract\v1\CustomerProfileType;
use net\authorize\api\contract\v1\GetCustomerPaymentProfileRequest;
use net\authorize\api\contract\v1\OpaqueDataType;
use net\authorize\api\contract\v1\PaymentType;
use net\authorize\api\controller\CreateCustomerPaymentProfileController;
use net\authorize\api\controller\CreateCustomerProfileController;
use net\authorize\api\controller\GetCustomerPaymentProfileController;
/**
* Class AuthorizePaymentMethod
* @package App\PaymentDrivers\AuthorizePaymentMethod
*
*/
class AuthorizePaymentMethod
{
public $authorize;
public $payment_method;
public function __construct(AuthorizePaymentDriver $authorize)
{
$this->authorize = $authorize;
}
public function authorizeView($payment_method)
{
$this->payment_method = $payment_method;
switch ($payment_method) {
case GatewayType::CREDIT_CARD:
return $this->authorizeCreditCard();
break;
case GatewayType::BANK_TRANSFER:
return $this->authorizeBankTransfer();
break;
default:
# code...
break;
}
}
public function authorizeResponseView($payment_method, $data)
{
$this->payment_method = $payment_method;
switch ($payment_method) {
case GatewayType::CREDIT_CARD:
return $this->authorizeCreditCardResponse($data);
break;
case GatewayType::BANK_TRANSFER:
return $this->authorizeBankTransferResponse($data);
break;
default:
# code...
break;
}
}
public function authorizeCreditCard()
{
$data['gateway'] = $this->authorize->company_gateway;
$data['public_client_id'] = $this->authorize->init()->getPublicClientKey();
$data['api_login_id'] = $this->authorize->company_gateway->getConfigField('apiLoginId');
return render('gateways.authorize.add_credit_card', $data);
}
public function authorizeBankTransfer()
{
}
public function authorizeCreditCardResponse($data)
{
$client_profile_id = null;
if($client_gateway_token = $this->authorize->findClientGatewayRecord()){
$payment_profile = $this->addPaymentMethodToClient($client_gateway_token->gateway_customer_reference, $data);
}
else{
$gateway_customer_reference = (new AuthorizeCreateCustomer($this->authorize, $this->authorize->client))->create($data);
$payment_profile = $this->addPaymentMethodToClient($gateway_customer_reference, $data);
}
$this->createClientGatewayToken($payment_profile, $gateway_customer_reference);
return redirect()->route('client.payment_methods.index');
}
public function authorizeBankTransferResponse($data)
{
}
public function createClientGatewayToken($payment_profile, $gateway_customer_reference)
{
info(print_r($payment_profile,1));
$client_gateway_token = new ClientGatewayToken();
$client_gateway_token->company_id = $this->authorize->client->company_id;
$client_gateway_token->client_id = $this->authorize->client->id;
$client_gateway_token->token = $payment_profile->getPaymentProfile()->getCustomerPaymentProfileId();
$client_gateway_token->company_gateway_id = $this->authorize->company_gateway->id;
$client_gateway_token->gateway_type_id = $this->payment_method;
$client_gateway_token->gateway_customer_reference = $gateway_customer_reference;
$client_gateway_token->meta = $this->buildPaymentMethod($payment_profile);
$client_gateway_token->save();
return $client_gateway_token;
}
public function buildPaymentMethod($payment_profile)
{
$payment_meta = new \stdClass;
$payment_meta->exp_month = 'xx';
$payment_meta->exp_year = 'xx';
$payment_meta->brand = $payment_profile->getPaymentProfile()->getPayment()->getCreditCard()->getCardType();
$payment_meta->last4 = $payment_profile->getPaymentProfile()->getPayment()->getCreditCard()->getCardNumber();
$payment_meta->type = $this->payment_method;
return $payment_meta;
}
public function addPaymentMethodToClient($gateway_customer_reference, $data)
{
error_reporting (E_ALL & ~E_DEPRECATED);
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref' . time();
// Set the payment data for the payment profile to a token obtained from Accept.js
$op = new OpaqueDataType();
$op->setDataDescriptor($data['dataDescriptor']);
$op->setDataValue($data['dataValue']);
$paymentOne = new PaymentType();
$paymentOne->setOpaqueData($op);
$contact = $this->authorize->client->primary_contact()->first();
if($contact){
// Create the Bill To info for new payment type
$billto = new CustomerAddressType();
$billto->setFirstName($contact->present()->first_name());
$billto->setLastName($contact->present()->last_name());
$billto->setCompany($this->authorize->client->present()->name());
$billto->setAddress($this->authorize->client->address1);
$billto->setCity($this->authorize->client->city);
$billto->setState($this->authorize->client->state);
$billto->setZip($this->authorize->client->postal_code);
if($this->authorize->client->country_id)
$billto->setCountry($this->authorize->client->country->name);
$billto->setPhoneNumber($this->authorize->client->phone);
}
// Create a new Customer Payment Profile object
$paymentprofile = new CustomerPaymentProfileType();
$paymentprofile->setCustomerType('individual');
if($billto)
$paymentprofile->setBillTo($billto);
$paymentprofile->setPayment($paymentOne);
$paymentprofile->setDefaultPaymentProfile(true);
$paymentprofiles[] = $paymentprofile;
// Assemble the complete transaction request
$paymentprofilerequest = new CreateCustomerPaymentProfileRequest();
$paymentprofilerequest->setMerchantAuthentication($this->authorize->merchant_authentication);
// Add an existing profile id to the request
$paymentprofilerequest->setCustomerProfileId($gateway_customer_reference);
$paymentprofilerequest->setPaymentProfile($paymentprofile);
$paymentprofilerequest->setValidationMode("liveMode");
// Create the controller and get the response
$controller = new CreateCustomerPaymentProfileController($paymentprofilerequest);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) {
return $this->getPaymentProfile($gateway_customer_reference, $response->getCustomerPaymentProfileId());
} else {
$errorMessages = $response->getMessages()->getMessage();
$message = "Unable to add customer to Authorize.net gateway";
if(is_array($errorMessages))
$message = $errorMessages[0]->getCode() . " " .$errorMessages[0]->getText();
throw new GenericPaymentDriverFailure($message);
}
}
public function getPaymentProfile($gateway_customer_reference, $payment_profile_id)
{
error_reporting (E_ALL & ~E_DEPRECATED);
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref' . time();
//request requires customerProfileId and customerPaymentProfileId
$request = new GetCustomerPaymentProfileRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId);
$request->setCustomerProfileId($gateway_customer_reference);
$request->setCustomerPaymentProfileId($payment_profile_id);
$controller = new GetCustomerPaymentProfileController($request);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if(($response != null) && ($response->getMessages()->getResultCode() == "Ok")) {
return $response;
}
else if($response){
$errorMessages = $response->getMessages()->getMessage();
$message = "Unable to add payment method to Authorize.net gateway";
if(is_array($errorMessages))
$message = $errorMessages[0]->getCode() . " " .$errorMessages[0]->getText();
throw new GenericPaymentDriverFailure($message);
}
else
throw new GenericPaymentDriverFailure("Error communicating with Authorize.net");
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use net\authorize\api\contract\v1\GetTransactionDetailsRequest;
use net\authorize\api\controller\GetTransactionDetailsController;
/**
* Class AuthorizeTransactions
* @package App\PaymentDrivers\Authorize
*
*/
class AuthorizeTransactions
{
public $authorize;
public function __construct(AuthorizePaymentDriver $authorize)
{
$this->authorize = $authorize;
}
function getTransactionDetails($transactionId)
{
/* Create a merchantAuthenticationType object with authentication details
retrieved from the constants file */
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref' . time();
$request = new GetTransactionDetailsRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setTransId($transactionId);
$controller = new GetTransactionDetailsController($request);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if (($response != null) && ($response->getMessages()->getResultCode() == "Ok"))
{
echo "SUCCESS: Transaction Status:" . $response->getTransaction()->getTransactionStatus() . "\n";
echo " Auth Amount:" . $response->getTransaction()->getAuthAmount() . "\n";
echo " Trans ID:" . $response->getTransaction()->getTransId() . "\n";
}
else
{
echo "ERROR : Invalid response\n";
$errorMessages = $response->getMessages()->getMessage();
echo "Response : " . $errorMessages[0]->getCode() . " " .$errorMessages[0]->getText() . "\n";
}
return $response;
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use App\PaymentDrivers\AuthorizePaymentDriver;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
use net\authorize\api\contract\v1\PaymentProfileType;
use net\authorize\api\contract\v1\TransactionRequestType;
use net\authorize\api\controller\CreateTransactionController;
/**
* Class ChargePaymentProfile
* @package App\PaymentDrivers\Authorize
*
*/
class ChargePaymentProfile
{
public function __construct(AuthorizePaymentDriver $authorize)
{
$this->authorize = $authorize;
}
function chargeCustomerProfile($profile_id, $payment_profile_id, $amount)
{
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref' . time();
$profileToCharge = new CustomerProfilePaymentType();
$profileToCharge->setCustomerProfileId($profile_id);
$paymentProfile = new PaymentProfileType();
$paymentProfile->setPaymentProfileId($payment_profile_id);
$profileToCharge->setPaymentProfile($paymentProfile);
$transactionRequestType = new TransactionRequestType();
$transactionRequestType->setTransactionType("authCaptureTransaction");
$transactionRequestType->setAmount($amount);
$transactionRequestType->setProfile($profileToCharge);
$transactionRequestType->setCurrencyCode($this->authorize->client->currency()->code);
$request = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId( $refId);
$request->setTransactionRequest( $transactionRequestType);
$controller = new CreateTransactionController($request);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if($response != null && $response->getMessages()->getResultCode() == "Ok")
{
$tresponse = $response->getTransactionResponse();
if ($tresponse != null && $tresponse->getMessages() != null)
{
info(" Transaction Response code : " . $tresponse->getResponseCode() );
info( "Charge Customer Profile APPROVED :" );
info(" Charge Customer Profile AUTH CODE : " . $tresponse->getAuthCode() );
info(" Charge Customer Profile TRANS ID : " . $tresponse->getTransId() );
info(" Code : " . $tresponse->getMessages()[0]->getCode());
info(" Description : " . $tresponse->getMessages()[0]->getDescription());
//info(" Charge Customer Profile TRANS STATUS : " . $tresponse->getTransactionStatus() );
//info(" Charge Customer Profile Amount : " . $tresponse->getAuthAmount());
info(" Code : " . $tresponse->getMessages()[0]->getCode() );
info(" Description : " . $tresponse->getMessages()[0]->getDescription() );
info(print_r($tresponse->getMessages()[0],1));
}
else
{
info("Transaction Failed ");
if($tresponse->getErrors() != null)
{
info(" Error code : " . $tresponse->getErrors()[0]->getErrorCode() );
info(" Error message : " . $tresponse->getErrors()[0]->getErrorText() );
info(print_r($tresponse->getErrors()[0],1));
}
}
}
else
{
info("Transaction Failed ");
$tresponse = $response->getTransactionResponse();
if($tresponse != null && $tresponse->getErrors() != null)
{
info(" Error code : " . $tresponse->getErrors()[0]->getErrorCode() );
info(" Error message : " . $tresponse->getErrors()[0]->getErrorText() );
info(print_r($tresponse->getErrors()[0],1));
}
else
{
info(" Error code : " . $response->getMessages()->getMessage()[0]->getCode() );
info(" Error message : " . $response->getMessages()->getMessage()[0]->getText() );
}
}
return [
'response' => $response,
'amount' => $amount,
'profile_id' => $profile_id,
'payment_profile_id' => $payment_profile_id
];
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Authorize;
use App\PaymentDrivers\AuthorizePaymentDriver;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\CustomerProfilePaymentType;
use net\authorize\api\contract\v1\PaymentProfileType;
use net\authorize\api\contract\v1\TransactionRequestType;
use net\authorize\api\controller\CreateTransactionController;
/**
* Class RefundTransaction
* @package App\PaymentDrivers\Authorize
*
*/
class RefundTransaction
{
public function __construct(AuthorizePaymentDriver $authorize)
{
$this->authorize = $authorize;
}
function refundTransaction($transaction_reference, $amount, $payment_profile_id, $profile_id)
{
$this->authorize->init();
// Set the transaction's refId
$refId = 'ref' . time();
$paymentProfile = new PaymentProfileType();
$paymentProfile->setPaymentProfileId( $payment_profile_id );
// set customer profile
$customerProfile = new CustomerProfilePaymentType();
$customerProfile->setCustomerProfileId( $profile_id );
$customerProfile->setPaymentProfile( $paymentProfile );
//create a transaction
$transactionRequest = new TransactionRequestType();
$transactionRequest->setTransactionType("refundTransaction");
$transactionRequest->setAmount($amount);
$transactionRequest->setProfile($customerProfile);
$transactionRequest->setRefTransId($transaction_reference);
$request = new CreateTransactionRequest();
$request->setMerchantAuthentication($this->authorize->merchant_authentication);
$request->setRefId($refId);
$request->setTransactionRequest($transactionRequest);
$controller = new CreateTransactionController($request);
$response = $controller->executeWithApiResponse($this->authorize->mode());
if ($response != null)
{
if($response->getMessages()->getResultCode() == "Ok")
{
$tresponse = $response->getTransactionResponse();
if ($tresponse != null && $tresponse->getMessages() != null)
{
echo " Transaction Response code : " . $tresponse->getResponseCode() . "\n";
echo "Refund SUCCESS: " . $tresponse->getTransId() . "\n";
echo " Code : " . $tresponse->getMessages()[0]->getCode() . "\n";
echo " Description : " . $tresponse->getMessages()[0]->getDescription() . "\n";
}
else
{
echo "Transaction Failed \n";
if($tresponse->getErrors() != null)
{
echo " Error code : " . $tresponse->getErrors()[0]->getErrorCode() . "\n";
echo " Error message : " . $tresponse->getErrors()[0]->getErrorText() . "\n";
}
}
}
else
{
echo "Transaction Failed \n";
$tresponse = $response->getTransactionResponse();
if($tresponse != null && $tresponse->getErrors() != null)
{
echo " Error code : " . $tresponse->getErrors()[0]->getErrorCode() . "\n";
echo " Error message : " . $tresponse->getErrors()[0]->getErrorText() . "\n";
}
else
{
echo " Error code : " . $response->getMessages()->getMessage()[0]->getCode() . "\n";
echo " Error message : " . $response->getMessages()->getMessage()[0]->getText() . "\n";
}
}
}
else
{
echo "No response returned \n";
}
return $response;
}
}

View File

@ -0,0 +1,141 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\PaymentDrivers\Authorize\AuthorizeCreditCard;
use App\PaymentDrivers\Authorize\AuthorizePaymentMethod;
use net\authorize\api\constants\ANetEnvironment;
use net\authorize\api\contract\v1\CreateTransactionRequest;
use net\authorize\api\contract\v1\GetMerchantDetailsRequest;
use net\authorize\api\contract\v1\MerchantAuthenticationType;
use net\authorize\api\controller\CreateTransactionController;
use net\authorize\api\controller\GetMerchantDetailsController;
/**
* Class BaseDriver
* @package App\PaymentDrivers
*
*/
class AuthorizePaymentDriver extends BaseDriver
{
public $merchant_authentication;
public static $methods = [
GatewayType::CREDIT_CARD => AuthorizeCreditCard::class,
];
public function setPaymentMethod($payment_method_id)
{
$class = self::$methods[$payment_method_id];
$this->payment_method = new $class($this);
return $this;
}
/**
* Returns the gateway types
*/
public function gatewayTypes() :array
{
$types = [
GatewayType::CREDIT_CARD,
];
return $types;
}
public function init()
{
error_reporting (E_ALL & ~E_DEPRECATED);
$this->merchant_authentication = new MerchantAuthenticationType();
$this->merchant_authentication->setName($this->company_gateway->getConfigField('apiLoginId'));
$this->merchant_authentication->setTransactionKey($this->company_gateway->getConfigField('transactionKey'));
return $this;
}
public function getPublicClientKey()
{
$request = new GetMerchantDetailsRequest();
$request->setMerchantAuthentication($this->merchant_authentication);
$controller = new GetMerchantDetailsController($request);
$response = $controller->executeWithApiResponse($this->mode());
return $response->getPublicClientKey();
}
public function mode()
{
if($this->company_gateway->getConfigField('testMode'))
return ANetEnvironment::SANDBOX;
return $env = ANetEnvironment::PRODUCTION;
}
public function authorizeView($payment_method)
{
return (new AuthorizePaymentMethod($this))->authorizeView($payment_method);
}
public function authorizeResponseView(array $data)
{
return (new AuthorizePaymentMethod($this))->authorizeResponseView($data['gateway_type_id'], $data);
}
public function authorize($payment_method)
{
return $this->authorizeView($payment_method);
}
public function processPaymentView($data)
{
return $this->payment_method->processPaymentView($data);
}
public function processPaymentResponse($request)
{
return $this->payment_method->processPaymentResponse($request);
}
public function purchase($amount, $return_client_response = false)
{
return false;
}
public function refund($amount, $transaction_reference, $return_client_response = false)
{
}
public function findClientGatewayRecord() :?ClientGatewayToken
{
return ClientGatewayToken::where('client_id', $this->client->id)
->where('company_gateway_id', $this->company_gateway->id)
->first();
}
}

View File

@ -0,0 +1,120 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Models\Client;
use App\Models\CompanyGateway;
use App\Models\Invoice;
use App\Models\Payment;
use App\PaymentDrivers\AbstractPaymentDriver;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SystemLogTrait;
/**
* Class BaseDriver
* @package App\PaymentDrivers
*
*/
class BaseDriver extends AbstractPaymentDriver
{
use SystemLogTrait;
use MakesHash;
/* The company gateway instance*/
public $company_gateway;
/* The Invitation */
public $invitation;
/* Gateway capabilities */
public $refundable = false;
/* Token billing */
public $token_billing = false;
/* Authorise payment methods */
public $can_authorise_credit_card = false;
/* The client */
public $client;
public $payment_method;
public static $methods = [];
public function __construct(CompanyGateway $company_gateway, Client $client = null, $invitation = false)
{
$this->company_gateway = $company_gateway;
$this->invitation = $invitation;
$this->client = $client;
}
/**
* Authorize a payment method.
*
* Returns a reusable token for storage for future payments
* @param const $payment_method the GatewayType::constant
* @return view Return a view for collecting payment method information
*/
public function authorize($payment_method) {}
/**
* Executes purchase attempt for a given amount
*
* @param float $amount The amount to be collected
* @param boolean $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment)
* @return mixed
*/
public function purchase($amount, $return_client_response = false) {}
/**
* Executes a refund attempt for a given amount with a transaction_reference
*
* @param float $amount The amount to be refunded
* @param string $transaction_reference The transaction reference
* @param boolean $return_client_response Whether the method needs to return a response (otherwise we assume an unattended payment)
* @return mixed
*/
public function refund($amount, $transaction_reference, $return_client_response = false) {}
/**
* Set the inbound request payment method type for access.
*
* @param int $payment_method_id The Payment Method ID
*/
public function setPaymentMethod($payment_method_id){}
/**
* Helper method to attach invoices to a payment
*
* @param Payment $payment The payment
* @param array $hashed_ids The array of invoice hashed_ids
* @return Payment The payment object
*/
public function attachInvoices(Payment $payment, $hashed_ids): Payment
{
$transformed = $this->transformKeys($hashed_ids);
$array = is_array($transformed) ? $transformed : [$transformed];
$invoices = Invoice::whereIn('id', $array)
->whereClientId($this->client->id)
->get();
$payment->invoices()->sync($invoices);
$payment->save();
return $payment;
}
}

View File

@ -48,7 +48,7 @@ class BasePaymentDriver
use MakesHash;
/* The company gateway instance*/
protected $company_gateway;
public $company_gateway;
/* The Omnipay payment driver instance*/
protected $gateway;
@ -259,12 +259,12 @@ class BasePaymentDriver
->send();
}
public function createPayment($data): Payment
public function createPayment($data, $status = Payment::STATUS_COMPLETED): Payment
{
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
$payment->client_id = $this->client->id;
$payment->company_gateway_id = $this->company_gateway->id;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->status_id = $status;
$payment->currency_id = $this->client->getSetting('currency_id');
$payment->date = Carbon::now();

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\CheckoutCom;
trait Utilities
{
public function getPublishableKey()
{
return $this->company_gateway->getConfigField('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

@ -0,0 +1,276 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Events\Payment\PaymentWasCreated;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
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\IdSource;
use Checkout\Models\Payments\Payment as CheckoutPayment;
use Checkout\Models\Payments\TokenSource;
class CheckoutComPaymentDriver extends BasePaymentDriver
{
use SystemLogTrait, Utilities;
/* The company gateway instance*/
public $company_gateway;
/* The Invitation */
protected $invitation;
/* Gateway capabilities */
protected $refundable = true;
/* Token billing */
protected $token_billing = true;
/* Authorise payment methods */
protected $can_authorise_credit_card = true;
/** Instance of \Checkout\CheckoutApi */
public $gateway;
/** Since with Checkout.com we handle only credit cards, this method should be empty. */
public function setPaymentMethod($string = null)
{
return $this;
}
public function init()
{
$config = [
'secret' => $this->company_gateway->getConfigField('secretApiKey'),
'public' => $this->company_gateway->getConfigField('publicApiKey'),
'sandbox' => $this->company_gateway->getConfigField('testMode'),
];
$this->gateway = new CheckoutApi($config['secret'], $config['sandbox'], $config['public']);
}
public function viewForType($gateway_type_id)
{
if ($gateway_type_id == GatewayType::CREDIT_CARD) {
return 'gateways.checkout.credit_card';
}
if ($gateway_type_id == GatewayType::TOKEN) {
return 'gateways.checkout.credit_card';
}
}
public function processPaymentView(array $data)
{
$data['gateway'] = $this;
$data['client'] = $this->client;
$data['currency'] = $this->client->getCurrencyCode();
$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);
}
public function processPaymentResponse($request)
{
$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());
$state['store_card'] = boolval($state['store_card']);
if ($request->has('token') && !is_null($request->token)) {
$method = new IdSource($state['token']);
$payment = new CheckoutPayment($method, $state['currency']);
$payment->capture = false;
$payment->amount = $state['value'];
} else {
$method = new TokenSource($state['server_response']->cardToken);
$payment = new CheckoutPayment($method, $state['currency']);
$payment->amount = $state['value'];
if ($this->client->currency()->code === 'EUR') {
$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']) && $state['store_card']) {
$this->saveCard($state);
}
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => PaymentType::parseCardType($state['payment_response']->source['scheme']),
'amount' => $state['raw_value'],
];
$payment = $this->createPayment($data, Payment::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)
{
$state['charge_id'] = $state['payment_response']->id;
if (isset($state['store_card']) && $state['store_card']) {
$this->saveCard($state);
}
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => PaymentType::parseCardType($state['payment_response']->source['scheme']),
'amount' => $state['raw_value'],
];
$payment = $this->createPayment($data, Payment::STATUS_PENDING);
$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);
try {
return redirect($state['payment_response']->_links['redirect']['href']);
} catch (\Exception $e) {
SystemLogger::dispatch($logger_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 processUnsuccessfulPayment($state)
{
PaymentFailureMailer::dispatch($this->client, $state['payment_response']->response_summary, $this->client->company, $state['payment_response']->amount);
$message = [
'server_response' => $state['server_response'],
'data' => $state,
];
SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_CHECKOUT, $this->client);
throw new \Exception('Failed to process the payment: ' . $state['payment_response']->response_summary, 1);
}
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 = Payment::STATUS_COMPLETED): Payment
{
$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;
}
public function saveCard($state)
{
$company_gateway_token = new ClientGatewayToken();
$company_gateway_token->company_id = $this->client->company->id;
$company_gateway_token->client_id = $this->client->id;
$company_gateway_token->token = $state['payment_response']->source['id'];
$company_gateway_token->company_gateway_id = $this->company_gateway->id;
$company_gateway_token->gateway_type_id = $state['payment_method_id'];
$company_gateway_token->meta = $state['payment_response']->source;
$company_gateway_token->save();
if ($this->client->gateway_tokens->count() == 1) {
$this->client->gateway_tokens()->update(['is_default' => 0]);
$company_gateway_token->is_default = 1;
$company_gateway_token->save();
}
}
}

View File

@ -1,139 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers;
use App\Factory\PaymentFactory;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Utils\Traits\SystemLogTrait;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Omnipay\Omnipay;
/**
* Class BasePaymentDriver
* @package App\PaymentDrivers
*
* Minimum dataset required for payment gateways
*
* $data = [
'amount' => $invoice->getRequestedAmount(),
'currency' => $invoice->getCurrencyCode(),
'returnUrl' => $completeUrl,
'cancelUrl' => $this->invitation->getLink(),
'description' => trans('texts.' . $invoice->getEntityType()) . " {$invoice->number}",
'transactionId' => $invoice->number,
'transactionType' => 'Purchase',
'clientIp' => Request::getClientIp(),
];
*/
class CheckoutPaymentDriver extends BasePaymentDriver
{
use SystemLogTrait;
/* The company gateway instance*/
protected $company_gateway;
/* The Omnipay payment driver instance*/
protected $gateway;
/* The Invitation */
protected $invitation;
/* Gateway capabilities */
protected $refundable = true;
/* Token billing */
protected $token_billing = true;
/* Authorise payment methods */
protected $can_authorise_credit_card = true;
public function createTransactionToken($amount)
{
// if ($this->invoice()->getCurrencyCode() == 'BHD') {
// $amount = $this->invoice()->getRequestedAmount() / 10;
// } elseif ($this->invoice()->getCurrencyCode() == 'KWD') {
// $amount = $this->invoice()->getRequestedAmount() * 10;
// } elseif ($this->invoice()->getCurrencyCode() == 'OMR') {
// $amount = $this->invoice()->getRequestedAmount();
// } else
// $amount = $this->invoice()->getRequestedAmount();
$response = $this->gateway()->purchase([
'amount' => $amount,
'currency' => $this->client->getCurrencyCode(),
])->send();
if ($response->isRedirect()) {
$token = $response->getTransactionReference();
session()->flash('transaction_reference', $token);
// On each request, session()->flash() || sesion('', value) || session[name] ||session->flash(key, value)
return $token;
}
return false;
}
public function viewForType($gateway_type_id)
{
switch ($gateway_type_id) {
case GatewayType::CREDIT_CARD:
return 'gateways.checkout.credit_card';
break;
case GatewayType::TOKEN:
break;
default:
break;
}
}
/**
*
* $data = [
'invoices' => $invoices,
'amount' => $amount,
'fee' => $gateway->calcGatewayFee($amount),
'amount_with_fee' => $amount + $gateway->calcGatewayFee($amount),
'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id),
'payment_method_id' => $payment_method_id,
'hashed_ids' => explode(",", request()->input('hashed_ids')),
];
*/
public function processPaymentView(array $data)
{
$data['token'] = $this->createTransactionToken($data['amount']);
$data['gateway'] = $this->gateway();
return render($this->viewForType($data['payment_method_id']), $data);
}
public function processPaymentResponse($request)
{
$data['token'] = session('transaction_reference');
$this->completeOffsitePurchase($data);
}
}

View File

@ -258,9 +258,9 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
return $items;
}
public function createPayment($data): Payment
public function createPayment($data, $status = Payment::STATUS_COMPLETED): Payment
{
$payment = parent::createPayment($data);
$payment = parent::createPayment($data, $status);
$client_contact = $this->getContact();
$client_contact_id = $client_contact ? $client_contact->id : null;

View File

@ -0,0 +1,221 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Stripe;
use App\Events\Payment\PaymentWasCreated;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\StripePaymentDriver;
use Stripe\Exception\InvalidRequestException;
class ACH
{
/** @var StripePaymentDriver */
public $stripe;
public function __construct(StripePaymentDriver $stripe)
{
$this->stripe = $stripe;
}
public function authorizeView(array $data)
{
return render('gateways.stripe.ach.authorize', array_merge($data));
}
public function authorizeResponse($request)
{
$state = [
'server_response' => json_decode($request->gateway_response),
'gateway_id' => $request->company_gateway_id,
'gateway_type_id' => $request->gateway_type_id,
'is_default' => $request->is_default,
];
$customer = $this->stripe->findOrCreateCustomer();
$this->stripe->init();
$local_stripe = new \Stripe\StripeClient(
$this->stripe->company_gateway->getConfigField('apiKey')
);
try {
$local_stripe->customers->createSource(
$customer->id,
['source' => $state['server_response']->token->id]
);
} catch (InvalidRequestException $e) {
return back()->with('ach_error', $e->getMessage());
}
$payment_meta = $state['server_response']->token->bank_account;
$payment_meta->brand = ctrans('texts.ach');
$payment_meta->type = ctrans('texts.bank_transfer');
$payment_meta->verified_at = null;
$payment_meta->token = $state['server_response']->token->id;
$client_gateway_token = new ClientGatewayToken();
$client_gateway_token->company_id = $this->stripe->client->company->id;
$client_gateway_token->client_id = $this->stripe->client->id;
$client_gateway_token->token = $state['server_response']->token->bank_account->id;
$client_gateway_token->company_gateway_id = $this->stripe->company_gateway->id;
$client_gateway_token->gateway_type_id = $state['gateway_type_id'];
$client_gateway_token->gateway_customer_reference = $customer->id;
$client_gateway_token->meta = $payment_meta;
$client_gateway_token->save();
if ($state['is_default'] == 'true' || $this->stripe->client->gateway_tokens->count() == 1) {
$this->stripe->client->gateway_tokens()->update(['is_default' => 0]);
$client_gateway_token->is_default = 1;
$client_gateway_token->save();
}
return redirect()->route('client.payment_methods.verification', $client_gateway_token->hashed_id);
}
public function verificationView(ClientGatewayToken $token)
{
return render('gateways.stripe.ach.verify', compact('token'));
}
public function processVerification(ClientGatewayToken $token)
{
$this->stripe->init();
$bank_account = \Stripe\Customer::retrieveSource(
request()->customer,
request()->source,
);
try {
$status = $bank_account->verify(['amounts' => request()->transactions]);
$token->meta->verified_at = now();
$token->save();
return redirect()
->route('client.invoices.index')
->with('success', __('texts.payment_method_verified'));
} catch (\Stripe\Exception\CardException $e) {
return back()->with('error', $e->getMessage());
}
}
public function paymentView(array $data)
{
$state = [
'amount' => $data['amount_with_fee'],
'currency' => $this->stripe->client->getCurrencyCode(),
'invoices' => $data['invoices'],
'gateway' => $this->stripe,
'payment_method_id' => GatewayType::BANK_TRANSFER,
'token' => $data['token'],
'customer' => $this->stripe->findOrCreateCustomer(),
];
return render('gateways.stripe.ach.pay', $state);
}
public function paymentResponse($request)
{
$state = [
'payment_method' => $request->payment_method_id,
'gateway_type_id' => $request->company_gateway_id,
'hashed_ids' => $request->hashed_ids,
'amount' => $this->stripe->convertToStripeAmount($request->amount, $this->stripe->client->currency()->precision),
'currency' => $request->currency,
'source' => $request->source,
'customer' => $request->customer,
];
if ($this->stripe->getContact()) {
$state['client_contact'] = $this->stripe->getContact();
} else {
$state['client_contact'] = $state['invoices']->first()->invitations->first()->contact;
}
$this->stripe->init();
try {
$state['charge'] = \Stripe\Charge::create([
'amount' => $state['amount'],
'currency' => $state['currency'],
'customer' => $state['customer'],
'source' => $state['source'],
]);
if ($state['charge']->status === 'pending' && is_null($state['charge']->failure_message)) {
return $this->processPendingPayment($state);
}
return $this->processUnsuccessfulPayment($state);
} catch (\Exception $e) {
if ($e instanceof \Stripe\Exception\CardException) {
return redirect()->route('client.payment_methods.verification', ClientGatewayToken::first()->hashed_id);
}
}
}
public function processPendingPayment($state)
{
$state['charge_id'] = $state['charge']->id;
$this->stripe->init();
$state['payment_type'] = PaymentType::ACH;
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => $state['payment_type'],
'amount' => $state['charge']->amount,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
$this->stripe->attachInvoices($payment, $state['hashed_ids']);
$payment->service()->updateInvoicePayment();
event(new PaymentWasCreated($payment, $payment->company));
$logger_message = [
'server_response' => $state['charge'],
'data' => $data,
];
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
}
public function processUnsuccessfulPayment($state)
{
PaymentFailureMailer::dispatch($this->stripe->client, $state['charge']->failure_message, $this->stripe->client->company, $state['amount']);
$message = [
'server_response' => $state['charge'],
'data' => $state,
];
SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client);
throw new \Exception('Failed to process the payment.', 1);
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Stripe;
use App\Events\Payment\PaymentWasCreated;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\StripePaymentDriver;
class Alipay
{
/** @var StripePaymentDriver */
public $stripe;
public function __construct(StripePaymentDriver $stripe)
{
$this->stripe = $stripe;
}
public function paymentView(array $data)
{
$data['gateway'] = $this->stripe;
$data['return_url'] = $this->buildReturnUrl($data);
$data['currency'] = $this->stripe->client->getCurrencyCode();
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['amount_with_fee'], $this->stripe->client->currency()->precision);
return render('gateways.stripe.alipay.pay', $data);
}
private function buildReturnUrl($data): string
{
return route('client.payments.response', [
'company_gateway_id' => $this->stripe->company_gateway->id,
'gateway_type_id' => GatewayType::SOFORT,
'hashed_ids' => implode(",", $data['hashed_ids']),
'amount' => $data['amount'],
'fee' => $data['fee'],
]);
}
public function paymentResponse($request)
{
$state = array_merge($request->all(), []);
$amount = $state['amount'] + $state['fee'];
$state['amount'] = $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision);
if ($request->redirect_status == 'succeeded') {
return $this->processSuccesfulRedirect($state);
}
return $this->processUnsuccesfulRedirect($state);
}
public function processSuccesfulRedirect($state)
{
$state['charge_id'] = $state['source'];
$this->stripe->init();
$state['payment_type'] = PaymentType::ALIPAY;
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => $state['payment_type'],
'amount' => $state['amount'],
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
if (isset($state['hashed_ids'])) {
$this->stripe->attachInvoices($payment, $state['hashed_ids']);
}
event(new PaymentWasCreated($payment, $payment->company));
$logger_message = [
'server_response' => $state,
'data' => $data
];
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
}
public function processUnsuccesfulRedirect($state)
{
PaymentFailureMailer::dispatch($this->stripe->client, $state['charge']->failure_message, $this->stripe->client->company, $state['amount']);
$message = [
'server_response' => $state['charge'],
'data' => $state,
];
SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client);
throw new \Exception('Failed to process the payment.', 1);
}
}

View File

@ -0,0 +1,233 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Stripe;
use App\Events\Payment\PaymentWasCreated;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\StripePaymentDriver;
use Stripe\PaymentMethod;
class CreditCard
{
public $stripe;
public function __construct(StripePaymentDriver $stripe)
{
$this->stripe = $stripe;
}
public function authorizeView(array $data)
{
$intent['intent'] = $this->stripe->getSetupIntent();
return render('gateways.stripe.add_credit_card', array_merge($data, $intent));
}
public function authorizeResponse($request)
{
$server_response = json_decode($request->input('gateway_response'));
$gateway_id = $request->input('gateway_id');
$gateway_type_id = $request->input('gateway_type_id');
$is_default = $request->input('is_default');
$payment_method = $server_response->payment_method;
$customer = $this->stripe->findOrCreateCustomer();
$this->stripe->init();
$stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method);
$stripe_payment_method_obj = $stripe_payment_method->jsonSerialize();
$stripe_payment_method->attach(['customer' => $customer->id]);
$payment_meta = new \stdClass;
$payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month'];
$payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year'];
$payment_meta->brand = $stripe_payment_method_obj['card']['brand'];
$payment_meta->last4 = $stripe_payment_method_obj['card']['last4'];
$payment_meta->type = GatewayType::CREDIT_CARD;
$client_gateway_token = new ClientGatewayToken();
$client_gateway_token->company_id = $this->stripe->client->company->id;
$client_gateway_token->client_id = $this->stripe->client->id;
$client_gateway_token->token = $payment_method;
$client_gateway_token->company_gateway_id = $this->stripe->company_gateway->id;
$client_gateway_token->gateway_type_id = $gateway_type_id;
$client_gateway_token->gateway_customer_reference = $customer->id;
$client_gateway_token->meta = $payment_meta;
$client_gateway_token->save();
if ($is_default == 'true' || $this->stripe->client->gateway_tokens->count() == 1) {
$this->stripe->client->gateway_tokens()->update(['is_default' => 0]);
$client_gateway_token->is_default = 1;
$client_gateway_token->save();
}
return redirect()->route('client.payment_methods.index');
}
public function paymentView(array $data)
{
$payment_intent_data = [
'amount' => $this->stripe->convertToStripeAmount($data['amount_with_fee'], $this->stripe->client->currency()->precision),
'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $data['invoices']->pluck('id'), //todo more meaningful description here:
];
if ($data['token']) {
$payment_intent_data['payment_method'] = $data['token']->token;
} else {
$payment_intent_data['setup_future_usage'] = 'off_session';
// $payment_intent_data['save_payment_method'] = true;
// $payment_intent_data['confirm'] = true;
}
$data['intent'] = $this->stripe->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this->stripe;
return render('gateways.stripe.credit_card', $data);
}
public function paymentResponse($request)
{
$server_response = json_decode($request->input('gateway_response'));
$state = [
'payment_method' => $server_response->payment_method,
'payment_status' => $server_response->status,
'save_card' => $request->store_card,
'gateway_type_id' => $request->payment_method_id,
'hashed_ids' => $request->hashed_ids,
'server_response' => $server_response,
];
$invoices = Invoice::whereIn('id', $this->stripe->transformKeys($state['hashed_ids']))
->whereClientId($this->stripe->client->id)
->get();
if ($this->stripe->getContact()) {
$client_contact = $this->stripe->getContact();
} else {
$client_contact = $invoices->first()->invitations->first()->contact;
}
$this->stripe->init();
$state['payment_intent'] = \Stripe\PaymentIntent::retrieve($server_response->id);
$state['customer'] = $state['payment_intent']->customer;
if ($state['payment_status'] == 'succeeded') {
return $this->processSuccessfulPayment($state);
}
return $this->processUnsuccessfulPayment($server_response);
}
private function processSuccessfulPayment($state)
{
$state['charge_id'] = $state['payment_intent']->charges->data[0]->id;
$this->stripe->init();
$state['payment_method'] = PaymentMethod::retrieve($state['payment_method']);
$payment_method_object = $state['payment_method']->jsonSerialize();
$state['payment_meta'] = [
'exp_month' => $payment_method_object['card']['exp_month'],
'exp_year' => $payment_method_object['card']['exp_year'],
'brand' => $payment_method_object['card']['brand'],
'last4' => $payment_method_object['card']['last4'],
'type' => $payment_method_object['type'],
];
$payment_type = PaymentType::parseCardType($payment_method_object['card']['brand']);
if ($state['save_card'] === true) {
$this->saveCard($state);
}
// Todo: Need to fix this to support payment types other than credit card.... sepa etc etc
if (!isset($state['payment_type'])) {
$state['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
}
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => $state['payment_type'],
'amount' => $state['server_response']->amount,
];
$payment = $this->stripe->createPayment($data, $status = Payment::STATUS_COMPLETED);
$this->stripe->attachInvoices($payment, $state['hashed_ids']);
$payment->service()->updateInvoicePayment();
event(new PaymentWasCreated($payment, $payment->company));
$logger_message = [
'server_response' => $state['payment_intent'],
'data' => $data
];
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
}
private function processUnsuccessfulPayment($server_response)
{
PaymentFailureMailer::dispatch($this->stripe->client, $server_response->cancellation_reason, $this->stripe->client->company, $server_response->amount);
$message = [
'server_response' => $server_response,
'data' => [],
];
SystemLogger::dispatch($message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client);
throw new \Exception('Failed to process the payment.', 1);
}
private function saveCard($state)
{
$state['payment_method']->attach(['customer' => $state['customer']]);
$company_gateway_token = new ClientGatewayToken();
$company_gateway_token->company_id = $this->stripe->client->company->id;
$company_gateway_token->client_id = $this->stripe->client->id;
$company_gateway_token->token = $state['payment_method'];
$company_gateway_token->company_gateway_id = $this->stripe->company_gateway->id;
$company_gateway_token->gateway_type_id = $state['gateway_type_id'];
$company_gateway_token->gateway_customer_reference = $state['customer'];
$company_gateway_token->meta = $state['payment_meta'];
$company_gateway_token->save();
if ($this->stripe->client->gateway_tokens->count() == 1) {
$this->stripe->client->gateway_tokens()->update(['is_default' => 0]);
$company_gateway_token->is_default = 1;
$company_gateway_token->save();
}
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Stripe;
use App\Events\Payment\PaymentWasCreated;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\StripePaymentDriver;
class SOFORT
{
/** @var StripePaymentDriver */
public $stripe;
public function __construct(StripePaymentDriver $stripe)
{
$this->stripe = $stripe;
}
public function paymentView(array $data)
{
$data['gateway'] = $this->stripe;
$data['return_url'] = $this->buildReturnUrl($data);
$data['stripe_amount'] = $this->stripe->convertToStripeAmount($data['amount_with_fee'], $this->stripe->client->currency()->precision);
$data['client'] = $this->stripe->client;
$data['country'] = $this->stripe->client->country->iso_3166_2;
return render('gateways.stripe.sofort.pay', $data);
}
private function buildReturnUrl($data): string
{
return route('client.payments.response', [
'company_gateway_id' => $this->stripe->company_gateway->id,
'gateway_type_id' => GatewayType::SOFORT,
'hashed_ids' => implode(",", $data['hashed_ids']),
'amount' => $data['amount'],
'fee' => $data['fee'],
]);
}
public function paymentResponse($request)
{
$state = array_merge($request->all(), []);
$amount = $state['amount'] + $state['fee'];
$state['amount'] = $this->stripe->convertToStripeAmount($amount, $this->stripe->client->currency()->precision);
if ($request->redirect_status == 'succeeded') {
return $this->processSuccessfulPayment($state);
}
return $this->processUnsuccessfulPayment($state);
}
public function processSuccessfulPayment($state)
{
$state['charge_id'] = $state['source'];
$this->stripe->init();
$state['payment_type'] = PaymentType::SOFORT;
$data = [
'payment_method' => $state['charge_id'],
'payment_type' => $state['payment_type'],
'amount' => $state['amount'],
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);
/** @todo: https://github.com/invoiceninja/invoiceninja/pull/3789/files#r436175798 */
if (isset($state['hashed_ids'])) {
$this->stripe->attachInvoices($payment, $state['hashed_ids']);
}
event(new PaymentWasCreated($payment, $payment->company));
$logger_message = [
'server_response' => $state,
'data' => $data
];
SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client);
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
}
public function processUnsuccessfulPayment($state)
{
return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_voided'));
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\PaymentDrivers\Stripe;
trait Utilities
{
public function convertFromStripeAmount($amount, $precision)
{
return $amount / pow(10, $precision);
}
public function convertToStripeAmount($amount, $precision)
{
return $amount * pow(10, $precision);
}
}

View File

@ -21,6 +21,7 @@ use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\Utilities;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
@ -30,7 +31,7 @@ use Stripe\Stripe;
class StripePaymentDriver extends BasePaymentDriver
{
use MakesHash;
use MakesHash, Utilities;
protected $refundable = true;
@ -40,6 +41,8 @@ class StripePaymentDriver extends BasePaymentDriver
protected $customer_reference = 'customerReferenceParam';
protected $payment_method;
/**
* Methods in this class are divided into
* two separate streams
@ -62,6 +65,15 @@ class StripePaymentDriver extends BasePaymentDriver
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
}
public function setPaymentMethod(string $method)
{
// Example: setPaymentMethod('App\\PaymentDrivers\\Stripe\\CreditCard');
$this->payment_method = new $method($this);
return $this;
}
/**
* Returns the gateway types
*/
@ -128,102 +140,39 @@ class StripePaymentDriver extends BasePaymentDriver
}
/**
* Authorises a credit card for future use.
*
* @param array $data Array of variables needed for the view
* Proxy method to pass the data into payment method authorizeView().
*
* @param array $data
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function authorizeCreditCardView(array $data)
public function authorizeView(array $data)
{
$intent['intent'] = $this->getSetupIntent();
return render('gateways.stripe.add_credit_card', array_merge($data, $intent));
return $this->payment_method->authorizeView($data);
}
/**
* Processes the gateway response for credit card authorization.
*
* @param Request $request The returning request object
* @return view Returns the user to payment methods screen.
* @throws \Stripe\Exception\ApiErrorException
* @param \Illuminate\Http\Request $request The returning request object
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function authorizeCreditCardResponse($request)
{
$server_response = json_decode($request->input('gateway_response'));
$gateway_id = $request->input('gateway_id');
$gateway_type_id = $request->input('gateway_type_id');
$is_default = $request->input('is_default');
$payment_method = $server_response->payment_method;
$customer = $this->findOrCreateCustomer();
$this->init();
$stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method);
$stripe_payment_method_obj = $stripe_payment_method->jsonSerialize();
$stripe_payment_method->attach(['customer' => $customer->id]);
$payment_meta = new \stdClass;
if ($stripe_payment_method_obj['type'] == 'card') {
$payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month'];
$payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year'];
$payment_meta->brand = $stripe_payment_method_obj['card']['brand'];
$payment_meta->last4 = $stripe_payment_method_obj['card']['last4'];
$payment_meta->type = GatewayType::CREDIT_CARD;
}
$cgt = new ClientGatewayToken;
$cgt->company_id = $this->client->company->id;
$cgt->client_id = $this->client->id;
$cgt->token = $payment_method;
$cgt->company_gateway_id = $this->company_gateway->id;
$cgt->gateway_type_id = $gateway_type_id;
$cgt->gateway_customer_reference = $customer->id;
$cgt->meta = $payment_meta;
$cgt->save();
if ($is_default == 'true' || $this->client->gateway_tokens->count() == 1) {
$this->client->gateway_tokens()->update(['is_default'=>0]);
$cgt->is_default = 1;
$cgt->save();
}
return redirect()->route('client.payment_methods.index');
return $this->payment_method->authorizeResponse($request);
}
/**
* Process the payment with gateway.
*
* @param array $data
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|void
* @throws \Exception
*/
public function processPaymentView(array $data)
{
$payment_intent_data = [
'amount' => $this->convertToStripeAmount($data['amount_with_fee'], $this->client->currency()->precision),
'currency' => $this->client->getCurrencyCode(),
'customer' => $this->findOrCreateCustomer(),
'description' => $data['invoices']->pluck('id'), //todo more meaningful description here:
];
if ($data['token']) {
$payment_intent_data['payment_method'] = $data['token']->token;
} else {
$payment_intent_data['setup_future_usage'] = 'off_session';
// $payment_intent_data['save_payment_method'] = true;
// $payment_intent_data['confirm'] = true;
}
$data['intent'] = $this->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this;
return render($this->viewForType($data['payment_method_id']), $data);
return $this->payment_method->paymentView($data);
}
/**
@ -256,137 +205,12 @@ class StripePaymentDriver extends BasePaymentDriver
*/
public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver.
{
$server_response = json_decode($request->input('gateway_response'));
$payment_method = $server_response->payment_method;
$payment_status = $server_response->status;
$save_card = $request->input('store_card');
$gateway_type_id = $request->input('payment_method_id');
$hashed_ids = $request->input('hashed_ids');
$invoices = Invoice::whereIn('id', $this->transformKeys($hashed_ids))
->whereClientId($this->client->id)
->get();
/**
* Potential statuses that can be returned
*
* requires_action
* processing
* canceled
* requires_action
* requires_confirmation
* requires_payment_method
*
*/
if ($this->getContact()) {
$client_contact = $this->getContact();
} else {
$client_contact = $invoices->first()->invitations->first()->contact;
}
$this->init();
$payment_intent = \Stripe\PaymentIntent::retrieve($server_response->id);
$customer = $payment_intent->customer;
if ($payment_status == 'succeeded') {
$charge_id = $payment_intent->charges->data[0]->id;
$this->init();
$stripe_payment_method = \Stripe\PaymentMethod::retrieve($payment_method);
$stripe_payment_method_obj = $stripe_payment_method->jsonSerialize();
$payment_meta = new \stdClass;
if ($stripe_payment_method_obj['type'] == 'card') {
$payment_meta->exp_month = $stripe_payment_method_obj['card']['exp_month'];
$payment_meta->exp_year = $stripe_payment_method_obj['card']['exp_year'];
$payment_meta->brand = $stripe_payment_method_obj['card']['brand'];
$payment_meta->last4 = $stripe_payment_method_obj['card']['last4'];
$payment_meta->type = $stripe_payment_method_obj['type'];
$payment_type = PaymentType::parseCardType($stripe_payment_method_obj['card']['brand']);
}
if ($save_card == 'true') {
$stripe_payment_method->attach(['customer' => $customer]);
$cgt = new ClientGatewayToken;
$cgt->company_id = $this->client->company->id;
$cgt->client_id = $this->client->id;
$cgt->token = $payment_method;
$cgt->company_gateway_id = $this->company_gateway->id;
$cgt->gateway_type_id = $gateway_type_id;
$cgt->gateway_customer_reference = $customer;
$cgt->meta = $payment_meta;
$cgt->save();
if ($this->client->gateway_tokens->count() == 1) {
$this->client->gateway_tokens()->update(['is_default'=>0]);
$cgt->is_default = 1;
$cgt->save();
}
}
//todo need to fix this to support payment types other than credit card.... sepa etc etc
if (!$payment_type) {
$payment_type = PaymentType::CREDIT_CARD_OTHER;
}
$data = [
'payment_method' => $charge_id,
'payment_type' => $payment_type,
'amount' => $server_response->amount,
];
/* Create payment*/
$payment = $this->createPayment($data);
/* Link invoices to payment*/
$this->attachInvoices($payment, $hashed_ids);
$payment->service()->UpdateInvoicePayment();
event(new PaymentWasCreated($payment, $payment->company));
SystemLogger::dispatch(
[
'server_response' => $payment_intent,
'data' => $data
],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE,
$this->client
);
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
} else {
PaymentFailureMailer::dispatch($this->client, $server_response->cancellation_reason, $this->client->company, $server_response->amount);
/*Fail and log*/
SystemLogger::dispatch(
[
'server_response' => $server_response,
'data' => $data
],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_STRIPE,
$this->client
);
throw new \Exception("Failed to process payment", 1);
}
return $this->payment_method->paymentResponse($request);
}
public function createPayment($data) :Payment
public function createPayment($data, $status = Payment::STATUS_COMPLETED) :Payment
{
$payment = parent::createPayment($data);
$payment = parent::createPayment($data, $status);
$client_contact = $this->getContact();
$client_contact_id = $client_contact ? $client_contact->id : null;
@ -400,15 +224,6 @@ class StripePaymentDriver extends BasePaymentDriver
return $payment;
}
private function convertFromStripeAmount($amount, $precision)
{
return $amount / pow(10, $precision);
}
private function convertToStripeAmount($amount, $precision)
{
return $amount * pow(10, $precision);
}
/**
* Creates a new String Payment Intent
*
@ -515,5 +330,15 @@ class StripePaymentDriver extends BasePaymentDriver
return false;
}
public function verificationView(ClientGatewayToken $payment_method)
{
return $this->payment_method->verificationView($payment_method);
}
public function processVerification(ClientGatewayToken $payment_method)
{
return $this->payment_method->processVerification($payment_method);
}
/************************************** Omnipay API methods **********************************************************/
}

View File

@ -11,6 +11,7 @@
namespace App\Repositories;
use App\Libraries\MultiDB;
use App\Models\Activity;
use App\Models\Backup;
use App\Models\Client;
@ -31,8 +32,11 @@ class ActivityRepository extends BaseRepository
* @param stdClass $fields The fields
* @param Collection $entity The entity that you wish to have backed up (typically Invoice, Quote etc etc rather than Payment)
*/
public function save($fields, $entity)
public function save($fields, $entity, $db = null)
{
if($db)
MultiDB::setDB($db);
$activity = new Activity();
$activity->is_system = app()->runningInConsole();

View File

@ -11,6 +11,7 @@
namespace App\Repositories;
use App\Events\Invoice\InvoiceWasUpdated;
use App\Factory\InvoiceInvitationFactory;
use App\Factory\QuoteInvitationFactory;
use App\Jobs\Product\UpdateOrCreateProduct;
@ -262,7 +263,7 @@ class BaseRepository
//make sure we are creating an invite for a contact who belongs to the client only!
$contact = ClientContact::find($invitation['client_contact_id']);
if ($model->client_id == $contact->client_id);
if ($contact && $model->client_id == $contact->client_id);
{
$new_invitation = $invitation_factory_class::create($model->company_id, $model->user_id);
$new_invitation->{$lcfirst_resource_id} = $model->id;
@ -294,6 +295,9 @@ class BaseRepository
}
$model = $model->calc()->getInvoice();
event(new InvoiceWasUpdated($model, $model->company));
}
if ($class->name == Credit::class) {

View File

@ -46,7 +46,6 @@ class PaymentRepository extends BaseRepository
/**
* Saves and updates a payment. //todo refactor to handle refunds and payments.
*
*
* @param array $data the request object
* @param Payment $payment The Payment object
* @return Payment|null Payment $payment
@ -57,61 +56,85 @@ class PaymentRepository extends BaseRepository
return $this->applyPayment($data, $payment);
}
return $this->refundPayment($data, $payment);
return $payment;
}
/**
* Handles a positive payment request
* @param array $data The data object
* @param Payment $payment The $payment entity
* @param array $data The data object
* @param Payment $payment The $payment entity
* @return Payment The updated/created payment object
*/
private function applyPayment(array $data, Payment $payment): ?Payment
{
//check currencies here and fill the exchange rate data if necessary
if (!$payment->id) {
$this->processExchangeRates($data, $payment);
/*We only update the paid to date ONCE per payment*/
if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) {
if($data['amount'] == '')
$data['amount'] = array_sum(array_column($data['invoices'], 'amount'));
$client = Client::find($data['client_id']);
$client->service()->updatePaidToDate($data['amount'])->save();
}
}
/*Fill the payment*/
$payment->fill($data);
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
/*Ensure payment number generated*/
if (!$payment->number || strlen($payment->number) == 0) {
$payment->number = $payment->client->getNextPaymentNumber($payment->client);
}
$payment->client->service()->updatePaidToDate($payment->amount)->save();
$invoice_totals = 0;
$credit_totals = 0;
if (array_key_exists('invoices', $data) && is_array($data['invoices'])) {
/*Iterate through invoices and apply payments*/
if (array_key_exists('invoices', $data) && is_array($data['invoices']) && count($data['invoices']) > 0) {
$invoice_totals = array_sum(array_column($data['invoices'], 'amount'));
$invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
$payment->invoices()->saveMany($invoices);
info("iterating through payment invoices");
foreach ($data['invoices'] as $paid_invoice) {
$invoice = Invoice::whereId($paid_invoice['invoice_id'])->first();
$invoice = Invoice::whereId($paid_invoice['invoice_id'])->with('client')->first();
info("current client balance = {$invoice->client->balance}");
if ($invoice) {
$invoice->service()->applyPayment($payment, $paid_invoice['amount'])->save();
info("apply payment amount {$paid_invoice['amount']}");
$invoice = $invoice->service()->markSent()->applyPayment($payment, $paid_invoice['amount'])->save();
info("after processing invoice the client balance is now {$invoice->client->balance}");
}
}
} else {
//payment is made, but not to any invoice, therefore we are applying the payment to the clients credit
$payment->client->processUnappliedPayment($payment->amount);
//payment is made, but not to any invoice, therefore we are applying the payment to the clients paid_to_date only
$payment->client->service()->updatePaidToDate($payment->amount)->save();
}
if (array_key_exists('credits', $data) && is_array($data['credits'])) {
$credit_totals = array_sum(array_column($data['credits'], 'amount'));
$credits = Credit::whereIn('id', $this->transformKeys(array_column($data['credits'], 'credit_id')))->get();
$payment->credits()->saveMany($credits);
foreach ($data['credits'] as $paid_credit) {
@ -128,8 +151,9 @@ class PaymentRepository extends BaseRepository
$invoice_totals -= $credit_totals;
//$payment->amount = $invoice_totals; //creates problems when setting amount like this.
if ($invoice_totals == $payment->amount) {
if($credit_totals == $payment->amount){
$payment->applied += $credit_totals;
} elseif ($invoice_totals == $payment->amount) {
$payment->applied += $payment->amount;
} elseif ($invoice_totals < $payment->amount) {
$payment->applied += $invoice_totals;
@ -140,53 +164,6 @@ class PaymentRepository extends BaseRepository
return $payment->fresh();
}
/**
* @deprecated Refundable trait replaces this.
*/
private function refundPayment(array $data, Payment $payment): string
{
// //temp variable to sum the total refund/credit amount
// $invoice_total_adjustment = 0;
// if (array_key_exists('invoices', $data) && is_array($data['invoices'])) {
// foreach ($data['invoices'] as $adjusted_invoice) {
// $invoice = Invoice::whereId($adjusted_invoice['invoice_id'])->first();
// $invoice_total_adjustment += $adjusted_invoice['amount'];
// if (array_key_exists('credits', $adjusted_invoice)) {
// //process and insert credit notes
// foreach ($adjusted_invoice['credits'] as $credit) {
// $credit = $this->credit_repo->save($credit, CreditFactory::create(auth()->user()->id, auth()->user()->id), $invoice);
// }
// } else {
// //todo - generate Credit Note for $amount on $invoice - the assumption here is that it is a FULL refund
// }
// }
// if (array_key_exists('amount', $data) && $data['amount'] != $invoice_total_adjustment)
// return 'Amount must equal the sum of invoice adjustments';
// }
// //adjust applied amount
// $payment->applied += $invoice_total_adjustment;
// //adjust clients paid to date
// $client = $payment->client;
// $client->paid_to_date += $invoice_total_adjustment;
// $payment->save();
// $client->save();
}
/**
* If the client is paying in a currency other than

View File

@ -37,7 +37,14 @@ class ApplyPayment extends AbstractService
->ledger()
->updatePaymentBalance($this->payment_amount*-1);
$this->payment->client->service()->updateBalance($this->payment_amount*-1)->save();
info("apply payment method - current client balance = {$this->payment->client->balance}");
info("reducing client balance by payment amount {$this->payment_amount}");
$this->invoice->client->service()->updateBalance($this->payment_amount*-1)->save();
// $this->invoice->client->service()->updateBalance($this->payment_amount*-1)->updatePaidToDate($this->payment_amount)->save();
info("post client balance = {$this->invoice->client->balance}");
/* Update Pivot Record amount */
$this->payment->invoices->each(function ($inv) {
@ -47,6 +54,10 @@ class ApplyPayment extends AbstractService
}
});
$this->invoice->fresh('client');
info("1 end of apply payment method the client balance = {$this->invoice->client->balance}");
if ($this->invoice->hasPartial()) {
//is partial and amount is exactly the partial amount
if ($this->invoice->partial == $this->payment_amount) {
@ -61,9 +72,11 @@ class ApplyPayment extends AbstractService
} elseif ($this->payment_amount < $this->invoice->balance) { //partial invoice payment made
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($this->payment_amount*-1);
}
info("2 end of apply payment method the client balnace = {$this->invoice->client->balance}");
$this->invoice->service()->applyNumber()->save();
info("3 end of apply payment method the client balnace = {$this->invoice->client->balance}");
return $this->invoice;
}
}

View File

@ -45,6 +45,9 @@ class HandleCancellation extends AbstractService
}
$adjustment = $this->invoice->balance*-1;
$this->backupCancellation($adjustment);
//set invoice balance to 0
$this->invoice->ledger()->updateInvoiceBalance($adjustment, "Invoice cancellation");
@ -56,6 +59,58 @@ class HandleCancellation extends AbstractService
event(new InvoiceWasCancelled($this->invoice));
return $this->invoice;
}
public function reverse()
{
$cancellation = $this->invoice->backup->cancellation;
$adjustment = $cancellation->adjustment*-1;
$this->invoice->ledger()->updateInvoiceBalance($adjustment, "Invoice cancellation REVERSAL");
/* Reverse the invoice status and balance */
$this->invoice->balance += $adjustment;
$this->invoice->status_id = $cancellation->status_id;
$this->invoice->client->service()->updateBalance($adjustment)->save();
/* Pop the cancellation out of the backup*/
$backup = $this->invoice->backup;
unset($backup->cancellation);
$this->invoice->backup = $backup;
$this->invoice->save();
return $this->invoice;
}
/**
* Backup the cancellation in case we ever need to reverse it.
*
* @param float $adjustment The amount the balance has been reduced by to cancel the invoice
* @return void
*/
private function backupCancellation($adjustment)
{
if(!is_object($this->invoice->backup)){
$backup = new \stdClass;
$this->invoice->backup = $backup;
}
$cancellation = new \stdClass;
$cancellation->adjustment = $adjustment;
$cancellation->status_id = $this->invoice->status_id;
$invoice_backup = $this->invoice->backup;
$invoice_backup->cancellation = $cancellation;
$this->invoice->backup = $invoice_backup;
$this->invoice->save();
}
}

View File

@ -44,12 +44,14 @@ class HandleReversal extends AbstractService
return $this->invoice;
}
if($this->invoice->status_id == Invoice::STATUS_CANCELLED)
$this->invoice = $this->invoice->service()->reverseCancellation()->save();
$balance_remaining = $this->invoice->balance;
$total_paid = $this->invoice->amount - $this->invoice->balance;
/*Adjust payment applied and the paymentables to the correct amount */
$paymentables = Paymentable::wherePaymentableType(Invoice::class)
->wherePaymentableId($this->invoice->id)
->get();
@ -69,7 +71,8 @@ class HandleReversal extends AbstractService
if ($total_paid > 0) {
$credit = CreditFactory::create($this->invoice->company_id, $this->invoice->user_id);
$credit->client_id = $this->invoice->client_id;
$credit->invoice_id = $this->invoice->id;
$item = InvoiceItemFactory::create();
$item->quantity = 1;
$item->cost = (float)$total_paid;
@ -88,10 +91,12 @@ class HandleReversal extends AbstractService
$credit->service()->markSent()->save();
}
/* Set invoice balance to 0 */
$this->invoice->ledger()->updateInvoiceBalance($balance_remaining*-1, $notes)->save();
$this->invoice->balance= 0;
/* Set invoice balance to 0 */
if($this->invoice->balance != 0)
$this->invoice->ledger()->updateInvoiceBalance($balance_remaining*-1, $notes)->save();
$this->invoice->balance=0;
/* Set invoice status to reversed... somehow*/
$this->invoice->service()->setStatus(Invoice::STATUS_REVERSED)->save();
@ -109,6 +114,42 @@ class HandleReversal extends AbstractService
return $this->invoice;
//create a ledger row for this with the resulting Credit ( also include an explanation in the notes section )
}
// public function run2()
// {
// /* Check again!! */
// if (!$this->invoice->invoiceReversable($this->invoice)) {
// return $this->invoice;
// }
// if($this->invoice->status_id == Invoice::STATUS_CANCELLED)
// $this->invoice = $this->invoice->service()->reverseCancellation()->save();
// //$balance_remaining = $this->invoice->balance;
// //$total_paid = $this->invoice->amount - $this->invoice->balance;
// /*Adjust payment applied and the paymentables to the correct amount */
// $paymentables = Paymentable::wherePaymentableType(Invoice::class)
// ->wherePaymentableId($this->invoice->id)
// ->get();
// $total_paid = 0;
// $paymentables->each(function ($paymentable) use ($total_paid) {
// $reversable_amount = $paymentable->amount - $paymentable->refunded;
// $total_paid -= $reversable_amount;
// $paymentable->amount = $paymentable->refunded;
// $paymentable->save();
// });
// //Unwinding any payments made to this invoice
// }
}
// The client paid to date amount is reduced by the calculated amount of (invoice balance - invoice amount).

View File

@ -129,6 +129,13 @@ class InvoiceService
return $this;
}
public function reverseCancellation()
{
$this->invoice = (new HandleCancellation($this->invoice))->reverse();
return $this;
}
public function markViewed()
{
$this->invoice->last_viewed = Carbon::now()->format('Y-m-d H:i');

View File

@ -48,10 +48,14 @@ class MarkSent extends AbstractService
->setDueDate()
->save();
info("marking invoice sent currently client balance = {$this->client->balance}");
$this->client->service()->updateBalance($this->invoice->balance)->save();
info("after marking invoice sent currently client balance = {$this->client->balance}");
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance);
return $this->invoice;
return $this->invoice->fresh();
}
}

Some files were not shown because too many files have changed in this diff Show More