Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: subscribe with the customer portal instead of stripe #6049

Draft
wants to merge 48 commits into
base: 4.x
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b475659
feat: subscribe with the customer portal instead of stripe
djaiss Mar 9, 2022
2cf35f6
Apply fixes from StyleCI
StyleCIBot Mar 9, 2022
7f16190
wip
djaiss Mar 9, 2022
1956aa1
Apply fixes from StyleCI
StyleCIBot Mar 9, 2022
fd4e04f
wip
djaiss Mar 9, 2022
736acae
Apply fixes from StyleCI
StyleCIBot Mar 9, 2022
af214e8
wip
djaiss Mar 9, 2022
b316fac
Apply fixes from StyleCI
StyleCIBot Mar 9, 2022
d313412
Merge branch 'main' into 2022-03-07-billing
djaiss Mar 9, 2022
0af387c
Update package.json
djaiss Mar 11, 2022
0ddd47b
Update composer.lock
djaiss Mar 11, 2022
2e93998
Update app/Services/Account/Subscription/ActivateLicenceKey.php
djaiss May 14, 2022
8778755
Apply fixes from StyleCI
StyleCIBot May 14, 2022
6e43528
Update resources/views/settings/subscriptions/blank.blade.php
djaiss May 15, 2022
0289cfe
Update resources/views/settings/subscriptions/blank.blade.php
djaiss May 15, 2022
5163375
Update app/Services/Account/Subscription/ActivateLicenceKey.php
djaiss May 15, 2022
d05d621
Update app/Services/Account/Subscription/ActivateLicenceKey.php
djaiss May 15, 2022
fecc952
Update app/Services/Account/Subscription/ActivateLicenceKey.php
djaiss May 15, 2022
444113e
Update app/Jobs/Settings/CheckLicenceKeys.php
djaiss May 15, 2022
c64e5d3
Update app/Models/Account/Account.php
djaiss May 15, 2022
cc37c80
Update app/Services/Account/Subscription/ActivateLicenceKey.php
djaiss May 15, 2022
f22a05e
changes
djaiss May 15, 2022
8a01c7e
adapt with last monicahq/customers changes
asbiin May 16, 2022
25f022c
Apply fixes from StyleCI
StyleCIBot May 16, 2022
4c74f4f
add tests
asbiin May 16, 2022
d806e91
Merge branch '2022-03-07-billing' of github.com:monicahq/monica into …
asbiin May 16, 2022
b5615db
Apply fixes from StyleCI
StyleCIBot May 16, 2022
94a3159
more tests
asbiin May 16, 2022
2df3c55
Apply fixes from StyleCI
StyleCIBot May 16, 2022
58af6db
Merge remote-tracking branch 'origin/main' into 2022-03-07-billing
asbiin May 16, 2022
0af744e
split decodeAndStoreKey
asbiin May 20, 2022
0b68cca
Apply fixes from StyleCI
StyleCIBot May 20, 2022
a9c6c64
Merge branch 'main' into 2022-03-07-billing
djaiss Jul 23, 2022
f8d6f42
Merge branch 'main' into 2022-03-07-billing
djaiss Aug 22, 2022
57f569b
Update AppServiceProvider.php
djaiss Aug 23, 2022
44ae474
Update AccountSubscriptionTest.php
djaiss Aug 23, 2022
ecb82c1
fixes
djaiss Aug 24, 2022
773b853
Delete AccountSubscriptionTest.php
djaiss Aug 25, 2022
7542222
fixes
djaiss Oct 2, 2022
a4fef9c
Merge branch 'main' into 2022-03-07-billing
djaiss Oct 2, 2022
30d9f99
Apply fixes from StyleCI
StyleCIBot Oct 2, 2022
fa6b646
fix
djaiss Oct 2, 2022
258c427
use MissingPrivateKeyException
asbiin Oct 18, 2022
9db1975
Apply fixes from StyleCI
StyleCIBot Oct 18, 2022
964972e
Merge remote-tracking branch 'origin/main' into 2022-03-07-billing
asbiin Oct 19, 2022
9884708
get next_check_at from api call
asbiin Oct 19, 2022
c6ac38e
fix job schedule
asbiin Oct 20, 2022
25a1916
update change and cancel links
asbiin Oct 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: subscribe with the customer portal instead of stripe
djaiss committed Mar 9, 2022
commit b475659248e5e06e01a8182080c06bcd09d48a8f
9 changes: 9 additions & 0 deletions app/Exceptions/NoCustomerPortalSetException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use RuntimeException;

class NoCustomerPortalSetException extends RuntimeException
{
}
9 changes: 9 additions & 0 deletions app/Exceptions/NoLicenceKeyEncryptionSetException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use RuntimeException;

class NoLicenceKeyEncryptionSetException extends RuntimeException
{
}
10 changes: 0 additions & 10 deletions app/Helpers/InstanceHelper.php
Original file line number Diff line number Diff line change
@@ -12,16 +12,6 @@

class InstanceHelper
{
/**
* Get the number of paid accounts in the instance.
*
* @return int
*/
public static function getNumberOfPaidSubscribers()
{
return Account::where('stripe_id', '!=', null)->count();
}

/**
* Get the plan information for the given time period.
*
81 changes: 29 additions & 52 deletions app/Http/Controllers/Settings/SubscriptionsController.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

namespace App\Http\Controllers\Settings;

use App\Exceptions\NoLicenceKeyEncryptionSetException;
use Illuminate\View\View;
use App\Traits\StripeCall;
use App\Helpers\DateHelper;
@@ -11,6 +12,7 @@
use App\Helpers\AccountHelper;
use App\Helpers\InstanceHelper;
use App\Exceptions\StripeException;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\App;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
@@ -19,6 +21,8 @@
use Stripe\PaymentIntent as StripePaymentIntent;
use Laravel\Cashier\Exceptions\IncompletePayment;
use App\Services\Account\Settings\ArchiveAllContacts;
use App\Services\Account\Subscription\ActivateLicenceKey;
use Exception;

class SubscriptionsController extends Controller
{
@@ -37,34 +41,38 @@ public function index()

$account = auth()->user()->account;

$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && (! $subscription || $subscription->ended())) {
if (! $account->isSubscribed()) {
return view('settings.subscriptions.blank', [
'numberOfCustomers' => InstanceHelper::getNumberOfPaidSubscribers(),
'customerPortalUrl' => config('monica.customer_portal_url'),
]);
}

$hasInvoices = $account->hasStripeId() && $account->hasInvoices();
$invoices = null;
if ($hasInvoices) {
$invoices = $account->invoices();
}
return view('settings.subscriptions.account', [
'planInformation' => trans('settings.subscriptions_licence_key_frequency_'.$account->frequency),
'nextBillingDate' => DateHelper::getFullDate($account->valid_until_at),
'accountHasLimitations' => AccountHelper::hasLimitations($account),
'customerPortalUrl' => config('monica.customer_portal_url'),
]);
}

public function store(Request $request)
{
try {
$planInformation = $this->stripeCall(function () use ($subscription) {
return InstanceHelper::getPlanInformationFromSubscription($subscription);
});
} catch (StripeException $e) {
$planInformation = null;
app(ActivateLicenceKey::class)->execute([
'account_id' => auth()->user()->account_id,
'licence_key' => $request->input('licence_key'),
]);
} catch (ValidationException $e) {
return back()
->withInput()
->withErrors($e->validator);
} catch (Exception $e) {
return back()
->withInput()
->withErrors($e->getMessage());
}

return view('settings.subscriptions.account', [
'planInformation' => $planInformation,
'subscription' => $subscription,
'hasInvoices' => $hasInvoices,
'invoices' => $invoices,
'accountHasLimitations' => AccountHelper::hasLimitations($account),
]);
return view('settings.subscriptions.success');
}

/**
@@ -117,9 +125,7 @@ public function update(Request $request)

$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && (! $subscription || $subscription->ended())) {
return view('settings.subscriptions.blank', [
'numberOfCustomers' => InstanceHelper::getNumberOfPaidSubscribers(),
]);
return view('settings.subscriptions.blank');
}

$planInformation = InstanceHelper::getPlanInformationFromSubscription($subscription);
@@ -276,35 +282,6 @@ public function downgrade()
->with('canDowngrade', AccountHelper::canDowngrade($account));
}

/**
* Process the downgrade process.
*
* @return RedirectResponse
*/
public function processDowngrade()
{
$account = auth()->user()->account;

if (! AccountHelper::canDowngrade($account)) {
return redirect()->route('settings.subscriptions.downgrade');
}

$subscription = $account->getSubscribedPlan();
if (! $account->isSubscribed() && ! $subscription) {
return redirect()->route('settings.index');
}

try {
$account->subscriptionCancel();
} catch (StripeException $e) {
return back()
->withInput()
->withErrors($e->getMessage());
}

return redirect()->route('settings.subscriptions.downgrade.success');
}

/**
* Process the upgrade payment.
*
12 changes: 12 additions & 0 deletions app/Models/Account/Account.php
Original file line number Diff line number Diff line change
@@ -66,6 +66,9 @@ class Account extends Model
'api_key',
'default_time_reminder_is_sent',
'default_gender_id',
'licence_key',
'valid_until_at',
'purchaser_email',
];

/**
@@ -77,6 +80,15 @@ class Account extends Model
'has_access_to_paid_version_for_free' => 'boolean',
];

/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = [
'valid_until_at',
];

/**
* Get the activity records associated with the account.
*
105 changes: 105 additions & 0 deletions app/Services/Account/Subscription/ActivateLicenceKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace App\Services\Account\Subscription;

use App\Exceptions\NoCustomerPortalSetException;
use App\Exceptions\NoLicenceKeyEncryptionSetException;
use App\Models\Account\Account;
use App\Models\Account\Photo;
use App\Services\BaseService;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\App;

class ActivateLicenceKey extends BaseService
{
private Account $account;
private array $data;
private Response $response;

/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'licence_key' => 'required|string:255',
];
}

/**
* Check if the licence key given by the user is a valid licence key.
* If it is, activate the licence key and set the valid_until_at date.
*
* @param array $data
* @return void
*/
public function execute(array $data): void
{
$this->validate($data);
$this->data = $data;
$this->account = Account::findOrFail($data['account_id']);

$this->validateEnvVariables();
$this->makeRequestToCustomerPortal();
$this->checkResponseCode();
$this->decodeAndStoreKey();
}

private function validateEnvVariables(): void
{
if (!config('monica.licence_key_encryption_key')) {
throw new NoLicenceKeyEncryptionSetException();
}

if (config('monica.customer_portal_url') == '') {
throw new NoCustomerPortalSetException();
}
}

private function makeRequestToCustomerPortal(): void
{
$url = config('monica.customer_portal_url') . '/validate/key/' . $this->data['licence_key'];

// necessary for testing purposes
if (App::environment('production')) {
$this->response = Http::get($url);
} else {
$this->response = Http::withOptions(['verify' => false])->get($url);
}
}

private function checkResponseCode(): void
{
if ($this->response->status() == 404) {
throw new Exception(trans('settings.subscriptions_licence_key_does_not_exist'));
}

if ($this->response->status() == 900) {
throw new Exception(trans('settings.subscriptions_licence_key_invalid'));
}

if ($this->response->status() != 200) {
throw new Exception(trans('settings.subscriptions_licence_key_problem'));
}
}

private function decodeAndStoreKey(): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decode and store should be 2 actions/functions

{
$licenceKey = base64_decode($this->data['licence_key']);
$licenceKey = Str::replace('123', '', $licenceKey);
$array = json_decode($licenceKey, true);

$this->account->licence_key = $this->data['licence_key'];
$this->account->valid_until_at = $array[0]['next_check_at'];
$this->account->purchaser_email = $array[0]['purchaser_email'];
$this->account->frequency = $array[0]['frequency'];
$this->account->save();
}
}
46 changes: 46 additions & 0 deletions app/Services/Account/Subscription/ValidateLicenceKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Services\Account\Photo;

use App\Models\Account\Photo;
use App\Services\BaseService;
use Illuminate\Support\Facades\Storage;

class ValidateLicenceKey extends BaseService
{
/**
* Get the validation rules that apply to the service.
*
* @return array
*/
public function rules()
{
return [
'account_id' => 'required|integer|exists:accounts,id',
'licence_key' => 'required|integer|exists:photos,id',
];
}

/**
* Destroy a photo.
*
* @param array $data
* @return bool
*/
public function execute(array $data): bool
{
$this->validate($data);

$photo = Photo::where('account_id', $data['account_id'])
->findOrFail($data['photo_id']);

// Delete the physical photo
// Throws FileNotFoundException
Storage::delete($photo->new_filename);

// Delete the object in the DB
$photo->delete();

return true;
}
}
10 changes: 9 additions & 1 deletion app/Traits/Subscription.php
Original file line number Diff line number Diff line change
@@ -77,7 +77,15 @@ public function isSubscribed()
return true;
}

return $this->getSubscribedPlan() !== null;
if (! $this->licence_key) {
return false;
}

if ($this->valid_until_at->isPast()) {
return false;
}

return true;
}

/**
1,371 changes: 731 additions & 640 deletions composer.lock

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions config/monica.php
Original file line number Diff line number Diff line change
@@ -284,4 +284,26 @@
*/
'export_size' => (int) env('EXPORT_SIZE', 5),

/*
|--------------------------------------------------------------------------
| Licence key server
|--------------------------------------------------------------------------
|
| When REQUIRES_SUBSCRIPTION is set to true, we need to check if the user
| has a valid licence key to unlock paid features. Licence keys are managed
| on our own customer portal.
|
*/
'customer_portal_url' => env('CUSTOMER_PORTAL_URL', ''),

/*
|--------------------------------------------------------------------------
| Licence key encryption key
|--------------------------------------------------------------------------
|
| All licence keys are encrypted with this key on the customer portal when
| the key is generated.
|
*/
'licence_key_encryption_key' => env('LICENCE_KEY_ENCRYPTION_KEY', null),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddLicenceKeysToAccounts extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('accounts', function (Blueprint $table) {
$table->string('licence_key')->after('uuid')->nullable();
$table->datetime('valid_until_at')->after('licence_key')->nullable();
$table->string('purchaser_email')->after('valid_until_at')->nullable();
$table->string('frequency')->after('purchaser_email')->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('accounts', function (Blueprint $table) {
$table->dropColumn('licence_keys');
$table->dropColumn('valid_until_at');
});
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@
"composer update": "COMPOSER_MEMORY_LIMIT=-1 composer update"
},
"engines": {
"node": "16.x",
"node": "17.x",
"yarn": "1.22.x"
},
"devDependencies": {
11 changes: 9 additions & 2 deletions resources/lang/en/settings.php
Original file line number Diff line number Diff line change
@@ -217,7 +217,7 @@
'subscriptions_account_free_plan_benefits_support' => 'Support the project in the long run, so we can introduce more great features.',
'subscriptions_account_upgrade' => 'Upgrade your account',
'subscriptions_account_upgrade_title' => 'Upgrade Monica today and have more meaningful relationships.',
'subscriptions_account_upgrade_choice' => 'Pick a plan below and join over :customers persons who upgraded their Monica.',
'subscriptions_account_upgrade_choice' => 'Monica requires a licence key to be completely functional.',
'subscriptions_account_update_title' => 'Update Monica subscription',
'subscriptions_account_update_description' => 'You can change your subscription’s frequency here.',
'subscriptions_account_update_information' => 'You will be billed immediately for the new amount. Your subscription will extend to the new period, depending on your choice.',
@@ -263,7 +263,7 @@
'subscriptions_pdf_title' => 'Your :name monthly subscription',
'subscriptions_plan_frequency_year' => ':amount / year',
'subscriptions_plan_frequency_month' => ':amount / month',
'subscriptions_plan_choose' => 'Choose this plan',
'subscriptions_plan_choose' => 'Get your licence key',
'subscriptions_plan_year_title' => 'Pay annually',
'subscriptions_plan_year_bonus' => 'Peace of mind for a whole year',
'subscriptions_plan_month_title' => 'Pay monthly',
@@ -272,6 +272,8 @@
'subscriptions_plan_include2' => 'Unlimited number of contacts • Unlimited number of users • Reminders by email • Import with vCard • Personalization of the contact sheet',
'subscriptions_plan_include3' => '100% of the profits go the development of this great open source project.',
'subscriptions_help_title' => 'Additional details you may be curious about',
'subscriptions_help_licencekey_title' => 'What is a licence key?',
'subscriptions_help_licencekey_desc' => 'A licence key is an unique identifier that you will get once you purchase a plan. It will be used to unlock all the paid features in your account. Licence keys are managed on https://customers.monicahq.com and you will need to create a new account on this site to get your licence key, that you will need to paste here.',
'subscriptions_help_opensource_title' => 'What is an open source project?',
'subscriptions_help_opensource_desc' => 'Monica is an open source project. This means it is built by a community who wants to build a great tool for the greater good. Being open source means the code is publicly available on GitHub, and everyone can inspect it, modify it or enhance it. All the money we raise is dedicated to building better features, paying for more powerful servers, and paying other costs. Thanks for your help. We couldn’t do it without you.',
'subscriptions_help_limits_title' => 'Is there a limit to the number of contacts we can have on the free plan?',
@@ -280,6 +282,11 @@
'subscriptions_help_discounts_desc' => 'We do! Monica is free for students, and free for non-profits and charities. Just contact <a href=":support">the support</a> with a proof of your status and we’ll apply this special status in your account.',
'subscriptions_help_change_title' => 'What if I change my mind?',
'subscriptions_help_change_desc' => 'You can cancel anytime, no questions asked, and all by yourself – no need to contact support. However, you will not be refunded for the current period.',
'subscriptions_licence_key_does_not_exist' => 'The licence key does not appear to exist in our system. Please try again.',
'subscriptions_licence_key_invalid' => 'This licence key is not valid anymore. Please renew your licence.',
'subscriptions_licence_key_problem' => 'There is a problem with the system.',
'subscriptions_licence_key_frequency_monthly' => 'Monthly',
'subscriptions_licence_key_frequency_annual' => 'Annual',

'stripe_error_card' => 'Your card was declined. Decline message is: :message',
'stripe_error_api_connection' => 'Network communication with Stripe failed. Try again later.',
43 changes: 5 additions & 38 deletions resources/views/settings/subscriptions/account.blade.php
Original file line number Diff line number Diff line change
@@ -33,13 +33,11 @@
<div class="col-12 col-sm-9 subscriptions">

<div class="br3 ba b--gray-monica bg-white mb4">
<div class="pa3 bb b--gray-monica">
<div class="pa3">

<h3>{{ trans('settings.subscriptions_account_current_plan') }}</h3>

<p>{{ trans('settings.subscriptions_account_current_paid_plan', ['name' => $planInformation['name']]) }}</p>

@include('partials.subscription')
<p>{{ trans('settings.subscriptions_account_current_paid_plan', ['name' => $planInformation]) }}</p>

<div class="dt dt--fixed w-100 collapse br--top br--bottom">
<div class="dt-row">
@@ -50,15 +48,12 @@
</div>
<div class="dtc w-60">
<div class="ph2">
{!! trans('settings.subscriptions_account_next_billing', ['date' => $planInformation['nextBillingDate']]) !!}
</div>
<div class="ph2 pb2">
{!! trans('settings.subscriptions_account_bill_' . $planInformation['type'], ['price' => $planInformation['friendlyPrice']]) !!}
{!! trans('settings.subscriptions_account_next_billing', ['date' => $nextBillingDate]) !!}
</div>
</div>
<div class="dtc {{ htmldir() == 'ltr' ? 'tr' : 'tl' }}">
<div class="pa2">
<a href="{{ route('settings.subscriptions.update') }}">{{ trans('settings.subscriptions_account_change') }}</a>
<a href="{{ $customerPortalUrl }}">{{ trans('settings.subscriptions_account_change') }}</a>
</div>
</div>
</div>
@@ -76,42 +71,14 @@
</div>
<div class="dtc {{ htmldir() == 'ltr' ? 'tr' : 'tl' }}">
<div class="pa2">
<a href="{{ route('settings.subscriptions.downgrade') }}">
<a href="{{ $customerPortalUrl }}">
{{ trans('settings.subscriptions_account_cancel_action') }}
</a>
</div>
</div>
</div>
</div>


{{-- Only display invoices if the subscription exists or existed --}}
@if ($hasInvoices)
<div class="invoices pt4">
<h3>{{ trans('settings.subscriptions_account_invoices') }}</h3>
<ul class="table">
@foreach ($invoices as $invoice)
<li class="table-row" title="{{
trans('settings.subscriptions_account_invoices_subscription', [
'startDate' => \App\Helpers\DateHelper::getFullDate(Arr::first($invoice->subscriptions())->startDateAsCarbon()),
'endDate' => \App\Helpers\DateHelper::getFullDate(Arr::first($invoice->subscriptions())->endDateAsCarbon())
])
}}">
<div class="table-cell date">
{{ \App\Helpers\DateHelper::getFullDate($invoice->date()) }}
</div>
<div class="table-cell">
{{ $invoice->total() }}
</div>
<div class="table-cell">
<a href="{{ route('settings.subscriptions.invoice', $invoice->id) }}">{{ trans('settings.subscriptions_account_invoices_download') }}</a>
</div>
</li>
@endforeach
</ul>
</div>
@endif

</div>
</div>

35 changes: 29 additions & 6 deletions resources/views/settings/subscriptions/blank.blade.php
Original file line number Diff line number Diff line change
@@ -28,13 +28,13 @@
<div class="main-content">
<div class="{{ Auth::user()->getFluidLayout() }}">
<div class="row">
<div class="col-12 col-sm-8 offset-sm-2">
<div class="col-12 col-sm-6 offset-sm-3">

<h2 class="tc mt4 fw4">{{ trans('settings.subscriptions_account_upgrade_title') }}</h2>
<p class="tc mb4">{{ trans('settings.subscriptions_account_upgrade_choice', ['customers' => $numberOfCustomers]) }}</p>
<p class="tc mb4">{{ trans('settings.subscriptions_account_upgrade_choice') }}</p>

<div class="br3 ba b--gray-monica bg-white mb4">
<div class="pa4 bb b--gray-monica">
<div class="pa4">

<h3 class="tc">{{ trans('settings.subscriptions_account_payment') }}</h3>
<div class="cf mb4">
@@ -43,7 +43,7 @@
<img src="img/settings/subscription/best_value.png" class="absolute" style="top: -30px; left: -20px;">
<h3 class="tc mb3 pt3">{{ trans('settings.subscriptions_plan_year_title') }}</h3>
<p class="tc">
<a href="{{ route('settings.subscriptions.upgrade') }}?plan=annual" class="btn btn-primary pv3">{{ trans('settings.subscriptions_plan_choose') }}</a>
<a href="{{ $customerPortalUrl }}" target="_blank" class="btn btn-primary pv3">{{ trans('settings.subscriptions_plan_choose') }}</a>
</p>
<p class="tc mt2">
{{ trans('settings.subscriptions_plan_frequency_year', ['amount' => \App\Helpers\InstanceHelper::getPlanInformationFromConfig('annual')['friendlyPrice']]) }}
@@ -68,7 +68,7 @@
<div class="b--gray-monica ba pt3 br3 bw1">
<h3 class="tc mb3 pt3">{{ trans('settings.subscriptions_plan_month_title') }}</h3>
<p class="tc">
<a href="{{ route('settings.subscriptions.upgrade') }}?plan=monthly" class="btn btn-primary pv3">{{ trans('settings.subscriptions_plan_choose') }}</a>
<a href="{{ $customerPortalUrl }}" target="_blank" class="btn btn-primary pv3">{{ trans('settings.subscriptions_plan_choose') }}</a>
</p>
<p class="tc mt2">
{{ trans('settings.subscriptions_plan_frequency_month', ['amount' => \App\Helpers\InstanceHelper::getPlanInformationFromConfig('monthly')['friendlyPrice']]) }}
@@ -90,13 +90,36 @@
</div>
</div>
</div>
<p class="mb1 tc">{{ trans('settings.subscriptions_plan_include1') }}</p>

<!-- perks -->
<p class="mb1 tc fw5">{{ trans('settings.subscriptions_plan_include1') }}</p>
<p class="mb1 tc">{{ trans('settings.subscriptions_plan_include2') }}</p>
<p class="mb1 tc">{{ trans('settings.subscriptions_plan_include3') }}</p>
</div>
</div>

<!-- licence key -->
<div class="br3 pa4 ba b--gray-monica bg-white mb4">

<h3 class="tc mb3">Do you have your licence key?</h3>

@include('partials.errors')

<form action="{{ route('settings.subscriptions.store') }}" method="POST">
@csrf

<div class="flex-ns items-end">
<form-input value="" :input-type="'text'" :id="'licence_key'" :required="true" :title="'Please paste your licence key here'">
</form-input>
<div><button name="save" type="submit" class="ml2 btn btn-primary w-auto-ns w-100 pb0-ns">Save</button></div>
</div>
</form>
</div>

<h3 class="tc mb4 mt3">{{ trans('settings.subscriptions_help_title') }}</h3>
<h4>{{ trans('settings.subscriptions_help_licencekey_title') }}</h4>
<p class="mb4">{{ trans('settings.subscriptions_help_licencekey_desc') }}</p>

<h4>{{ trans('settings.subscriptions_help_opensource_title') }}</h4>
<p class="mb4">{{ trans('settings.subscriptions_help_opensource_desc') }}</p>

2 changes: 1 addition & 1 deletion resources/views/settings/subscriptions/success.blade.php
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
<a href="{{ route('dashboard.index') }}">{{ trans('app.breadcrumb_dashboard') }}</a>
</li>
<li>
<a href="{{ route('settings.index') }}">{{ trans('app.breadcrumb_settings') }}</a>
<a href="{{ route('settings.subscriptions.index') }}">{{ trans('app.breadcrumb_settings') }}</a>
</li>
<li>
{{ trans('app.breadcrumb_settings_subscriptions') }}
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
@@ -272,6 +272,8 @@

Route::name('subscriptions.')->group(function () {
Route::get('/settings/subscriptions', 'Settings\\SubscriptionsController@index')->name('index');
Route::post('/settings/subscriptions', 'Settings\\SubscriptionsController@store')->name('store');

Route::get('/settings/subscriptions/upgrade', 'Settings\\SubscriptionsController@upgrade')->name('upgrade');
Route::get('/settings/subscriptions/upgrade/success', 'Settings\\SubscriptionsController@upgradeSuccess')->name('upgrade.success');
Route::get('/settings/subscriptions/update', 'Settings\\SubscriptionsController@update')->name('update');
16 changes: 0 additions & 16 deletions tests/Feature/AccountSubscriptionTest.php
Original file line number Diff line number Diff line change
@@ -156,22 +156,6 @@ public function test_it_get_the_plan_name()
$this->assertEquals('Annual', $user->account->getSubscribedPlanName());
}

public function test_it_throw_an_error_on_cancel()
{
$user = $this->signin();

factory(Subscription::class)->create([
'account_id' => $user->account_id,
'name' => 'Annual',
'stripe_plan' => 'annual',
'stripe_id' => 'test',
'quantity' => 1,
]);

$this->expectException(\App\Exceptions\StripeException::class);
$user->account->subscriptionCancel();
}

public function test_it_get_subscription_page()
{
$user = $this->signin();
13 changes: 0 additions & 13 deletions tests/Unit/Helpers/InstanceHelperTest.php
Original file line number Diff line number Diff line change
@@ -14,19 +14,6 @@ class InstanceHelperTest extends TestCase
{
use DatabaseTransactions;

/** @test */
public function it_gets_the_number_of_paid_subscribers()
{
factory(Account::class)->create(['stripe_id' => 'id292839']);
factory(Account::class)->create();
factory(Account::class)->create(['stripe_id' => 'id2sdf92839']);

$this->assertEquals(
2,
InstanceHelper::getNumberOfPaidSubscribers()
);
}

/** @test */
public function it_fetches_the_monthly_plan_information()
{
43 changes: 12 additions & 31 deletions tests/Unit/Models/AccountTest.php
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
use App\Models\Account\ActivityTypeCategory;
use App\Models\Relationship\RelationshipType;
use App\Models\Relationship\RelationshipTypeGroup;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class AccountTest extends FeatureTestCase
@@ -277,6 +278,7 @@ public function user_is_subscribed_returns_false_if_not_subcribed()
{
$account = factory(Account::class)->make([
'has_access_to_paid_version_for_free' => false,
'licence_key' => null,
]);

$this->assertEquals(
@@ -286,50 +288,29 @@ public function user_is_subscribed_returns_false_if_not_subcribed()
}

/** @test */
public function user_is_subscribed_returns_true_if_monthly_plan_is_set()
public function user_is_subscribed_returns_true_if_there_is_a_valid_licence_key()
{
$account = factory(Account::class)->create();

$plan = factory(\Laravel\Cashier\Subscription::class)->create([
'account_id' => $account->id,
'stripe_plan' => 'chandler_5',
'stripe_id' => 'sub_C0R444pbxddhW7',
'name' => 'fakePlan',
Carbon::setTestNow(Carbon::create(2018, 1, 1));
$account = factory(Account::class)->create([
'licence_key' => '123',
'valid_until_at' => '2022-01-01',
]);

config(['monica.paid_plan_monthly_friendly_name' => 'fakePlan']);

$this->assertEquals(
true,
$account->isSubscribed()
);
}

/** @test */
public function user_is_subscribed_returns_true_if_annual_plan_is_set()
public function user_is_subscribed_returns_false_if_there_is_a_valid_key_but_expired()
{
$account = factory(Account::class)->create();

$plan = factory(\Laravel\Cashier\Subscription::class)->create([
'account_id' => $account->id,
'stripe_plan' => 'chandler_annual',
'stripe_id' => 'sub_C0R444pbxddhW7',
'name' => 'annualPlan',
Carbon::setTestNow(Carbon::create(2018, 1, 1));
$account = factory(Account::class)->create([
'licence_key' => '123',
'valid_until_at' => '1999-01-01',
]);

config(['monica.paid_plan_annual_friendly_name' => 'annualPlan']);

$this->assertEquals(
true,
$account->isSubscribed()
);
}

/** @test */
public function user_is_subscribed_returns_false_if_no_plan_is_set()
{
$account = factory(Account::class)->create();

$this->assertEquals(
false,
$account->isSubscribed()
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Tests\Unit\Services\Account\Subscription;

use App\Models\Account\Account;
use Tests\TestCase;
use App\Models\User\User;
use App\Models\Contact\Contact;
use App\Services\Account\Subscription\ActivateLicenceKey;
use Illuminate\Validation\ValidationException;
use App\Services\Account\Settings\DestroyAccount;
use Exception;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Http;

class ActivateLicenceKeyTest extends TestCase
{
use DatabaseTransactions;

/** @test */
public function it_activates_a_licence_key()
{
config(['monica.licence_key_encryption_key' => '123']);
Http::fake();

$key = 'W3siZnJlcXVlbmN5IjoibW9udGhseSIsInB1cmNoYXNlcl9lbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm5leHRfY2hlY2tfYXQiOiIyMDIyLTA0LTAzVDAwOjAwOjAwLjAwMDAwMFoifV0=';
$account = factory(Account::class)->create([]);

$request = [
'account_id' => $account->id,
'licence_key' => $key,
];

app(ActivateLicenceKey::class)->execute($request);

$this->assertDatabaseHas('accounts', [
'id' => $account->id,
'licence_key' => 'W3siZnJlcXVlbmN5IjoibW9udGhseSIsInB1cmNoYXNlcl9lbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm5leHRfY2hlY2tfYXQiOiIyMDIyLTA0LTAzVDAwOjAwOjAwLjAwMDAwMFoifV0=',
'valid_until_at' => '2022-04-03 00:00:00',
'purchaser_email' => 'admin@admin.com',
'frequency' => 'monthly',
]);
}

/** @test */
public function it_fails_if_wrong_parameters_are_given()
{
$request = [];

$this->expectException(ValidationException::class);
app(ActivateLicenceKey::class)->execute($request);
}

/** @test */
public function it_fails_if_the_licence_key_does_not_exist()
{
$this->expectException(Exception::class);

Http::fake(function ($request) {
return Http::response('', 404);
});

$account = factory(Account::class)->create([]);

$request = [
'account_id' => $account->id,
'licence_key' => '',
];

app(ActivateLicenceKey::class)->execute($request);
}

/** @test */
public function it_fails_if_the_licence_key_is_not_valid_anymore()
{
$this->expectException(Exception::class);

Http::fake(function ($request) {
return Http::response('', 900);
});

$key = 'W3siZnJlcXVlbmN5IjoibW9udGhseSIsInB1cmNoYXNlcl9lbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm5leHRfY2hlY2tfYXQiOiIyMDIyLTA0LTAzVDAwOjAwOjAwLjAwMDAwMFoifV0=';
$account = factory(Account::class)->create([]);

$request = [
'account_id' => $account->id,
'licence_key' => $key,
];

app(ActivateLicenceKey::class)->execute($request);
}
}