From 708e9129db87b8eb65b3e8b14058ef408bcd3e0c Mon Sep 17 00:00:00 2001 From: LovroColic Date: Thu, 17 Nov 2022 15:25:41 +0100 Subject: [PATCH] feat (gocardless): Handle changes for Gocardless integration (#523) * add base gocardless setup * add gocardless details page * update gcl details page * finalise gcl details page with mutation that creates access token * remove createCustomer property from organisation level * update graphql.tsx * update create/update customers form * add webhook secret key support in integrations section * fix helper text below payment provider field * apply several smaller fixes * fix translations * fix linter issues * use env variable for lago proxy * fix jest file * fix oauth proxy url * fix env var usage * update api keys developer page * fix ApiKey page Co-authored-by: Jeremy Denquin --- .env.sh | 2 + .storybook/main.js | 1 + ditto/base.json | 29 +- ditto/config.yml | 2 + ditto/index.js | 3 + globals.d.ts | 1 + jest.config.js | 1 + .../customers/AddCustomerDrawer.tsx | 72 ++++- .../customers/CustomerMainInfos.tsx | 16 +- .../apolloClient/reactiveVars/envGlobalVar.ts | 2 + src/core/router/index.tsx | 12 + src/generated/graphql.tsx | 153 +++++++-- src/hooks/useCreateEditCustomer.ts | 24 +- src/layouts/Developers.tsx | 2 +- src/pages/developers/ApiKeys.tsx | 124 +++++--- src/pages/settings/GocardlessIntegration.tsx | 294 ++++++++++++++++++ src/pages/settings/Integrations.tsx | 84 +++-- src/pages/settings/StripeIntegration.tsx | 48 +-- src/public/images/gocardless-large.svg | 4 + src/public/images/gocardless.svg | 11 + webpack.common.js | 1 + 21 files changed, 706 insertions(+), 180 deletions(-) create mode 100644 src/pages/settings/GocardlessIntegration.tsx create mode 100644 src/public/images/gocardless-large.svg create mode 100644 src/public/images/gocardless.svg diff --git a/.env.sh b/.env.sh index 36511657e..0f5452225 100755 --- a/.env.sh +++ b/.env.sh @@ -5,8 +5,10 @@ touch ./env-config.js api_url_value=$(echo $API_URL) app_env_value=$(echo $APP_ENV) +lago_oauth_proxy_url_value=$(echo $LAGO_OAUTH_PROXY_URL) lago_disable_signup_value=$(echo $LAGO_DISABLE_SIGNUP) echo "window.API_URL = \"$api_url_value\"" >> ./env-config.js echo "window.APP_ENV = \"$app_env_value\"" >> ./env-config.js +echo "window.LAGO_OAUTH_PROXY_URL = \"$lago_oauth_proxy_url_value\"" >> ./env-config.js echo "window.LAGO_DISABLE_SIGNUP = \"$lago_disable_signup_value\"" >> ./env-config.js \ No newline at end of file diff --git a/.storybook/main.js b/.storybook/main.js index a9b2fbc9c..7173432ed 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -44,6 +44,7 @@ module.exports = { APP_ENV: JSON.stringify('production'), API_URL: JSON.stringify(process.env.API_URL), LAGO_DISABLE_SIGNUP: process.env.LAGO_DISABLE_SIGNUP, + LAGO_OAUTH_PROXY_URL: process.env.LAGO_OAUTH_PROXY_URL, APP_VERSION: JSON.stringify('0.0'), }) ) diff --git a/ditto/base.json b/ditto/base.json index 16350e179..3617532bb 100644 --- a/ditto/base.json +++ b/ditto/base.json @@ -315,6 +315,30 @@ "text_6304e74aab6dbc18d615f416": "{{lastRowFirstUnit}} x {{lastRowPerUnit}} + {{lastRowFlatFee}} = {{value}}", "text_6304e74aab6dbc18d615f421": "Delete tier", "text_6304e74aab6dbc18d615f420": "This tier overlap with another one, please put a value upper than {{value}}", + "text_634ea0ecc6147de10ddb6625": "GoCardless", + "text_634ea0ecc6147de10ddb6629": "GoCardless", + "text_634ea0ecc6147de10ddb662d": "Connected", + "text_634ea0ecc6147de10ddb6631": "Payment provider", + "text_634ea0ecc6147de10ddb6635": "Connection settings", + "text_635bd8acb686f18909a57c87": "Reconnect", + "text_634ea0ecc6147de10ddb663d": "OAuth connection", + "text_634ea0ecc6147de10ddb6641": "Lago is connected to GoCardless via an OAuth connection. From Lago, you can only reconnect the two apps, to remove this connection, please do it in GoCardless.", + "text_635bd8acb686f18909a57c89": "Secret key", + "text_635bd8acb686f18909a57c8d": "Secret key", + "text_635bd8acb686f18909a57c93": "Use this secret key to setup webhooks in GoCardless", + "text_634ea0ecc6147de10ddb6645": "GoCardless successfully connected", + "text_634ea0ecc6147de10ddb6643": "Payment provider", + "text_634ea0ecc6147de10ddb6646": "Connected", + "text_634ea0ecc6147de10ddb6648": "GoCardless", + "text_635bdbda84c98758f9bba8a0": "Connect Payment service provider in the Integrations section", + "text_635bdbda84c98758f9bba8aa": "Create automatically this customer in GoCardless", + "text_635bdbda84c98758f9bba8ae": "To create a customer in GoCardless, you must fill in an email address", + "text_635bdbda84c98758f9bba89e": "Create automatically this customer in Stripe", + "text_6360ddae753a8b3e11c80c66": "Copy text", + "text_6360ddae753a8b3e11c80c6c": "Secret key copied to clipboard", + "text_636df520279a9e1b3c68cc67": "API keys & ID", + "text_636df520279a9e1b3c68cc75": "Organization ID", + "text_636df520279a9e1b3c68cc7d": "Organization ID copy to clipboard", "text_6295e58352f39200d902b01c": "Documentation", "text_6295e58352f39200d902b02a": "Apply add-on", "text_629781ec7c6c1500d94fbb00": "Apply an add-on to this customer", @@ -649,10 +673,6 @@ "text_62b1edddbf5f461ab971275b": "API secret key", "text_62b1edddbf5f461ab9712787": "Edit", "text_62b1edddbf5f461ab971279f": "Delete", - "text_62b1edddbf5f461ab97127b4": "Other settings", - "text_62b1edddbf5f461ab97127c8": "Create Lago customers in Stripe", - "text_62b1edddbf5f461ab97127d8": "By switching on, customers from Lago will automatically be created in Stripe", - "text_62b1edddbf5f461ab9712819": "Settings successfully updated", "text_62b1edddbf5f461ab971272d": "Edit Stripe API secret key", "text_62b1edddbf5f461ab9712737": "By editing the API secret key, upcoming data will not be synchronised to the connected Stripe account.", "text_62b1edddbf5f461ab9712754": "Type a new API secret key", @@ -965,7 +985,6 @@ "text_62bb10ad2a10bd182d002077": "Invoice template information saved", "text_6271200984178801ba8bdeac": "Developers", "text_6271200984178801ba8bdebe": "Developers", - "text_6271200984178801ba8bdeca": "API keys", "text_6271200984178801ba8bdede": "Webhooks", "text_6271200984178801ba8bdef2": "Webhooks", "text_6271200984178801ba8bdf06": "Provide real-time information to other applications", diff --git a/ditto/config.yml b/ditto/config.yml index ea978c577..ecf4baf26 100644 --- a/ditto/config.yml +++ b/ditto/config.yml @@ -85,5 +85,7 @@ projects: id: 634687058efb4a10996fdbdc - name: '⚙️ [WIP] - B.Metrics and Plans - Dimension' id: 633b622919283cdbfb2f7233 + - name: "\U0001F44D [Ready for dev] - Settings - Customers - Lago gocardless connection" + id: 634ea0e94c99df2bb59820d9 format: flat variants: true diff --git a/ditto/index.js b/ditto/index.js index cbfa1d8c4..4e4ac295d 100644 --- a/ditto/index.js +++ b/ditto/index.js @@ -127,5 +127,8 @@ module.exports = { }, "project_6304e74718860633c15905f2": { "base": require('./👍 [Ready for dev] - Plans - Add Volume in charge model__base.json') + }, + "project_634ea0e94c99df2bb59820d9": { + "base": require('./👍 [Ready for dev] - Settings - Customers - Lago gocardless connection__base.json') } } \ No newline at end of file diff --git a/globals.d.ts b/globals.d.ts index ca8dc45a3..1170e6fe4 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -3,6 +3,7 @@ declare type AppEnvEnum = import('./src/globalTypes').AppEnvEnum declare var APP_ENV: AppEnvEnum declare var API_URL: string; declare var APP_VERSION: string; +declare var LAGO_OAUTH_PROXY_URL: string; declare var LAGO_DISABLE_SIGNUP: string; declare module "*.svg" { diff --git a/jest.config.js b/jest.config.js index f29dd5f67..f6d456dec 100644 --- a/jest.config.js +++ b/jest.config.js @@ -54,6 +54,7 @@ module.exports = { API_URL: 'http://localhost:3000', APP_VERSION: '1.0.0', IS_REACT_ACT_ENVIRONMENT: true, + LAGO_OAUTH_PROXY_URL: 'https://proxy.lago.dev', LAGO_DISABLE_SIGNUP: 'false', }, } diff --git a/src/components/customers/AddCustomerDrawer.tsx b/src/components/customers/AddCustomerDrawer.tsx index 2c42eef10..9de18fa8f 100644 --- a/src/components/customers/AddCustomerDrawer.tsx +++ b/src/components/customers/AddCustomerDrawer.tsx @@ -1,10 +1,10 @@ -import { forwardRef, useEffect, RefObject } from 'react' +import { forwardRef, useEffect, useState, RefObject } from 'react' import styled from 'styled-components' import { useFormik } from 'formik' import { object, string } from 'yup' -import { Drawer, Button, DrawerRef, Typography, Accordion } from '~/components/designSystem' -import { TextInputField, ComboBoxField } from '~/components/form' +import { Drawer, Button, DrawerRef, Typography, Accordion, Alert } from '~/components/designSystem' +import { TextInputField, ComboBoxField, Checkbox } from '~/components/form' import { useInternationalization } from '~/hooks/core/useInternationalization' import { hasDefinedGQLError } from '~/core/apolloClient' import { theme, Card, DrawerTitle, DrawerContent, DrawerSubmitButton } from '~/styles' @@ -18,6 +18,7 @@ import { } from '~/generated/graphql' import { useCreateEditCustomer } from '~/hooks/useCreateEditCustomer' import CountryCodes from '~/public/countryCode.json' +import { INTEGRATIONS_ROUTE } from '~/core/router' const countryData: { value: string; label: string }[] = Object.keys(CountryCodes).map( (countryKey) => { @@ -49,6 +50,7 @@ export const AddCustomerDrawer = forwardRef( const { isEdition, onSave } = useCreateEditCustomer({ customer, }) + const [isDisabled, setIsDisabled] = useState(false) const formikProps = useFormik({ initialValues: { name: customer?.name ?? '', @@ -64,8 +66,9 @@ export const AddCustomerDrawer = forwardRef( country: customer?.country ?? undefined, city: customer?.city ?? undefined, zipcode: customer?.zipcode ?? undefined, - stripeCustomer: { - providerCustomerId: customer?.stripeCustomer?.providerCustomerId ?? undefined, + providerCustomer: { + providerCustomerId: customer?.providerCustomer?.providerCustomerId ?? undefined, + syncWithProvider: customer?.providerCustomer?.syncWithProvider ?? false, }, paymentProvider: customer?.paymentProvider ?? undefined, }, @@ -91,7 +94,8 @@ export const AddCustomerDrawer = forwardRef( useEffect(() => { if (!formikProps.values.paymentProvider) { // If no payment provider, reset stripe customer - formikProps.setFieldValue('stripeCustomer.providerCustomerId', undefined) + formikProps.setFieldValue('providerCustomer.providerCustomerId', undefined) + formikProps.setFieldValue('providerCustomer.syncWithProvider', false) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [formikProps.values.paymentProvider]) @@ -125,8 +129,9 @@ export const AddCustomerDrawer = forwardRef( country: customer?.country ?? undefined, city: customer?.city ?? undefined, zipcode: customer?.zipcode ?? undefined, - stripeCustomer: { - providerCustomerId: customer?.stripeCustomer?.providerCustomerId ?? undefined, + providerCustomer: { + providerCustomerId: customer?.providerCustomer?.providerCustomerId ?? undefined, + syncWithProvider: customer?.providerCustomer?.syncWithProvider ?? false, }, paymentProvider: customer?.paymentProvider ?? undefined, }, @@ -276,14 +281,48 @@ export const AddCustomerDrawer = forwardRef( label={translate('text_62b328ead9a4caef81cd9c9c')} placeholder={translate('text_62b328ead9a4caef81cd9c9e')} formikProps={formikProps} + helperText={ + !isEdition && ( + + ) + } PopperProps={{ displayInDialog: true }} /> - + {(formikProps.values.paymentProvider === ProviderTypeEnum.Gocardless || + formikProps.values.paymentProvider === ProviderTypeEnum.Stripe) && ( + <> + + { + setIsDisabled(checked) + formikProps.setFieldValue('providerCustomer.syncWithProvider', checked) + if (!isEdition && checked) { + formikProps.setFieldValue('providerCustomer.providerCustomerId', undefined) + } + }} + /> + + )} + {isDisabled && formikProps.values.paymentProvider === ProviderTypeEnum.Gocardless && ( + {translate('text_635bdbda84c98758f9bba8ae')} + )} @@ -307,6 +346,11 @@ export const AddCustomerDrawer = forwardRef( } ) +const HelperText = styled(Typography)` + font-size: 14px; + line-height: 20px; +` + const BillingBlock = styled.div<{ $first?: boolean }>` margin-bottom: ${({ $first }) => ($first ? theme.spacing(6) : 0)}; diff --git a/src/components/customers/CustomerMainInfos.tsx b/src/components/customers/CustomerMainInfos.tsx index 8ced2ae50..0be05b7bf 100644 --- a/src/components/customers/CustomerMainInfos.tsx +++ b/src/components/customers/CustomerMainInfos.tsx @@ -26,7 +26,7 @@ gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId } @@ -74,7 +74,7 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf city, zipcode, paymentProvider, - stripeCustomer, + providerCustomer, } = customer return ( @@ -146,10 +146,18 @@ export const CustomerMainInfos = ({ loading, customer, onEdit }: CustomerMainInf )} - {!!stripeCustomer && !!stripeCustomer?.providerCustomerId && ( + {paymentProvider === ProviderTypeEnum?.Gocardless && ( +
+ {translate('text_62b5c912506c4905fa755248')} + + {translate('text_634ea0ecc6147de10ddb6648')} + +
+ )} + {!!providerCustomer && !!providerCustomer?.providerCustomerId && (
{translate('text_62b5c912506c4905fa75524c')} - {stripeCustomer?.providerCustomerId} + {providerCustomer?.providerCustomerId}
)} diff --git a/src/core/apolloClient/reactiveVars/envGlobalVar.ts b/src/core/apolloClient/reactiveVars/envGlobalVar.ts index 9c451fef6..9aba42710 100644 --- a/src/core/apolloClient/reactiveVars/envGlobalVar.ts +++ b/src/core/apolloClient/reactiveVars/envGlobalVar.ts @@ -5,6 +5,7 @@ import { AppEnvEnum } from '~/globalTypes' interface EnvGlobal { appEnv: AppEnvEnum apiUrl: string + lagoOauthProxyUrl: string disableSignUp: boolean appVersion: string } @@ -12,6 +13,7 @@ interface EnvGlobal { export const envGlobalVar = makeVar({ appEnv: window.APP_ENV || APP_ENV, apiUrl: window.API_URL || API_URL, + lagoOauthProxyUrl: window.LAGO_OAUTH_PROXY_URL || LAGO_OAUTH_PROXY_URL, disableSignUp: (window.LAGO_DISABLE_SIGNUP || LAGO_DISABLE_SIGNUP) === 'true', appVersion: APP_VERSION, }) diff --git a/src/core/router/index.tsx b/src/core/router/index.tsx index 32053ed48..2e9b5e9b7 100644 --- a/src/core/router/index.tsx +++ b/src/core/router/index.tsx @@ -38,6 +38,12 @@ const Integrations = lazy( const StripeIntegration = lazy( () => import(/* webpackChunkName: 'stripe-integration' */ '~/pages/settings/StripeIntegration') ) +const GocardlessIntegration = lazy( + () => + import( + /* webpackChunkName: 'gocardless-integration' */ '~/pages/settings/GocardlessIntegration' + ) +) const Members = lazy(() => import(/* webpackChunkName: 'members' */ '~/pages/settings/Members')) const BillableMetricsList = lazy( @@ -134,6 +140,7 @@ export const VAT_RATE_ROUTE = `${SETTINGS_ROUTE}/tax-rate` export const ORGANIZATION_INFORMATIONS_ROUTE = `${SETTINGS_ROUTE}/organization-informations` export const INTEGRATIONS_ROUTE = `${SETTINGS_ROUTE}/integrations` export const STRIPE_INTEGRATION_ROUTE = `${SETTINGS_ROUTE}/integrations/stripe` +export const GOCARDLESS_INTEGRATION_ROUTE = `${SETTINGS_ROUTE}/integrations/gocardless` export const MEMBERS_ROUTE = `${SETTINGS_ROUTE}/members` // *********************** Route Available only on dev mode @@ -214,6 +221,11 @@ export const routes: CustomRouteObject[] = [ private: true, element: , }, + { + path: GOCARDLESS_INTEGRATION_ROUTE, + private: true, + element: , + }, { path: PLANS_ROUTE, private: true, diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index f5aa13727..bc84fa449 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -871,8 +871,8 @@ export type CreateCustomerInput = { name: Scalars['String']; paymentProvider?: InputMaybe; phone?: InputMaybe; + providerCustomer?: InputMaybe; state?: InputMaybe; - stripeCustomer?: InputMaybe; url?: InputMaybe; vatRate?: InputMaybe; zipcode?: InputMaybe; @@ -1310,10 +1310,10 @@ export type Customer = { name?: Maybe; paymentProvider?: Maybe; phone?: Maybe; + providerCustomer?: Maybe; sequentialId: Scalars['String']; slug: Scalars['String']; state?: Maybe; - stripeCustomer?: Maybe; subscriptions?: Maybe>; updatedAt: Scalars['ISO8601DateTime']; url?: Maybe; @@ -1356,10 +1356,10 @@ export type CustomerDetails = { name?: Maybe; paymentProvider?: Maybe; phone?: Maybe; + providerCustomer?: Maybe; sequentialId: Scalars['String']; slug: Scalars['String']; state?: Maybe; - stripeCustomer?: Maybe; /** Query subscriptions of a customer */ subscriptions: Array; updatedAt: Scalars['ISO8601DateTime']; @@ -1544,6 +1544,7 @@ export type GocardlessProvider = { __typename?: 'GocardlessProvider'; hasAccessToken: Scalars['Boolean']; id: Scalars['ID']; + webhookSecret?: Maybe; }; export type GraduatedRange = { @@ -2043,6 +2044,7 @@ export type Organization = { country?: Maybe; createdAt: Scalars['ISO8601DateTime']; email?: Maybe; + gocardlessPaymentProvider?: Maybe; id: Scalars['ID']; invoiceFooter?: Maybe; legalName?: Maybe; @@ -2144,7 +2146,20 @@ export type PropertiesInput = { volumeRanges?: InputMaybe>; }; +export type ProviderCustomer = { + __typename?: 'ProviderCustomer'; + id: Scalars['ID']; + providerCustomerId?: Maybe; + syncWithProvider?: Maybe; +}; + +export type ProviderCustomerInput = { + providerCustomerId?: InputMaybe; + syncWithProvider?: InputMaybe; +}; + export enum ProviderTypeEnum { + Gocardless = 'gocardless', Stripe = 'stripe' } @@ -2366,16 +2381,6 @@ export enum StatusTypeEnum { Terminated = 'terminated' } -export type StripeCustomer = { - __typename?: 'StripeCustomer'; - id: Scalars['ID']; - providerCustomerId?: Maybe; -}; - -export type StripeCustomerInput = { - providerCustomerId?: InputMaybe; -}; - export type StripeProvider = { __typename?: 'StripeProvider'; createCustomers: Scalars['Boolean']; @@ -2501,8 +2506,8 @@ export type UpdateCustomerInput = { name: Scalars['String']; paymentProvider?: InputMaybe; phone?: InputMaybe; + providerCustomer?: InputMaybe; state?: InputMaybe; - stripeCustomer?: InputMaybe; url?: InputMaybe; vatRate?: InputMaybe; zipcode?: InputMaybe; @@ -2800,9 +2805,9 @@ export type DownloadInvoiceMutationVariables = Exact<{ export type DownloadInvoiceMutation = { __typename?: 'Mutation', downloadInvoice?: { __typename?: 'Invoice', id: string, fileUrl?: string | null } | null }; -export type CustomerItemFragment = { __typename?: 'Customer', id: string, name?: string | null, externalId: string, createdAt: any, canBeDeleted: boolean, activeSubscriptionCount: number, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }; +export type CustomerItemFragment = { __typename?: 'Customer', id: string, name?: string | null, externalId: string, createdAt: any, canBeDeleted: boolean, activeSubscriptionCount: number, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null }; -export type CustomerMainInfosFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, currency?: CurrencyEnum | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }; +export type CustomerMainInfosFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, currency?: CurrencyEnum | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null } | null }; export type VatRateOrganizationFragment = { __typename?: 'Organization', id: string, vatRate: number }; @@ -3137,23 +3142,23 @@ export type UpdateCouponMutationVariables = Exact<{ export type UpdateCouponMutation = { __typename?: 'Mutation', updateCoupon?: { __typename?: 'Coupon', id: string, name: string, customerCount: number, status: CouponStatusEnum, amountCurrency?: CurrencyEnum | null, amountCents?: number | null, canBeDeleted: boolean, expiration: CouponExpiration, expirationDate?: any | null, couponType: CouponTypeEnum, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null } | null }; -export type AddCustomerDrawerFragment = { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }; +export type AddCustomerDrawerFragment = { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null }; -export type AddCustomerDrawerDetailFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }; +export type AddCustomerDrawerDetailFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null }; export type CreateCustomerMutationVariables = Exact<{ input: CreateCustomerInput; }>; -export type CreateCustomerMutation = { __typename?: 'Mutation', createCustomer?: { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, createdAt: any, activeSubscriptionCount: number, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null } | null }; +export type CreateCustomerMutation = { __typename?: 'Mutation', createCustomer?: { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, createdAt: any, activeSubscriptionCount: number, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null } | null }; export type UpdateCustomerMutationVariables = Exact<{ input: UpdateCustomerInput; }>; -export type UpdateCustomerMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, createdAt: any, activeSubscriptionCount: number, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null } | null }; +export type UpdateCustomerMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, createdAt: any, activeSubscriptionCount: number, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null } | null }; export type GetInvoiceDetailsQueryVariables = Exact<{ id: Scalars['ID']; @@ -3193,14 +3198,14 @@ export type CouponsQuery = { __typename?: 'Query', coupons: { __typename?: 'Coup export type EditPlanFragment = { __typename?: 'PlanDetails', id: string, name: string, code: string, description?: string | null, interval: PlanInterval, payInAdvance: boolean, amountCents: number, amountCurrency: CurrencyEnum, trialPeriod?: number | null, canBeDeleted: boolean, billChargesMonthly?: boolean | null, charges?: Array<{ __typename?: 'Charge', id: string, chargeModel: ChargeModelEnum, billableMetric: { __typename?: 'BillableMetric', id: string, name: string, code: string, flatGroups?: Array<{ __typename?: 'Group', id: string, key?: string | null, value: string }> | null }, properties?: { __typename?: 'Properties', amount?: string | null, packageSize?: number | null, freeUnits?: number | null, fixedAmount?: string | null, freeUnitsPerEvents?: number | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: number, perUnitAmount: string, toValue?: number | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: number, perUnitAmount: string, toValue?: number | null }> | null } | null, groupProperties?: Array<{ __typename?: 'GroupProperties', groupId: string, values: { __typename?: 'Properties', amount?: string | null, packageSize?: number | null, freeUnits?: number | null, fixedAmount?: string | null, freeUnitsPerEvents?: number | null, freeUnitsPerTotalAggregation?: string | null, rate?: string | null, graduatedRanges?: Array<{ __typename?: 'GraduatedRange', flatAmount: string, fromValue: number, perUnitAmount: string, toValue?: number | null }> | null, volumeRanges?: Array<{ __typename?: 'VolumeRange', flatAmount: string, fromValue: number, perUnitAmount: string, toValue?: number | null }> | null } }> | null }> | null }; -export type CustomerDetailsFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, hasActiveWallet: boolean, currency?: CurrencyEnum | null, vatRate?: number | null, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, subscriptions: Array<{ __typename?: 'Subscription', id: string, status?: StatusTypeEnum | null, startedAt?: any | null, nextPendingStartDate?: any | null, name?: string | null, nextName?: string | null, externalId: string, periodEndDate?: any | null, subscriptionDate?: any | null, plan: { __typename?: 'Plan', id: string, amountCurrency: CurrencyEnum, name: string, code: string }, nextPlan?: { __typename?: 'Plan', id: string, name: string, code: string } | null }>, invoices?: Array<{ __typename?: 'Invoice', id: string, amountCurrency: CurrencyEnum, issuingDate: any, number: string, status: InvoiceStatusTypeEnum, totalAmountCents: number }> | null, appliedCoupons?: Array<{ __typename?: 'AppliedCoupon', id: string, amountCurrency?: CurrencyEnum | null, amountCents?: number | null, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null, coupon: { __typename?: 'Coupon', id: string, name: string } }> | null, appliedAddOns?: Array<{ __typename?: 'AppliedAddOn', id: string, amountCents: number, amountCurrency: CurrencyEnum, createdAt: any, addOn: { __typename?: 'AddOn', id: string, name: string } }> | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }; +export type CustomerDetailsFragment = { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, hasActiveWallet: boolean, currency?: CurrencyEnum | null, vatRate?: number | null, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, subscriptions: Array<{ __typename?: 'Subscription', id: string, status?: StatusTypeEnum | null, startedAt?: any | null, nextPendingStartDate?: any | null, name?: string | null, nextName?: string | null, externalId: string, periodEndDate?: any | null, subscriptionDate?: any | null, plan: { __typename?: 'Plan', id: string, amountCurrency: CurrencyEnum, name: string, code: string }, nextPlan?: { __typename?: 'Plan', id: string, name: string, code: string } | null }>, invoices?: Array<{ __typename?: 'Invoice', id: string, amountCurrency: CurrencyEnum, issuingDate: any, number: string, status: InvoiceStatusTypeEnum, totalAmountCents: number }> | null, appliedCoupons?: Array<{ __typename?: 'AppliedCoupon', id: string, amountCurrency?: CurrencyEnum | null, amountCents?: number | null, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null, coupon: { __typename?: 'Coupon', id: string, name: string } }> | null, appliedAddOns?: Array<{ __typename?: 'AppliedAddOn', id: string, amountCents: number, amountCurrency: CurrencyEnum, createdAt: any, addOn: { __typename?: 'AddOn', id: string, name: string } }> | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null }; export type GetCustomerQueryVariables = Exact<{ id: Scalars['ID']; }>; -export type GetCustomerQuery = { __typename?: 'Query', customer?: { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, hasActiveWallet: boolean, currency?: CurrencyEnum | null, vatRate?: number | null, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, subscriptions: Array<{ __typename?: 'Subscription', id: string, status?: StatusTypeEnum | null, startedAt?: any | null, nextPendingStartDate?: any | null, name?: string | null, nextName?: string | null, externalId: string, periodEndDate?: any | null, subscriptionDate?: any | null, plan: { __typename?: 'Plan', id: string, amountCurrency: CurrencyEnum, name: string, code: string }, nextPlan?: { __typename?: 'Plan', id: string, name: string, code: string } | null }>, invoices?: Array<{ __typename?: 'Invoice', id: string, amountCurrency: CurrencyEnum, issuingDate: any, number: string, status: InvoiceStatusTypeEnum, totalAmountCents: number }> | null, appliedCoupons?: Array<{ __typename?: 'AppliedCoupon', id: string, amountCurrency?: CurrencyEnum | null, amountCents?: number | null, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null, coupon: { __typename?: 'Coupon', id: string, name: string } }> | null, appliedAddOns?: Array<{ __typename?: 'AppliedAddOn', id: string, amountCents: number, amountCurrency: CurrencyEnum, createdAt: any, addOn: { __typename?: 'AddOn', id: string, name: string } }> | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null } | null }; +export type GetCustomerQuery = { __typename?: 'Query', customer?: { __typename?: 'CustomerDetails', id: string, name?: string | null, externalId: string, canBeDeleted: boolean, hasActiveWallet: boolean, currency?: CurrencyEnum | null, vatRate?: number | null, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, canEditAttributes: boolean, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, subscriptions: Array<{ __typename?: 'Subscription', id: string, status?: StatusTypeEnum | null, startedAt?: any | null, nextPendingStartDate?: any | null, name?: string | null, nextName?: string | null, externalId: string, periodEndDate?: any | null, subscriptionDate?: any | null, plan: { __typename?: 'Plan', id: string, amountCurrency: CurrencyEnum, name: string, code: string }, nextPlan?: { __typename?: 'Plan', id: string, name: string, code: string } | null }>, invoices?: Array<{ __typename?: 'Invoice', id: string, amountCurrency: CurrencyEnum, issuingDate: any, number: string, status: InvoiceStatusTypeEnum, totalAmountCents: number }> | null, appliedCoupons?: Array<{ __typename?: 'AppliedCoupon', id: string, amountCurrency?: CurrencyEnum | null, amountCents?: number | null, percentageRate?: number | null, frequency: CouponFrequency, frequencyDuration?: number | null, coupon: { __typename?: 'Coupon', id: string, name: string } }> | null, appliedAddOns?: Array<{ __typename?: 'AppliedAddOn', id: string, amountCents: number, amountCurrency: CurrencyEnum, createdAt: any, addOn: { __typename?: 'AddOn', id: string, name: string } }> | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null } | null }; export type CustomersQueryVariables = Exact<{ page?: InputMaybe; @@ -3208,7 +3213,7 @@ export type CustomersQueryVariables = Exact<{ }>; -export type CustomersQuery = { __typename?: 'Query', customers: { __typename?: 'CustomerCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'Customer', id: string, name?: string | null, externalId: string, createdAt: any, canBeDeleted: boolean, activeSubscriptionCount: number, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, stripeCustomer?: { __typename?: 'StripeCustomer', id: string, providerCustomerId?: string | null } | null }> } }; +export type CustomersQuery = { __typename?: 'Query', customers: { __typename?: 'CustomerCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'Customer', id: string, name?: string | null, externalId: string, createdAt: any, canBeDeleted: boolean, activeSubscriptionCount: number, legalName?: string | null, legalNumber?: string | null, phone?: string | null, email?: string | null, addressLine1?: string | null, addressLine2?: string | null, state?: string | null, country?: CountryCode | null, currency?: CurrencyEnum | null, canEditAttributes: boolean, city?: string | null, zipcode?: string | null, paymentProvider?: ProviderTypeEnum | null, providerCustomer?: { __typename?: 'ProviderCustomer', id: string, providerCustomerId?: string | null, syncWithProvider?: boolean | null } | null }> } }; export type GetinviteQueryVariables = Exact<{ token: Scalars['String']; @@ -3263,10 +3268,22 @@ export type WehbookSettingQueryVariables = Exact<{ [key: string]: never; }>; export type WehbookSettingQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, organizations?: Array<{ __typename?: 'Organization', id: string, webhookUrl?: string | null }> | null } }; +export type GocardlessIntegrationsSettingQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GocardlessIntegrationsSettingQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, organizations?: Array<{ __typename?: 'Organization', id: string, gocardlessPaymentProvider?: { __typename?: 'GocardlessProvider', id: string, hasAccessToken: boolean, webhookSecret?: string | null } | null }> | null } }; + +export type AddGocardlessPaymentProviderMutationVariables = Exact<{ + input: AddGocardlessPaymentProviderInput; +}>; + + +export type AddGocardlessPaymentProviderMutation = { __typename?: 'Mutation', addGocardlessPaymentProvider?: { __typename?: 'GocardlessProvider', id: string, hasAccessToken: boolean, webhookSecret?: string | null } | null }; + export type IntegrationsSettingQueryVariables = Exact<{ [key: string]: never; }>; -export type IntegrationsSettingQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, organizations?: Array<{ __typename?: 'Organization', id: string, stripePaymentProvider?: { __typename?: 'StripeProvider', id: string } | null }> | null } }; +export type IntegrationsSettingQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, organizations?: Array<{ __typename?: 'Organization', id: string, stripePaymentProvider?: { __typename?: 'StripeProvider', id: string } | null, gocardlessPaymentProvider?: { __typename?: 'GocardlessProvider', id: string } | null }> | null } }; export type GetInvitesQueryVariables = Exact<{ page?: InputMaybe; @@ -3442,9 +3459,10 @@ export const AddCustomerDrawerFragmentDoc = gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId + syncWithProvider } } `; @@ -3905,9 +3923,10 @@ export const AddCustomerDrawerDetailFragmentDoc = gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId + syncWithProvider } } `; @@ -3929,7 +3948,7 @@ export const CustomerMainInfosFragmentDoc = gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId } @@ -6392,6 +6411,83 @@ export function useWehbookSettingLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt export type WehbookSettingQueryHookResult = ReturnType; export type WehbookSettingLazyQueryHookResult = ReturnType; export type WehbookSettingQueryResult = Apollo.QueryResult; +export const GocardlessIntegrationsSettingDocument = gql` + query gocardlessIntegrationsSetting { + currentUser { + id + organizations { + id + gocardlessPaymentProvider { + id + hasAccessToken + webhookSecret + } + } + } +} + `; + +/** + * __useGocardlessIntegrationsSettingQuery__ + * + * To run a query within a React component, call `useGocardlessIntegrationsSettingQuery` and pass it any options that fit your needs. + * When your component renders, `useGocardlessIntegrationsSettingQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGocardlessIntegrationsSettingQuery({ + * variables: { + * }, + * }); + */ +export function useGocardlessIntegrationsSettingQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GocardlessIntegrationsSettingDocument, options); + } +export function useGocardlessIntegrationsSettingLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GocardlessIntegrationsSettingDocument, options); + } +export type GocardlessIntegrationsSettingQueryHookResult = ReturnType; +export type GocardlessIntegrationsSettingLazyQueryHookResult = ReturnType; +export type GocardlessIntegrationsSettingQueryResult = Apollo.QueryResult; +export const AddGocardlessPaymentProviderDocument = gql` + mutation addGocardlessPaymentProvider($input: AddGocardlessPaymentProviderInput!) { + addGocardlessPaymentProvider(input: $input) { + id + hasAccessToken + webhookSecret + } +} + `; +export type AddGocardlessPaymentProviderMutationFn = Apollo.MutationFunction; + +/** + * __useAddGocardlessPaymentProviderMutation__ + * + * To run a mutation, you first call `useAddGocardlessPaymentProviderMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useAddGocardlessPaymentProviderMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [addGocardlessPaymentProviderMutation, { data, loading, error }] = useAddGocardlessPaymentProviderMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useAddGocardlessPaymentProviderMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(AddGocardlessPaymentProviderDocument, options); + } +export type AddGocardlessPaymentProviderMutationHookResult = ReturnType; +export type AddGocardlessPaymentProviderMutationResult = Apollo.MutationResult; +export type AddGocardlessPaymentProviderMutationOptions = Apollo.BaseMutationOptions; export const IntegrationsSettingDocument = gql` query integrationsSetting { currentUser { @@ -6401,6 +6497,9 @@ export const IntegrationsSettingDocument = gql` stripePaymentProvider { id } + gocardlessPaymentProvider { + id + } } } } diff --git a/src/hooks/useCreateEditCustomer.ts b/src/hooks/useCreateEditCustomer.ts index 8dfe0c4a2..67b5b99b2 100644 --- a/src/hooks/useCreateEditCustomer.ts +++ b/src/hooks/useCreateEditCustomer.ts @@ -37,9 +37,10 @@ gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId + syncWithProvider } } @@ -61,9 +62,10 @@ gql` city zipcode paymentProvider - stripeCustomer { + providerCustomer { id providerCustomerId + syncWithProvider } } @@ -134,27 +136,33 @@ export const useCreateEditCustomer: UseCreateEditCustomer = ({ customer }) => { return { isEdition: !!customer, onSave: !!customer - ? async ({ stripeCustomer, paymentProvider, ...values }) => + ? async ({ providerCustomer, paymentProvider, ...values }) => await update({ variables: { input: { id: customer?.id as string, paymentProvider, - stripeCustomer: { - providerCustomerId: !paymentProvider ? null : stripeCustomer?.providerCustomerId, + providerCustomer: { + providerCustomerId: !paymentProvider + ? null + : providerCustomer?.providerCustomerId, + syncWithProvider: !paymentProvider ? null : providerCustomer?.syncWithProvider, }, ...values, }, }, }) - : async ({ stripeCustomer, paymentProvider, ...values }) => + : async ({ providerCustomer, paymentProvider, ...values }) => await create({ variables: { input: { ...values, paymentProvider, - stripeCustomer: { - providerCustomerId: !paymentProvider ? null : stripeCustomer?.providerCustomerId, + providerCustomer: { + providerCustomerId: !paymentProvider + ? null + : providerCustomer?.providerCustomerId, + syncWithProvider: !paymentProvider ? null : providerCustomer?.syncWithProvider, }, }, }, diff --git a/src/layouts/Developers.tsx b/src/layouts/Developers.tsx index ca673da92..edc1f37f5 100644 --- a/src/layouts/Developers.tsx +++ b/src/layouts/Developers.tsx @@ -11,7 +11,7 @@ const Developers = () => { const { translate } = useInternationalization() const tabsOptions = [ { - title: translate('text_6271200984178801ba8bdeca'), + title: translate('text_636df520279a9e1b3c68cc67'), link: API_KEYS_ROUTE, match: [DEVELOPERS_ROUTE, API_KEYS_ROUTE], }, diff --git a/src/pages/developers/ApiKeys.tsx b/src/pages/developers/ApiKeys.tsx index de0b6fcec..49da81e68 100644 --- a/src/pages/developers/ApiKeys.tsx +++ b/src/pages/developers/ApiKeys.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { useCurrentUserInfosVar, addToast } from '~/core/apolloClient' import { useInternationalization } from '~/hooks/core/useInternationalization' -import { Typography, Button, Icon, Avatar } from '~/components/designSystem' +import { Typography, Button, Avatar, Icon } from '~/components/designSystem' import { HEADER_TABLE_HEIGHT, NAV_HEIGHT, theme } from '~/styles' gql` @@ -18,58 +18,76 @@ const ApiKeys = () => { const { translate } = useInternationalization() return ( - - - {translate('text_6227a2e847fcd700e903893f')} - - - {translate('text_6227a2e847fcd700e9038947')} - - - {translate('text_6227a2e847fcd700e903893f')} - - - - + <> + + {translate('text_6227a2e847fcd700e903893f')} + {translate('text_6227a2e847fcd700e9038947')} + + {translate('text_6227a2e847fcd700e903893f')} + + + - - - {currentOrganization?.apiKey} - - - - - + + {currentOrganization?.apiKey} + + + + + {translate('text_636df520279a9e1b3c68cc75')} + + {translate('text_636df520279a9e1b3c68cc75')} + + + + + + {currentOrganization?.id} + + + + ) } -const Page = styled.div` - padding: ${theme.spacing(8)} ${theme.spacing(12)}; +const ApiKey = styled.div` + padding: ${theme.spacing(12)}; ` -const Subtitle = styled(Typography)` - margin-bottom: ${theme.spacing(8)}; +const OrganizationId = styled.div` + padding-right: ${theme.spacing(12)}; + padding-left: ${theme.spacing(12)}; + padding-bottom: ${theme.spacing(12)}; ` -const CopyBlockTitle = styled(Typography)` - display: flex; - align-items: center; - height: ${HEADER_TABLE_HEIGHT}px; - width: 100%; - box-shadow: ${theme.shadows[7]}; +const Subtitle = styled(Typography)` + margin-top: ${theme.spacing(2)}; ` const CopyBlock = styled.div` @@ -78,17 +96,23 @@ const CopyBlock = styled.div` box-shadow: ${theme.shadows[7]}; display: flex; align-items: center; - justify-content: space-between; - margin-bottom: ${theme.spacing(12)}; + + > *:first-child { + margin-right: ${theme.spacing(3)}; + } ` -const CopyBlockLeft = styled.div` +const SubtitleSecretKey = styled(Typography)` + height: ${HEADER_TABLE_HEIGHT}px; + width: 100%; display: flex; align-items: center; + margin-top: ${theme.spacing(8)}; + box-shadow: ${theme.shadows[7]}; ` -const CopyIcon = styled(Avatar)` - margin-right: ${theme.spacing(3)}; +const SecretKey = styled(Typography)` + margin-right: auto; ` export default ApiKeys diff --git a/src/pages/settings/GocardlessIntegration.tsx b/src/pages/settings/GocardlessIntegration.tsx new file mode 100644 index 000000000..8a8c1532e --- /dev/null +++ b/src/pages/settings/GocardlessIntegration.tsx @@ -0,0 +1,294 @@ +import styled from 'styled-components' +import { useLocation } from 'react-router-dom' +import { useState, useEffect } from 'react' +import { gql } from '@apollo/client' + +import { theme, PageHeader, NAV_HEIGHT, HEADER_TABLE_HEIGHT } from '~/styles' +import { INTEGRATIONS_ROUTE, GOCARDLESS_INTEGRATION_ROUTE } from '~/core/router' +import { envGlobalVar } from '~/core/apolloClient' +import { + Typography, + ButtonLink, + Skeleton, + Avatar, + Chip, + NavigationTab, + Button, + Icon, + Tooltip, +} from '~/components/designSystem' +import { + useGocardlessIntegrationsSettingQuery, + useAddGocardlessPaymentProviderMutation, +} from '~/generated/graphql' +import { useInternationalization } from '~/hooks/core/useInternationalization' +import GoCardless from '~/public/images/gocardless-large.svg' +import { addToast } from '~/core/apolloClient' + +gql` + query gocardlessIntegrationsSetting { + currentUser { + id + organizations { + id + gocardlessPaymentProvider { + id + hasAccessToken + webhookSecret + } + } + } + } + + mutation addGocardlessPaymentProvider($input: AddGocardlessPaymentProviderInput!) { + addGocardlessPaymentProvider(input: $input) { + id + hasAccessToken + webhookSecret + } + } +` + +const GocardlessIntegration = () => { + const { translate } = useInternationalization() + const { data, loading } = useGocardlessIntegrationsSettingQuery() + const query = new URLSearchParams(useLocation().search) + const code = query.get('code') + const [isConnectionEstablished, setIsConnectionEstablished] = useState(false) + const [webhookSecretKey, setWebhookSecretKey] = useState('') + const tabsOptions = [ + { + title: translate('text_634ea0ecc6147de10ddb6635'), + link: GOCARDLESS_INTEGRATION_ROUTE, + }, + ] + const gocardlessPaymentProvider = (data?.currentUser?.organizations || [])[0] + ?.gocardlessPaymentProvider + const { lagoOauthProxyUrl } = envGlobalVar() + const [addPaymentProvider] = useAddGocardlessPaymentProviderMutation({ + onCompleted({ addGocardlessPaymentProvider }) { + if (addGocardlessPaymentProvider?.id && addGocardlessPaymentProvider?.webhookSecret) { + setIsConnectionEstablished(true) + setWebhookSecretKey(addGocardlessPaymentProvider?.webhookSecret) + addToast({ + message: translate('text_634ea0ecc6147de10ddb6645'), + severity: 'success', + }) + } + }, + }) + + useEffect(() => { + if (code) { + addPaymentProvider({ + variables: { + input: { + accessCode: code, + }, + }, + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (gocardlessPaymentProvider && gocardlessPaymentProvider.webhookSecret) { + setIsConnectionEstablished(true) + setWebhookSecretKey(gocardlessPaymentProvider.webhookSecret) + } + }, [gocardlessPaymentProvider]) + + return ( +
+ + + + {loading ? ( + + ) : ( + + {translate('text_634ea0ecc6147de10ddb6629')} + + )} + + + + {loading ? ( + <> + +
+ + +
+ + ) : ( + <> + + + +
+ + + {translate('text_634ea0ecc6147de10ddb6648')} + + {isConnectionEstablished && ( + + )} + + {translate('text_634ea0ecc6147de10ddb6643')} +
+ + )} +
+ + + + {translate('text_634ea0ecc6147de10ddb663d')} + + + {isConnectionEstablished && ( + {translate('text_634ea0ecc6147de10ddb6641')} + )} + + + {translate('text_635bd8acb686f18909a57c89')} + + {translate('text_635bd8acb686f18909a57c8d')} + + + {loading ? ( + <> + + + + ) : ( + isConnectionEstablished && ( + <> + + + + {webhookSecretKey} + + + + + ) + )} + + {!loading && {translate('text_635bd8acb686f18909a57c93')}} + +
+ ) +} + +const HeaderBlock = styled.div` + display: flex; + align-items: center; + + > *:first-child  { + margin-right: ${theme.spacing(3)}; + } +` + +const MainInfos = styled.div` + display: flex; + align-items: center; + padding: ${theme.spacing(8)} ${theme.spacing(12)}; +` + +const Settings = styled.div` + padding: 0 ${theme.spacing(12)}; + margin-bottom: ${theme.spacing(12)}; +` + +const Title = styled(Typography)` + height: ${NAV_HEIGHT}px; + width: 100%; + box-shadow: ${theme.shadows[7]}; + display: flex; + align-items: center; +` + +const Head = styled.div` + height: ${NAV_HEIGHT}px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: ${theme.shadows[7]}; + margin-bottom: ${theme.spacing(4)}; +` + +const Subtitle = styled(Typography)` + height: ${HEADER_TABLE_HEIGHT}px; + width: 100%; + display: flex; + align-items: center; +` + +const SubtitleSecretKey = styled(Typography)` + height: ${HEADER_TABLE_HEIGHT}px; + width: 100%; + display: flex; + align-items: center; + box-shadow: ${theme.shadows[7]}; +` + +const SecretKeyItem = styled.div` + height: ${NAV_HEIGHT}px; + width: 100%; + box-shadow: ${theme.shadows[7]}; + display: flex; + align-items: center; + + > *:first-child { + margin-right: ${theme.spacing(3)}; + } +` + +const SecretKey = styled(Typography)` + margin-right: auto; +` + +const Info = styled(Typography)` + height: ${HEADER_TABLE_HEIGHT}px; + box-shadow: ${theme.shadows[7]}; + display: flex; + justify-content: flex-start; + align-items: center; +` + +const StyledAvatar = styled(Avatar)` + margin-right: ${theme.spacing(4)}; +` + +const Line = styled.div` + display: flex; + align-items: center; + + > *:first-child { + margin-right: ${theme.spacing(2)}; + } +` + +export default GocardlessIntegration diff --git a/src/pages/settings/Integrations.tsx b/src/pages/settings/Integrations.tsx index cb59e9b05..f0222389d 100644 --- a/src/pages/settings/Integrations.tsx +++ b/src/pages/settings/Integrations.tsx @@ -7,8 +7,10 @@ import { useInternationalization } from '~/hooks/core/useInternationalization' import { theme } from '~/styles' import { Typography, Selector, Avatar, SelectorSkeleton, Chip } from '~/components/designSystem' import Stripe from '~/public/images/stripe.svg' +import GoCardless from '~/public/images/gocardless.svg' import { useIntegrationsSettingQuery } from '~/generated/graphql' -import { STRIPE_INTEGRATION_ROUTE } from '~/core/router' +import { STRIPE_INTEGRATION_ROUTE, GOCARDLESS_INTEGRATION_ROUTE } from '~/core/router' +import { envGlobalVar } from '~/core/apolloClient' import { AddStripeDialog, AddStripeDialogRef, @@ -23,6 +25,9 @@ gql` stripePaymentProvider { id } + gocardlessPaymentProvider { + id + } } } } @@ -35,6 +40,9 @@ const Integrations = () => { const { data, loading } = useIntegrationsSettingQuery() const hasStripeIntegration = !!(data?.currentUser?.organizations || [])[0]?.stripePaymentProvider ?.id + const hasGocardlessIntegration = !!(data?.currentUser?.organizations || [])[0] + ?.gocardlessPaymentProvider?.id + const { lagoOauthProxyUrl } = envGlobalVar() return ( @@ -48,31 +56,55 @@ const Integrations = () => { ))} ) : ( - - - - } - endIcon={ - hasStripeIntegration ? ( - - ) : undefined - } - onClick={() => { - if (hasStripeIntegration) { - navigate(STRIPE_INTEGRATION_ROUTE) - } else { - const element = document.activeElement as HTMLElement + <> + + + + } + endIcon={ + hasStripeIntegration ? ( + + ) : undefined + } + onClick={() => { + if (hasStripeIntegration) { + navigate(STRIPE_INTEGRATION_ROUTE) + } else { + const element = document.activeElement as HTMLElement - element.blur && element.blur() - addDialogRef.current?.openDialog() + element.blur && element.blur() + addDialogRef.current?.openDialog() + } + }} + fullWidth + /> + + + } - }} - fullWidth - /> + endIcon={ + hasGocardlessIntegration ? ( + + ) : undefined + } + onClick={() => { + if (hasGocardlessIntegration) { + navigate(GOCARDLESS_INTEGRATION_ROUTE) + } else { + window.open(`${lagoOauthProxyUrl}/gocardless/auth`, '_blank') + } + }} + fullWidth + /> + )} @@ -87,6 +119,10 @@ const Title = styled(Typography)` margin-bottom: ${theme.spacing(2)}; ` +const StyledSelector = styled(Selector)` + margin-bottom: ${theme.spacing(4)}; +` + const Subtitle = styled(Typography)` margin-bottom: ${theme.spacing(8)}; ` diff --git a/src/pages/settings/StripeIntegration.tsx b/src/pages/settings/StripeIntegration.tsx index db5b6c651..f3d17b416 100644 --- a/src/pages/settings/StripeIntegration.tsx +++ b/src/pages/settings/StripeIntegration.tsx @@ -15,11 +15,7 @@ import { NavigationTab, Popper, } from '~/components/designSystem' -import { Switch } from '~/components/form' -import { - useStripeIntegrationsSettingQuery, - useUpdateStripeIntegrationMutation, -} from '~/generated/graphql' +import { useStripeIntegrationsSettingQuery } from '~/generated/graphql' import { useInternationalization } from '~/hooks/core/useInternationalization' import Stripe from '~/public/images/stripe.svg' import { @@ -30,7 +26,6 @@ import { DeleteStripeIntegrationDialog, DeleteStripeIntegrationDialogRef, } from '~/components/settings/integrations/DeleteStripeIntegrationDialog' -import { addToast } from '~/core/apolloClient' gql` fragment StripeIntegration on StripeProvider { @@ -63,16 +58,6 @@ const StripeIntegration = () => { const deleteDialogRef = useRef(null) const { translate } = useInternationalization() const { data, loading } = useStripeIntegrationsSettingQuery() - const [update] = useUpdateStripeIntegrationMutation({ - onCompleted({ addStripePaymentProvider }) { - if (addStripePaymentProvider?.id) { - addToast({ - message: translate('text_62b1edddbf5f461ab9712819'), - severity: 'success', - }) - } - }, - }) const tabsOptions = [ { title: translate('text_62b1edddbf5f461ab9712725'), @@ -179,24 +164,6 @@ const StripeIntegration = () => { )} - {!loading && ( - <> - - {translate('text_62b1edddbf5f461ab97127b4')} - - - - await update({ variables: { input: { createCustomers: value } } }) - } - label={translate('text_62b1edddbf5f461ab97127c8')} - subLabel={translate('text_62b1edddbf5f461ab97127d8')} - /> - - - )} @@ -213,15 +180,6 @@ const HeaderBlock = styled.div` } ` -const SwitchBlock = styled.div` - > * { - margin-bottom: ${theme.spacing(6)}; - &:last-child { - margin-bottom: ${theme.spacing(20)}; - } - } -` - const MainInfos = styled.div` display: flex; align-items: center; @@ -248,10 +206,6 @@ const Subtitle = styled(Typography)` align-items: center; ` -const TitleWithMargin = styled(Title)` - margin-bottom: ${theme.spacing(6)}; -` - const ApiKeyItem = styled.div` height: ${NAV_HEIGHT}px; width: 100%; diff --git a/src/public/images/gocardless-large.svg b/src/public/images/gocardless-large.svg new file mode 100644 index 000000000..a296dada1 --- /dev/null +++ b/src/public/images/gocardless-large.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/public/images/gocardless.svg b/src/public/images/gocardless.svg new file mode 100644 index 000000000..81bc470ca --- /dev/null +++ b/src/public/images/gocardless.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/webpack.common.js b/webpack.common.js index 3fa4e8ecb..18bb64d41 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -30,6 +30,7 @@ module.exports = () => { APP_ENV: JSON.stringify(APP_ENV), API_URL: JSON.stringify(process.env.API_URL), APP_VERSION: JSON.stringify(version), + LAGO_OAUTH_PROXY_URL: JSON.stringify(process.env.LAGO_OAUTH_PROXY_URL), LAGO_DISABLE_SIGNUP: JSON.stringify(process.env.LAGO_DISABLE_SIGNUP), }), ],