Skip to content

Commit 15dc402

Browse files
author
Rajat
committed
Add reset payment method functionality and related documentation updates
1 parent ddc626d commit 15dc402

File tree

9 files changed

+352
-65
lines changed

9 files changed

+352
-65
lines changed

AGENTS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
- Check the name field inside each package's package.json to confirm the right name—skip the top-level one.
1111
- While working with forms, always use zod and react-hook-form to validate the form. Take reference implementation from `apps/web/components/admin/settings/sso/new.tsx`.
1212

13+
## Documentation tips
14+
15+
- We manage product's documentation in `apps/docs`.
16+
- When working on a new feature or changing an existing feature significantly, see if documenation should be updated.
17+
- No need to update documentation while doing bug fixes and refactors.
18+
- If browser tool is available, see if you can automatically take revelant screenshots and include it in the documentation.
19+
1320
## Testing instructions
1421

1522
- Always add or update test when introducing changes to `apps/web/graphql` folder, even if nobody asked.
84.9 KB
Loading
179 KB
Loading

apps/docs/src/pages/en/schools/set-up-payments.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ CourseLit offers integrations with the following payment platforms:
1717
## Stripe setup
1818

1919
1. Sign up for an account on Stripe and get your business approved (or use a test account).
20-
2. In the Stripe dashboard, go to `Developers > API Keys` section as shown below.
20+
2. In the Stripe dashboard, go to the `Developers > API Keys` section as shown below.
2121
![Stripe dashboard](/assets/schools/stripe-api-keys.png)
2222
3. In your CourseLit school, go to the `Settings > Payment` tab and select `Stripe` in the `Payment Method` dropdown.
2323
4. Enter your Stripe publishable key and secret key in the `Stripe Publishable Key` and `Stripe Secret Key` input boxes as shown below:
2424
![Payment setup for Stripe](/assets/schools/payment-setup-stripe.png)
2525
5. Set up the webhooks. Using webhooks, your school receives timely updates about payments from Stripe.
26-
6. Open the webhook configuration dock, by clicking on `Developers > Webhooks` menu option.
26+
6. Open the webhook configuration dock by clicking on the `Developers > Webhooks` menu option.
2727
![Stripe webhook navigation](/assets/schools/stripe-webhook-navigation.png)
2828
7. Create a new webhook using the button as shown below:
2929
![Stripe add webhook](/assets/schools/stripe-add-webhook.png)
@@ -58,9 +58,9 @@ CourseLit offers integrations with the following payment platforms:
5858
![Razorpay webhook configuration](/assets/schools/razorpay-webhook-config.png)
5959
10. That's it! Your Razorpay configuration is complete, and you are ready to receive payments.
6060

61-
## Lemon squeezy setup
61+
## Lemon Squeezy setup
6262

63-
> Lemon Squeezy does not support creating custom products on the fly. Hence, we have built around the restrictions laid down by Lemon Squeezy. That's why we are calling our integration experimental. If something does not work, reach out to us.
63+
> Lemon Squeezy does not support creating custom products on the fly. Hence, we have built around the restrictions laid down by Lemon Squeezy. That's why we call our integration experimental. If something does not work, reach out to us.
6464
6565
1. Sign up for an account on Lemon Squeezy and get your business approved (or use a test account).
6666
2. In the Lemon Squeezy dashboard, go to `Products` and click on the `New product` button to create a generic product.
@@ -78,14 +78,14 @@ CourseLit offers integrations with the following payment platforms:
7878
4. The following screenshot shows how to select a variant's pricing.
7979
![Lemon Squeezy variant pricing](/assets/schools/lemon-variant-payment-config.png)
8080

81-
5. In your CourseLit school's dashboard, go to `Settings > Payments` and configure the settings as described below.
81+
5. In your CourseLit school's dashboard, go to `Settings > Payment` and configure the settings as described below.
8282
![CourseLit Lemon Squeezy config](/assets/schools/courselit-lemonsqueezy-config.png)
8383

8484
1. **Currency**: This will be visible throughout your school but won't affect Lemon Squeezy checkouts, as Lemon Squeezy does not allow overriding it via custom checkout.
8585
2. **Payment method**: Select Lemon Squeezy.
8686
3. **Lemon Squeezy Store ID**: In the Lemon Squeezy dashboard, go to `Settings > Stores` as shown below. Copy and paste this ID into the CourseLit settings.
8787
![Lemon Squeezy store ID](/assets/schools/lemon-store-id.png)
88-
4. **One-time variant ID**: In the Lemon Squeezy dashboard, go to `Products` and click on the product you configured in the steps above. In the slider popup, scroll down to the `Variants` section, click on the triple dots menu of the one-time variant, and `Copy ID`. Paste this ID into the CourseLit settings.
88+
4. **One-time variant ID**: In the Lemon Squeezy dashboard, go to `Products` and click on the product you configured in the steps above. In the slider popup, scroll down to the `Variants` section, click the three-dot menu for the one-time variant, and then click `Copy ID`. Paste this ID into the CourseLit settings.
8989
5. **Subscription (Monthly) variant ID**: Do the same as #4.
9090
6. **Subscription (Yearly) variant ID**: Do the same as #4.
9191
7. **Lemon Squeezy Key**: In the Lemon Squeezy dashboard, go to `Settings > API` and click on the `+` icon to generate a new key. Paste this key into the CourseLit settings.
@@ -107,9 +107,23 @@ CourseLit offers integrations with the following payment platforms:
107107

108108
9. That's it! Your Lemon Squeezy configuration is complete, and you are ready to receive payments.
109109

110+
## Reset payment method
111+
112+
If you want to stop using the currently selected payment platform, go to `Settings > Payment` and click the reset icon next to the `Payment Method` dropdown.
113+
114+
- This sets the payment method to `None`.
115+
- This does **not** delete existing gateway credentials (keys/secrets) from your settings.
116+
- You can pick another payment method later and save the settings again.
117+
118+
![Reset payment method](/assets/schools/reset-payment-method.png)
119+
120+
> After reset, all paid plans of all products will fail at checkout with the error `Payment configuration is invalid`. Free plans will keep on working..
121+
122+
![Reset payment paid plan error](/assets/schools/checkout-payment-invalid.png)
123+
110124
## Looking for developer docs?
111125

112-
We have created a detailed documentation for understanding the payment flow in CourseLit. Check it out [here]().
126+
We have created detailed documentation to help you understand the payment flow in CourseLit. Check it out [here]().
113127

114128
## Stuck somewhere?
115129

apps/web/components/admin/settings/index.tsx

Lines changed: 185 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import {
2222
SUBHEADER_SECTION_PAYMENT_CONFIRMATION_WEBHOOK,
2323
BUTTON_SAVE,
2424
SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL,
25+
SITE_SETTINGS_PAYMENT_METHOD_RESET_BUTTON,
26+
SITE_SETTINGS_PAYMENT_METHOD_RESET_TOOLTIP,
27+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_TITLE,
28+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_DESCRIPTION,
29+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_ACTION,
2530
SITE_CUSTOMISATIONS_SETTING_CODEINJECTION_BODY,
2631
SITE_MAILS_HEADER,
2732
SITE_MAILING_ADDRESS_SETTING_HEADER,
@@ -41,6 +46,7 @@ import {
4146
SITE_SETTINGS_LEMONSQUEEZY_SUB_YEARLY_TEXT,
4247
SETTINGS_RESOURCE_PAYMENT,
4348
SITE_MISCELLANEOUS_SETTING_HEADER,
49+
BUTTON_CANCEL_TEXT,
4450
} from "@/ui-config/strings";
4551
import { FetchBuilder, capitalize } from "@courselit/utils";
4652
import { decode, encode } from "base-64";
@@ -49,6 +55,7 @@ import type { SiteInfo, Media } from "@courselit/common-models";
4955
import currencies from "@/data/currencies.json";
5056
import {
5157
Select,
58+
Tooltip,
5259
MediaSelector,
5360
Tabbs,
5461
Form,
@@ -65,11 +72,21 @@ import {
6572
CardHeader,
6673
CardTitle,
6774
} from "@components/ui/card";
68-
import { Copy, Info } from "lucide-react";
75+
import { Copy, Info, RotateCcw } from "lucide-react";
6976
import { Input } from "@components/ui/input";
7077
import Resources from "@components/resources";
7178
import { AddressContext } from "@components/contexts";
7279
import { Button } from "@components/ui/button";
80+
import {
81+
AlertDialog,
82+
AlertDialogAction,
83+
AlertDialogCancel,
84+
AlertDialogContent,
85+
AlertDialogDescription,
86+
AlertDialogFooter,
87+
AlertDialogHeader,
88+
AlertDialogTitle,
89+
} from "@components/ui/alert-dialog";
7390
import dynamic from "next/dynamic";
7491

7592
const MiscellaneousTab = dynamic(() => import("./tabs/miscellaneous"));
@@ -80,7 +97,6 @@ const {
8097
PAYMENT_METHOD_RAZORPAY,
8198
PAYMENT_METHOD_STRIPE,
8299
PAYMENT_METHOD_LEMONSQUEEZY,
83-
PAYMENT_METHOD_NONE,
84100
MIMETYPE_IMAGE,
85101
} = UIConstants;
86102

@@ -99,6 +115,8 @@ const Settings = (props: SettingsProps) => {
99115
const [settings, setSettings] = useState<Partial<SiteInfo>>({});
100116
const [newSettings, setNewSettings] = useState<Partial<SiteInfo>>({});
101117
const [loading, setLoading] = useState(false);
118+
const [isResetPaymentMethodDialogOpen, setIsResetPaymentMethodDialogOpen] =
119+
useState(false);
102120
const selectedTab = [
103121
SITE_SETTINGS_SECTION_GENERAL,
104122
SITE_SETTINGS_SECTION_PAYMENT,
@@ -607,6 +625,61 @@ const Settings = (props: SettingsProps) => {
607625
}
608626
};
609627

628+
const handleResetPaymentMethod = async () => {
629+
const query = `
630+
mutation {
631+
settings: resetPaymentMethod {
632+
settings {
633+
title,
634+
subtitle,
635+
logo {
636+
mediaId,
637+
originalFileName,
638+
mimeType,
639+
size,
640+
access,
641+
file,
642+
thumbnail,
643+
caption
644+
},
645+
currencyISOCode,
646+
paymentMethod,
647+
stripeKey,
648+
razorpayKey,
649+
lemonsqueezyStoreId,
650+
lemonsqueezyOneTimeVariantId,
651+
lemonsqueezySubscriptionMonthlyVariantId,
652+
lemonsqueezySubscriptionYearlyVariantId,
653+
codeInjectionHead,
654+
codeInjectionBody,
655+
mailingAddress,
656+
hideCourseLitBranding
657+
}
658+
}
659+
}`;
660+
661+
try {
662+
setLoading(true);
663+
const fetchRequest = fetch.setPayload(query).build();
664+
const response = await fetchRequest.exec();
665+
if (response.settings.settings) {
666+
setSettingsState(response.settings.settings);
667+
toast({
668+
title: TOAST_TITLE_SUCCESS,
669+
description: APP_MESSAGE_SETTINGS_SAVED,
670+
});
671+
}
672+
} catch (e: any) {
673+
toast({
674+
title: TOAST_TITLE_ERROR,
675+
description: e.message,
676+
variant: "destructive",
677+
});
678+
} finally {
679+
setLoading(false);
680+
}
681+
};
682+
610683
const getPaymentSettings = (getNewSettings = false) => ({
611684
currencyISOCode: getNewSettings
612685
? newSettings.currencyISOCode
@@ -807,62 +880,116 @@ const Settings = (props: SettingsProps) => {
807880
</p>
808881
)}
809882
</div>
810-
<Select
811-
title={SITE_ADMIN_SETTINGS_PAYMENT_METHOD}
812-
value={
813-
newSettings.paymentMethod || PAYMENT_METHOD_NONE
814-
}
815-
options={[
816-
{
817-
label: SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL,
818-
value: PAYMENT_METHOD_NONE,
819-
},
820-
{
821-
label: capitalize(
822-
PAYMENT_METHOD_STRIPE.toLowerCase(),
823-
),
824-
value: PAYMENT_METHOD_STRIPE,
825-
disabled: currencies.some(
826-
(x) =>
827-
x.isoCode ===
828-
newSettings.currencyISOCode?.toUpperCase() &&
829-
!x.stripe,
830-
),
831-
},
832-
{
833-
label: capitalize(
834-
PAYMENT_METHOD_RAZORPAY.toLowerCase(),
835-
),
836-
value: PAYMENT_METHOD_RAZORPAY,
837-
disabled: currencies.some(
838-
(x) =>
839-
x.isoCode ===
840-
newSettings.currencyISOCode?.toUpperCase() &&
841-
!x.razorpay,
842-
),
843-
},
844-
{
845-
label: capitalize(
846-
PAYMENT_METHOD_LEMONSQUEEZY.toLowerCase(),
847-
),
848-
value: PAYMENT_METHOD_LEMONSQUEEZY,
849-
disabled: currencies.some(
850-
(x) =>
851-
x.isoCode ===
852-
newSettings.currencyISOCode?.toUpperCase() &&
853-
!x.lemonsqueezy,
854-
),
855-
},
856-
]}
857-
onChange={(value) =>
858-
setNewSettings(
859-
Object.assign({}, newSettings, {
860-
paymentMethod: value,
861-
}),
862-
)
863-
}
864-
disabled={!newSettings.currencyISOCode}
865-
/>
883+
<div className="flex items-end gap-2">
884+
<Select
885+
className="w-full"
886+
title={SITE_ADMIN_SETTINGS_PAYMENT_METHOD}
887+
value={newSettings.paymentMethod || ""}
888+
options={[
889+
{
890+
label: capitalize(
891+
PAYMENT_METHOD_STRIPE.toLowerCase(),
892+
),
893+
value: PAYMENT_METHOD_STRIPE,
894+
disabled: currencies.some(
895+
(x) =>
896+
x.isoCode ===
897+
newSettings.currencyISOCode?.toUpperCase() &&
898+
!x.stripe,
899+
),
900+
},
901+
{
902+
label: capitalize(
903+
PAYMENT_METHOD_RAZORPAY.toLowerCase(),
904+
),
905+
value: PAYMENT_METHOD_RAZORPAY,
906+
disabled: currencies.some(
907+
(x) =>
908+
x.isoCode ===
909+
newSettings.currencyISOCode?.toUpperCase() &&
910+
!x.razorpay,
911+
),
912+
},
913+
{
914+
label: capitalize(
915+
PAYMENT_METHOD_LEMONSQUEEZY.toLowerCase(),
916+
),
917+
value: PAYMENT_METHOD_LEMONSQUEEZY,
918+
disabled: currencies.some(
919+
(x) =>
920+
x.isoCode ===
921+
newSettings.currencyISOCode?.toUpperCase() &&
922+
!x.lemonsqueezy,
923+
),
924+
},
925+
]}
926+
onChange={(value) =>
927+
setNewSettings(
928+
Object.assign({}, newSettings, {
929+
paymentMethod: value,
930+
}),
931+
)
932+
}
933+
disabled={!newSettings.currencyISOCode}
934+
placeholderMessage={
935+
SITE_SETTINGS_PAYMENT_METHOD_NONE_LABEL
936+
}
937+
/>
938+
<Tooltip
939+
title={
940+
SITE_SETTINGS_PAYMENT_METHOD_RESET_TOOLTIP
941+
}
942+
side="top"
943+
>
944+
<Button
945+
type="button"
946+
variant="outline"
947+
size="icon"
948+
aria-label={
949+
SITE_SETTINGS_PAYMENT_METHOD_RESET_BUTTON
950+
}
951+
disabled={
952+
loading || !newSettings.paymentMethod
953+
}
954+
onClick={() =>
955+
setIsResetPaymentMethodDialogOpen(true)
956+
}
957+
>
958+
<RotateCcw className="h-4 w-4" />
959+
</Button>
960+
</Tooltip>
961+
</div>
962+
<AlertDialog
963+
open={isResetPaymentMethodDialogOpen}
964+
onOpenChange={setIsResetPaymentMethodDialogOpen}
965+
>
966+
<AlertDialogContent>
967+
<AlertDialogHeader>
968+
<AlertDialogTitle>
969+
{
970+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_TITLE
971+
}
972+
</AlertDialogTitle>
973+
<AlertDialogDescription>
974+
{
975+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_DESCRIPTION
976+
}
977+
</AlertDialogDescription>
978+
</AlertDialogHeader>
979+
<AlertDialogFooter>
980+
<AlertDialogCancel>
981+
{BUTTON_CANCEL_TEXT}
982+
</AlertDialogCancel>
983+
<AlertDialogAction
984+
onClick={handleResetPaymentMethod}
985+
>
986+
{
987+
SITE_SETTINGS_PAYMENT_METHOD_RESET_CONFIRM_ACTION
988+
}
989+
</AlertDialogAction>
990+
</AlertDialogFooter>
991+
</AlertDialogContent>
992+
</AlertDialog>
866993

867994
{newSettings.paymentMethod ===
868995
PAYMENT_METHOD_STRIPE && (

0 commit comments

Comments
 (0)