mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 15:44:33 -04:00
Fixes
This commit is contained in:
commit
f1131c07fe
47
app/DataMapper/Billing/SubscriptionContextMapper.php
Normal file
47
app/DataMapper/Billing/SubscriptionContextMapper.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataMapper\Billing;
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriptionContextMapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $subscription_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $client_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $invoice_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
public $casts = [
|
||||||
|
'subscription_id' => 'integer',
|
||||||
|
'email' => 'string',
|
||||||
|
'client_id' => 'integer',
|
||||||
|
'invoice_id' => 'integer',
|
||||||
|
];
|
||||||
|
}
|
55
app/Events/Subscription/SubscriptionWasCreated.php
Normal file
55
app/Events/Subscription/SubscriptionWasCreated.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events\Subscription;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Models\Company;
|
||||||
|
use Illuminate\Broadcasting\Channel;
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PresenceChannel;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class SubscriptionWasCreated
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Subscription
|
||||||
|
*/
|
||||||
|
public $subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Company
|
||||||
|
*/
|
||||||
|
public $company;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $event_vars;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(Subscription $subscription, Company $company, array $event_vars)
|
||||||
|
{
|
||||||
|
$this->subscription = $subscription;
|
||||||
|
$this->company = $company;
|
||||||
|
$this->event_vars = $event_vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channels the event should broadcast on.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Broadcasting\Channel|array
|
||||||
|
*/
|
||||||
|
public function broadcastOn()
|
||||||
|
{
|
||||||
|
return new PrivateChannel('channel-name');
|
||||||
|
}
|
||||||
|
}
|
26
app/Factory/SubscriptionFactory.php
Normal file
26
app/Factory/SubscriptionFactory.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Factory;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
|
||||||
|
class SubscriptionFactory
|
||||||
|
{
|
||||||
|
public static function create(int $company_id, int $user_id): Subscription
|
||||||
|
{
|
||||||
|
$billing_subscription = new Subscription();
|
||||||
|
$billing_subscription->company_id = $company_id;
|
||||||
|
$billing_subscription->user_id = $user_id;
|
||||||
|
|
||||||
|
return $billing_subscription;
|
||||||
|
}
|
||||||
|
}
|
24
app/Http/Controllers/ClientPortal/SubscriptionController.php
Normal file
24
app/Http/Controllers/ClientPortal/SubscriptionController.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SubscriptionController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return render('subscriptions.index');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\ClientPortal;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class SubscriptionPurchaseController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Subscription $subscription, Request $request)
|
||||||
|
{
|
||||||
|
if ($request->has('locale')) {
|
||||||
|
$this->setLocale($request->query('locale'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('billing-portal.purchase', [
|
||||||
|
'subscription' => $subscription,
|
||||||
|
'hash' => Str::uuid()->toString(),
|
||||||
|
'request_data' => $request->all(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set locale for incoming request.
|
||||||
|
*
|
||||||
|
* @param string $locale
|
||||||
|
*/
|
||||||
|
private function setLocale(string $locale): void
|
||||||
|
{
|
||||||
|
$record = Cache::get('languages')->filter(function ($item) use ($locale) {
|
||||||
|
return $item->locale == $locale;
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($record) {
|
||||||
|
App::setLocale($record->locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Controllers/SubdomainController.php
Normal file
39
app/Http/Controllers/SubdomainController.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
|
||||||
|
class SubdomainController extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$subdomain_exists = MultiDB::findAndSetDbByDomain(request()->input('subdomain'));
|
||||||
|
|
||||||
|
if($subdomain_exists)
|
||||||
|
return response()->json(['message' => 'Domain not available'] , 401);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Domain available'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
410
app/Http/Controllers/SubscriptionController.php
Normal file
410
app/Http/Controllers/SubscriptionController.php
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Events\Subscription\SubscriptionWasCreated;
|
||||||
|
use App\Factory\SubscriptionFactory;
|
||||||
|
use App\Http\Requests\Subscription\CreateSubscriptionRequest;
|
||||||
|
use App\Http\Requests\Subscription\DestroySubscriptionRequest;
|
||||||
|
use App\Http\Requests\Subscription\EditSubscriptionRequest;
|
||||||
|
use App\Http\Requests\Subscription\ShowSubscriptionRequest;
|
||||||
|
use App\Http\Requests\Subscription\StoreSubscriptionRequest;
|
||||||
|
use App\Http\Requests\Subscription\UpdateSubscriptionRequest;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Repositories\SubscriptionRepository;
|
||||||
|
use App\Transformers\SubscriptionTransformer;
|
||||||
|
use App\Utils\Ninja;
|
||||||
|
|
||||||
|
class SubscriptionController extends BaseController
|
||||||
|
{
|
||||||
|
protected $entity_type = Subscription::class;
|
||||||
|
|
||||||
|
protected $entity_transformer = SubscriptionTransformer::class;
|
||||||
|
|
||||||
|
protected $subscription_repo;
|
||||||
|
|
||||||
|
public function __construct(SubscriptionRepository $subscription_repo)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->subscription_repo = $subscription_repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the list of Subscriptions.
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/subscriptions",
|
||||||
|
* operationId="getSubscriptions",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Gets a list of subscriptions",
|
||||||
|
* description="Lists subscriptions.",
|
||||||
|
*
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="A list of subscriptions",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 index(): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
$subscriptions = Subscription::query()->company();
|
||||||
|
|
||||||
|
return $this->listResponse($subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @param CreateSubscriptionRequest $request The request
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/subscriptions/create",
|
||||||
|
* operationId="getSubscriptionsCreate",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Gets a new blank subscriptions object",
|
||||||
|
* description="Returns a blank object with default values",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="A blank subscriptions object",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 create(CreateSubscriptionRequest $request): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
$subscription = SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||||
|
|
||||||
|
return $this->itemResponse($subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param StoreSubscriptionRequest $request The request
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @OA\Post(
|
||||||
|
* path="/api/v1/subscriptions",
|
||||||
|
* operationId="storeSubscription",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Adds a subscriptions",
|
||||||
|
* description="Adds an subscriptions to the system",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Returns the saved subscriptions object",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 store(StoreSubscriptionRequest $request): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
$subscription = $this->subscription_repo->save($request->all(), SubscriptionFactory::create(auth()->user()->company()->id, auth()->user()->id));
|
||||||
|
|
||||||
|
event(new SubscriptionWasCreated($subscription, $subscription->company, Ninja::eventVars()));
|
||||||
|
|
||||||
|
return $this->itemResponse($subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*
|
||||||
|
* @param ShowSubscriptionRequest $request The request
|
||||||
|
* @param Invoice $subscription The invoice
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/subscriptions/{id}",
|
||||||
|
* operationId="showSubscription",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Shows an subscriptions",
|
||||||
|
* description="Displays an subscriptions by id",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* description="The Subscription Hashed ID",
|
||||||
|
* example="D2J234DFA",
|
||||||
|
* required=true,
|
||||||
|
* @OA\Schema(
|
||||||
|
* type="string",
|
||||||
|
* format="string",
|
||||||
|
* ),
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Returns the Subscription object",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 show(ShowSubscriptionRequest $request, Subscription $subscription): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
return $this->itemResponse($subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param EditSubscriptionRequest $request The request
|
||||||
|
* @param Invoice $subscription The invoice
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/v1/subscriptions/{id}/edit",
|
||||||
|
* operationId="editSubscription",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Shows an subscriptions for editting",
|
||||||
|
* description="Displays an subscriptions by id",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* description="The Subscription Hashed ID",
|
||||||
|
* example="D2J234DFA",
|
||||||
|
* required=true,
|
||||||
|
* @OA\Schema(
|
||||||
|
* type="string",
|
||||||
|
* format="string",
|
||||||
|
* ),
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Returns the invoice object",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 edit(EditSubscriptionRequest $request, Subscription $subscription): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
return $this->itemResponse($subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param UpdateSubscriptionRequest $request The request
|
||||||
|
* @param Subscription $subscription The invoice
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @OA\Put(
|
||||||
|
* path="/api/v1/subscriptions/{id}",
|
||||||
|
* operationId="updateSubscription",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Updates an subscriptions",
|
||||||
|
* description="Handles the updating of an subscriptions by id",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* description="The Subscription Hashed ID",
|
||||||
|
* example="D2J234DFA",
|
||||||
|
* required=true,
|
||||||
|
* @OA\Schema(
|
||||||
|
* type="string",
|
||||||
|
* format="string",
|
||||||
|
* ),
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Returns the subscriptions object",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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/Subscription"),
|
||||||
|
* ),
|
||||||
|
* @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 update(UpdateSubscriptionRequest $request, Subscription $subscription)
|
||||||
|
{
|
||||||
|
if ($request->entityIsDeleted($subscription)) {
|
||||||
|
return $request->disallowUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscription = $this->subscription_repo->save($request->all(), $subscription);
|
||||||
|
|
||||||
|
return $this->itemResponse($subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param DestroySubscriptionRequest $request
|
||||||
|
* @param Subscription $invoice
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
* @OA\Delete(
|
||||||
|
* path="/api/v1/subscriptions/{id}",
|
||||||
|
* operationId="deleteSubscription",
|
||||||
|
* tags={"subscriptions"},
|
||||||
|
* summary="Deletes a subscriptions",
|
||||||
|
* description="Handles the deletion of an subscriptions by id",
|
||||||
|
* @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/include"),
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* description="The Subscription Hashed ID",
|
||||||
|
* example="D2J234DFA",
|
||||||
|
* required=true,
|
||||||
|
* @OA\Schema(
|
||||||
|
* type="string",
|
||||||
|
* format="string",
|
||||||
|
* ),
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Returns a HTTP status",
|
||||||
|
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-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\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 destroy(DestroySubscriptionRequest $request, Subscription $subscription): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
$this->subscription_repo->delete($subscription);
|
||||||
|
|
||||||
|
return $this->itemResponse($subscription->fresh());
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Livewire/SubscriptionInvoicesTable.php
Normal file
39
app/Http/Livewire/SubscriptionInvoicesTable.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Utils\Traits\WithSorting;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class SubscriptionInvoicesTable extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
use WithSorting;
|
||||||
|
|
||||||
|
public $per_page = 10;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$query = Invoice::query()
|
||||||
|
->where('client_id', auth('contact')->user()->client->id)
|
||||||
|
->whereNotNull('subscription_id')
|
||||||
|
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||||
|
->paginate($this->per_page);
|
||||||
|
|
||||||
|
return render('components.livewire.subscriptions-invoices-table', [
|
||||||
|
'invoices' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
39
app/Http/Livewire/SubscriptionRecurringInvoicesTable.php
Normal file
39
app/Http/Livewire/SubscriptionRecurringInvoicesTable.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
|
use App\Utils\Traits\WithSorting;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
|
||||||
|
class SubscriptionRecurringInvoicesTable extends Component
|
||||||
|
{
|
||||||
|
use WithPagination;
|
||||||
|
use WithSorting;
|
||||||
|
|
||||||
|
public $per_page = 10;
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$query = RecurringInvoice::query()
|
||||||
|
->where('client_id', auth('contact')->user()->client->id)
|
||||||
|
->whereNotNull('subscription_id')
|
||||||
|
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
|
||||||
|
->paginate($this->per_page);
|
||||||
|
|
||||||
|
return render('components.livewire.subscriptions-recurring-invoices-table', [
|
||||||
|
'recurring_invoices' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
40
app/Http/Requests/Subscription/CreateSubscriptionRequest.php
Normal file
40
app/Http/Requests/Subscription/CreateSubscriptionRequest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
|
||||||
|
class CreateSubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return auth()->user()->can('create', Subscription::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class DestroySubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth()->user()->can('edit', $this->subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
40
app/Http/Requests/Subscription/EditSubscriptionRequest.php
Normal file
40
app/Http/Requests/Subscription/EditSubscriptionRequest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class EditSubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth()->user()->can('edit', $this->subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
40
app/Http/Requests/Subscription/ShowSubscriptionRequest.php
Normal file
40
app/Http/Requests/Subscription/ShowSubscriptionRequest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ShowSubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() : bool
|
||||||
|
{
|
||||||
|
return auth()->user()->can('view', $this->subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
73
app/Http/Requests/Subscription/StoreSubscriptionRequest.php
Normal file
73
app/Http/Requests/Subscription/StoreSubscriptionRequest.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class StoreSubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth()->user()->can('create', Subscription::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'product_id' => ['sometimes'],
|
||||||
|
'assigned_user_id' => ['sometimes'],
|
||||||
|
'is_recurring' => ['sometimes'],
|
||||||
|
'frequency_id' => ['sometimes'],
|
||||||
|
'auto_bill' => ['sometimes'],
|
||||||
|
'promo_code' => ['sometimes'],
|
||||||
|
'promo_discount' => ['sometimes'],
|
||||||
|
'is_amount_discount' => ['sometimes'],
|
||||||
|
'allow_cancellation' => ['sometimes'],
|
||||||
|
'per_set_enabled' => ['sometimes'],
|
||||||
|
'min_seats_limit' => ['sometimes'],
|
||||||
|
'max_seats_limit' => ['sometimes'],
|
||||||
|
'trial_enabled' => ['sometimes'],
|
||||||
|
'trial_duration' => ['sometimes'],
|
||||||
|
'allow_query_overrides' => ['sometimes'],
|
||||||
|
'allow_plan_changes' => ['sometimes'],
|
||||||
|
'plan_map' => ['sometimes'],
|
||||||
|
'refund_period' => ['sometimes'],
|
||||||
|
'webhook_configuration' => ['array'],
|
||||||
|
'name' => Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareForValidation()
|
||||||
|
{
|
||||||
|
$input = $this->all();
|
||||||
|
|
||||||
|
// if(array_key_exists('webhook_configuration', $input) && (!is_object(json_decode($input['webhook_configuration']))))
|
||||||
|
// $input['webhook_configuration'] = new \stdClass;
|
||||||
|
|
||||||
|
// if(!array_key_exists('webhook_configuration', $input))
|
||||||
|
// $input['webhook_configuration'] = new \stdClass;
|
||||||
|
|
||||||
|
$this->replace($input);
|
||||||
|
}
|
||||||
|
}
|
42
app/Http/Requests/Subscription/UpdateSubscriptionRequest.php
Normal file
42
app/Http/Requests/Subscription/UpdateSubscriptionRequest.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Subscription;
|
||||||
|
|
||||||
|
use App\Http\Requests\Request;
|
||||||
|
use App\Utils\Traits\ChecksEntityStatus;
|
||||||
|
|
||||||
|
class UpdateSubscriptionRequest extends Request
|
||||||
|
{
|
||||||
|
use ChecksEntityStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return auth()->user()->can('edit', $this->subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
73
app/Jobs/Cron/SubscriptionCron.php
Normal file
73
app/Jobs/Cron/SubscriptionCron.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Jobs\Cron;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class SubscriptionCron
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle() : void
|
||||||
|
{
|
||||||
|
|
||||||
|
if (! config('ninja.db.multi_db_enabled')) {
|
||||||
|
$this->loopSubscriptions();
|
||||||
|
} else {
|
||||||
|
//multiDB environment, need to
|
||||||
|
foreach (MultiDB::$dbs as $db) {
|
||||||
|
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
$this->loopSubscriptions();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loopSubscriptions()
|
||||||
|
{
|
||||||
|
//looop recurring invoices with subscription id
|
||||||
|
|
||||||
|
// $client_subs = ClientSubscription::whereNull('deleted_at')
|
||||||
|
// ->cursor()
|
||||||
|
// ->each(function ($cs){
|
||||||
|
// $this->processSubscription($cs);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Our daily cron should check
|
||||||
|
|
||||||
|
1. Is the subscription still in trial phase?
|
||||||
|
2. Check the recurring invoice and its remaining_cycles to see whether we need to cancel or perform any other function.
|
||||||
|
3. Any notifications that need to fire?
|
||||||
|
*/
|
||||||
|
private function processSubscription($client_subscription)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
71
app/Models/Subscription.php
Normal file
71
app/Models/Subscription.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Services\Subscription\SubscriptionService;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class Subscription extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'product_ids',
|
||||||
|
'recurring_product_ids',
|
||||||
|
'frequency_id',
|
||||||
|
'auto_bill',
|
||||||
|
'promo_code',
|
||||||
|
'promo_discount',
|
||||||
|
'is_amount_discount',
|
||||||
|
'allow_cancellation',
|
||||||
|
'per_set_enabled',
|
||||||
|
'min_seats_limit',
|
||||||
|
'max_seats_limit',
|
||||||
|
'trial_enabled',
|
||||||
|
'trial_duration',
|
||||||
|
'allow_query_overrides',
|
||||||
|
'allow_plan_changes',
|
||||||
|
'plan_map',
|
||||||
|
'refund_period',
|
||||||
|
'webhook_configuration',
|
||||||
|
'currency_id',
|
||||||
|
'group_id',
|
||||||
|
'price',
|
||||||
|
'name',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_deleted' => 'boolean',
|
||||||
|
'plan_map' => 'object',
|
||||||
|
'webhook_configuration' => 'array',
|
||||||
|
'updated_at' => 'timestamp',
|
||||||
|
'created_at' => 'timestamp',
|
||||||
|
'deleted_at' => 'timestamp',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function service(): SubscriptionService
|
||||||
|
{
|
||||||
|
return new SubscriptionService($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Company::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
72
app/Observers/SubscriptionObserver.php
Normal file
72
app/Observers/SubscriptionObserver.php
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Observers;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
|
||||||
|
class SubscriptionObserver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle the billing_subscription "created" event.
|
||||||
|
*
|
||||||
|
* @param Subscription $billing_subscription
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function created(Subscription $subscription)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the billing_subscription "updated" event.
|
||||||
|
*
|
||||||
|
* @param Subscription $billing_subscription
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updated(Subscription $subscription)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the billing_subscription "deleted" event.
|
||||||
|
*
|
||||||
|
* @param Subscription $billing_subscription
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleted(Subscription $subscription)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the billing_subscription "restored" event.
|
||||||
|
*
|
||||||
|
* @param Subscription $billing_subscription
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restored(Subscription $subscription)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the billing_subscription "force deleted" event.
|
||||||
|
*
|
||||||
|
* @param Subscription $billing_subscription
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function forceDeleted(Subscription $subscription)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
31
app/Policies/SubscriptionPolicy.php
Normal file
31
app/Policies/SubscriptionPolicy.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SubscriptionPolicy.
|
||||||
|
*/
|
||||||
|
class SubscriptionPolicy extends EntityPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if the user has create permissions.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function create(User $user) : bool
|
||||||
|
{
|
||||||
|
return $user->isAdmin() || $user->hasPermission('create_subscription') || $user->hasPermission('create_all');
|
||||||
|
}
|
||||||
|
}
|
140
app/Repositories/SubscriptionRepository.php
Normal file
140
app/Repositories/SubscriptionRepository.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
|
||||||
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Utils\Traits\CleanLineItems;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class SubscriptionRepository extends BaseRepository
|
||||||
|
{
|
||||||
|
use CleanLineItems;
|
||||||
|
|
||||||
|
public function save($data, Subscription $subscription): ?Subscription
|
||||||
|
{
|
||||||
|
$subscription->fill($data);
|
||||||
|
|
||||||
|
$calculated_prices = $this->calculatePrice($subscription);
|
||||||
|
|
||||||
|
$subscription->price = $calculated_prices['price'];
|
||||||
|
$subscription->promo_price = $calculated_prices['promo_price'];
|
||||||
|
|
||||||
|
$subscription->save();
|
||||||
|
|
||||||
|
return $subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculatePrice($subscription) :array
|
||||||
|
{
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
$client = Client::factory()->create([
|
||||||
|
'user_id' => $subscription->user_id,
|
||||||
|
'company_id' => $subscription->company_id,
|
||||||
|
'group_settings_id' => $subscription->group_id,
|
||||||
|
'country_id' => $subscription->company->settings->country_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$contact = ClientContact::factory()->create([
|
||||||
|
'user_id' => $subscription->user_id,
|
||||||
|
'company_id' => $subscription->company_id,
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'is_primary' => 1,
|
||||||
|
'send_email' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice = InvoiceFactory::create($subscription->company_id, $subscription->user_id);
|
||||||
|
$invoice->client_id = $client->id;
|
||||||
|
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
|
$invitation = InvoiceInvitation::factory()->create([
|
||||||
|
'user_id' => $subscription->user_id,
|
||||||
|
'company_id' => $subscription->company_id,
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'client_contact_id' => $contact->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$invoice->setRelation('invitations', $invitation);
|
||||||
|
$invoice->setRelation('client', $client);
|
||||||
|
$invoice->setRelation('company', $subscription->company);
|
||||||
|
$invoice->load('client');
|
||||||
|
$invoice->line_items = $this->generateLineItems($subscription);
|
||||||
|
|
||||||
|
$data['price'] = $invoice->calc()->getTotal();
|
||||||
|
|
||||||
|
$invoice->discount = $subscription->promo_discount;
|
||||||
|
$invoice->is_amount_discount = $subscription->is_amount_discount;
|
||||||
|
|
||||||
|
$data['promo_price'] = $invoice->calc()->getTotal();
|
||||||
|
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateLineItems($subscription, $is_recurring = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
$line_items = [];
|
||||||
|
|
||||||
|
if(!$is_recurring)
|
||||||
|
{
|
||||||
|
foreach($subscription->service()->products() as $product)
|
||||||
|
{
|
||||||
|
$line_items[] = (array)$this->makeLineItem($product);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($subscription->service()->recurringProducts() as $product)
|
||||||
|
{
|
||||||
|
$line_items[] = (array)$this->makeLineItem($product);
|
||||||
|
}
|
||||||
|
|
||||||
|
$line_items = $this->cleanItems($line_items);
|
||||||
|
|
||||||
|
return $line_items;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeLineItem($product)
|
||||||
|
{
|
||||||
|
$item = new InvoiceItem;
|
||||||
|
$item->quantity = $product->quantity;
|
||||||
|
$item->product_key = $product->product_key;
|
||||||
|
$item->notes = $product->notes;
|
||||||
|
$item->cost = $product->price;
|
||||||
|
$item->tax_rate1 = $product->tax_rate1 ?: 0;
|
||||||
|
$item->tax_name1 = $product->tax_name1 ?: '';
|
||||||
|
$item->tax_rate2 = $product->tax_rate2 ?: 0;
|
||||||
|
$item->tax_name2 = $product->tax_name2 ?: '';
|
||||||
|
$item->tax_rate3 = $product->tax_rate3 ?: 0;
|
||||||
|
$item->tax_name3 = $product->tax_name3 ?: '';
|
||||||
|
$item->custom_value1 = $product->custom_value1 ?: '';
|
||||||
|
$item->custom_value2 = $product->custom_value2 ?: '';
|
||||||
|
$item->custom_value3 = $product->custom_value3 ?: '';
|
||||||
|
$item->custom_value4 = $product->custom_value4 ?: '';
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
}
|
229
app/Services/Subscription/SubscriptionService.php
Normal file
229
app/Services/Subscription/SubscriptionService.php
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\Subscription;
|
||||||
|
|
||||||
|
use App\DataMapper\InvoiceItem;
|
||||||
|
use App\Factory\InvoiceFactory;
|
||||||
|
use App\Factory\InvoiceToRecurringInvoiceFactory;
|
||||||
|
use App\Factory\RecurringInvoiceFactory;
|
||||||
|
use App\Jobs\Util\SystemLogger;
|
||||||
|
use App\Models\ClientContact;
|
||||||
|
use App\Models\ClientSubscription;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\PaymentHash;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Models\SystemLog;
|
||||||
|
use App\Repositories\InvoiceRepository;
|
||||||
|
use App\Repositories\RecurringInvoiceRepository;
|
||||||
|
use App\Repositories\SubscriptionRepository;
|
||||||
|
use App\Utils\Traits\CleanLineItems;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
|
||||||
|
class SubscriptionService
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
use CleanLineItems;
|
||||||
|
|
||||||
|
/** @var subscription */
|
||||||
|
private $subscription;
|
||||||
|
|
||||||
|
/** @var client_subscription */
|
||||||
|
// private $client_subscription;
|
||||||
|
|
||||||
|
public function __construct(Subscription $subscription)
|
||||||
|
{
|
||||||
|
$this->subscription = $subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function completePurchase(PaymentHash $payment_hash)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!property_exists($payment_hash->data, 'billing_context')) {
|
||||||
|
throw new \Exception("Illegal entrypoint into method, payload must contain billing context");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a recurring product - then generate a recurring invoice
|
||||||
|
// if trial is enabled, generate the recurring invoice to fire when the trial ends.
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
'email' => $this->email ?? $this->contact->email,
|
||||||
|
'quantity' => $this->quantity,
|
||||||
|
'contact_id' => $this->contact->id,
|
||||||
|
*/
|
||||||
|
public function startTrial(array $data)
|
||||||
|
{
|
||||||
|
// Redirects from here work just fine. Livewire will respect it.
|
||||||
|
$client_contact = ClientContact::find($data['contact_id']);
|
||||||
|
|
||||||
|
if(!$this->subscription->trial_enabled)
|
||||||
|
return new \Exception("Trials are disabled for this product");
|
||||||
|
|
||||||
|
//create recurring invoice with start date = trial_duration + 1 day
|
||||||
|
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||||
|
$subscription_repo = new SubscriptionRepository();
|
||||||
|
|
||||||
|
$recurring_invoice = RecurringInvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||||
|
$recurring_invoice->client_id = $client_contact->client_id;
|
||||||
|
$recurring_invoice->line_items = $subscription_repo->generateLineItems($this->subscription, true);
|
||||||
|
$recurring_invoice->subscription_id = $this->subscription->id;
|
||||||
|
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
|
||||||
|
$recurring_invoice->date = now();
|
||||||
|
$recurring_invoice->next_send_date = now()->addSeconds($this->subscription->trial_duration);
|
||||||
|
$recurring_invoice->remaining_cycles = -1;
|
||||||
|
$recurring_invoice->backup = 'is_trial';
|
||||||
|
|
||||||
|
if(array_key_exists('coupon', $data) && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||||
|
{
|
||||||
|
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||||
|
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
||||||
|
|
||||||
|
/* Start the recurring service */
|
||||||
|
$recurring_invoice->service()
|
||||||
|
->start()
|
||||||
|
->save();
|
||||||
|
|
||||||
|
//execute any webhooks
|
||||||
|
$this->triggerWebhook();
|
||||||
|
|
||||||
|
if(array_key_exists('post_purchase_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['post_purchase_url']) >=1)
|
||||||
|
return redirect($this->subscription->webhook_configuration['post_purchase_url']);
|
||||||
|
|
||||||
|
return redirect('/client/recurring_invoices/'.$recurring_invoice->hashed_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createInvoice($data): ?\App\Models\Invoice
|
||||||
|
{
|
||||||
|
|
||||||
|
$invoice_repo = new InvoiceRepository();
|
||||||
|
$subscription_repo = new SubscriptionRepository();
|
||||||
|
|
||||||
|
$invoice = InvoiceFactory::create($this->subscription->company_id, $this->subscription->user_id);
|
||||||
|
$invoice->line_items = $subscription_repo->generateLineItems($this->subscription);
|
||||||
|
$invoice->subscription_id = $this->subscription->id;
|
||||||
|
|
||||||
|
if(strlen($data['coupon']) >=1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0)
|
||||||
|
{
|
||||||
|
$invoice->discount = $this->subscription->promo_discount;
|
||||||
|
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $invoice_repo->save($data, $invoice);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function convertInvoiceToRecurring($payment_hash)
|
||||||
|
{
|
||||||
|
//The first invoice is a plain invoice - the second is fired on the recurring schedule.
|
||||||
|
$invoice = Invoice::find($payment_hash->billing_context->invoice_id);
|
||||||
|
|
||||||
|
if(!$invoice)
|
||||||
|
throw new \Exception("Could not match an invoice for payment of billing subscription");
|
||||||
|
|
||||||
|
return InvoiceToRecurringInvoiceFactory::create($invoice);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated due to change in architecture
|
||||||
|
|
||||||
|
// public function createClientSubscription($payment_hash)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// //is this a recurring or one off subscription.
|
||||||
|
|
||||||
|
// $cs = new ClientSubscription();
|
||||||
|
// $cs->subscription_id = $this->subscription->id;
|
||||||
|
// $cs->company_id = $this->subscription->company_id;
|
||||||
|
|
||||||
|
// $cs->invoice_id = $payment_hash->billing_context->invoice_id;
|
||||||
|
// $cs->client_id = $payment_hash->billing_context->client_id;
|
||||||
|
// $cs->quantity = $payment_hash->billing_context->quantity;
|
||||||
|
|
||||||
|
// //if is_recurring
|
||||||
|
// //create recurring invoice from invoice
|
||||||
|
// if($this->subscription->is_recurring)
|
||||||
|
// {
|
||||||
|
// $recurring_invoice = $this->convertInvoiceToRecurring($payment_hash);
|
||||||
|
// $recurring_invoice->frequency_id = $this->subscription->frequency_id;
|
||||||
|
// $recurring_invoice->next_send_date = $recurring_invoice->nextDateByFrequency(now()->format('Y-m-d'));
|
||||||
|
// $recurring_invoice->save();
|
||||||
|
// $cs->recurring_invoice_id = $recurring_invoice->id;
|
||||||
|
|
||||||
|
// //?set the recurring invoice as active - set the date here also based on the frequency?
|
||||||
|
// $recurring_invoice->service()->start();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// $cs->save();
|
||||||
|
|
||||||
|
// $this->client_subscription = $cs;
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
//@todo - need refactor
|
||||||
|
public function triggerWebhook()
|
||||||
|
{
|
||||||
|
//hit the webhook to after a successful onboarding
|
||||||
|
|
||||||
|
// $body = [
|
||||||
|
// 'subscription' => $this->subscription,
|
||||||
|
// 'client_subscription' => $this->client_subscription,
|
||||||
|
// 'client' => $this->client_subscription->client->toArray(),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
|
||||||
|
// $client = new \GuzzleHttp\Client(['headers' => $this->subscription->webhook_configuration->post_purchase_headers]);
|
||||||
|
|
||||||
|
// $response = $client->{$this->subscription->webhook_configuration->post_purchase_rest_method}($this->subscription->post_purchase_url,[
|
||||||
|
// RequestOptions::JSON => ['body' => $body]
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// SystemLogger::dispatch(
|
||||||
|
// $body,
|
||||||
|
// SystemLog::CATEGORY_WEBHOOK,
|
||||||
|
// SystemLog::EVENT_WEBHOOK_RESPONSE,
|
||||||
|
// SystemLog::TYPE_WEBHOOK_RESPONSE,
|
||||||
|
// $this->client_subscription->client,
|
||||||
|
// );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fireNotifications()
|
||||||
|
{
|
||||||
|
//scan for any notification we are required to send
|
||||||
|
}
|
||||||
|
|
||||||
|
public function products()
|
||||||
|
{
|
||||||
|
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->product_ids)))->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recurringProducts()
|
||||||
|
{
|
||||||
|
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->recurring_product_ids)))->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function price()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
app/Transformers/SubscriptionTransformer.php
Normal file
71
app/Transformers/SubscriptionTransformer.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Transformers;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
|
||||||
|
class SubscriptionTransformer extends EntityTransformer
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $defaultIncludes = [
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $availableIncludes = [
|
||||||
|
];
|
||||||
|
|
||||||
|
public function transform(Subscription $subscription): array
|
||||||
|
{
|
||||||
|
$std = new \stdClass;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $this->encodePrimaryKey($subscription->id),
|
||||||
|
'user_id' => $this->encodePrimaryKey($subscription->user_id),
|
||||||
|
'group_id' => $this->encodePrimaryKey($subscription->group_id),
|
||||||
|
'product_ids' => (string)$subscription->product_ids,
|
||||||
|
'recurring_product_ids' => (string)$subscription->recurring_product_ids,
|
||||||
|
'assigned_user_id' => $this->encodePrimaryKey($subscription->assigned_user_id),
|
||||||
|
'company_id' => $this->encodePrimaryKey($subscription->company_id),
|
||||||
|
'price' => (float) $subscription->price,
|
||||||
|
'frequency_id' => (string)$subscription->frequency_id,
|
||||||
|
'auto_bill' => (string)$subscription->auto_bill,
|
||||||
|
'promo_code' => (string)$subscription->promo_code,
|
||||||
|
'promo_discount' => (float)$subscription->promo_discount,
|
||||||
|
'is_amount_discount' => (bool)$subscription->is_amount_discount,
|
||||||
|
'allow_cancellation' => (bool)$subscription->allow_cancellation,
|
||||||
|
'per_seat_enabled' => (bool)$subscription->per_set_enabled,
|
||||||
|
'min_seats_limit' => (int)$subscription->min_seats_limit,
|
||||||
|
'max_seats_limit' => (int)$subscription->max_seats_limit,
|
||||||
|
'trial_enabled' => (bool)$subscription->trial_enabled,
|
||||||
|
'trial_duration' => (int)$subscription->trial_duration,
|
||||||
|
'allow_query_overrides' => (bool)$subscription->allow_query_overrides,
|
||||||
|
'allow_plan_changes' => (bool)$subscription->allow_plan_changes,
|
||||||
|
'plan_map' => (string)$subscription->plan_map,
|
||||||
|
'refund_period' => (int)$subscription->refund_period,
|
||||||
|
'webhook_configuration' => $subscription->webhook_configuration ?: $std,
|
||||||
|
'purchase_page' => (string)route('client.subscription.purchase', $subscription->hashed_id),
|
||||||
|
'is_deleted' => (bool)$subscription->is_deleted,
|
||||||
|
'created_at' => (int)$subscription->created_at,
|
||||||
|
'updated_at' => (int)$subscription->updated_at,
|
||||||
|
'archived_at' => (int)$subscription->deleted_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
database/factories/SubscriptionFactory.php
Normal file
38
database/factories/SubscriptionFactory.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class SubscriptionFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name of the factory's corresponding model.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $model = Subscription::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function definition()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddNullableConstraintToRecurringInvoiceId extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('client_subscriptions', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('recurring_invoice_id')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class RefactorBillingScriptionsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
|
||||||
|
Schema::rename('billing_subscriptions', 'subscriptions');
|
||||||
|
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->text('product_id')->change();
|
||||||
|
$table->text('recurring_product_ids');
|
||||||
|
$table->string('name');
|
||||||
|
$table->unique(['company_id', 'name']);
|
||||||
|
$table->unsignedInteger('group_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->renameColumn('product_id', 'product_ids');
|
||||||
|
$table->dropColumn('is_recurring');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddPriceColumnToSubscriptionsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->decimal('price', 20, 6)->default(0);
|
||||||
|
$table->decimal('promo_price', 20, 6)->default(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('recurring_invoices', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('subscription_id')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('group_id')->nullable()->change();
|
||||||
|
$table->text('product_ids')->nullable()->change();
|
||||||
|
$table->text('recurring_product_ids')->nullable()->change();
|
||||||
|
$table->text('auto_bill')->nullable()->change();
|
||||||
|
$table->text('promo_code')->nullable()->change();
|
||||||
|
$table->unsignedInteger('frequency_id')->nullable()->change();
|
||||||
|
$table->text('plan_map')->nullable()->change();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
BIN
public/assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf
Executable file → Normal file
BIN
public/assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf
Executable file → Normal file
Binary file not shown.
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
40
public/flutter_service_worker.js
vendored
40
public/flutter_service_worker.js
vendored
@ -3,36 +3,36 @@ const MANIFEST = 'flutter-app-manifest';
|
|||||||
const TEMP = 'flutter-temp-cache';
|
const TEMP = 'flutter-temp-cache';
|
||||||
const CACHE_NAME = 'flutter-app-cache';
|
const CACHE_NAME = 'flutter-app-cache';
|
||||||
const RESOURCES = {
|
const RESOURCES = {
|
||||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
||||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
|
||||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
|
||||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
|
||||||
"assets/NOTICES": "e80e999afd09f0f14597c78d582d9c7c",
|
|
||||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
|
||||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
|
||||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
|
||||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
|
||||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
|
||||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||||
|
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||||
|
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||||
|
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||||
|
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||||
|
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
|
||||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
|
||||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||||
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||||
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||||
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",
|
"assets/AssetManifest.json": "659dcf9d1baf3aed3ab1b9c42112bf8f",
|
||||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "3e722fd57a6db80ee119f0e2c230ccff",
|
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||||
|
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
||||||
|
"assets/NOTICES": "e80e999afd09f0f14597c78d582d9c7c",
|
||||||
|
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||||
|
"main.dart.js": "a4cd245dcb265cb370c6b7f611011035",
|
||||||
"/": "23224b5e03519aaa87594403d54412cf",
|
"/": "23224b5e03519aaa87594403d54412cf",
|
||||||
"main.dart.js": "114d8affe0f4b7576170753cf9fb4c0a",
|
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
|
||||||
|
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||||
|
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||||
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
"version.json": "b7c8971e1ab5b627fd2a4317c52b843e",
|
||||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
|
"favicon.ico": "51636d3a390451561744c42188ccd628"
|
||||||
};
|
};
|
||||||
|
|
||||||
// The application shell files that are downloaded before a service worker can
|
// The application shell files that are downloaded before a service worker can
|
||||||
|
1
public/images/svg/calendar.svg
Normal file
1
public/images/svg/calendar.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
|
After Width: | Height: | Size: 403 B |
264509
public/main.dart.js
vendored
264509
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -11,7 +11,6 @@
|
|||||||
"related_applications": [
|
"related_applications": [
|
||||||
{
|
{
|
||||||
"platform": "play",
|
"platform": "play",
|
||||||
"url": "https://play.google.com/store/apps/details?id=com.invoiceninja.app",
|
|
||||||
"id": "com.invoiceninja.app"
|
"id": "com.invoiceninja.app"
|
||||||
}, {
|
}, {
|
||||||
"platform": "itunes",
|
"platform": "itunes",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||||
"/css/app.css": "/css/app.css?id=e8d6d5e8cb60bc2f15b3",
|
"/css/app.css": "/css/app.css?id=9525909664b98602bd2a",
|
||||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||||
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||||
|
2
public/vendor/livewire/livewire.js
vendored
2
public/vendor/livewire/livewire.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/livewire.js.map
vendored
2
public/vendor/livewire/livewire.js.map
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/livewire/manifest.json
vendored
2
public/vendor/livewire/manifest.json
vendored
@ -1 +1 @@
|
|||||||
{"/livewire.js":"/livewire.js?id=eb510e851dceb24afd36"}
|
{"/livewire.js":"/livewire.js?id=d9e06c155e467adb5de2"}
|
@ -0,0 +1,75 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
|
One-time payments
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,75 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white">
|
||||||
|
Subscriptions
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<p role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<p role="button" wire:click="sortBy('date')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</p>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($recurring_invoices as $recurring_invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.recurring_invoice.show', $recurring_invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $recurring_invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($recurring_invoice->amount, $recurring_invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $recurring_invoice->formatDate($recurring_invoice->date, $recurring_invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($recurring_invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $recurring_invoices->firstItem(), 'last' => $recurring_invoices->lastItem(), 'total' => $recurring_invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $recurring_invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,150 @@
|
|||||||
|
<div>
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white"
|
||||||
|
translate="yes">
|
||||||
|
One-time payments
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mb-4 uppercase leading-4 tracking-wide inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary text-white mt-4"
|
||||||
|
translate="yes">
|
||||||
|
Subscriptions
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between mt-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-sm hidden md:block">{{ ctrans('texts.per_page') }}</span>
|
||||||
|
<select wire:model="per_page" class="form-select py-1 text-sm">
|
||||||
|
<option>5</option>
|
||||||
|
<option selected>10</option>
|
||||||
|
<option>15</option>
|
||||||
|
<option>20</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||||
|
<div class="align-middle inline-block min-w-full overflow-hidden rounded">
|
||||||
|
<table class="min-w-full shadow rounded border border-gray-200 mt-4 credits-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-xs font-medium leading-4 tracking-wider text-left text-white uppercase border-b border-gray-200 bg-primary">
|
||||||
|
<span role="button" wire:click="sortBy('number')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.invoice') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('amount')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.total') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||||
|
<span role="button" wire:click="sortBy('public_notes')" class="cursor-pointer">
|
||||||
|
{{ ctrans('texts.date') }}
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($invoices as $invoice)
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}"
|
||||||
|
class="button-link text-primary">
|
||||||
|
{{ $invoice->number }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ App\Utils\Number::formatMoney($invoice->amount, $invoice->client) }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
|
||||||
|
{{ $invoice->formatDate($invoice->date, $invoice->client->date_format()) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr class="bg-white group hover:bg-gray-100">
|
||||||
|
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500" colspan="100%">
|
||||||
|
{{ ctrans('texts.no_results') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center md:justify-between mt-6 mb-6">
|
||||||
|
@if($invoices->total() > 0)
|
||||||
|
<span class="text-gray-700 text-sm hidden md:block">
|
||||||
|
{{ ctrans('texts.showing_x_of', ['first' => $invoices->firstItem(), 'last' => $invoices->lastItem(), 'total' => $invoices->total()]) }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
{{ $invoices->links('portal/ninja2020/vendor/pagination') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,9 @@
|
|||||||
|
@extends('portal.ninja2020.layout.app')
|
||||||
|
@section('meta_title', ctrans('texts.subscriptions'))
|
||||||
|
|
||||||
|
@section('body')
|
||||||
|
<div class="flex flex-col">
|
||||||
|
@livewire('subscription-invoices-table')
|
||||||
|
@livewire('subscription-recurring-invoices-table')
|
||||||
|
</div>
|
||||||
|
@endsection
|
138
tests/Feature/SubscriptionApiTest.php
Normal file
138
tests/Feature/SubscriptionApiTest.php
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
|
use App\Models\Product;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Foundation\Testing\WithFaker;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @covers App\Http\Controllers\SubscriptionController
|
||||||
|
*/
|
||||||
|
class SubscriptionApiTest extends TestCase
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use MockAccountData;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->makeTestData();
|
||||||
|
|
||||||
|
Session::start();
|
||||||
|
|
||||||
|
$this->faker = \Faker\Factory::create();
|
||||||
|
|
||||||
|
Model::reguard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscriptionsGet()
|
||||||
|
{
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$billing_subscription = Subscription::factory()->create([
|
||||||
|
'product_ids' => $product->id,
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $this->token,
|
||||||
|
])->get('/api/v1/subscriptions/' . $this->encodePrimaryKey($billing_subscription->id));
|
||||||
|
|
||||||
|
// nlog($response);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscriptionsPost()
|
||||||
|
{
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'X-API-SECRET' => config('ninja.api_secret'),
|
||||||
|
'X-API-TOKEN' => $this->token,
|
||||||
|
])->post('/api/v1/subscriptions', ['product_ids' => $product->id, 'allow_cancellation' => true]);
|
||||||
|
|
||||||
|
// nlog($response);
|
||||||
|
$response->assertStatus(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubscriptionPut()
|
||||||
|
{
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response1 = $this
|
||||||
|
->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token])
|
||||||
|
->post('/api/v1/subscriptions', ['product_ids' => $product->id])
|
||||||
|
->assertStatus(200)
|
||||||
|
->json();
|
||||||
|
|
||||||
|
$response2 = $this
|
||||||
|
->withHeaders(['X-API-SECRET' => config('ninja.api_secret'),'X-API-TOKEN' => $this->token])
|
||||||
|
->put('/api/v1/subscriptions/' . $response1['data']['id'], ['allow_cancellation' => true])
|
||||||
|
->assertStatus(200)
|
||||||
|
->json();
|
||||||
|
|
||||||
|
$this->assertNotEquals($response1['data']['allow_cancellation'], $response2['data']['allow_cancellation']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TypeError : Argument 1 passed to App\Transformers\SubscriptionTransformer::transform() must be an instance of App\Models\Subscription, bool given, called in /var/www/html/vendor/league/fractal/src/Scope.php on line 407
|
||||||
|
/var/www/html/app/Transformers/SubscriptionTransformer.php:35
|
||||||
|
/var/www/html/vendor/league/fractal/src/Scope.php:407
|
||||||
|
/var/www/html/vendor/league/fractal/src/Scope.php:349
|
||||||
|
/var/www/html/vendor/league/fractal/src/Scope.php:235
|
||||||
|
/var/www/html/app/Http/Controllers/BaseController.php:395
|
||||||
|
/var/www/html/app/Http/Controllers/SubscriptionController.php:408
|
||||||
|
*/
|
||||||
|
public function testSubscriptionDeleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
$product = Product::factory()->create([
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
'user_id' => $this->user->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$billing_subscription = Subscription::factory()->create([
|
||||||
|
'product_ids' => $product->id,
|
||||||
|
'company_id' => $this->company->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this
|
||||||
|
->withHeaders(['X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token])
|
||||||
|
->delete('/api/v1/subscriptions/' . $this->encodePrimaryKey($billing_subscription->id))
|
||||||
|
->assertStatus(200)
|
||||||
|
->json();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
44
tests/Unit/RecurringDateTest.php
Normal file
44
tests/Unit/RecurringDateTest.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\MockAccountData;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
class RecurringDateTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
use MockAccountData;
|
||||||
|
|
||||||
|
public function setUp() :void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
//$this->makeTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNextDay()
|
||||||
|
{
|
||||||
|
$trial = 60*60*24;
|
||||||
|
|
||||||
|
$now = Carbon::parse('2021-12-01');
|
||||||
|
|
||||||
|
$trial_ends = $now->addSeconds($trial)->addDays(1);
|
||||||
|
|
||||||
|
$this->assertequals($trial_ends->format('Y-m-d'), '2021-12-03');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user