Add support for multiple plans/terms

This commit is contained in:
Joshua Dwire 2016-04-16 18:34:39 -04:00
parent 51cd82cba8
commit 9b1bfef698
13 changed files with 687 additions and 122 deletions

View File

@ -30,6 +30,7 @@ use App\Ninja\Mailers\ContactMailer;
use App\Events\UserSignedUp;
use App\Events\UserSettingsChanged;
use App\Services\AuthService;
use App\Services\PaymentService;
use App\Http\Requests\UpdateAccountRequest;
@ -39,8 +40,9 @@ class AccountController extends BaseController
protected $userMailer;
protected $contactMailer;
protected $referralRepository;
protected $paymentService;
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository)
public function __construct(AccountRepository $accountRepo, UserMailer $userMailer, ContactMailer $contactMailer, ReferralRepository $referralRepository, PaymentService $paymentService)
{
//parent::__construct();
@ -48,6 +50,7 @@ class AccountController extends BaseController
$this->userMailer = $userMailer;
$this->contactMailer = $contactMailer;
$this->referralRepository = $referralRepository;
$this->paymentService = $paymentService;
}
public function demo()
@ -110,10 +113,125 @@ class AccountController extends BaseController
public function enableProPlan()
{
$invitation = $this->accountRepo->enableProPlan();
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
return false;
}
$invitation = $this->accountRepo->enablePlan();
return $invitation->invitation_key;
}
public function changePlan() {
$user = Auth::user();
$account = $user->account;
$plan = Input::get('plan');
$term = Input::get('plan_term');
$planDetails = $account->getPlanDetails(false);
$credit = 0;
if ($planDetails['active']) {
if ($planDetails['plan'] == PLAN_PRO && $plan == PLAN_ENTERPRISE) {
// Upgrade from pro to enterprise
if($planDetails['term'] == PLAN_TERM_YEARLY && $term == PLAN_TERM_MONTHLY) {
// Upgrade to yearly for now; switch to monthly in a year
$pending_monthly = true;
$term = PLAN_TERM_YEARLY;
}
$new_plan = array(
'plan' => PLAN_ENTERPRISE,
'term' => $term,
);
} elseif ($planDetails['plan'] == $plan) {
// Term switch
if ($planDetails['term'] == PLAN_TERM_YEARLY && $term == PLAN_TERM_MONTHLY) {
$pending_change = array(
'plan' => $plan,
'term' => $term
);
} elseif ($planDetails['term'] == PLAN_TERM_MONTHLY && $term == PLAN_TERM_YEARLY) {
$new_plan = array(
'plan' => $plan,
'term' => $term,
);
} else {
// Cancel the pending change
$account->company->pending_plan = null;
$account->company->pending_term = null;
$account->company->save();
Session::flash('message', trans('texts.updated_plan'));
}
} else {
// Downgrade
$refund_deadline = clone $planDetails['started'];
$refund_deadline->modify('+30 days');
if ($plan == PLAN_FREE && $refund_deadline > date_create()) {
// Refund
$account->company->plan = null;
$account->company->plan_term = null;
$account->company->plan_started = null;
$account->company->plan_expires = null;
$account->company->plan_paid = null;
$account->company->pending_plan = null;
$account->company->pending_term = null;
if ($account->company->payment) {
$payment = $account->company->payment;
$gateway = $this->paymentService->createGateway($payment->account_gateway);
$refund = $gateway->refund(array(
'transactionReference' => $payment->transaction_reference,
'amount' => $payment->amount * 100
));
$refund->send();
$payment->delete();
Session::flash('message', trans('texts.plan_refunded'));
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
} else {
Session::flash('message', trans('texts.updated_plan'));
}
$account->company->save();
} else {
$pending_change = array(
'plan' => $plan,
'term' => $plan == PLAN_FREE ? null : $term,
);
}
}
if (!empty($new_plan)) {
$percent_used = $planDetails['paid']->diff(date_create())->days / $planDetails['paid']->diff($planDetails['expires'])->days;
$old_plan_price = Account::$plan_prices[$planDetails['plan']][$planDetails['term']];
$credit = $old_plan_price * (1 - $percent_used);
}
} else {
$new_plan = array(
'plan' => $plan,
'term' => $term,
);
}
if (!empty($pending_change) && empty($new_plan)) {
$account->company->pending_plan = $pending_change['plan'];
$account->company->pending_term = $pending_change['term'];
$account->company->save();
Session::flash('message', trans('texts.updated_plan'));
}
if (!empty($new_plan)) {
$invitation = $this->accountRepo->enablePlan($new_plan['plan'], $new_plan['term'], $credit, !empty($pending_monthly));
return Redirect::to('payment/'.$invitation->invitation_key);
}
return Redirect::to('/settings/'.ACCOUNT_MANAGEMENT, 301);
}
public function setTrashVisible($entityType, $visible)
{
@ -235,9 +353,11 @@ class AccountController extends BaseController
private function showAccountManagement()
{
$account = Auth::user()->account;
$data = [
'account' => Auth::user()->account,
'title' => trans('texts.acount_management'),
'account' => $account,
'planDetails' => $account->getPlanDetails(),
'title' => trans('texts.account_management'),
];
return View::make('accounts.management', $data);
@ -955,7 +1075,7 @@ class AccountController extends BaseController
$user->registered = true;
$user->save();
$user->account->startTrial();
$user->account->startTrial(PLAN_ENTERPRISE);
if (Input::get('go_pro') == 'true') {
Session::set(REQUESTED_PRO_PLAN, true);
@ -1027,12 +1147,12 @@ class AccountController extends BaseController
return Redirect::to('/settings/'.ACCOUNT_USER_DETAILS)->with('message', trans('texts.confirmation_resent'));
}
public function startTrial()
public function startTrial($plan)
{
$user = Auth::user();
if ($user->isEligibleForTrial()) {
$user->account->startTrial();
if ($user->isEligibleForTrial($plan)) {
$user->account->startTrial($plan);
}
return Redirect::back()->with('message', trans('texts.trial_success'));

View File

@ -188,7 +188,8 @@ Route::group([
Route::resource('users', 'UserController');
Route::post('users/bulk', 'UserController@bulk');
Route::get('send_confirmation/{user_id}', 'UserController@sendConfirmation');
Route::get('start_trial', 'AccountController@startTrial');
Route::get('start_trial/{plan}', 'AccountController@startTrial')
->where(['plan'=>'pro|enterprise']);
Route::get('restore_user/{user_id}', 'UserController@restoreUser');
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
@ -212,6 +213,7 @@ Route::group([
Route::get('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/charts_and_reports', 'ReportController@showReports');
Route::post('settings/change_plan', 'AccountController@changePlan');
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
Route::post('settings/company_details', 'AccountController@updateDetails');
Route::get('settings/{section?}', 'AccountController@showSection');
@ -583,6 +585,10 @@ if (!defined('CONTACT_EMAIL')) {
define('SELF_HOST_AFFILIATE_KEY', '8S69AD');
define('PRO_PLAN_PRICE', 50);
define('PLAN_PRICE_PRO_MONTHLY', 5);
define('PLAN_PRICE_PRO_YEARLY', 50);
define('PLAN_PRICE_ENTERPRISE_MONTHLY', 10);
define('PLAN_PRICE_ENTERPRISE_YEARLY', 100);
define('WHITE_LABEL_PRICE', 20);
define('INVOICE_DESIGNS_PRICE', 10);
@ -645,6 +651,13 @@ if (!defined('CONTACT_EMAIL')) {
define('RESELLER_REVENUE_SHARE', 'A');
define('RESELLER_LIMITED_USERS', 'B');
// These must be lowercase
define('PLAN_FREE', 'free');
define('PLAN_PRO', 'pro');
define('PLAN_ENTERPRISE', 'enterprise');
define('PLAN_TERM_MONTHLY', 'month');
define('PLAN_TERM_YEARLY', 'year');
$creditCards = [
1 => ['card' => 'images/credit_cards/Test-Visa-Icon.png', 'text' => 'Visa'],

View File

@ -440,7 +440,12 @@ class Utils
return false;
}
$dateTime = new DateTime($date);
if ($date instanceof DateTime) {
$dateTime = $date;
} else {
$dateTime = new DateTime($date);
}
$timestamp = $dateTime->getTimestamp();
$format = Session::get(SESSION_DATE_FORMAT, DEFAULT_DATE_FORMAT);
@ -961,38 +966,6 @@ class Utils
return $entity1;
}
public static function withinPastYear($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
$interval = $today->diff($datePaid);
return $interval->y == 0;
}
public static function getInterval($date)
{
if (!$date || $date == '0000-00-00') {
return false;
}
$today = new DateTime('now');
$datePaid = DateTime::createFromFormat('Y-m-d', $date);
return $today->diff($datePaid);
}
public static function withinPastTwoWeeks($date)
{
$interval = Utils::getInterval($date);
return $interval && $interval->d <= 14;
}
public static function addHttp($url)
{
if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {

View File

@ -18,6 +18,17 @@ class Account extends Eloquent
{
use PresentableTrait;
use SoftDeletes;
public static $plan_prices = array(
PLAN_PRO => array(
PLAN_TERM_MONTHLY => PLAN_PRICE_PRO_MONTHLY,
PLAN_TERM_YEARLY => PLAN_PRICE_PRO_YEARLY,
),
PLAN_ENTERPRISE => array(
PLAN_TERM_MONTHLY => PLAN_PRICE_ENTERPRISE_MONTHLY,
PLAN_TERM_YEARLY => PLAN_PRICE_ENTERPRISE_YEARLY,
),
);
protected $presenter = 'App\Ninja\Presenters\AccountPresenter';
protected $dates = ['deleted_at'];
@ -177,6 +188,11 @@ class Account extends Eloquent
return $this->hasMany('App\Models\Payment','account_id','id')->withTrashed();
}
public function company()
{
return $this->belongsTo('App\Models\Company');
}
public function setIndustryIdAttribute($value)
{
$this->attributes['industry_id'] = $value ?: null;
@ -762,17 +778,18 @@ class Account extends Eloquent
return $this->account_key === NINJA_ACCOUNT_KEY;
}
public function startTrial()
public function startTrial($plan)
{
if ( ! Utils::isNinja()) {
return;
}
$this->pro_plan_trial = date_create()->format('Y-m-d');
$this->save();
$this->company->trial_plan = $plan;
$this->company->trial_started = date_create()->format('Y-m-d');
$this->company->save();
}
public function isPro()
public function isPro(&$plan_details = null)
{
if (!Utils::isNinjaProd()) {
return true;
@ -782,14 +799,109 @@ class Account extends Eloquent
return true;
}
$datePaid = $this->pro_plan_paid;
$trialStart = $this->pro_plan_trial;
$plan_details = $this->getPlanDetails();
return $plan_details && $plan_details['active'];
}
if ($datePaid == NINJA_DATE) {
public function isEnterprise(&$plan_details = null)
{
if (!Utils::isNinjaProd()) {
return true;
}
return Utils::withinPastTwoWeeks($trialStart) || Utils::withinPastYear($datePaid);
if ($this->isNinjaAccount()) {
return true;
}
$plan_details = $this->getPlanDetails();
return $plan_details && $plan_details['active'] && $plan_details['plan'] == PLAN_ENTERPRISE;
}
public function getPlanDetails($include_trial = true)
{
if (!$this->company) {
return null;
}
$plan = $this->company->plan;
$trial_plan = $this->company->trial_plan;
if(!$plan && (!$trial_plan || !$include_trial)) {
return null;
}
$trial_active = false;
if ($trial_plan && $include_trial) {
$trial_started = DateTime::createFromFormat('Y-m-d', $this->company->trial_started);
$trial_expires = clone $trial_started;
$trial_expires->modify('+2 weeks');
if ($trial_expires > date_create()) {
$trial_active = true;
}
}
$plan_active = false;
if ($plan) {
if ($this->company->plan_expires == null && $this->company->plan_paid == NINJA_DATE) {
$plan_active = true;
$plan_expires = false;
} else {
$plan_expires = DateTime::createFromFormat('Y-m-d', $this->company->plan_expires);
if ($plan_expires > date_create()) {
$plan_active = true;
}
}
}
// Should we show plan details or trial details?
if (($plan && !$trial_plan) || !$include_trial) {
$use_plan = true;
} elseif (!$plan && $trial_plan) {
$use_plan = false;
} else {
// There is both a plan and a trial
if (!empty($plan_active) && empty($trial_active)) {
$use_plan = true;
} elseif (empty($plan_active) && !empty($trial_active)) {
$use_plan = false;
} elseif (empty($plan_active) && empty($trial_active)) {
// Neither are active; use whichever expired most recently
$use_plan = $plan_expires >= $trial_expires;
} else {
// Both are active; use whichever is a better plan
if ($plan == PLAN_ENTERPRISE) {
$use_plan = true;
} elseif ($trial_plan == PLAN_ENTERPRISE) {
$use_plan = false;
} else {
// They're both the same; show the plan
$use_plan = true;
}
}
}
if ($use_plan) {
return array(
'trial' => false,
'plan' => $plan,
'started' => DateTime::createFromFormat('Y-m-d', $this->company->plan_started),
'expires' => $plan_expires,
'paid' => DateTime::createFromFormat('Y-m-d', $this->company->plan_paid),
'term' => $this->company->plan_term,
'active' => $plan_active,
);
} else {
return array(
'trial' => true,
'plan' => $trial_plan,
'started' => $trial_started,
'expires' => $trial_expires,
'active' => $trial_active,
);
}
}
public function isTrial()
@ -797,35 +909,54 @@ class Account extends Eloquent
if (!Utils::isNinjaProd()) {
return false;
}
$plan_details = $this->getPlanDetails();
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
return false;
}
return Utils::withinPastTwoWeeks($this->pro_plan_trial);
return $plan_details && $plan_details['trial'] && $plan_details['active'];;
}
public function isEligibleForTrial()
public function isEligibleForTrial($plan = null)
{
return ! $this->pro_plan_trial || $this->pro_plan_trial == '0000-00-00';
if (!$this->company->trial_plan) {
if ($plan) {
return $plan == PLAN_PRO || $plan == PLAN_ENTERPRISE;
} else {
return array(PLAN_PRO, PLAN_ENTERPRISE);
}
}
if ($this->company->trial_plan == PLAN_PRO) {
if ($plan) {
return $plan != PLAN_PRO;
} else {
return array(PLAN_ENTERPRISE);
}
}
return false;
}
public function getCountTrialDaysLeft()
{
$interval = Utils::getInterval($this->pro_plan_trial);
$planDetails = $this->getPlanDetails();
if(!$planDetails || !$planDetails['trial']) {
return 0;
}
$today = new DateTime('now');
$interval = $today->diff($planDetails['expires']);
return $interval ? 14 - $interval->d : 0;
}
public function getRenewalDate()
{
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
$date = DateTime::createFromFormat('Y-m-d', $this->pro_plan_paid);
$date->modify('+1 year');
$planDetails = $this->getPlanDetails();
if ($planDetails && $planDetails['active']) {
$date = $planDetails['expires'];
$date = max($date, date_create());
} elseif ($this->isTrial()) {
$date = date_create();
$date->modify('+'.$this->getCountTrialDaysLeft().' day');
} else {
$date = date_create();
}
@ -840,13 +971,10 @@ class Account extends Eloquent
}
if (Utils::isNinjaProd()) {
return self::isPro() && $this->pro_plan_paid != NINJA_DATE;
return self::isPro($plan_details) && $plan_details['expires'];
} else {
if ($this->pro_plan_paid == NINJA_DATE) {
return true;
}
return Utils::withinPastYear($this->pro_plan_paid);
$plan_details = $this->getPlanDetails();
return $plan_details && $plan_details['active'];
}
}

21
app/Models/Company.php Normal file
View File

@ -0,0 +1,21 @@
<?php namespace App\Models;
use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
class Company extends Eloquent
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function accounts()
{
return $this->hasMany('App\Models\Account');
}
public function payment()
{
return $this->belongsTo('App\Models\Payment');
}
}

View File

@ -122,9 +122,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isTrial();
}
public function isEligibleForTrial()
public function isEligibleForTrial($plan = null)
{
return $this->account->isEligibleForTrial();
return $this->account->isEligibleForTrial($plan);
}
public function maxInvoiceDesignId()

View File

@ -205,21 +205,23 @@ class AccountRepository
return $data;
}
public function enableProPlan()
public function enablePlan($plan = PLAN_PRO, $term = PLAN_TERM_MONTHLY, $credit = 0, $pending_monthly = false)
{
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
return false;
}
$account = Auth::user()->account;
$client = $this->getNinjaClient($account);
$invitation = $this->createNinjaInvoice($client, $account);
$invitation = $this->createNinjaInvoice($client, $account, $plan, $term, $credit, $pending_monthly);
return $invitation;
}
public function createNinjaInvoice($client, $clientAccount)
public function createNinjaInvoice($client, $clientAccount, $plan = PLAN_PRO, $term = PLAN_TERM_MONTHLY, $credit = 0, $pending_monthly = false)
{
if ($credit < 0) {
$credit = 0;
}
$plan_cost = Account::$plan_prices[$plan][$term];
$account = $this->getNinjaAccount();
$lastInvoice = Invoice::withTrashed()->whereAccountId($account->id)->orderBy('public_id', 'DESC')->first();
$publicId = $lastInvoice ? ($lastInvoice->public_id + 1) : 1;
@ -230,19 +232,39 @@ class AccountRepository
$invoice->client_id = $client->id;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->invoice_date = $clientAccount->getRenewalDate();
$invoice->amount = PRO_PLAN_PRICE;
$invoice->balance = PRO_PLAN_PRICE;
$invoice->amount = $invoice->balance = $plan_cost - $credit;
$invoice->save();
$item = new InvoiceItem();
$item->account_id = $account->id;
$item->user_id = $account->users()->first()->id;
$item->public_id = $publicId;
if ($credit) {
$credit_item = InvoiceItem::createNew($invoice);
$credit_item->qty = 1;
$credit_item->cost = -$credit;
$credit_item->notes = trans('texts.plan_credit_description');
$credit_item->product_key = trans('texts.plan_credit_product');
$invoice->invoice_items()->save($credit_item);
}
$item = InvoiceItem::createNew($invoice);
$item->qty = 1;
$item->cost = PRO_PLAN_PRICE;
$item->notes = trans('texts.pro_plan_description');
$item->product_key = trans('texts.pro_plan_product');
$item->cost = $plan_cost;
$item->notes = trans("texts.{$plan}_plan_{$term}_description");
// Don't change this without updating the regex in PaymentService->createPayment()
$item->product_key = 'Plan - '.ucfirst($plan).' ('.ucfirst($term).')';
$invoice->invoice_items()->save($item);
if ($pending_monthly) {
$term_end = $term == PLAN_MONTHLY ? date_create('+1 month') : date_create('+1 year');
$pending_monthly_item = InvoiceItem::createNew($invoice);
$item->qty = 1;
$pending_monthly_item->cost = 0;
$pending_monthly_item->notes = trans("texts.plan_pending_monthly", array('date', Utils::dateToString($term_end)));
// Don't change this without updating the text in PaymentService->createPayment()
$pending_monthly_item->product_key = 'Pending Monthly';
$invoice->invoice_items()->save($pending_monthly_item);
}
$invitation = new Invitation();
$invitation->account_id = $account->id;
@ -355,7 +377,7 @@ class AccountRepository
$user->last_name = $lastName;
$user->registered = true;
$user->account->startTrial();
$user->account->startTrial(PLAN_ENTERPRISE);
}
$user->oauth_provider_id = $providerId;

View File

@ -13,13 +13,17 @@ class ReferralRepository
$counts = [
'free' => 0,
'pro' => 0
'pro' => 0,
'enterprise' => 0
];
foreach ($accounts as $account) {
$counts['free']++;
if (Utils::withinPastYear($account->pro_plan_paid)) {
if ($account->isPro()) {
$counts['pro']++;
if ($account->isEnterprise()) {
$counts['enterprise']++;
}
}
}

View File

@ -216,17 +216,6 @@ class PaymentService extends BaseService
{
$invoice = $invitation->invoice;
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY && $invoice->amount == PRO_PLAN_PRICE) {
$account = Account::with('users')->find($invoice->client->public_id);
$account->pro_plan_paid = $account->getRenewalDate();
$account->save();
// sync pro accounts
$user = $account->users()->first();
$this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid);
}
$payment = Payment::createNew($invitation);
$payment->invitation_id = $invitation->id;
$payment->account_gateway_id = $accountGateway->id;
@ -236,13 +225,45 @@ class PaymentService extends BaseService
$payment->contact_id = $invitation->contact_id;
$payment->transaction_reference = $ref;
$payment->payment_date = date_create()->format('Y-m-d');
if ($payerId) {
$payment->payer_id = $payerId;
}
$payment->save();
// enable pro plan for hosted users
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY) {
foreach ($invoice->invoice_items as $invoice_item) {
// Hacky, but invoices don't have meta fields to allow us to store this easily
if (1 == preg_match('/^Plan - (.+) \((.+)\)$/', $invoice_item->product_key, $matches)) {
$plan = strtolower($matches[1]);
$term = strtolower($matches[2]);
} elseif ($invoice_item->product_key == 'Pending Monthly') {
$pending_monthly = true;
}
}
if (!empty($plan)) {
$account = Account::with('users')->find($invoice->client->public_id);
$account->company->payment_id = $payment->id;
$account->company->plan = $plan;
$account->company->plan_term = $term;
$account->company->plan_paid = $account->company->plan_started = date_create()->format('Y-m-d');
$account->company->plan_expires = date_create($term == PLAN_TERM_MONTHLY ? '+1 month' : '+1 year')->format('Y-m-d');
if (!empty($pending_monthly)) {
$account->company->pending_plan = $plan;
$account->company->pending_term = PLAN_TERM_MONTHLY;
} else {
$account->company->pending_plan = null;
$account->company->pending_term = null;
}
$account->company->save();
}
}
return $payment;
}

View File

@ -0,0 +1,111 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use App\Models\Company;
use App\Models\Account;
class EnterprisePlan extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('companies', function($table)
{
$table->increments('id');
$table->enum('plan', array('pro', 'enterprise'))->nullable();
$table->enum('plan_term', array('month', 'year'))->nullable();
$table->date('plan_started')->nullable();
$table->date('plan_paid')->nullable();
$table->date('plan_expires')->nullable();
$table->unsignedInteger('payment_id')->nullable();
$table->foreign('payment_id')->references('id')->on('payments');
$table->date('trial_started')->nullable();
$table->enum('trial_plan', array('pro', 'enterprise'))->nullable();
$table->enum('pending_plan', array('pro', 'enterprise', 'free'))->nullable();
$table->enum('pending_term', array('month', 'year'))->nullable();
// Used when a user has started changing a plan but hasn't finished paying yet
$table->enum('temp_pending_plan', array('pro', 'enterprise'))->nullable();
$table->enum('temp_pending_term', array('month', 'year'))->nullable();
$table->timestamps();
$table->softDeletes();
});
Schema::table('accounts', function($table)
{
$table->unsignedInteger('company_id')->nullable();
$table->foreign('company_id')->references('id')->on('companies');
});
foreach (Account::all() as $account) {
$company = Company::create();
if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') {
$company->plan = 'pro';
$company->plan_term = 'year';
$company->plan_started = $account->pro_plan_paid;
$company->plan_paid = $account->pro_plan_paid;
if ($company->plan_paid != NINJA_DATE) {
$expires = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid);
$expires->modify('+1 year');
$company->plan_expires = $expires->format('Y-m-d');
}
}
if ($account->pro_plan_trial && $account->pro_plan_trial != '0000-00-00') {
$company->trial_started = $account->pro_plan_trial;
$company->trial_plan = 'pro';
}
$company->save();
$account->company_id = $company->id;
$account->save();
}
/*Schema::table('accounts', function($table)
{
$table->dropColumn('pro_plan_paid');
$table->dropColumn('pro_plan_trial');
});*/
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
/*Schema::table('accounts', function($table)
{
$table->date('pro_plan_paid')->nullable();
$table->date('pro_plan_trial')->nullable();
});*/
foreach (Company::all() as $company) {
foreach ($company->accounts as $account) {
$account->pro_plan_paid = $company->plan_paid;
$account->pro_plan_trial = $company->trial_started;
$account->save();
}
}
Schema::table('accounts', function($table)
{
$table->dropForeign('accounts_company_id_foreign');
$table->dropColumn('company_id');
});
Schema::drop('companies');
}
}

View File

@ -268,7 +268,6 @@ $LANG = array(
'erase_data' => 'This will permanently erase your data.',
'password' => 'Password',
'pro_plan_product' => 'Pro Plan',
'pro_plan_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_success' => 'Thanks for choosing Invoice Ninja\'s Pro plan!<p/>&nbsp;<br/>
<b>Next Steps</b><p/>A payable invoice has been sent to the email
address associated with your account. To unlock all of the awesome
@ -1127,13 +1126,40 @@ $LANG = array(
'enable_client_portal_dashboard' => 'Dashboard',
'enable_client_portal_dashboard_help' => 'Show/hide the dashboard page in the client portal.',
// Plans
'account_management' => 'Account Management',
'plan_status' => 'Plan Status',
'plan_upgrade' => 'Upgrade',
'plan_change' => 'Change Plan',
'pending_change_to' => 'Pending Change To',
'plan' => 'Plan',
'expires' => 'Expires',
'expired' => 'Expired',
'never' => 'never',
'plan_free' => 'Free',
'plan_pro' => 'Pro',
'plan_enterprise' => 'Enterprise'
'plan_enterprise' => 'Enterprise',
'plan_trial' => 'Trial',
'plan_term' => 'Term',
'plan_term_monthly' => 'Monthly',
'plan_term_yearly' => 'Yearly',
'plan_term_month' => 'Month',
'plan_term_year' => 'Year',
'plan_price_monthly' => '$:price/Month',
'plan_price_yearly' => '$:price/Year',
'updated_plan' => 'Updated plan settings',
'pro_plan_year_description' => 'One year enrollment in the Invoice Ninja Pro Plan.',
'pro_plan_month_description' => 'One month enrollment in the Invoice Ninja Pro Plan.',
'enterprise_plan_product' => 'Enterprise Plan',
'enterprise_plan_year_description' => 'One year enrollment in the Invoice Ninja Enterprise Plan.',
'enterprise_plan_month_description' => 'One month enrollment in the Invoice Ninja Enterprise Plan.',
'plan_credit_product' => 'Credit',
'plan_credit_description' => 'Credit for unused time',
'plan_pending_monthly' => 'Will switch to monthly on :date',
'plan_refunded' => 'A refund has been issued.'
);
return $LANG;

View File

@ -7,21 +7,113 @@
<div class="row">
<div class="col-md-12">
<!--<div class="panel panel-default">
@if (Utils::isNinjaProd())
{!! Former::open('settings/change_plan')->addClass('change-plan') !!}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{!! trans('texts.plan_status') !!}</h3>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-sm-4 control-label">{{ trans('texts.plan') }}</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($planDetails && $planDetails['active'])
{{ trans('texts.plan_'.$planDetails['plan']) }}
@if ($planDetails['trial'])
({{ trans('texts.plan_trial') }})
@elseif ($planDetails['expires'])
({{ trans('texts.plan_term_'.$planDetails['term'].'ly') }})
@endif
@else
{{ trans('texts.plan_free') }}
@endif
</p>
</div>
</div>
@if ($planDetails && $planDetails['active'])
<div class="form-group">
<label class="col-sm-2 control-label">Plan</label>
<div class="col-sm-10">
<p class="form-control-static">{{ trans('texts.plan_'.$account->plan) }}</p>
<label class="col-sm-4 control-label">
@if($planDetails['active'])
{{ trans('texts.expires') }}
@else
{{ trans('texts.expired') }}
@endif
</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($planDetails['expires'] === false)
{{ trans('texts.never') }}
@else
{{ Utils::dateToString($planDetails['expires']) }}
@endif
</p>
</div>
</div>
</form>
@if ($account->company->pending_plan)
<div class="form-group">
<label class="col-sm-4 control-label">{{ trans('texts.pending_change_to') }}</label>
<div class="col-sm-8">
<p class="form-control-static">
@if ($account->company->pending_plan == PLAN_FREE)
{{ trans('texts.plan_free') }}
@else
{{ trans('texts.plan_'.$account->company->pending_plan) }}
({{ trans('texts.plan_term_'.$account->company->pending_term.'ly') }})
@endif
<a href="#" onclick="cancelPendingChange()">{{ trans('texts.cancel') }}</a>
</p>
</div>
</div>
@endif
{!! Former::actions( Button::info(trans('texts.plan_change'))->large()->withAttributes(['onclick' => 'showChangePlan()'])->appendIcon(Icon::create('edit'))) !!}
@else
{!! Former::actions( Button::success(trans('texts.plan_upgrade'))->large()->withAttributes(['onclick' => 'showChangePlan()'])->appendIcon(Icon::create('plus-sign'))) !!}
@endif
</div>
</div>-->
</div>
<div class="modal fade" id="changePlanModel" tabindex="-1" role="dialog" aria-labelledby="changePlanModelLabel" aria-hidden="true">
<div class="modal-dialog" style="min-width:150px">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="changePlanModelLabel">
@if ($planDetails && $planDetails['active'])
{!! trans('texts.plan_change') !!}
@else
{!! trans('texts.plan_upgrade') !!}
@endif
</h4>
</div>
<div class="modal-body">
@if ($planDetails && $planDetails['active'])
{!! Former::select('plan')
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)
->addOption(trans('texts.plan_free'), PLAN_FREE)!!}
@else
{!! Former::select('plan')
->addOption(trans('texts.plan_enterprise'), PLAN_ENTERPRISE)
->addOption(trans('texts.plan_pro'), PLAN_PRO)!!}
@endif
{!! Former::select('plan_term')
->addOption(trans('texts.plan_term_yearly'), PLAN_TERM_YEARLY)
->addOption(trans('texts.plan_term_monthly'), PLAN_TERM_MONTHLY)!!}
</div>
<div class="modal-footer" style="margin-top: 0px">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.go_back') }}</button>
@if ($planDetails && $planDetails['active'])
<button type="button" class="btn btn-primary" onclick="confirmChangePlan()">{{ trans('texts.plan_change') }}</button>
@else
<button type="button" class="btn btn-success" onclick="confirmChangePlan()">{{ trans('texts.plan_upgrade') }}</button>
@endif
</div>
</div>
</div>
</div>
{!! Former::close() !!}
@endif
{!! Former::open('settings/cancel_account')->addClass('cancel-account') !!}
<div class="panel panel-default">
@ -54,17 +146,51 @@
</div>
</div>
</div>
{!! Former::close() !!}
{!! Former::close() !!}
</div>
</div>
<script type="text/javascript">
function showConfirm() {
$('#confirmCancelModal').modal('show');
}
function showChangePlan() {
$('#changePlanModel').modal('show');
}
function confirmCancel() {
$('form.cancel-account').submit();
}
function confirmChangePlan() {
$('form.change-plan').submit();
}
function showConfirm() {
$('#confirmCancelModal').modal('show');
}
function confirmCancel() {
$('form.cancel-account').submit();
}
@if ($account->company->pending_plan)
function cancelPendingChange(){
$('#plan').val('{{ $planDetails['plan'] }}')
$('#plan_term').val('{{ $planDetails['term'] }}')
confirmChangePlan();
return false;
}
@endif
jQuery(document).ready(function($){
function updatePlanModal() {
var plan = $('#plan').val();
$('#plan_term').closest('.form-group').toggle(plan!='free');
if(plan=='{{PLAN_PRO}}'){
$('#plan_term option[value=month]').text({!! json_encode(trans('texts.plan_price_monthly', ['price'=>PLAN_PRICE_PRO_MONTHLY])) !!});
$('#plan_term option[value=year]').text({!! json_encode(trans('texts.plan_price_yearly', ['price'=>PLAN_PRICE_PRO_YEARLY])) !!});
} else if(plan=='{{PLAN_ENTERPRISE}}') {
$('#plan_term option[value=month]').text({!! json_encode(trans('texts.plan_price_monthly', ['price'=>PLAN_PRICE_ENTERPRISE_MONTHLY])) !!});
$('#plan_term option[value=year]').text({!! json_encode(trans('texts.plan_price_yearly', ['price'=>PLAN_PRICE_ENTERPRISE_YEARLY])) !!});
}
}
$('#plan_term, #plan').change(updatePlanModal);
updatePlanModal();
});
</script>
@stop

View File

@ -726,8 +726,8 @@
<center>
<h2>{{ trans('texts.pro_plan_title') }}</h2>
<img class="img-responsive price" alt="Only $50 Per Year" src="{{ asset('images/pro_plan/price.png') }}"/>
@if (Auth::user()->isEligibleForTrial())
<a class="button" href="{{ URL::to('start_trial') }}">{{ trans('texts.trial_call_to_action') }}</a>
@if (Auth::user()->isEligibleForTrial(PLAN_PRO))
<a class="button" href="{{ URL::to('start_trial/'.PLAN_PRO) }}">{{ trans('texts.trial_call_to_action') }}</a>
@else
<a class="button" href="#" onclick="submitProPlan()">{{ trans('texts.pro_plan_call_to_action') }}</a>
@endif