Skip to content

Commit

Permalink
feat: implement payment beta (#5831)
Browse files Browse the repository at this point in the history
* feat: add script to add payment betaflag by email

* feat: update user schema with payment betaflag

* feat: remove rendering of payment settings and sidebar for non payment beta users

* feat: add utility function to verify if user has selected betaflag

* feat: prevent user from accessing payment and stripe apis if beta flag is not enabled

* fix: typo bug in payments flag in user.server.model
  • Loading branch information
foochifa authored Feb 27, 2023
1 parent e841303 commit 7e8ab55
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
DrawerTabs,
useCreatePageSidebar,
} from '~features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext'
import { useUser } from '~features/user/queries'

import {
isDirtySelector,
Expand All @@ -31,6 +32,7 @@ export const CreatePageSidebar = (): JSX.Element | null => {
const isMobile = useIsMobile()
const setFieldsToInactive = useFieldBuilderStore(setToInactiveSelector)
const isDirty = useDirtyFieldStore(isDirtySelector)
const { user } = useUser()
const {
activeTab,
handleBuilderClick,
Expand Down Expand Up @@ -102,12 +104,15 @@ export const CreatePageSidebar = (): JSX.Element | null => {
isActive={activeTab === DrawerTabs.Logic}
id={FEATURE_TOUR[2].id}
/>
<DrawerTabIcon
label="Add payment"
icon={<BiCreditCard fontSize="1.5rem" />}
onClick={handleDrawerPaymentClick}
isActive={activeTab === DrawerTabs.Payment}
/>
{user?.betaFlags?.payment && (
<DrawerTabIcon
label="Add payment"
icon={<BiCreditCard fontSize="1.5rem" />}
onClick={handleDrawerPaymentClick}
isActive={activeTab === DrawerTabs.Payment}
/>
)}

<DrawerTabIcon
label="Edit Thank you page"
icon={<PhHandsClapping fontSize="1.5rem" />}
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/features/admin-form/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
import { ADMINFORM_RESULTS_SUBROUTE, ADMINFORM_ROUTE } from '~constants/routes'
import { useDraggable } from '~hooks/useDraggable'

import { useUser } from '~features/user/queries'

import { useAdminFormCollaborators } from '../common/queries'

import { SettingsTab } from './components/SettingsTab'
Expand All @@ -25,6 +27,8 @@ import { SettingsWebhooksPage } from './SettingsWebhooksPage'

export const SettingsPage = (): JSX.Element => {
const { formId } = useParams()
const { user } = useUser()

if (!formId) throw new Error('No formId provided')

const { hasEditAccess, isLoading: isCollabLoading } =
Expand Down Expand Up @@ -80,7 +84,9 @@ export const SettingsPage = (): JSX.Element => {
<SettingsTab label="Singpass" icon={BiKey} />
<SettingsTab label="Twilio credentials" icon={BiMessage} />
<SettingsTab label="Webhooks" icon={BiCodeBlock} />
<SettingsTab label="Payments" icon={BiDollar} />
{user?.betaFlags?.payment && (
<SettingsTab label="Payments" icon={BiDollar} />
)}
</TabList>
</Flex>
<TabPanels
Expand All @@ -100,9 +106,11 @@ export const SettingsPage = (): JSX.Element => {
<TabPanel>
<SettingsWebhooksPage />
</TabPanel>
<TabPanel>
<SettingsPaymentsPage />
</TabPanel>
{user?.betaFlags?.payment && (
<TabPanel>
<SettingsPaymentsPage />
</TabPanel>
)}
</TabPanels>
<Spacer />
</Tabs>
Expand Down
58 changes: 58 additions & 0 deletions scripts/20232202_add-payments-flag/add-payment-flag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable */

/*
Adding payment beta flag to specfic user
*/

/*
List of user emails to add payment flag to
*/
const emails = [
// user emails to update
]

/*
Count of users who should have payment flag added to
*/
emails.length

/*
Get count of users who are to
*/

db.getCollection('users')
.find({email: {$in: emails}})
.count();

// check length of emails to count of users

/*
Get count of current users who have payment set to true
*/
db.getCollection('users').find({ betaFlags: {
payment: true
} }).count()

/*
Update selected user's payment betaflag to true
*/
db.getCollection('users').update({
email: {$in: emails}
}, {
$set: {
betaFlags: {
payment: true
}
}
}, {
multi: true
})

/*
Get count of updated number of users who have payment set to true
*/
db.getCollection('users').find({ betaFlags: {
payment: true
} }).count()

// check that beforeCount === afterCount + noOfUsersToAdd
6 changes: 5 additions & 1 deletion shared/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ export type UserId = Opaque<string, 'UserId'>
export const UserBase = z.object({
email: z.string().email(),
agency: AgencyBase.shape._id,
betaFlags: z.object({}).optional(),
betaFlags: z
.object({
payment: z.boolean().optional(),
})
.optional(),
flags: z
.object({ lastSeenFeatureUpdateVersion: z.number().optional() })
.optional(),
Expand Down
4 changes: 3 additions & 1 deletion src/app/models/user.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ const compileUserModel = (db: Mongoose) => {
type: Date,
default: () => Date.now(),
},
betaFlags: {},
betaFlags: {
payment: Boolean,
},
flags: {
lastSeenFeatureUpdateVersion: Number,
},
Expand Down
8 changes: 7 additions & 1 deletion src/app/modules/form/admin-form/admin-form.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ import { EditFieldError } from './admin-form.errors'
import { updateSettingsValidator } from './admin-form.middlewares'
import * as AdminFormService from './admin-form.service'
import { PermissionLevel } from './admin-form.types'
import { mapRouteError, verifyValidUnicodeString } from './admin-form.utils'
import {
mapRouteError,
verifyUserBetaflag,
verifyValidUnicodeString,
} from './admin-form.utils'

// NOTE: Refer to this for documentation: https://github.com/sideway/joi-date/blob/master/API.md
const Joi = BaseJoi.extend(JoiDate) as typeof BaseJoi
Expand Down Expand Up @@ -2720,6 +2724,8 @@ export const _handleUpdatePayments: ControllerHandler<
// Step 1: Retrieve currently logged in user.
return (
UserService.getPopulatedUserById(sessionUserId)
// Step 2: Check if user has 'payment' betaflag
.andThen((user) => verifyUserBetaflag(user, 'payment'))
.andThen((user) =>
// Step 2: Retrieve form with write permission check.
AuthService.getFormAfterPermissionChecks({
Expand Down
60 changes: 32 additions & 28 deletions src/app/modules/form/admin-form/admin-form.payments.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { getPopulatedUserById } from '../../user/user.service'

import { PermissionLevel } from './admin-form.types'
import { mapRouteError } from './admin-form.utils'
import { mapRouteError, verifyUserBetaflag } from './admin-form.utils'

const logger = createLoggerWithLabel(module)

Expand All @@ -24,35 +24,39 @@ export const handleConnectAccount: ControllerHandler<{
const sessionUserId = (req.session as AuthedSessionData).user._id

// Step 1: Retrieve currently logged in user.
return getPopulatedUserById(sessionUserId)
.andThen((user) =>
// Step 2: Retrieve form with write permission check.
getFormAfterPermissionChecks({
user,
formId,
level: PermissionLevel.Write,
}),
)
.andThen((form) => getStripeOauthUrl(form))
.map(({ authUrl, state }) => {
res.cookie('stripeState', state, { signed: true })
return res.json({
authUrl,
})
})
.mapErr((error) => {
logger.error({
message: 'Error connecting admin form payment account',
meta: {
action: 'handleConnectAccount',
...createReqMeta(req),
},
error,
return (
getPopulatedUserById(sessionUserId)
// Step 2: Check if user has 'payment' betaflag
.andThen((user) => verifyUserBetaflag(user, 'payment'))
.andThen((user) =>
// Step 3: Retrieve form with write permission check.
getFormAfterPermissionChecks({
user,
formId,
level: PermissionLevel.Write,
}),
)
.andThen((form) => getStripeOauthUrl(form))
.map(({ authUrl, state }) => {
res.cookie('stripeState', state, { signed: true })
return res.json({
authUrl,
})
})
.mapErr((error) => {
logger.error({
message: 'Error connecting admin form payment account',
meta: {
action: 'handleConnectAccount',
...createReqMeta(req),
},
error,
})

const { statusCode, errorMessage } = mapRouteError(error)
return res.status(statusCode).json({ message: errorMessage })
})
const { statusCode, errorMessage } = mapRouteError(error)
return res.status(statusCode).json({ message: errorMessage })
})
)
}

export const handleUnlinkAccount: ControllerHandler<{
Expand Down
22 changes: 21 additions & 1 deletion src/app/modules/form/admin-form/admin-form.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
replaceAt,
} from '../../../../../shared/utils/immutable-array-fns'
import { EditFieldActions } from '../../../../shared/constants'
import { FormFieldSchema, IPopulatedForm } from '../../../../types'
import { FormFieldSchema, IPopulatedForm, IUserSchema } from '../../../../types'
import { EditFormFieldParams } from '../../../../types/api'
import config from '../../../config/config'
import { createLoggerWithLabel } from '../../../config/logger'
Expand Down Expand Up @@ -484,3 +484,23 @@ export const verifyValidUnicodeString = (value: any, helpers: any) => {
}
return value
}

/**
* Checks if the user has the specified beta flag enabled
* @param user
* @param flag which is the string representation of the beta flag
* @returns ok(user) if the user has the beta flag enabled
* @returns err(ForbiddenFormUser) to deny user access to the beta feature
*/
export const verifyUserBetaflag = (
user: IUserSchema,
betaFlag: keyof Exclude<IUserSchema['betaFlags'], undefined>,
) => {
return user?.betaFlags?.[betaFlag]
? ok(user)
: err(
new ForbiddenFormError(
`User ${user.email} is not authorized to access ${betaFlag} beta features`,
),
)
}

0 comments on commit 7e8ab55

Please sign in to comment.