diff --git a/apps/backend/src/api/routes/billing.controller.ts b/apps/backend/src/api/routes/billing.controller.ts
index 039513fb..2ba8eb1f 100644
--- a/apps/backend/src/api/routes/billing.controller.ts
+++ b/apps/backend/src/api/routes/billing.controller.ts
@@ -9,6 +9,7 @@ import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.req
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
import { Request } from 'express';
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
+import { AuthService } from '@gitroom/helpers/auth/auth.service';
@ApiTags('Billing')
@Controller('/billing')
@@ -30,6 +31,20 @@ export class BillingController {
};
}
+ @Get('/check-discount')
+ async checkDiscount(@GetOrgFromRequest() org: Organization) {
+ return {
+ offerCoupon: !(await this._stripeService.checkDiscount(org.paymentId))
+ ? false
+ : AuthService.signJWT({ discount: true }),
+ };
+ }
+
+ @Post('/apply-discount')
+ async applyDiscount(@GetOrgFromRequest() org: Organization) {
+ await this._stripeService.applyDiscount(org.paymentId);
+ }
+
@Post('/finish-trial')
async finishTrial(@GetOrgFromRequest() org: Organization) {
try {
diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx
index 3485012a..a52b6e63 100644
--- a/apps/frontend/src/components/billing/main.billing.component.tsx
+++ b/apps/frontend/src/components/billing/main.billing.component.tsx
@@ -135,6 +135,38 @@ export const Features: FC<{
);
};
+
+const Accept: FC<{ resolve: (res: boolean) => void }> = ({ resolve }) => {
+ const [loading, setLoading] = useState(false);
+ const fetch = useFetch();
+ const toaster = useToaster();
+
+ const apply = useCallback(async () => {
+ setLoading(true);
+ await fetch('/billing/apply-discount', {
+ method: 'POST',
+ });
+
+ resolve(true);
+ toaster.show('50% discount applied successfully');
+ }, []);
+
+ return (
+
+
+ Would you accept 50% discount for 3 months instead? 🙏🏻
+
+
+
+
+
+
+ );
+};
const Info: FC<{
proceed: (feedback: string) => void;
}> = (props) => {
@@ -277,13 +309,34 @@ export const MainBillingComponent: FC<{
if (
subscription?.cancelAt ||
(await deleteDialog(
- `Are you sure you want to cancel your subscription? ${messages.join(
- ', '
- )}`,
+ `Are you sure you want to cancel your subscription?
+ ${messages.join(', ')}`,
'Yes, cancel',
'Cancel Subscription'
))
) {
+ const checkDiscount = await (
+ await fetch('/billing/check-discount')
+ ).json();
+ if (checkDiscount.offerCoupon) {
+ const info = await new Promise((res) => {
+ modal.openModal({
+ title: 'Before you cancel',
+ withCloseButton: true,
+ classNames: {
+ modal: 'bg-transparent text-textColor',
+ },
+ children: ,
+ });
+ });
+
+ modal.closeAll();
+
+ if (info) {
+ return;
+ }
+ }
+
const info = await new Promise((res) => {
modal.openModal({
title: t(
@@ -297,6 +350,7 @@ export const MainBillingComponent: FC<{
children: res(e)} />,
});
});
+
setLoading(true);
const { cancel_at } = await (
await fetch('/billing/cancel', {
diff --git a/libraries/nestjs-libraries/src/services/stripe.service.ts b/libraries/nestjs-libraries/src/services/stripe.service.ts
index 5a7f5949..e27e0a8b 100644
--- a/libraries/nestjs-libraries/src/services/stripe.service.ts
+++ b/libraries/nestjs-libraries/src/services/stripe.service.ts
@@ -475,6 +475,72 @@ export class StripeService {
});
}
+ async checkDiscount(customer: string) {
+ if (!process.env.STRIPE_DISCOUNT_ID) {
+ return false;
+ }
+
+ const list = await stripe.charges.list({
+ customer,
+ limit: 1,
+ });
+
+ if (!list.data.filter(f => f.amount > 1000).length) {
+ return false;
+ }
+
+ const currentUserSubscription = {
+ data: (
+ await stripe.subscriptions.list({
+ customer,
+ status: 'all',
+ expand: ['data.discounts'],
+ })
+ ).data.find((f) => f.status === 'active' || f.status === 'trialing'),
+ };
+
+ if (!currentUserSubscription) {
+ return false;
+ }
+
+ if (
+ currentUserSubscription.data?.items.data[0]?.price.recurring?.interval ===
+ 'year' ||
+ currentUserSubscription.data?.discounts.length
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ async applyDiscount(customer: string) {
+ const check = this.checkDiscount(customer);
+ if (!check) {
+ return false;
+ }
+
+ const currentUserSubscription = {
+ data: (
+ await stripe.subscriptions.list({
+ customer,
+ status: 'all',
+ expand: ['data.discounts'],
+ })
+ ).data.find((f) => f.status === 'active' || f.status === 'trialing'),
+ };
+
+ await stripe.subscriptions.update(currentUserSubscription.data.id, {
+ discounts: [
+ {
+ coupon: process.env.STRIPE_DISCOUNT_ID!,
+ },
+ ],
+ });
+
+ return true;
+ }
+
async checkSubscription(organizationId: string, subscriptionId: string) {
const orgValue = await this._subscriptionService.checkSubscription(
organizationId,