mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: pause, resume main subscription
This commit is contained in:
@@ -8,6 +8,7 @@ import SubscriptionService from '@/services/Subscription/SubscriptionService';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
|
import { LemonSqueezyService } from '@/services/Subscription/LemonSqueezyService';
|
||||||
|
import { SubscriptionApplication } from '@/services/Subscription/SubscriptionApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SubscriptionController extends BaseController {
|
export class SubscriptionController extends BaseController {
|
||||||
@@ -17,6 +18,9 @@ export class SubscriptionController extends BaseController {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private lemonSqueezyService: LemonSqueezyService;
|
private lemonSqueezyService: LemonSqueezyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private subscriptionApp: SubscriptionApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Router constructor.
|
* Router constructor.
|
||||||
*/
|
*/
|
||||||
@@ -33,6 +37,14 @@ export class SubscriptionController extends BaseController {
|
|||||||
this.validationResult,
|
this.validationResult,
|
||||||
this.getCheckoutUrl.bind(this)
|
this.getCheckoutUrl.bind(this)
|
||||||
);
|
);
|
||||||
|
router.post('/cancel', asyncMiddleware(this.cancelSubscription.bind(this)));
|
||||||
|
router.post('/resume', asyncMiddleware(this.resumeSubscription.bind(this)));
|
||||||
|
router.post(
|
||||||
|
'/change',
|
||||||
|
[body('variant_id').exists().trim()],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.changeSubscriptionPlan.bind(this))
|
||||||
|
);
|
||||||
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
|
router.get('/', asyncMiddleware(this.getSubscriptions.bind(this)));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
@@ -85,4 +97,84 @@ export class SubscriptionController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the subscription of the current organization.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response|null>}
|
||||||
|
*/
|
||||||
|
private async cancelSubscription(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.subscriptionApp.cancelSubscription(tenantId, '455610');
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
status: 200,
|
||||||
|
message: 'The organization subscription has been canceled.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the subscription of the current organization.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response | null>}
|
||||||
|
*/
|
||||||
|
private async resumeSubscription(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.subscriptionApp.resumeSubscription(tenantId);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
status: 200,
|
||||||
|
message: 'The organization subscription has been resumed.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the main subscription plan of the current organization.
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
* @returns {Promise<Response | null>}
|
||||||
|
*/
|
||||||
|
public async changeSubscriptionPlan(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { tenantId } = req;
|
||||||
|
const body = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.subscriptionApp.changeSubscriptionPlan(
|
||||||
|
tenantId,
|
||||||
|
body.variantId
|
||||||
|
);
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'The subscription plan has been changed.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { cancelSubscription } from '@lemonsqueezy/lemonsqueezy.js';
|
||||||
|
import { configureLemonSqueezy } from './utils';
|
||||||
|
import { PlanSubscription } from '@/system/models';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { ERRORS, IOrganizationSubscriptionCanceled } from './types';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class LemonCancelSubscription {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the subscription of the given tenant.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} subscriptionId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async cancelSubscription(tenantId: number) {
|
||||||
|
configureLemonSqueezy();
|
||||||
|
|
||||||
|
const subscription = await PlanSubscription.query().findOne({
|
||||||
|
tenantId,
|
||||||
|
slug: 'main',
|
||||||
|
});
|
||||||
|
if (!subscription) {
|
||||||
|
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||||
|
}
|
||||||
|
const lemonSusbcriptionId = subscription.lemonSubscriptionId;
|
||||||
|
const subscriptionId = subscription.id;
|
||||||
|
const cancelledSub = await cancelSubscription(lemonSusbcriptionId);
|
||||||
|
|
||||||
|
if (cancelledSub.error) {
|
||||||
|
throw new Error(cancelledSub.error.message);
|
||||||
|
}
|
||||||
|
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||||
|
canceledAt: new Date(),
|
||||||
|
});
|
||||||
|
// Triggers `onSubscriptionCanceled` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.subscription.onSubscriptionCanceled,
|
||||||
|
{ tenantId, subscriptionId } as IOrganizationSubscriptionCanceled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { updateSubscription } from '@lemonsqueezy/lemonsqueezy.js';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { PlanSubscription } from '@/system/models';
|
||||||
|
import { configureLemonSqueezy } from './utils';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { IOrganizationSubscriptionChanged } from './types';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class LemonChangeSubscriptionPlan {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the given organization subscription plan.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} newVariantId - New variant id.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async changeSubscriptionPlan(tenantId: number, newVariantId: number) {
|
||||||
|
configureLemonSqueezy();
|
||||||
|
|
||||||
|
const subscription = await PlanSubscription.query().findOne({
|
||||||
|
tenantId,
|
||||||
|
slug: 'main',
|
||||||
|
});
|
||||||
|
const lemonSubscriptionId = subscription.lemonSubscriptionId;
|
||||||
|
|
||||||
|
// Send request to Lemon Squeezy to change the subscription.
|
||||||
|
const updatedSub = await updateSubscription(lemonSubscriptionId, {
|
||||||
|
variantId: newVariantId,
|
||||||
|
});
|
||||||
|
if (updatedSub.error) {
|
||||||
|
throw new ServiceError('SOMETHING_WENT_WRONG');
|
||||||
|
}
|
||||||
|
// Triggers `onSubscriptionPlanChanged` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.subscription.onSubscriptionPlanChanged,
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
lemonSubscriptionId,
|
||||||
|
newVariantId,
|
||||||
|
} as IOrganizationSubscriptionChanged
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { configureLemonSqueezy } from './utils';
|
||||||
|
import { PlanSubscription } from '@/system/models';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { ERRORS, IOrganizationSubscriptionResumed } from './types';
|
||||||
|
import { updateSubscription } from '@lemonsqueezy/lemonsqueezy.js';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class LemonResumeSubscription {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the main subscription of the given tenant.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async resumeSubscription(tenantId: number) {
|
||||||
|
configureLemonSqueezy();
|
||||||
|
|
||||||
|
const subscription = await PlanSubscription.query().findOne({
|
||||||
|
tenantId,
|
||||||
|
slug: 'main',
|
||||||
|
});
|
||||||
|
if (!subscription) {
|
||||||
|
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||||
|
}
|
||||||
|
const subscriptionId = subscription.id;
|
||||||
|
const lemonSubscriptionId = subscription.lemonSubscriptionId;
|
||||||
|
const returnedSub = await updateSubscription(lemonSubscriptionId, {
|
||||||
|
cancelled: false,
|
||||||
|
});
|
||||||
|
if (returnedSub.error) {
|
||||||
|
throw new ServiceError('');
|
||||||
|
}
|
||||||
|
// Update the subscription of the organization.
|
||||||
|
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||||
|
canceledAt: null,
|
||||||
|
});
|
||||||
|
// Triggers `onSubscriptionCanceled` event.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.subscription.onSubscriptionResumed,
|
||||||
|
{ tenantId, subscriptionId } as IOrganizationSubscriptionResumed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { LemonCancelSubscription } from './LemonCancelSubscription';
|
||||||
|
import { LemonChangeSubscriptionPlan } from './LemonChangeSubscriptionPlan';
|
||||||
|
import { LemonResumeSubscription } from './LemonResumeSubscription';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SubscriptionApplication {
|
||||||
|
@Inject()
|
||||||
|
private cancelSubscriptionService: LemonCancelSubscription;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private resumeSubscriptionService: LemonResumeSubscription;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private changeSubscriptionPlanService: LemonChangeSubscriptionPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the subscription of the given tenant.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public cancelSubscription(tenantId: number, id: string) {
|
||||||
|
return this.cancelSubscriptionService.cancelSubscription(tenantId, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the subscription of the given tenant.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public resumeSubscription(tenantId: number) {
|
||||||
|
return this.resumeSubscriptionService.resumeSubscription(tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the given organization subscription plan.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} newVariantId
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
public changeSubscriptionPlan(tenantId: number, newVariantId: number) {
|
||||||
|
return this.changeSubscriptionPlanService.changeSubscriptionPlan(
|
||||||
|
tenantId,
|
||||||
|
newVariantId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/server/src/services/Subscription/types.ts
Normal file
20
packages/server/src/services/Subscription/types.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT:
|
||||||
|
'SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT',
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IOrganizationSubscriptionChanged {
|
||||||
|
tenantId: number;
|
||||||
|
lemonSubscriptionId: string;
|
||||||
|
newVariantId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOrganizationSubscriptionCanceled {
|
||||||
|
tenantId: number;
|
||||||
|
subscriptionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOrganizationSubscriptionResumed {
|
||||||
|
tenantId: number;
|
||||||
|
subscriptionId: number;
|
||||||
|
}
|
||||||
@@ -40,6 +40,15 @@ export default {
|
|||||||
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Organization subscription.
|
||||||
|
*/
|
||||||
|
subscription: {
|
||||||
|
onSubscriptionCanceled: 'onSubscriptionCanceled',
|
||||||
|
onSubscriptionResumed: 'onSubscriptionResumed',
|
||||||
|
onSubscriptionPlanChanged: 'onSubscriptionPlanChanged',
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tenants managment service.
|
* Tenants managment service.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||||
|
table.string('lemon_subscription_id').nullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('subscription_plan_subscriptions', (table) => {
|
||||||
|
table.dropColumn('lemon_subscription_id');
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -4,6 +4,8 @@ import moment from 'moment';
|
|||||||
import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod';
|
import SubscriptionPeriod from '@/services/Subscription/SubscriptionPeriod';
|
||||||
|
|
||||||
export default class PlanSubscription extends mixin(SystemModel) {
|
export default class PlanSubscription extends mixin(SystemModel) {
|
||||||
|
lemonSubscriptionId: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import ProjectAlerts from '@/containers/Projects/containers/ProjectAlerts';
|
|||||||
import TaxRatesAlerts from '@/containers/TaxRates/alerts';
|
import TaxRatesAlerts from '@/containers/TaxRates/alerts';
|
||||||
import { CashflowAlerts } from '../CashFlow/CashflowAlerts';
|
import { CashflowAlerts } from '../CashFlow/CashflowAlerts';
|
||||||
import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts';
|
||||||
|
import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
...AccountsAlerts,
|
...AccountsAlerts,
|
||||||
@@ -56,5 +57,6 @@ export default [
|
|||||||
...ProjectAlerts,
|
...ProjectAlerts,
|
||||||
...TaxRatesAlerts,
|
...TaxRatesAlerts,
|
||||||
...CashflowAlerts,
|
...CashflowAlerts,
|
||||||
...BankRulesAlerts
|
...BankRulesAlerts,
|
||||||
|
...SubscriptionAlerts
|
||||||
];
|
];
|
||||||
|
|||||||
24
packages/webapp/src/containers/Subscriptions/BillingPage.tsx
Normal file
24
packages/webapp/src/containers/Subscriptions/BillingPage.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Button } from '@blueprintjs/core';
|
||||||
|
import withAlertActions from '../Alert/withAlertActions';
|
||||||
|
|
||||||
|
function BillingPageRoot({ openAlert }) {
|
||||||
|
const handleCancelSubBtnClick = () => {
|
||||||
|
openAlert('cancel-main-subscription');
|
||||||
|
};
|
||||||
|
const handleResumeSubBtnClick = () => {
|
||||||
|
openAlert('resume-main-subscription');
|
||||||
|
};
|
||||||
|
const handleUpdatePaymentMethod = () => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<h1>
|
||||||
|
<Button onClick={handleCancelSubBtnClick}>Cancel Subscription</Button>
|
||||||
|
<Button onClick={handleResumeSubBtnClick}>Resume Subscription</Button>
|
||||||
|
<Button>Update Payment Method</Button>
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(withAlertActions)(BillingPageRoot);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function BillingPageBoot() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { useCancelMainSubscription } from '@/hooks/query/subscription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel Unlocking partial transactions alerts.
|
||||||
|
*/
|
||||||
|
function CancelMainSubscriptionAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { module },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: cancelSubscription, isLoading } =
|
||||||
|
useCancelMainSubscription();
|
||||||
|
|
||||||
|
// Handle cancel.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
// Handle confirm.
|
||||||
|
const handleConfirm = () => {
|
||||||
|
const values = {
|
||||||
|
module: module,
|
||||||
|
};
|
||||||
|
cancelSubscription()
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The subscription has been cancel.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={'Cancel Subscription'}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>asdfsadf asdf asdfdsaf</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(CancelMainSubscriptionAlert);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
import { useResumeMainSubscription } from '@/hooks/query/subscription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume Unlocking partial transactions alerts.
|
||||||
|
*/
|
||||||
|
function ResumeMainSubscriptionAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { module },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: resumeSubscription, isLoading } =
|
||||||
|
useResumeMainSubscription();
|
||||||
|
|
||||||
|
// Handle cancel.
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
// Handle confirm.
|
||||||
|
const handleConfirm = () => {
|
||||||
|
const values = {
|
||||||
|
module: module,
|
||||||
|
};
|
||||||
|
resumeSubscription()
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The subscription has been resumed.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={'Resume Subscription'}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>asdfsadf asdf asdfdsaf</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default R.compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ResumeMainSubscriptionAlert);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const CancelMainSubscriptionAlert = React.lazy(
|
||||||
|
() => import('./CancelMainSubscriptionAlert'),
|
||||||
|
);
|
||||||
|
const ResumeMainSubscriptionAlert = React.lazy(
|
||||||
|
() => import('./ResumeMainSubscriptionAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription alert.
|
||||||
|
*/
|
||||||
|
export const SubscriptionAlerts = [
|
||||||
|
{
|
||||||
|
name: 'cancel-main-subscription',
|
||||||
|
component: CancelMainSubscriptionAlert,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'resume-main-subscription',
|
||||||
|
component: ResumeMainSubscriptionAlert,
|
||||||
|
},
|
||||||
|
];
|
||||||
115
packages/webapp/src/hooks/query/subscription.tsx
Normal file
115
packages/webapp/src/hooks/query/subscription.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import {
|
||||||
|
useMutation,
|
||||||
|
UseMutationOptions,
|
||||||
|
UseMutationResult,
|
||||||
|
useQueryClient,
|
||||||
|
} from 'react-query';
|
||||||
|
import useApiRequest from '../useRequest';
|
||||||
|
|
||||||
|
interface CancelMainSubscriptionValues {}
|
||||||
|
interface CancelMainSubscriptionResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the main subscription of the current organization.
|
||||||
|
* @param {UseMutationOptions<CreateBankRuleValues, Error, CreateBankRuleValues>} options -
|
||||||
|
* @returns {UseMutationResult<CreateBankRuleValues, Error, CreateBankRuleValues>}TCHES
|
||||||
|
*/
|
||||||
|
export function useCancelMainSubscription(
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
CancelMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
CancelMainSubscriptionResponse
|
||||||
|
>,
|
||||||
|
): UseMutationResult<
|
||||||
|
CancelMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
CancelMainSubscriptionResponse
|
||||||
|
> {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
CancelMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
CancelMainSubscriptionResponse
|
||||||
|
>(
|
||||||
|
(values) =>
|
||||||
|
apiRequest.post(`/subscription/cancel`, values).then((res) => res.data),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResumeMainSubscriptionValues {}
|
||||||
|
interface ResumeMainSubscriptionResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the main subscription of the current organization.
|
||||||
|
* @param {UseMutationOptions<CreateBankRuleValues, Error, CreateBankRuleValues>} options -
|
||||||
|
* @returns {UseMutationResult<CreateBankRuleValues, Error, CreateBankRuleValues>}TCHES
|
||||||
|
*/
|
||||||
|
export function useResumeMainSubscription(
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
ResumeMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
ResumeMainSubscriptionResponse
|
||||||
|
>,
|
||||||
|
): UseMutationResult<
|
||||||
|
ResumeMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
ResumeMainSubscriptionResponse
|
||||||
|
> {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
ResumeMainSubscriptionValues,
|
||||||
|
Error,
|
||||||
|
ResumeMainSubscriptionResponse
|
||||||
|
>(
|
||||||
|
(values) =>
|
||||||
|
apiRequest.post(`/subscription/resume`, values).then((res) => res.data),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChangeMainSubscriptionPlanValues {
|
||||||
|
variantId: string;
|
||||||
|
}
|
||||||
|
interface ChangeMainSubscriptionPlanResponse {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changese the main subscription of the current organization.
|
||||||
|
* @param {UseMutationOptions<ChangeMainSubscriptionPlanValues, Error, ChangeMainSubscriptionPlanResponse>} options -
|
||||||
|
* @returns {UseMutationResult<ChangeMainSubscriptionPlanValues, Error, ChangeMainSubscriptionPlanResponse>}
|
||||||
|
*/
|
||||||
|
export function useChangeSubscriptionPlan(
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
ChangeMainSubscriptionPlanValues,
|
||||||
|
Error,
|
||||||
|
ChangeMainSubscriptionPlanResponse
|
||||||
|
>,
|
||||||
|
): UseMutationResult<
|
||||||
|
ChangeMainSubscriptionPlanValues,
|
||||||
|
Error,
|
||||||
|
ChangeMainSubscriptionPlanResponse
|
||||||
|
> {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation<
|
||||||
|
ChangeMainSubscriptionPlanValues,
|
||||||
|
Error,
|
||||||
|
ChangeMainSubscriptionPlanResponse
|
||||||
|
>(
|
||||||
|
(values) =>
|
||||||
|
apiRequest.post(`/subscription/change`, values).then((res) => res.data),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1231,6 +1231,13 @@ export const getDashboardRoutes = () => [
|
|||||||
breadcrumb: 'Bank Rules',
|
breadcrumb: 'Bank Rules',
|
||||||
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
|
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/billing',
|
||||||
|
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
|
||||||
|
pageTitle: 'Billing',
|
||||||
|
breadcrumb: 'Billing',
|
||||||
|
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
|
||||||
|
},
|
||||||
// Homepage
|
// Homepage
|
||||||
{
|
{
|
||||||
path: `/`,
|
path: `/`,
|
||||||
|
|||||||
Reference in New Issue
Block a user