1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Added two week trial for hosted pro plan

This commit is contained in:
Hillel Coren 2016-02-11 17:12:27 +02:00
parent 1b740d77fc
commit 1794811f9b
17 changed files with 186 additions and 46 deletions

View File

@ -916,6 +916,8 @@ class AccountController extends BaseController
$user->registered = true;
$user->save();
$user->account->startTrial();
if (Input::get('go_pro') == 'true') {
Session::set(REQUESTED_PRO_PLAN, true);
}
@ -980,6 +982,17 @@ class AccountController extends BaseController
return Redirect::to('/settings/'.ACCOUNT_USER_DETAILS)->with('message', trans('texts.confirmation_resent'));
}
public function startTrial()
{
$user = Auth::user();
if ($user->isEligibleForTrial()) {
$user->account->startTrial();
}
return Redirect::back()->with('message', trans('texts.trial_success'));
}
public function redirectLegacy($section, $subSection = false)
{
if ($section === 'details') {

View File

@ -200,7 +200,11 @@ class AccountGatewayController extends BaseController
if ($gatewayId == GATEWAY_DWOLLA) {
$optional = array_merge($optional, ['key', 'secret']);
} elseif ($gatewayId == GATEWAY_STRIPE) {
$rules['publishable_key'] = 'required';
if (Utils::isNinjaDev() && Input::get('23_apiKey') == env('TEST_API_KEY')) {
// do nothing - we're unable to acceptance test with StripeJS
} else {
$rules['publishable_key'] = 'required';
}
}
foreach ($fields as $field => $details) {

View File

@ -166,7 +166,7 @@ class UserController extends BaseController
*/
public function save($userPublicId = false)
{
if (Auth::user()->account->isPro()) {
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
$rules = [
'first_name' => 'required',
'last_name' => 'required',

View File

@ -97,6 +97,7 @@ Route::group(['middleware' => 'auth'], function() {
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('restore_user/{user_id}', 'UserController@restoreUser');
Route::post('users/change_password', 'UserController@changePassword');
Route::get('/switch_account/{user_id}', 'UserController@switchAccount');
@ -425,7 +426,6 @@ if (!defined('CONTACT_EMAIL')) {
define('MAX_NUM_VENDORS', 100);
define('MAX_NUM_VENDORS_PRO', 20000);
define('MAX_NUM_VENDORS_LEGACY', 500);
define('INVOICE_STATUS_DRAFT', 1);
define('INVOICE_STATUS_SENT', 2);
@ -669,9 +669,8 @@ if (Utils::isNinjaDev()) {
*/
/*
if (Auth::check() && Auth::user()->id === 1)
if (Utils::isNinjaDev() && Auth::check() && Auth::user()->id === 1)
{
Auth::loginUsingId(1);
}
*/
*/

View File

@ -72,6 +72,10 @@ class Utils
public static function requireHTTPS()
{
if (Request::root() === 'http://ninja.dev') {
return false;
}
return Utils::isNinjaProd() || (isset($_ENV['REQUIRE_HTTPS']) && $_ENV['REQUIRE_HTTPS'] == 'true');
}
@ -114,6 +118,11 @@ class Utils
return Auth::check() && Auth::user()->isPro();
}
public static function isTrial()
{
return Auth::check() && Auth::user()->isTrial();
}
public static function isEnglish()
{
return App::getLocale() == 'en';
@ -961,6 +970,25 @@ class Utils
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

@ -464,8 +464,21 @@ class Account extends Eloquent
return $invoice;
}
public function getNumberPrefix($isQuote)
{
if ( ! $this->isPro()) {
return '';
}
return ($isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix) ?: '';
}
public function hasNumberPattern($isQuote)
{
if ( ! $this->isPro()) {
return false;
}
return $isQuote ? ($this->quote_number_pattern ? true : false) : ($this->invoice_number_pattern ? true : false);
}
@ -549,7 +562,7 @@ class Account extends Eloquent
}
$counter = $this->getCounter($invoice->is_quote);
$prefix = $invoice->is_quote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$prefix = $this->getNumberPrefix($invoice->is_quote);
$counterOffset = 0;
// confirm the invoice number isn't already taken
@ -681,6 +694,16 @@ class Account extends Eloquent
return $this->account_key === NINJA_ACCOUNT_KEY;
}
public function startTrial()
{
if ( ! Utils::isNinja()) {
return;
}
$this->pro_plan_trial = date_create()->format('Y-m-d');
$this->save();
}
public function isPro()
{
if (!Utils::isNinjaProd()) {
@ -692,12 +715,50 @@ class Account extends Eloquent
}
$datePaid = $this->pro_plan_paid;
$trialStart = $this->pro_plan_trial;
if ($datePaid == NINJA_DATE) {
return true;
}
return Utils::withinPastYear($datePaid);
return Utils::withinPastTwoWeeks($trialStart) || Utils::withinPastYear($datePaid);
}
public function isTrial()
{
if ($this->pro_plan_paid && $this->pro_plan_paid != '0000-00-00') {
return false;
}
return Utils::withinPastTwoWeeks($this->pro_plan_trial);
}
public function isEligibleForTrial()
{
return ! $this->pro_plan_trial || $this->pro_plan_trial == '0000-00-00';
}
public function getCountTrialDaysLeft()
{
$interval = Utils::getInterval($this->pro_plan_trial);
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');
$date = max($date, date_create());
} elseif ($this->isTrial()) {
$date = date_create();
$date->modify('+'.$this->getCountTrialDaysLeft().' day');
} else {
$date = date_create();
}
return $date->format('Y-m-d');
}
public function isWhiteLabel()
@ -944,6 +1005,11 @@ class Account extends Eloquent
return $this->isPro() && $this->pdf_email_attachment;
}
public function getEmailDesignId()
{
return $this->isPro() ? $this->email_design_id : EMAIL_DESIGN_PLAIN;
}
public function clientViewCSS(){
$css = null;

View File

@ -107,6 +107,16 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->account->isPro();
}
public function isTrial()
{
return $this->account->isTrial();
}
public function isEligibleForTrial()
{
return $this->account->isEligibleForTrial();
}
public function maxInvoiceDesignId()
{
return $this->isPro() ? 11 : (Utils::isNinja() ? COUNT_FREE_DESIGNS : COUNT_FREE_DESIGNS_SELF_HOST);
@ -153,7 +163,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getMaxNumClients()
{
if ($this->isPro()) {
if ($this->isPro() && ! $this->isTrial()) {
return MAX_NUM_CLIENTS_PRO;
}
@ -166,14 +176,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
public function getMaxNumVendors()
{
if ($this->isPro()) {
if ($this->isPro() && ! $this->isTrial()) {
return MAX_NUM_VENDORS_PRO;
}
if ($this->id < LEGACY_CUTOFF) {
return MAX_NUM_VENDORS_LEGACY;
}
return MAX_NUM_VENDORS;
}

View File

@ -127,10 +127,10 @@ class ContactMailer extends Mailer
$subject = $this->processVariables($subject, $variables);
$fromEmail = $user->email;
if ($account->email_design_id == EMAIL_DESIGN_PLAIN) {
if ($account->getEmailDesignId() == EMAIL_DESIGN_PLAIN) {
$view = ENTITY_INVOICE;
} else {
$view = 'design' . ($account->email_design_id - 1);
$view = 'design' . ($account->getEmailDesignId() - 1);
}
$response = $this->sendTo($invitation->contact->email, $fromEmail, $account->getDisplayName(), $subject, $view, $data);
@ -189,10 +189,10 @@ class ContactMailer extends Mailer
$subject = $this->processVariables($emailSubject, $variables);
$data['invoice_id'] = $payment->invoice->id;
if ($account->email_design_id == EMAIL_DESIGN_PLAIN) {
if ($account->getEmailDesignId() == EMAIL_DESIGN_PLAIN) {
$view = 'payment_confirmation';
} else {
$view = 'design' . ($account->email_design_id - 1);
$view = 'design' . ($account->getEmailDesignId() - 1);
}
if ($user->email && $contact->email) {

View File

@ -119,7 +119,7 @@ class AccountRepository
public function enableProPlan()
{
if (Auth::user()->isPro()) {
if (Auth::user()->isPro() && ! Auth::user()->isTrial()) {
return false;
}
@ -141,7 +141,7 @@ class AccountRepository
$invoice->public_id = $publicId;
$invoice->client_id = $client->id;
$invoice->invoice_number = $account->getNextInvoiceNumber($invoice);
$invoice->invoice_date = date_create()->format('Y-m-d');
$invoice->invoice_date = Auth::user()->account->getRenewalDate();
$invoice->amount = PRO_PLAN_PRICE;
$invoice->balance = PRO_PLAN_PRICE;
$invoice->save();
@ -266,6 +266,8 @@ class AccountRepository
$user->first_name = $firstName;
$user->last_name = $lastName;
$user->registered = true;
$user->account->startTrial();
}
$user->oauth_provider_id = $providerId;

View File

@ -84,7 +84,7 @@ class InvoiceService extends BaseService
return null;
}
if ($account->auto_convert_quote) {
if ($account->auto_convert_quote || ! $account->isPro()) {
$invoice = $this->convertQuote($quote, $invitation);
event(new QuoteInvitationWasApproved($quote, $invoice, $invitation));

View File

@ -212,20 +212,13 @@ class PaymentService extends BaseService
{
$invoice = $invitation->invoice;
// sync pro accounts
if ($invoice->account->account_key == NINJA_ACCOUNT_KEY
&& $invoice->amount == PRO_PLAN_PRICE) {
// 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);
if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') {
$date = DateTime::createFromFormat('Y-m-d', $account->pro_plan_paid);
$date->modify('+1 year');
$date = max($date, date_create());
$account->pro_plan_paid = $date->format('Y-m-d');
} else {
$account->pro_plan_paid = date_create()->format('Y-m-d');
}
$account->pro_plan_paid = $account->getRenewalDate();
$account->save();
// sync pro accounts
$user = $account->users()->first();
$this->accountRepo->syncAccounts($user->id, $account->pro_plan_paid);
}

View File

@ -1103,7 +1103,7 @@ return array(
'quote_message_button' => 'To view your quote for :amount, click the button below.',
'payment_message_button' => 'Thank you for your payment of :amount.',
'payment_type_direct_debit' => 'Direct Debit',
'bank_accounts' => 'Bank Accounts',
'bank_accounts' => 'Credit Cards & Banks',
'add_bank_account' => 'Add Bank Account',
'setup_account' => 'Setup Account',
'import_expenses' => 'Import Expenses',
@ -1144,4 +1144,9 @@ return array(
'enable_https' => 'We strongly recommend using HTTPS to accept credit card details online.',
'quote_issued_to' => 'Quote issued to',
'show_currency_code' => 'Currency Code',
'trial_message' => 'Your account will receive a free two week trial of our pro plan.',
'trial_footer' => 'Your free trial lasts :count more days, :link to upgrade now.',
'trial_footer_last_day' => 'This is the last day of your free trial, :link to upgrade now.',
'trial_call_to_action' => 'Start Free Trial',
'trial_success' => 'Successfully enabled two week free pro plan trial',
);

View File

@ -6,7 +6,7 @@
<div class="pull-right">
@if (Utils::isPro())
@if (Utils::isPro() && ! Utils::isTrial())
{!! Button::primary(trans('texts.add_user'))->asLinkTo(URL::to('/users/create'))->appendIcon(Icon::create('plus-sign')) !!}
@endif
</div>

View File

@ -185,7 +185,7 @@
window.open('{{ Utils::isNinjaDev() ? '' : NINJA_APP_URL }}/license?affiliate_key=' + affiliateKey + '&product_id=' + productId + '&return_url=' + window.location);
}
@if (Auth::check() && !Auth::user()->isPro())
@if (Auth::check() && (!Auth::user()->isPro() || Auth::user()->isTrial()))
function submitProPlan() {
fbq('track', 'AddPaymentInfo');
trackEvent('/account', '/submit_pro_plan/' + NINJA.proPlanFeature);
@ -382,7 +382,7 @@
@if (Auth::check())
@if (!Auth::user()->registered)
{!! Button::success(trans('texts.sign_up'))->withAttributes(array('id' => 'signUpButton', 'data-toggle'=>'modal', 'data-target'=>'#signUpModal'))->small() !!} &nbsp;
@elseif (!Auth::user()->isPro())
@elseif (Utils::isNinjaProd() && (!Auth::user()->isPro() || Auth::user()->isTrial()))
{!! Button::success(trans('texts.go_pro'))->withAttributes(array('id' => 'proPlanButton', 'onclick' => 'showProPlan("")'))->small() !!} &nbsp;
@endif
@endif
@ -599,10 +599,16 @@
{{ Former::setOption('TwitterBootstrap3.labelWidths.large', 4) }}
{{ Former::setOption('TwitterBootstrap3.labelWidths.small', 4) }}
</div>
<div class="col-md-11 col-md-offset-1">
<div style="padding-top:20px;padding-bottom:10px;">{{ trans('texts.trial_message') }}</div>
</div>
</div>
{!! Former::close() !!}
<center><div id="errorTaken" style="display:none">&nbsp;<br/>{{ trans('texts.email_taken') }}</div></center>
<br/>
@ -655,11 +661,10 @@
</div>
@endif
@if (Auth::check() && !Auth::user()->isPro())
@if (Auth::check() && (!Auth::user()->isPro() || Auth::user()->isTrial()))
<div class="modal fade" id="proPlanModal" tabindex="-1" role="dialog" aria-labelledby="proPlanModalLabel" aria-hidden="true">
<div class="modal-dialog large-dialog">
<div class="modal-content pro-plan-modal">
<div class="pull-right">
<img onclick="hideProPlan()" class="close" src="{{ asset('images/pro_plan/close.png') }}"/>
@ -670,7 +675,11 @@
<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') }}"/>
<a class="button" href="#" onclick="submitProPlan()">{{ trans('texts.pro_plan_call_to_action') }}</a>
@if (Auth::user()->isEligibleForTrial())
<a class="button" href="{{ URL::to('start_trial') }}">{{ trans('texts.trial_call_to_action') }}</a>
@else
<a class="button" href="#" onclick="submitProPlan()">{{ trans('texts.pro_plan_call_to_action') }}</a>
@endif
</center>
</div>
<div class="col-md-5">
@ -694,12 +703,19 @@
@endif
{{-- Per our license, please do not remove or modify this section. --}}
@if (!Utils::isNinjaProd())
</div>
<p>&nbsp;</p>
<br/>
<div class="container">
@if (Utils::isNinjaProd())
@if (Auth::check() && Auth::user()->isTrial())
{!! trans(Auth::user()->account->getCountTrialDaysLeft() == 0 ? 'texts.trial_footer_last_day' : 'texts.trial_footer', [
'count' => Auth::user()->account->getCountTrialDaysLeft(),
'link' => '<a href="javascript:submitProPlan()">' . trans('texts.click_here') . '</a>'
]) !!}
@endif
@else
{{ trans('texts.powered_by') }}
{{-- Per our license, please do not remove or modify this section. --}}
{!! link_to('https://www.invoiceninja.com/?utm_source=powered_by', 'InvoiceNinja.com', ['target' => '_blank', 'title' => 'invoiceninja.com']) !!} -
{!! link_to(RELEASES_URL, 'v' . NINJA_VERSION, ['target' => '_blank', 'title' => trans('texts.trello_roadmap')]) !!} |
@if (Auth::user()->account->isWhiteLabel())

View File

@ -215,6 +215,7 @@ function InvoiceModel(data) {
self.invoice_date = ko.observable('');
self.invoice_number = ko.observable('');
self.due_date = ko.observable('');
self.recurring_due_date = ko.observable('');
self.start_date = ko.observable('');
self.end_date = ko.observable('');
self.last_sent_date = ko.observable('');

View File

@ -201,6 +201,7 @@
<div class="row">
<div class="col-md-9">
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number')
->id('card_number')
->placeholder(trans('texts.card_number'))
->autocomplete('cc-number')
->data_stripe('number')
@ -208,6 +209,7 @@
</div>
<div class="col-md-3">
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv')
->id('cvv')
->placeholder(trans('texts.cvv'))
->autocomplete('off')
->data_stripe('cvc')
@ -217,6 +219,7 @@
<div class="row">
<div class="col-md-6">
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month')
->id('expiration_month')
->autocomplete('cc-exp-month')
->data_stripe('exp-month')
->placeholder(trans('texts.expiration_month'))
@ -236,6 +239,7 @@
</div>
<div class="col-md-6">
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year')
->id('expiration_year')
->autocomplete('cc-exp-year')
->data_stripe('exp-year')
->placeholder(trans('texts.expiration_year'))

View File

@ -27,7 +27,9 @@ class OnlinePaymentCest
$I->amOnPage('/settings/online_payments');
if (strpos($I->grabFromCurrentUrl(), 'create') !== false) {
$I->fillField(['name' =>'23_apiKey'], Fixtures::get('gateway_key'));
$I->fillField(['name' =>'23_apiKey'], Fixtures::get('secret_key'));
// Fails to load StripeJS causing "ReferenceError: Can't find variable: Stripe"
//$I->fillField(['name' =>'publishable_key'], Fixtures::get('publishable_key'));
$I->selectOption('#token_billing_type_id', 4);
$I->click('Save');
$I->see('Successfully created gateway');
@ -73,11 +75,12 @@ class OnlinePaymentCest
$I->fillField(['name' => 'state'], $this->faker->state);
$I->fillField(['name' => 'postal_code'], $this->faker->postcode);
$I->selectDropdown($I, 'United States', '.country-select .dropdown-toggle');
$I->fillField(['name' => 'card_number'], '4242424242424242');
$I->fillField(['name' => 'cvv'], '1234');
$I->fillField('#card_number', '4242424242424242');
$I->fillField('#cvv', '1234');
$I->selectOption('#expiration_month', 12);
$I->selectOption('#expiration_year', date('Y'));
$I->click('.btn-success');
$I->wait(3);
$I->see('Successfully applied payment');
});