1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Checkout stubs (#3672)

* minor fixes for name spaces, collector facade and composer version bump

* Fixes for invoiceworkflowsettings

* Add more context to support emails

* Working on Firing Subscriptions

* Minor fixes

* Fixes for gateway filtering

* Checkout Driver
This commit is contained in:
David Bomba 2020-05-04 21:13:46 +10:00 committed by GitHub
parent 1da69b1f17
commit 42ccfe0700
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 347 additions and 50 deletions

View File

@ -462,36 +462,14 @@ class CreateTestData extends Command
$invoice = $invoice_calc->getInvoice(); $invoice = $invoice_calc->getInvoice();
$invoice->save(); $invoice->save();
$invoice->service()->createInvitations(); $invoice->service()->createInvitations()->markSent();
$invoice->ledger()->updateInvoiceBalance($invoice->balance);
//UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, $invoice->balance, $invoice->company);
$this->invoice_repo->markSent($invoice); $this->invoice_repo->markSent($invoice);
$invoice->service()->createInvitations();
if (rand(0, 1)) { if (rand(0, 1)) {
// $payment = PaymentFactory::create($client->company->id, $client->user->id);
// $payment->date = $dateable;
// $payment->client_id = $client->id;
// $payment->amount = $invoice->balance;
// $payment->transaction_reference = rand(0, 500);
// $payment->type_id = PaymentType::CREDIT_CARD_OTHER;
// $payment->status_id = Payment::STATUS_COMPLETED;
// $payment->number = $client->getNextPaymentNumber($client);
// $payment->currency_id = 1;
// $payment->save();
// $payment->invoices()->save($invoice);
$invoice = $invoice->service()->markPaid()->save(); $invoice = $invoice->service()->markPaid()->save();
//$payment = $invoice->payments->first();
//$payment->service()->updateInvoicePayment();
//UpdateInvoicePayment::dispatchNow($payment, $payment->company);
} }
//@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging. //@todo this slow things down, but gives us PDFs of the invoices for inspection whilst debugging.
event(new InvoiceWasCreated($invoice, $invoice->company)); event(new InvoiceWasCreated($invoice, $invoice->company));

View File

@ -28,7 +28,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Laravel\Socialite\Facades\Socialite; use Laravel\Socialite\Facades\Socialite;
use Turbo124\Collector\CollectorFacade as Collector; use Turbo124\Collector\Facades\LightLogs;
class LoginController extends BaseController class LoginController extends BaseController
{ {
@ -172,7 +172,7 @@ class LoginController extends BaseController
if ($this->attemptLogin($request)) { if ($this->attemptLogin($request)) {
Collector::create(new LoginSuccess()) LightLogs::create(new LoginSuccess())
->increment() ->increment()
->batch(); ->batch();
@ -186,7 +186,7 @@ class LoginController extends BaseController
return $this->listResponse($ct); return $this->listResponse($ct);
} else { } else {
Collector::create(new LoginFailure()) LightLogs::create(new LoginFailure())
->increment() ->increment()
->batch(); ->batch();

View File

@ -201,7 +201,8 @@ class MigrationController extends BaseController
if ($checks['same_keys'] && $checks['with_force']) { if ($checks['same_keys'] && $checks['with_force']) {
info('Migrating: Same company keys, with force.'); info('Migrating: Same company keys, with force.');
$this->purgeCompany($company); if($company)
$this->purgeCompany($company);
$account = auth()->user()->account; $account = auth()->user()->account;
$company = (new ImportMigrations())->getCompany($account); $company = (new ImportMigrations())->getCompany($account);
@ -255,7 +256,8 @@ class MigrationController extends BaseController
if (!$checks['same_keys'] && $checks['existing_company'] && $checks['with_force']) { if (!$checks['same_keys'] && $checks['existing_company'] && $checks['with_force']) {
info('Migrating: Different keys, exisiting company with force option.'); info('Migrating: Different keys, exisiting company with force option.');
$this->purgeCompany($company); if($company)
$this->purgeCompany($company);
$account = auth()->user()->account; $account = auth()->user()->account;
$company = (new ImportMigrations())->getCompany($account); $company = (new ImportMigrations())->getCompany($account);
@ -283,7 +285,8 @@ class MigrationController extends BaseController
if (!$checks['same_keys'] && $checks['with_force']) { if (!$checks['same_keys'] && $checks['with_force']) {
info('Migrating: Different keys with force.'); info('Migrating: Different keys with force.');
$this->purgeCompany($existing_company); if($existing_company)
$this->purgeCompany($existing_company);
$account = auth()->user()->account; $account = auth()->user()->account;
$company = (new ImportMigrations())->getCompany($account); $company = (new ImportMigrations())->getCompany($account);

View File

@ -302,7 +302,9 @@ class SubscriptionController extends BaseController
public function create(CreateSubscriptionRequest $request) public function create(CreateSubscriptionRequest $request)
{ {
$subscription = SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id); $subscription = SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id);
$subscription->fill($request->all());
$subscription->save();
return $this->itemResponse($subscription); return $this->itemResponse($subscription);
} }

View File

@ -80,6 +80,6 @@ class SendingController extends Controller
return response()->json([ return response()->json([
'success' => true 'success' => true
]); ], 200);
} }
} }

View File

@ -3,7 +3,7 @@
namespace App\Http\Livewire; namespace App\Http\Livewire;
use App\Models\Invoice; use App\Models\Invoice;
use App\Traits\WithSorting; use App\Utils\Traits\WithSorting;
use Carbon\Carbon; use Carbon\Carbon;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;

View File

@ -55,10 +55,11 @@ class InvoiceWorkflowSettings implements ShouldQueue
$this->base_repository->archive($this->invoice); $this->base_repository->archive($this->invoice);
} }
if ($this->client->getSetting('auto_email_invoice')) { //@TODO this setting should only fire for recurring invoices
$this->invoice->invitations->each(function ($invitation, $key) { // if ($this->client->getSetting('auto_email_invoice')) {
$this->invoice->service()->sendEmail($invitation->contact); // $this->invoice->invitations->each(function ($invitation, $key) {
}); // $this->invoice->service()->sendEmail($invitation->contact);
} // });
// }
} }
} }

View File

@ -0,0 +1,34 @@
<?php
namespace App\Jobs\Util;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SubscriptionHandler 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()
{
//
}
}

View File

@ -47,10 +47,20 @@ class SupportMessageSent extends Mailable
$lines = new \LimitIterator($log_file, $last_line - 10, $last_line); $lines = new \LimitIterator($log_file, $last_line - 10, $last_line);
$log_lines = iterator_to_array($lines); $log_lines = iterator_to_array($lines);
} }
$account = auth()->user()->account;
$plan = $account->plan ?: 'Self Hosted';
$company = auth()->user()->company();
$user = auth()->user();
$subject = "Customer MSG {$user->present()->name} - [{$plan} - DB:{$company->db}]";
return $this->from(config('mail.from.address')) //todo this needs to be fixed to handle the hosted version return $this->from(config('mail.from.address')) //todo this needs to be fixed to handle the hosted version
->subject(ctrans('texts.new_support_message')) ->subject($subject)
->markdown('email.support.message', [ ->markdown('email.support.message', [
'message' => $this->message, 'message' => $this->message,
'system_info' => $system_info, 'system_info' => $system_info,

View File

@ -16,7 +16,6 @@ use App\DataMapper\CompanySettings;
use App\Factory\CreditFactory; use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory; use App\Factory\InvoiceFactory;
use App\Factory\QuoteFactory; use App\Factory\QuoteFactory;
use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
use App\Models\Country; use App\Models\Country;
@ -346,7 +345,7 @@ class Client extends BaseModel implements HasLocalePreference
$company_gateways = $this->getSetting('company_gateway_ids'); $company_gateways = $this->getSetting('company_gateway_ids');
if (strlen($company_gateways)>=1) { if (strlen($company_gateways)>=1) {
$gateways = $this->company->company_gateways->whereIn('id', $payment_gateways); $gateways = $this->company->company_gateways->whereIn('id', $company_gateways);
} else { } else {
$gateways = $this->company->company_gateways; $gateways = $this->company->company_gateways;
} }

View File

@ -143,20 +143,21 @@ class BasePaymentDriver
*/ */
public function refundPayment($payment, $amount = 0) public function refundPayment($payment, $amount = 0)
{ {
if ($amount) {
$amount = min($amount, $payment->getCompletedAmount());
} else {
$amount = $payment->getCompletedAmount();
}
if ($payment->is_deleted) { if ($payment->is_deleted) {
return false; return false;
} }
if (! $amount) { if (!$amount || $amount == 0) {
return false; return false;
} }
if ($amount) {
$amount = min($amount, $payment->getCompletedAmount());
} else {
$amount = $payment->getCompletedAmount();
}
if ($payment->type_id == Payment::TYPE_CREDIT_CARD) { if ($payment->type_id == Payment::TYPE_CREDIT_CARD) {
return $payment->recordRefund($amount); return $payment->recordRefund($amount);
} }
@ -177,6 +178,15 @@ class BasePaymentDriver
return false; return false;
} }
protected function refundDetails($payment, $amount)
{
return [
'amount' => $amount,
'transactionReference' => $payment->transaction_reference,
'currency' => $payment->client->getCurrencyCode(),
];
}
protected function attemptVoidPayment($response, $payment, $amount) protected function attemptVoidPayment($response, $payment, $amount)
{ {
// Partial refund not allowed for unsettled transactions // Partial refund not allowed for unsettled transactions
@ -226,7 +236,7 @@ class BasePaymentDriver
acceptNotification() - convert an incoming request from an off-site gateway to a generic notification object for further processing acceptNotification() - convert an incoming request from an off-site gateway to a generic notification object for further processing
*/ */
protected function paymentDetails($input) : array protected function paymentDetails() : array
{ {
$data = [ $data = [
'currency' => $this->client->getCurrencyCode(), 'currency' => $this->client->getCurrencyCode(),
@ -285,4 +295,58 @@ class BasePaymentDriver
return $payment; return $payment;
} }
/////////////////////////////// V1 cut and paste
public function completeOffsitePurchase($input)
{
$this->input = $input;
$transRef = array_get($this->input, 'token');
if (method_exists($this->gateway(), 'completePurchase')) {
$details = $this->paymentDetails();
$response = $this->gateway()->completePurchase($details)->send();
$paymentRef = $response->getTransactionReference() ?: $transRef;
if ($response->isCancelled()) {
return false;
} elseif (!$response->isSuccessful()) {
throw new \Exception($response->getMessage());
}
} else {
$paymentRef = $transRef;
}
//$this->updateClientFromOffsite($transRef, $paymentRef);
// check invoice still has balance
if (!floatval($this->invoice()->balance)) {
throw new Exception(trans('texts.payment_error_code', ['code' => 'NB']));
}
// check this isn't a duplicate transaction reference
if (Payment::whereAccountId($this->invitation->account_id)
->whereTransactionReference($paymentRef)
->first()
) {
throw new Exception(trans('texts.payment_error_code', ['code' => 'DT']));
}
return $this->createPayment($paymentRef);
}
} }

View File

@ -0,0 +1,139 @@
<?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

@ -13,7 +13,6 @@ namespace App\PaymentDrivers;
use App\Events\Payment\PaymentWasCreated; use App\Events\Payment\PaymentWasCreated;
use App\Factory\PaymentFactory; use App\Factory\PaymentFactory;
//use App\Jobs\Invoice\UpdateInvoicePayment;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;

View File

@ -22,6 +22,7 @@
"ext-json": "*", "ext-json": "*",
"asgrim/ofxparser": "^1.2", "asgrim/ofxparser": "^1.2",
"ashallendesign/laravel-exchange-rates": "^2.1", "ashallendesign/laravel-exchange-rates": "^2.1",
"beganovich/omnipay-checkout": "dev-master",
"cleverit/ubl_invoice": "^1.3", "cleverit/ubl_invoice": "^1.3",
"codedge/laravel-selfupdater": "~3.0", "codedge/laravel-selfupdater": "~3.0",
"composer/composer": "^1.10", "composer/composer": "^1.10",
@ -53,7 +54,7 @@
"staudenmeir/eloquent-has-many-deep": "^1.11", "staudenmeir/eloquent-has-many-deep": "^1.11",
"stripe/stripe-php": "^7.0", "stripe/stripe-php": "^7.0",
"superbalist/laravel-google-cloud-storage": "^2.2", "superbalist/laravel-google-cloud-storage": "^2.2",
"turbo124/collector": "^0.2.0", "turbo124/collector": "^0",
"webpatser/laravel-countries": "dev-master#75992ad", "webpatser/laravel-countries": "dev-master#75992ad",
"yajra/laravel-datatables-oracle": "~9.0" "yajra/laravel-datatables-oracle": "~9.0"
}, },

View File

@ -5,7 +5,7 @@ return [
/** /**
* Enable or disable the collector * Enable or disable the collector
*/ */
'enabled' => true, 'enabled' => false,
/** /**
* The API endpoint for logs * The API endpoint for logs
@ -28,4 +28,15 @@ return [
*/ */
'cache_key' => 'collector', 'cache_key' => 'collector',
/**
* Determines whether to log the
* host system variables using
* the built in metrics.
*/
'system_logging' => [
'Turbo124\Collector\Jobs\System\CpuMetric',
'Turbo124\Collector\Jobs\System\HdMetric',
'Turbo124\Collector\Jobs\System\MemMetric',
],
]; ];

View File

@ -65,6 +65,7 @@ return [
'password' => 'password', 'password' => 'password',
'stripe' => env('STRIPE_KEYS', ''), 'stripe' => env('STRIPE_KEYS', ''),
'paypal' => env('PAYPAL_KEYS', ''), 'paypal' => env('PAYPAL_KEYS', ''),
'checkout' => env('CHECKOUT_KEYS',''),
'travis' => env('TRAVIS', false), 'travis' => env('TRAVIS', false),
'test_email' => env('TEST_EMAIL', 'test@example.com'), 'test_email' => env('TEST_EMAIL', 'test@example.com'),
], ],

View File

@ -275,5 +275,18 @@ class RandomDataSeeder extends Seeder
$cg->config = encrypt(config('ninja.testvars.paypal')); $cg->config = encrypt(config('ninja.testvars.paypal'));
$cg->save(); $cg->save();
} }
if(config('ninja.testvars.checkout')) {
$cg = new CompanyGateway;
$cg->company_id = $company->id;
$cg->user_id = $user->id;
$cg->gateway_key = '3758e7f7c6f4cecf0f4f348b9a00f456';
$cg->require_cvv = true;
$cg->show_billing_address = true;
$cg->show_shipping_address = true;
$cg->update_details = true;
$cg->config = encrypt(config('ninja.testvars.checkout'));
$cg->save();
}
} }
} }

View File

@ -0,0 +1,42 @@
@extends('portal.ninja2020.layout.app')
<script src="https://cdn.checkout.com/sandbox/js/checkout.js"></script>
<form class="payment-form" method="POST" action="https://merchant.com/successUrl">
<script>
Checkout.render({
publicKey: 'pk_test_6ff46046-30af-41d9-bf58-929022d2cd14',
paymentToken: 'pay_tok_SPECIMEN-000',
customerEmail: 'user@email.com',
value: 100,
currency: 'GBP',
cardFormMode: 'cardTokenisation',
cardTokenised: function(event) {
console.log(event.data.cardToken);
}
});
</script>
</form>
<!--
Checkout.render({
debugMode: {{ $gateway->getConfigField('testMode') ? 'true' : 'false' }},
publicKey: '{{ $gateway->getConfigField('publicApiKey') }}',
paymentToken: '{{ $token }}',
customerEmail: '{{ $contact->email }}',
customerName: '{{ $contact->getFullName() }}',
@if( $invoice->getCurrencyCode() == 'BHD' || $invoice->getCurrencyCode() == 'KWD' || $invoice->getCurrencyCode() == 'OMR')
value: {{ $invoice->getRequestedAmount() * 1000 }},
@else
value: {{ $invoice->getRequestedAmount() * 100 }},
@endif
currency: '{{ $invoice->getCurrencyCode() }}',
widgetContainerSelector: '.payment-form',
widgetColor: '#333',
themeColor: '#3075dd',
buttonColor:'#51c470',
cardCharged: function(event){
location.href = '{{ URL::to('/complete/'. $invitation->invitation_key . '/credit_card?token=' . $transactionToken) }}';
}
});
-->