Skip to content

Commit 36a6015

Browse files
feat(condo): DOMA-12895 subscription auto payments (#7375)
* feat(condo): DOMA-12895 added access to Invoice * feat(condo): DOMA-12895 update SubscriptionContext fields * feat(condo): DOMA-12895 added recurrent fields to SubscriptionContext * feat(condo): DOMA-12895 recreate migration * feat(condo): DOMA-12895 added status to SubscriptionContext * feat(condo): DOMA-12895 added registerSubscriptionContext mutation * feat(condo): DOMA-12895 rename activateSubscriptionPlan to activateSubscriptionContext * feat(condo): DOMA-12895 activate subscription when invoice became paid * feat(condo): DOMA-12895 added processRecurrentSubscriptionPayments task * feat(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 fix build * feat(condo): DOMA-12895 fix front error * feat(condo): DOMA-12895 recreate migration * feat(condo): DOMA-12895 added adminFragment to subscription context * feat(condo): DOMA-12895 refactor subscription context fields * feat(condo-yookassa-gateway): DOMA-12895 added UpdateSubscriptionContextPaymentMethodService * feat(condo): DOMA-12895 handle error in subscription autopay * feat(condo): DOMA-12895 added submodule * feat(condo): DOMA-12895 remove old migrations * feat(config): DOMA-12895 added app config * feat(config): DOMA-12895 recreate migration * feat(condo): DOMA-12895 get organizationId for payment query from RegisterSubscriptionContext * feat(condo-b2b-payments-gateway): DOMA-12895 rename app * feat(condo): DOMA-12895 update linked cards frontend * feat(condo): DOMA-12895 added payment history modal * feat(condo): DOMA-12895 added validation to Invoice schema * feat(condo): DOMA-12895 added log if subscription context not found for paid b2b invoice * feat(condo): DOMA-12895 fix tests and fix getting context and multiPayment in register subscription context * feat(condo): DOMA-12895 remove lodash get * feat(condo): DOMA-12895 update migration, rebase * feat(condo): DOMA-12895 update test * feat(condo): DOMA-12895 filter only contexts in DONE status in existingContexts in registerSubscriptionContext * feat(condo): DOMA-12895 filter only done subscription context in validation * fix(condo): DOMA-12895 fix tests, fix deps * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 added subscription plan validation in context * fix(condo): DOMA-12895 fix invoice tests * fix(condo): DOMA-12895 fix other tests * fix(condo): DOMA-12895 rebase on main * fix(condo): DOMA-12895 recreate migration * fix(condo): DOMA-12895 update context before call acquiring in UpdateSubscriptionContextPaymentMethodService.js * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 added validation for SubscriptionContext.invoice * fix(condo): DOMA-12895 use registerOrganization in create-organization script * fix(condo): DOMA-12895 update condo prepare js * fix(condo): DOMA-12895 remove recipient creation from prepare * fix(condo): DOMA-12895 fix calculateSubscriptionStartDate calculation * fix(condo): DOMA-12895 reused previously created subscription contexts in registerSubscriptionContext, added ERROR_NEED_RETRY status * fix(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 add error alert * feat(condo): DOMA-12895 added buffer period * feat(condo): DOMA-12895 added notifications in bell * feat(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 get toPay from invoice * feat(condo): DOMA-12895 fix nest level in function * feat(condo): DOMA-12895 fixes from review, added basic auth to cards route * feat(condo): DOMA-12895 fixes after self review * feat(callcenter): DOMA-12895 add partial to type * feat(condo): DOMA-12895 fix subscription recipient creation * feat(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 fix tests * feat(condo-b2b-payments-gateway): DOMA-12895 update prepare and env example * feat(condo-b2b-payments-gateway): DOMA-12895 remove unused dep * feat(condo-b2b-payments-gateway): DOMA-12895 remove env example * feat(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 fix tests * feat(condo): DOMA-12895 fix subscriptions gql * feat(condo): DOMA-12895 fix directAccess config, recreate migration * feat(condo): DOMA-12895 activate subscription context in task * feat(condo): DOMA-12895 fix Invoice tests * feat(condo): DOMA-12895 move frozenPaymentInfo creation to billingFridge * feat(condo): DOMA-12895 added spec for subscriptionContext.js * feat(condo): DOMA-12895 update UpdateSubscriptionContextPaymentMethodService acesses * feat(condo): DOMA-12895 added status transitions validation to subscription context * feat(condo): DOMA-12895 rename status, recreate migration * feat(condo): DOMA-12895 update schema ts * feat(condo): DOMA-12895 fix build * feat(condo): DOMA-12895 update miniapp * feat(condo): DOMA-12895 create subscription recipient organization in prepare * feat(condo): DOMA-12895 return buffer days logic * feat(condo): DOMA-12895 use dayjs in util * feat(condo): DOMA-12895 fixes after review * feat(b2b-payments-gateway): DOMA-12895 rename app * feat(b2b-payments-gateway): DOMA-12895 fix linter issue * feat(config): DOMA-12895 update config * feat(config): DOMA-13104 added config for review * feat(condo): DOMA-12895 pass provider and returnUrl params to directUrl * chore(condo): DOMA-12895 rebase, recreate migration * fix(config): DOMA-12895 fix subscription recipient env * fix(b2b-payments-gateway): DOMA-12895 fix queries * fix(b2b-payments-gateway): DOMA-12895 add logging * fix(b2b-payments-gateway): DOMA-12895 fix queries * chore(condo): DOMA-12895 allow to update startAt and endAt for test purposes * fix(b2b-payments-gateway): DOMA-12895 fix queries * fix(b2b-payments-gateway): DOMA-12895 fix imports * fix(b2b-payments-gateway): DOMA-12895 normalize payment * fix(condo): DOMA-12895 fix getSchemaCtx * fix(condo): DOMA-12895 fix getSchemaCtx * fix(b2b-payments-gateway): DOMA-12895 fix multiPayment update * fix(b2b-payments-gateway): DOMA-12895 fix Payment update * fix(b2b-payments-gateway): DOMA-12895 fix Payment update * fix(b2b-payments-gateway): DOMA-12895 return fields in B2BCardBinding queries * fix(b2b-payments-gateway): DOMA-12895 fix proccess payment flow * fix(b2b-payments-gateway): DOMA-12895 fix import * fix(condo): DOMA-12895 check context.authedItem in RegisterMultiPayment * fix(condo): DOMA-12895 fix proceedPayment responce * fix(condo): DOMA-12895 front fixes and update status enum to lowercase * fix(condo): DOMA-12895 get last payment for new context and added logs * fix(condo): DOMA-12895 fix lint issues and return update false to startAt and endAt * fix(condo): DOMA-12895 pass successPayment param * fix(condo): DOMA-12895 fix lint issue * fix(condo): DOMA-12895 rebase helm * fix(condo): DOMA-12895 return enum to upperCase * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 fix tests * fix(condo): DOMA-12895 fix connect button * fix(condo): DOMA-12895 fix yarn lock * chore(condo): DOMA-12895 move submodules to main
1 parent 6f5793e commit 36a6015

File tree

81 files changed

+7233
-1332
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+7233
-1332
lines changed

.github/workflows/deploy_development.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ jobs:
7575
WERF_SET_CI_POS_INTEGRATION_URL: "global.ci_pos_integration_url=pos-integration.d.doma.ai"
7676
WERF_SET_CI_ACCRUALS_GATEWAY_URL: "global.ci_accruals_gateway_url=accruals-gateway.d.doma.ai"
7777
WERF_SET_CI_PROPERTY_AI_ASSISTANT_URL: "global.ci_property_ai_assistant_url=property-ai-assistant.d.doma.ai"
78+
WERF_SET_CI_B2B_PAYMENTS_GATEWAY_URL: "global.ci_b2b_payments_gateway_url=b2b-payments-gateway.d.doma.ai"
7879
WERF_SET_CI_NAMESPACE: "global.ci_namespace=development"
7980
WERF_NAMESPACE: "development"
8081
WERF_VIRTUAL_MERGE_FROM_COMMIT: "true"

.github/workflows/deploy_production.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ jobs:
8282
WERF_SET_CI_POS_INTEGRATION_URL: "global.ci_pos_integration_url=pos-integration.doma.ai"
8383
WERF_SET_CI_ACCRUALS_GATEWAY_URL: "global.ci_accruals_gateway_url=accruals-gateway.doma.ai"
8484
WERF_SET_CI_PROPERTY_AI_ASSISTANT_URL: "global.ci_property_ai_assistant_url=property-ai-assistant.doma.ai"
85+
WERF_SET_CI_B2B_PAYMENTS_GATEWAY_URL: "global.ci_b2b_payments_gateway_url=b2b-payments-gateway.doma.ai"
8586
WERF_SET_CI_NAMESPACE: "global.ci_namespace=production"
8687
WERF_NAMESPACE: "production"
8788
WERF_VIRTUAL_MERGE_FROM_COMMIT: "true"

.github/workflows/deploy_review.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ jobs:
7474
WERF_SET_CI_METER_IMPORTER_URL: "global.ci_meter_importer_url=meter-importer.r.doma.ai"
7575
WERF_SET_CI_BILLING_CONNECTOR_URL: "global.ci_billing_connector_url=review-${{ env.REVIEW_URL_PREFIX }}-billing-connector.r.doma.ai"
7676
WERF_SET_CI_POS_INTEGRATION_URL: "global.ci_pos_integration_url=review-${{ env.REVIEW_URL_PREFIX }}-pos-integration.r.doma.ai"
77+
WERF_SET_CI_B2B_PAYMENTS_GATEWAY_URL: "global.ci_b2b_payments_gateway_url=review-${{ env.REVIEW_URL_PREFIX }}-b2b-payments-gateway.r.doma.ai"
7778
WERF_SET_CI_NAMESPACE: "global.ci_namespace=${{ env.REVIEW_NAMESPACE }}"
7879
WERF_SET_CI_URL_PREFIX: "global.ci_url_prefix=${{ env.REVIEW_URL_PREFIX }}"
7980
WERF_NAMESPACE: ${{ env.REVIEW_NAMESPACE }}

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,4 @@
7878
url = [email protected]:open-condo-software/condo-accruals-gateway.git
7979
[submodule "apps/b2b-payments-gateway"]
8080
path = apps/b2b-payments-gateway
81-
url = [email protected]:open-condo-software/condo-b2b-payments-gateway.git
81+
url = [email protected]:open-condo-software/condo-b2b-payments-gateway.git

.helm

Submodule .helm updated from 2fc3119 to 8fee508

apps/b2b-payments-gateway

apps/callcenter

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
const path = require('path')
2+
3+
const conf = require('@open-condo/config')
4+
const { prepareKeystoneExpressApp } = require('@open-condo/keystone/prepareKeystoneApp')
5+
6+
const { CONTEXT_FINISHED_STATUS } = require('@condo/domains/acquiring/constants/context')
7+
const { AcquiringIntegration, AcquiringIntegrationContext } = require('@condo/domains/acquiring/utils/serverSchema')
8+
const { DEFAULT_BILLING_INTEGRATION_GROUP } = require('@condo/domains/billing/constants')
9+
const { BillingIntegration } = require('@condo/domains/billing/utils/serverSchema')
10+
const { createTestRecipient } = require('@condo/domains/billing/utils/testSchema')
11+
const { Organization } = require('@condo/domains/organization/utils/serverSchema')
12+
13+
async function main (args) {
14+
const [name] = args
15+
if (!name) throw new Error('use: create-subscription-payment-recipient <name>')
16+
17+
const { keystone: context } = await prepareKeystoneExpressApp(path.resolve('./index.js'), { excludeApps: ['NextApp', 'AdminUIApp'] })
18+
19+
const dv = 1
20+
const sender = { dv: 1, fingerprint: 'create-subscription-payment-recipient' }
21+
22+
const existingOrgId = conf.SUBSCRIPTION_PAYMENT_RECIPIENT
23+
let organization
24+
25+
if (existingOrgId) {
26+
console.info(`Checking existing organization ID from env: ${existingOrgId}`)
27+
organization = await Organization.getOne(context, { id: existingOrgId, deletedAt: null })
28+
29+
if (organization) {
30+
console.info('Organization already exists!')
31+
} else {
32+
console.warn(`Organization with ID ${existingOrgId} not found, creating new one`)
33+
organization = null
34+
}
35+
}
36+
37+
if (!organization) {
38+
console.info(`Creating organization: ${name}`)
39+
40+
try {
41+
organization = await Organization.create(context, {
42+
dv,
43+
sender,
44+
name,
45+
country: 'ru',
46+
type: 'MANAGING_COMPANY',
47+
tin: '0000000000',
48+
meta: { dv: 1 },
49+
})
50+
console.info('Organization created!')
51+
} catch (e) {
52+
console.error('Failed to create organization:', e)
53+
if (e.errors) console.error('Errors:', JSON.stringify(e.errors))
54+
throw e
55+
}
56+
}
57+
58+
console.info(`ORGANIZATION ID: ${organization.id}`)
59+
60+
const existingBillingIntegrations = await BillingIntegration.getAll(context, {
61+
group: DEFAULT_BILLING_INTEGRATION_GROUP,
62+
deletedAt: null,
63+
})
64+
65+
if (existingBillingIntegrations.length === 0) {
66+
console.info('Creating BillingIntegration...')
67+
try {
68+
await BillingIntegration.create(context, {
69+
dv,
70+
sender,
71+
name: 'Test Billing Integration',
72+
group: DEFAULT_BILLING_INTEGRATION_GROUP,
73+
targetDescription: 'Test billing integration for subscription payments',
74+
bannerColor: '#4CD174',
75+
bannerTextColor: 'WHITE',
76+
receiptsLoadingTime: 'Instant',
77+
currencyCode: 'RUB',
78+
instruction: 'This is a test billing integration for subscription payments. No setup required.',
79+
isHidden: true,
80+
shortDescription: 'Test billing integration for internal subscription payments',
81+
detailedDescription: 'This integration is used internally for processing subscription payments. It does not require any external setup or configuration.',
82+
})
83+
console.info('BillingIntegration created!')
84+
} catch (e) {
85+
console.error('Failed to create BillingIntegration:', e)
86+
if (e.errors) console.error('Errors:', JSON.stringify(e.errors))
87+
if (e.extensions) console.error('Extensions:', JSON.stringify(e.extensions))
88+
throw e
89+
}
90+
} else {
91+
console.info('BillingIntegration already exists!')
92+
}
93+
94+
const existingContext = await AcquiringIntegrationContext.getOne(context, {
95+
organization: { id: organization.id },
96+
deletedAt: null,
97+
})
98+
99+
let integration
100+
if (existingContext) {
101+
console.info('AcquiringIntegrationContext already exists!')
102+
integration = await AcquiringIntegration.getOne(context, { id: existingContext.integration })
103+
console.info(`ACQUIRING INTEGRATION ID: ${integration.id}`)
104+
} else {
105+
console.info('Creating AcquiringIntegration...')
106+
try {
107+
integration = await AcquiringIntegration.create(context, {
108+
dv,
109+
sender,
110+
name: 'Test Acquiring Integration',
111+
hostUrl: 'http://localhost:3000',
112+
canGroupReceipts: true,
113+
isHidden: true,
114+
explicitFeeDistributionSchema: [{ recipient: 'acquiring', percent: '0.65', minAmount: '0', maxAmount: '0', category: '' }, { recipient: 'service', percent: '0.55', minAmount: '0', maxAmount: '0', category: '' }],
115+
vatPercentOptions: '0,5,6,10,15,20',
116+
})
117+
console.info('AcquiringIntegration created!')
118+
} catch (e) {
119+
console.error('Failed to create AcquiringIntegration:', e)
120+
if (e.errors) console.error('Errors:', JSON.stringify(e.errors))
121+
if (e.extensions) console.error('Extensions:', JSON.stringify(e.extensions))
122+
throw e
123+
}
124+
125+
console.info(`ACQUIRING INTEGRATION ID: ${integration.id}`)
126+
127+
console.info('Creating AcquiringIntegrationContext...')
128+
try {
129+
await AcquiringIntegrationContext.create(context, {
130+
dv,
131+
sender,
132+
organization: { connect: { id: organization.id } },
133+
integration: { connect: { id: integration.id } },
134+
settings: { dv: 1 },
135+
state: { dv: 1 },
136+
invoiceStatus: CONTEXT_FINISHED_STATUS,
137+
invoiceImplicitFeeDistributionSchema: [],
138+
invoiceRecipient: createTestRecipient(),
139+
})
140+
console.info('AcquiringIntegrationContext created!')
141+
} catch (e) {
142+
console.error('Failed to create AcquiringIntegrationContext:', e)
143+
if (e.errors) console.error('Errors:', JSON.stringify(e.errors))
144+
if (e.extensions) console.error('Extensions:', JSON.stringify(e.extensions))
145+
throw e
146+
}
147+
}
148+
149+
console.info(organization.id)
150+
return organization.id
151+
}
152+
153+
main(process.argv.slice(2)).then(
154+
() => process.exit(),
155+
(error) => {
156+
console.error(error)
157+
process.exit(1)
158+
},
159+
)

apps/condo/bin/prepare.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { getAppServerUrl, updateAppEnvFile, prepareAppEnvLocalAdminUsers } = require('@open-condo/cli')
1+
const { getAppServerUrl, updateAppEnvFile, prepareAppEnvLocalAdminUsers, safeExec, getAppEnvValue } = require('@open-condo/cli')
22

33
async function updateAppEnvAddressSuggestionConfig (serviceName) {
44
const addressServiceUrl = await getAppServerUrl('address-service')
@@ -19,12 +19,27 @@ async function updateAppEnvFileClients (appName) {
1919
await updateAppEnvFile(appName, 'FILE_SECRET', appName + '-secret')
2020
}
2121

22+
async function prepareSubscriptionPaymentRecipient (appName) {
23+
const existingOrgId = await getAppEnvValue(appName, 'SUBSCRIPTION_PAYMENT_RECIPIENT')
24+
if (existingOrgId) {
25+
console.log(`SUBSCRIPTION_PAYMENT_RECIPIENT already set: ${existingOrgId}`)
26+
return existingOrgId
27+
}
28+
29+
const { stdout } = await safeExec(`yarn workspace @app/${appName} node ./bin/create-subscription-payment-recipient.js "Subscription Payment Recipient"`)
30+
const organizationId = stdout.trim().split('\n').pop()
31+
await updateAppEnvFile(appName, 'SUBSCRIPTION_PAYMENT_RECIPIENT', organizationId)
32+
console.log(`SUBSCRIPTION_PAYMENT_RECIPIENT=${organizationId}`)
33+
return organizationId
34+
}
35+
2236
async function main () {
2337
// 1) add local admin users!
2438
const appName = 'condo'
2539
await prepareAppEnvLocalAdminUsers(appName)
2640
await updateAppEnvAddressSuggestionConfig(appName)
2741
await updateAppEnvFileClients(appName)
42+
await prepareSubscriptionPaymentRecipient(appName)
2843
console.log('done')
2944
}
3045

apps/condo/domains/acquiring/schema/RegisterMultiPaymentService.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,8 @@ const RegisterMultiPaymentService = new GQLCustomSchema('RegisterMultiPaymentSer
652652
throw new GQLError(ERRORS.UNPUBLISHED_INVOICE, context)
653653
}
654654
// All invoices with client must be related to the current user
655-
if (foundInvoices.some(({ client }) => !!client && client !== context.authedItem.id)) {
655+
const authedItemId = context.authedItem?.id
656+
if (foundInvoices.some(({ client }) => !!client && client !== authedItemId)) {
656657
throw new GQLError(ERRORS.INVOICES_FOR_THIRD_USER, context)
657658
}
658659
// All acquiring contexts must be finished
@@ -755,12 +756,13 @@ const RegisterMultiPaymentService = new GQLCustomSchema('RegisterMultiPaymentSer
755756
const recurrentPaymentContextField = recurrentPaymentContext ? {
756757
recurrentPaymentContext: { connect: { id: recurrentPaymentContext.id } },
757758
} : {}
759+
const authedItemId = context.authedItem?.id
758760
const multiPayment = await MultiPayment.create(context, {
759761
dv: 1,
760762
sender,
761763
...Object.fromEntries(Object.entries(totalAmount).map(([key, value]) => ([key, value.toFixed(2)]))),
762764
currencyCode,
763-
user: { connect: { id: context.authedItem.id } },
765+
...isNil(authedItemId) ? {} : { user: { connect: { id: authedItemId } } },
764766
integration: { connect: { id: acquiringIntegration.id } },
765767
payments: { connect: paymentIds },
766768
// TODO(DOMA-1574): add correct category
@@ -773,7 +775,7 @@ const RegisterMultiPaymentService = new GQLCustomSchema('RegisterMultiPaymentSer
773775
webViewUrl: `${acquiringIntegration.hostUrl}${WEB_VIEW_PATH.replace('[id]', multiPayment.id)}`,
774776
feeCalculationUrl: `${acquiringIntegration.hostUrl}${FEE_CALCULATION_PATH.replace('[id]', multiPayment.id)}`,
775777
directPaymentUrl: `${acquiringIntegration.hostUrl}${DIRECT_PAYMENT_PATH.replace('[id]', multiPayment.id)}`,
776-
getCardTokensUrl: `${acquiringIntegration.hostUrl}${GET_CARD_TOKENS_PATH.replace('[id]', context.authedItem.id)}`,
778+
getCardTokensUrl: isNil(authedItemId) ? '' : `${acquiringIntegration.hostUrl}${GET_CARD_TOKENS_PATH.replace('[id]', authedItemId)}`,
777779
}
778780
},
779781
},

0 commit comments

Comments
 (0)