diff --git a/helm/suitebde/templates/deployment.yaml b/helm/suitebde/templates/deployment.yaml index 2ae88a8d..6e9667d8 100644 --- a/helm/suitebde/templates/deployment.yaml +++ b/helm/suitebde/templates/deployment.yaml @@ -113,6 +113,15 @@ spec: name: {{ .Values.existingSecret }} {{- end }} key: cloudflare-token + - name: STRIPE_KEY + valueFrom: + secretKeyRef: + {{ if not .Values.existingSecret -}} + name: {{ include "suitebde.fullname" . }} + {{- else }} + name: {{ .Values.existingSecret }} + {{- end }} + key: stripe-key livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: diff --git a/helm/suitebde/templates/secret.yaml b/helm/suitebde/templates/secret.yaml index 28da65c9..1cb507a2 100644 --- a/helm/suitebde/templates/secret.yaml +++ b/helm/suitebde/templates/secret.yaml @@ -20,4 +20,5 @@ data: email-password: {{ .Values.email.password | b64enc | quote }} cloudflare-account: {{ .Values.cloudflare.account | b64enc | quote }} cloudflare-token: {{ .Values.cloudflare.token | b64enc | quote }} + stripe-key: {{ .Values.stripe.key | b64enc | quote }} {{ end }} diff --git a/helm/suitebde/values.yaml b/helm/suitebde/values.yaml index d3479301..aabaff14 100644 --- a/helm/suitebde/values.yaml +++ b/helm/suitebde/values.yaml @@ -152,6 +152,9 @@ cloudflare: account: '' token: '' +stripe: + key: '' + # MySQL configuration mysql: diff --git a/suitebde-backend/build.gradle.kts b/suitebde-backend/build.gradle.kts index 62496ded..71c9586b 100644 --- a/suitebde-backend/build.gradle.kts +++ b/suitebde-backend/build.gradle.kts @@ -88,6 +88,7 @@ kotlin { implementation("at.favre.lib:bcrypt:0.9.0") implementation("org.apache.commons:commons-email:1.5") implementation("io.sentry:sentry:6.34.0") + implementation("com.stripe:stripe-java:24.17.0") api(project(":suitebde-commons")) } diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardController.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardController.kt index 1f0936e9..d7fb4012 100644 --- a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardController.kt +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardController.kt @@ -1,7 +1,29 @@ package me.nathanfallet.suitebde.controllers.dashboard -class DashboardController : IDashboardController { +import io.ktor.server.application.* +import io.ktor.server.request.* +import me.nathanfallet.ktorx.models.responses.RedirectResponse +import me.nathanfallet.suitebde.usecases.associations.ICreateStripeAccountLinkUseCase +import me.nathanfallet.suitebde.usecases.associations.IRefreshStripeAccountUseCase +import me.nathanfallet.suitebde.usecases.associations.IRequireAssociationForCallUseCase + +class DashboardController( + private val requireAssociationForCallUseCase: IRequireAssociationForCallUseCase, + private val refreshStripeAccountUseCase: IRefreshStripeAccountUseCase, + private val createStripeAccountLinkUseCase: ICreateStripeAccountLinkUseCase, +) : IDashboardController { override suspend fun dashboard() {} + override suspend fun settings(call: ApplicationCall) { + val association = requireAssociationForCallUseCase(call) + refreshStripeAccountUseCase(association) + } + + override suspend fun settingsStripe(call: ApplicationCall): RedirectResponse { + val association = requireAssociationForCallUseCase(call) + val returnUrl = "https://${call.request.host()}/admin/settings" + return RedirectResponse(createStripeAccountLinkUseCase(association, returnUrl) ?: returnUrl) + } + } diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardRouter.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardRouter.kt index 7a3d7e35..fdd93c8e 100644 --- a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardRouter.kt +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/DashboardRouter.kt @@ -1,8 +1,12 @@ package me.nathanfallet.suitebde.controllers.dashboard +import io.ktor.util.reflect.* import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase import me.nathanfallet.ktorx.usecases.users.IRequireUserForCallUseCase -import me.nathanfallet.suitebde.controllers.models.AdminUnitRouter +import me.nathanfallet.suitebde.controllers.models.AdminModelRouter +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.suitebde.models.associations.CreateAssociationPayload +import me.nathanfallet.suitebde.models.associations.UpdateAssociationPayload import me.nathanfallet.suitebde.usecases.web.IGetAdminMenuForCallUseCase import me.nathanfallet.usecases.localization.ITranslateUseCase @@ -12,11 +16,15 @@ class DashboardRouter( translateUseCase: ITranslateUseCase, requireUserForCallUseCase: IRequireUserForCallUseCase, getAdminMenuForCallUseCase: IGetAdminMenuForCallUseCase, -) : AdminUnitRouter( +) : AdminModelRouter( + typeInfo(), + typeInfo(), + typeInfo(), controller, IDashboardController::class, getLocaleForCallUseCase, translateUseCase, requireUserForCallUseCase, - getAdminMenuForCallUseCase + getAdminMenuForCallUseCase, + route = "" ) diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/IDashboardController.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/IDashboardController.kt index 50810921..7149cfcb 100644 --- a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/IDashboardController.kt +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/controllers/dashboard/IDashboardController.kt @@ -1,13 +1,27 @@ package me.nathanfallet.suitebde.controllers.dashboard -import me.nathanfallet.ktorx.controllers.IUnitController +import io.ktor.server.application.* +import me.nathanfallet.ktorx.controllers.IModelController import me.nathanfallet.ktorx.models.annotations.AdminTemplateMapping import me.nathanfallet.ktorx.models.annotations.Path +import me.nathanfallet.ktorx.models.responses.RedirectResponse +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.suitebde.models.associations.CreateAssociationPayload +import me.nathanfallet.suitebde.models.associations.UpdateAssociationPayload -interface IDashboardController : IUnitController { +interface IDashboardController : + IModelController { - @AdminTemplateMapping("admin/dashboard.ftl") + @AdminTemplateMapping("admin/dashboard/dashboard.ftl") @Path("GET", "/") suspend fun dashboard() + @AdminTemplateMapping("admin/dashboard/settings.ftl") + @Path("GET", "/settings") + suspend fun settings(call: ApplicationCall) + + @AdminTemplateMapping("admin/dashboard/settings.ftl") + @Path("GET", "/settings/stripe") + suspend fun settingsStripe(call: ApplicationCall): RedirectResponse + } diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociations.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociations.kt new file mode 100644 index 00000000..2dc68d63 --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociations.kt @@ -0,0 +1,23 @@ +package me.nathanfallet.suitebde.database.associations + +import me.nathanfallet.suitebde.models.associations.StripeAccountInAssociation +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.Table + +object StripeAccountsInAssociations : Table() { + + val associationId = varchar("association_id", 32).index() + val accountId = varchar("account_id", 255).index() + val chargesEnabled = bool("charges_enabled") + + override val primaryKey = PrimaryKey(associationId, accountId) + + fun toStripeAccountInAssociation( + row: ResultRow, + ) = StripeAccountInAssociation( + row[associationId], + row[accountId], + row[chargesEnabled] + ) + +} diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociationsDatabaseRepository.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociationsDatabaseRepository.kt new file mode 100644 index 00000000..547596bc --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/database/associations/StripeAccountsInAssociationsDatabaseRepository.kt @@ -0,0 +1,73 @@ +package me.nathanfallet.suitebde.database.associations + +import me.nathanfallet.suitebde.models.associations.CreateStripeAccountInAssociationPayload +import me.nathanfallet.suitebde.models.associations.StripeAccountInAssociation +import me.nathanfallet.suitebde.models.associations.UpdateStripeAccountInAssociationPayload +import me.nathanfallet.suitebde.repositories.associations.IStripeAccountsInAssociationsRepository +import me.nathanfallet.surexposed.database.IDatabase +import me.nathanfallet.usecases.context.IContext +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq + +class StripeAccountsInAssociationsDatabaseRepository( + private val database: IDatabase, +) : IStripeAccountsInAssociationsRepository { + + init { + database.transaction { + SchemaUtils.create(StripeAccountsInAssociations) + } + } + + override suspend fun list(parentId: String, context: IContext?): List = + database.suspendedTransaction { + StripeAccountsInAssociations + .selectAll() + .where { StripeAccountsInAssociations.associationId eq parentId } + .map(StripeAccountsInAssociations::toStripeAccountInAssociation) + } + + override suspend fun create( + payload: CreateStripeAccountInAssociationPayload, + parentId: String, + context: IContext?, + ): StripeAccountInAssociation? = + database.suspendedTransaction { + StripeAccountsInAssociations.insert { + it[associationId] = parentId + it[accountId] = payload.accountId + it[chargesEnabled] = payload.chargesEnabled + }.resultedValues?.map(StripeAccountsInAssociations::toStripeAccountInAssociation)?.singleOrNull() + } + + override suspend fun get(id: String, parentId: String, context: IContext?): StripeAccountInAssociation? = + database.suspendedTransaction { + StripeAccountsInAssociations + .selectAll() + .where { + StripeAccountsInAssociations.accountId eq id and (StripeAccountsInAssociations.associationId eq parentId) + } + .map(StripeAccountsInAssociations::toStripeAccountInAssociation) + .singleOrNull() + } + + override suspend fun update( + id: String, + payload: UpdateStripeAccountInAssociationPayload, + parentId: String, + context: IContext?, + ): Boolean = + database.suspendedTransaction { + StripeAccountsInAssociations.update({ StripeAccountsInAssociations.accountId eq id and (StripeAccountsInAssociations.associationId eq parentId) }) { + it[chargesEnabled] = payload.chargesEnabled + } == 1 + } + + override suspend fun delete(id: String, parentId: String, context: IContext?): Boolean = + database.suspendedTransaction { + StripeAccountsInAssociations.deleteWhere { + StripeAccountsInAssociations.accountId eq id and (StripeAccountsInAssociations.associationId eq parentId) + } == 1 + } + +} diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/plugins/Koin.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/plugins/Koin.kt index 8cc89867..9d024a2a 100644 --- a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/plugins/Koin.kt +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/plugins/Koin.kt @@ -31,10 +31,7 @@ import me.nathanfallet.suitebde.controllers.users.* import me.nathanfallet.suitebde.controllers.web.* import me.nathanfallet.suitebde.database.Database import me.nathanfallet.suitebde.database.application.ClientsDatabaseRepository -import me.nathanfallet.suitebde.database.associations.AssociationsDatabaseRepository -import me.nathanfallet.suitebde.database.associations.CodesInEmailsDatabaseRepository -import me.nathanfallet.suitebde.database.associations.DomainsInAssociationsDatabaseRepository -import me.nathanfallet.suitebde.database.associations.SubscriptionsInAssociationsDatabaseRepository +import me.nathanfallet.suitebde.database.associations.* import me.nathanfallet.suitebde.database.clubs.ClubsDatabaseRepository import me.nathanfallet.suitebde.database.clubs.RolesInClubsDatabaseRepository import me.nathanfallet.suitebde.database.clubs.UsersInClubsDatabaseRepository @@ -59,10 +56,7 @@ import me.nathanfallet.suitebde.models.events.UpdateEventPayload import me.nathanfallet.suitebde.models.roles.* import me.nathanfallet.suitebde.models.users.* import me.nathanfallet.suitebde.models.web.* -import me.nathanfallet.suitebde.repositories.associations.IAssociationsRepository -import me.nathanfallet.suitebde.repositories.associations.ICodesInEmailsRepository -import me.nathanfallet.suitebde.repositories.associations.IDomainsInAssociationsRepository -import me.nathanfallet.suitebde.repositories.associations.ISubscriptionsInAssociationsRepository +import me.nathanfallet.suitebde.repositories.associations.* import me.nathanfallet.suitebde.repositories.clubs.IClubsRepository import me.nathanfallet.suitebde.repositories.clubs.IRolesInClubsRepository import me.nathanfallet.suitebde.repositories.clubs.IUsersInClubsRepository @@ -77,6 +71,8 @@ import me.nathanfallet.suitebde.services.emails.EmailsService import me.nathanfallet.suitebde.services.emails.IEmailsService import me.nathanfallet.suitebde.services.jwt.IJWTService import me.nathanfallet.suitebde.services.jwt.JWTService +import me.nathanfallet.suitebde.services.stripe.IStripeService +import me.nathanfallet.suitebde.services.stripe.StripeService import me.nathanfallet.suitebde.usecases.application.* import me.nathanfallet.suitebde.usecases.associations.* import me.nathanfallet.suitebde.usecases.auth.* @@ -152,6 +148,11 @@ fun Application.configureKoin() { environment.config.property("jwt.issuer").getString() ) } + single { + StripeService( + environment.config.property("stripe.key").getString() + ) + } single { CloudflareClient( environment.config.property("cloudflare.token").getString() @@ -170,6 +171,7 @@ fun Application.configureKoin() { single { CodesInEmailsDatabaseRepository(get()) } single { DomainsInAssociationsDatabaseRepository(get()) } single { SubscriptionsInAssociationsDatabaseRepository(get()) } + single { StripeAccountsInAssociationsDatabaseRepository(get()) } // Users single { UsersDatabaseRepository(get()) } @@ -293,6 +295,38 @@ fun Application.configureKoin() { UpdateChildModelFromRepositorySuspendUseCase(get()) } + // Stripe accounts in associations + single>(named()) { + ListChildModelFromRepositorySuspendUseCase(get()) + } + single>(named()) { + GetChildModelFromRepositorySuspendUseCase(get()) + } + single>( + named() + ) { + CreateChildModelFromRepositorySuspendUseCase(get()) + } + single>( + named() + ) { + UpdateChildModelFromRepositorySuspendUseCase(get()) + } + single { + RefreshStripeAccountUseCase( + get(), + get(named()), + get(named()) + ) + } + single { + CreateStripeAccountLinkUseCase( + get(), + get(named()), + get(named()) + ) + } + // Auth single { HashPasswordUseCase() } single { VerifyPasswordUseCase() } @@ -583,7 +617,7 @@ fun Application.configureKoin() { ) } single { RootController(get(), get()) } - single { DashboardController() } + single { DashboardController(get(), get(), get()) } // Auth single { diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/repositories/associations/IStripeAccountsInAssociationsRepository.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/repositories/associations/IStripeAccountsInAssociationsRepository.kt new file mode 100644 index 00000000..752c0f30 --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/repositories/associations/IStripeAccountsInAssociationsRepository.kt @@ -0,0 +1,9 @@ +package me.nathanfallet.suitebde.repositories.associations + +import me.nathanfallet.suitebde.models.associations.CreateStripeAccountInAssociationPayload +import me.nathanfallet.suitebde.models.associations.StripeAccountInAssociation +import me.nathanfallet.suitebde.models.associations.UpdateStripeAccountInAssociationPayload +import me.nathanfallet.usecases.models.repositories.IChildModelSuspendRepository + +interface IStripeAccountsInAssociationsRepository : + IChildModelSuspendRepository diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/IStripeService.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/IStripeService.kt new file mode 100644 index 00000000..952bdd1c --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/IStripeService.kt @@ -0,0 +1,15 @@ +package me.nathanfallet.suitebde.services.stripe + +import com.stripe.model.Account +import com.stripe.model.AccountLink +import com.stripe.param.AccountLinkCreateParams +import me.nathanfallet.suitebde.models.associations.Association + +interface IStripeService { + + suspend fun getAccount(accountId: String): Account? + suspend fun createAccount(association: Association): Account + + suspend fun createAccountLink(accountId: String, type: AccountLinkCreateParams.Type, returnUrl: String): AccountLink + +} diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/StripeService.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/StripeService.kt new file mode 100644 index 00000000..8eb3ed5f --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/services/stripe/StripeService.kt @@ -0,0 +1,49 @@ +package me.nathanfallet.suitebde.services.stripe + +import com.stripe.Stripe +import com.stripe.model.Account +import com.stripe.model.AccountLink +import com.stripe.param.AccountCreateParams +import com.stripe.param.AccountLinkCreateParams +import me.nathanfallet.suitebde.models.associations.Association + +class StripeService( + private val apiKey: String, +) : IStripeService { + + init { + // Set API key + Stripe.apiKey = apiKey + } + + override suspend fun getAccount(accountId: String): Account? = + Account.retrieve(accountId) + + override suspend fun createAccount(association: Association): Account = + Account.create( + AccountCreateParams.builder() + .setType(AccountCreateParams.Type.EXPRESS) + .setBusinessType(AccountCreateParams.BusinessType.NON_PROFIT) + .setBusinessProfile( + AccountCreateParams.BusinessProfile.builder() + .setName(association.name) + .build() + ) + .build() + ) + + override suspend fun createAccountLink( + accountId: String, + type: AccountLinkCreateParams.Type, + returnUrl: String, + ): AccountLink = + AccountLink.create( + AccountLinkCreateParams.builder() + .setAccount(accountId) + .setType(type) + .setReturnUrl(returnUrl) + .setRefreshUrl(returnUrl) + .build() + ) + +} diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/CreateStripeAccountLinkUseCase.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/CreateStripeAccountLinkUseCase.kt new file mode 100644 index 00000000..f07f3b74 --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/CreateStripeAccountLinkUseCase.kt @@ -0,0 +1,36 @@ +package me.nathanfallet.suitebde.usecases.associations + +import com.stripe.param.AccountLinkCreateParams +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.suitebde.models.associations.CreateStripeAccountInAssociationPayload +import me.nathanfallet.suitebde.models.associations.StripeAccountInAssociation +import me.nathanfallet.suitebde.services.stripe.IStripeService +import me.nathanfallet.usecases.models.create.ICreateChildModelSuspendUseCase +import me.nathanfallet.usecases.models.list.IListChildModelSuspendUseCase + +class CreateStripeAccountLinkUseCase( + private val stripeService: IStripeService, + private val listStripeAccountsInAssociationsUseCase: IListChildModelSuspendUseCase, + private val createStripeAccountUseCase: ICreateChildModelSuspendUseCase, +) : ICreateStripeAccountLinkUseCase { + + override suspend fun invoke(input1: Association, input2: String): String? { + val account = listStripeAccountsInAssociationsUseCase(input1.id).firstOrNull() + ?: stripeService.createAccount(input1).let { + createStripeAccountUseCase( + CreateStripeAccountInAssociationPayload( + accountId = it.id, + chargesEnabled = it.chargesEnabled + ), + input1.id + ) + } + ?: return null + return stripeService.createAccountLink( + account.accountId, + if (account.chargesEnabled) AccountLinkCreateParams.Type.ACCOUNT_UPDATE else AccountLinkCreateParams.Type.ACCOUNT_ONBOARDING, + input2 + ).url + } + +} diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/ICreateStripeAccountLinkUseCase.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/ICreateStripeAccountLinkUseCase.kt new file mode 100644 index 00000000..41561a29 --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/ICreateStripeAccountLinkUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.suitebde.usecases.associations + +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.usecases.base.IPairSuspendUseCase + +interface ICreateStripeAccountLinkUseCase : IPairSuspendUseCase diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/IRefreshStripeAccountUseCase.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/IRefreshStripeAccountUseCase.kt new file mode 100644 index 00000000..c9b2577b --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/IRefreshStripeAccountUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.suitebde.usecases.associations + +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IRefreshStripeAccountUseCase : ISuspendUseCase diff --git a/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/RefreshStripeAccountUseCase.kt b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/RefreshStripeAccountUseCase.kt new file mode 100644 index 00000000..026def5d --- /dev/null +++ b/suitebde-backend/src/commonMain/kotlin/me/nathanfallet/suitebde/usecases/associations/RefreshStripeAccountUseCase.kt @@ -0,0 +1,29 @@ +package me.nathanfallet.suitebde.usecases.associations + +import me.nathanfallet.suitebde.models.associations.Association +import me.nathanfallet.suitebde.models.associations.StripeAccountInAssociation +import me.nathanfallet.suitebde.models.associations.UpdateStripeAccountInAssociationPayload +import me.nathanfallet.suitebde.services.stripe.IStripeService +import me.nathanfallet.usecases.models.list.IListChildModelSuspendUseCase +import me.nathanfallet.usecases.models.update.IUpdateChildModelSuspendUseCase + +class RefreshStripeAccountUseCase( + private val stripeService: IStripeService, + private val listStripeAccountsInAssociationsUseCase: IListChildModelSuspendUseCase, + private val updateStripeAccountUseCase: IUpdateChildModelSuspendUseCase, +) : IRefreshStripeAccountUseCase { + + override suspend fun invoke(input: Association) { + listStripeAccountsInAssociationsUseCase(input.id).forEach { + if (!it.chargesEnabled && stripeService.getAccount(it.accountId)?.chargesEnabled == true) + updateStripeAccountUseCase( + it.accountId, + UpdateStripeAccountInAssociationPayload( + chargesEnabled = true + ), + input.id + ) + } + } + +} diff --git a/suitebde-backend/src/commonMain/resources/application.conf b/suitebde-backend/src/commonMain/resources/application.conf index 37c51f56..556bb2a8 100644 --- a/suitebde-backend/src/commonMain/resources/application.conf +++ b/suitebde-backend/src/commonMain/resources/application.conf @@ -37,3 +37,7 @@ cloudflare { account = ${?CLOUDFLARE_ACCOUNT} token = ${?CLOUDFLARE_TOKEN} } +stripe { + key = "" + key = ${?STRIPE_KEY} +} diff --git a/suitebde-backend/src/commonMain/resources/application.test.conf b/suitebde-backend/src/commonMain/resources/application.test.conf index b618b4ef..cdf30afe 100644 --- a/suitebde-backend/src/commonMain/resources/application.test.conf +++ b/suitebde-backend/src/commonMain/resources/application.test.conf @@ -28,3 +28,6 @@ cloudflare { account = "" token = "" } +stripe { + key = "" +} diff --git a/suitebde-backend/src/commonMain/resources/i18n/Messages_en.properties b/suitebde-backend/src/commonMain/resources/i18n/Messages_en.properties index 3dd98569..b2468a4d 100644 --- a/suitebde-backend/src/commonMain/resources/i18n/Messages_en.properties +++ b/suitebde-backend/src/commonMain/resources/i18n/Messages_en.properties @@ -142,6 +142,9 @@ admin_clubs_logo=Logo admin_clubs_validated=Validated admin_clubs_memberRoleName=Member role name admin_clubs_adminRoleName=Admin role name +admin_settings=Settings +admin_settings_stripe=Stripe account +admin_settings_stripe_link=Create or update your Stripe account hero_title=One app for everything hero_description=Join the ecosystem of your school\'s association by logging in or creating an account. hero_email=Email us at {0} diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/dashboard.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/dashboard.ftl deleted file mode 100644 index 1ccc8db9..00000000 --- a/suitebde-backend/src/commonMain/resources/templates/admin/dashboard.ftl +++ /dev/null @@ -1,4 +0,0 @@ -<#import "template.ftl" as template> -<@template.page> - - diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/dashboard.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/dashboard.ftl new file mode 100644 index 00000000..9cdeb3b9 --- /dev/null +++ b/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/dashboard.ftl @@ -0,0 +1,4 @@ +<#import "../template.ftl" as template> +<@template.page> + + diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/settings.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/settings.ftl new file mode 100644 index 00000000..f4028b76 --- /dev/null +++ b/suitebde-backend/src/commonMain/resources/templates/admin/dashboard/settings.ftl @@ -0,0 +1,47 @@ +<#import "../template.ftl" as template> +<@template.page> + +
+

+ <@t key="admin_settings" /> +

+
+ + + +
+
+
+

<@t key="admin_settings_stripe"/>

+
+
+ + +
+ + diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/models/delete.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/models/delete.ftl index 5d7cd956..7f2b31ad 100644 --- a/suitebde-backend/src/commonMain/resources/templates/admin/models/delete.ftl +++ b/suitebde-backend/src/commonMain/resources/templates/admin/models/delete.ftl @@ -1,35 +1,33 @@ <#import "../template.ftl" as template> <@template.page> -
- -
-

- <@t key="admin_" + route + "_delete" /> -

-
- + +
+

+ <@t key="admin_" + route + "_delete" /> +

+
+ - -
-
- -
-
-
- + +
+
+ + +
-
+ diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/models/form.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/models/form.ftl index a4b15fdb..3e9a32dc 100644 --- a/suitebde-backend/src/commonMain/resources/templates/admin/models/form.ftl +++ b/suitebde-backend/src/commonMain/resources/templates/admin/models/form.ftl @@ -1,55 +1,53 @@ <#import "../template.ftl" as template> <@template.page> -
- -
- <#if item??> -

- <@t key="admin_" + route + "_update" /> -

- <#else> -

- <@t key="admin_" + route + "_create" /> -

- -
- + +
+ <#if item??> +

+ <@t key="admin_" + route + "_update" /> +

+ <#else> +

+ <@t key="admin_" + route + "_create" /> +

+ +
+ - -
-
- -
-
-
-
- <#list keys as key> - <@field key /> - -
+ +
+
+ +
+ +
+
+ <#list keys as key> + <@field key /> + +
- - -
+
+
-
+ <#if flatpickr?? && flatpickr> diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/models/list.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/models/list.ftl index 82050749..f6dd3534 100644 --- a/suitebde-backend/src/commonMain/resources/templates/admin/models/list.ftl +++ b/suitebde-backend/src/commonMain/resources/templates/admin/models/list.ftl @@ -1,68 +1,66 @@ <#import "../template.ftl" as template> <@template.page> -
- -
-

- <@t key="admin_menu_" + route /> -

+ +
+

+ <@t key="admin_menu_" + route /> +

- -
- + +
+ -
- -
-
- - - - <#list keys as key> -
-
-

<@t key="admin_${route}_${key.key}" />

-
+
+ +
+
+ + + + <#list keys as key> + + + + + + + + <#list items as item> + + <#list keys as key> + <@cell item key /> - - - <#list items as item> - - <#list keys as key> - <@cell item key /> - - - - -
+
+

<@t key="admin_${route}_${key.key}" />

+
- + -
-
-
+ + +
-
+
diff --git a/suitebde-backend/src/commonMain/resources/templates/admin/template.ftl b/suitebde-backend/src/commonMain/resources/templates/admin/template.ftl index a6b3d7f1..ec9efca5 100644 --- a/suitebde-backend/src/commonMain/resources/templates/admin/template.ftl +++ b/suitebde-backend/src/commonMain/resources/templates/admin/template.ftl @@ -245,7 +245,9 @@
- <#nested> +
+ <#nested> +
diff --git a/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/CreateStripeAccountInAssociationPayload.kt b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/CreateStripeAccountInAssociationPayload.kt new file mode 100644 index 00000000..77714343 --- /dev/null +++ b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/CreateStripeAccountInAssociationPayload.kt @@ -0,0 +1,9 @@ +package me.nathanfallet.suitebde.models.associations + +import kotlinx.serialization.Serializable + +@Serializable +data class CreateStripeAccountInAssociationPayload( + val accountId: String, + val chargesEnabled: Boolean, +) diff --git a/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/StripeAccountInAssociation.kt b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/StripeAccountInAssociation.kt new file mode 100644 index 00000000..ece3b699 --- /dev/null +++ b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/StripeAccountInAssociation.kt @@ -0,0 +1,19 @@ +package me.nathanfallet.suitebde.models.associations + +import kotlinx.serialization.Serializable +import me.nathanfallet.usecases.models.IChildModel + +@Serializable +data class StripeAccountInAssociation( + val associationId: String, + val accountId: String, + val chargesEnabled: Boolean, +) : IChildModel { + + override val id: String + get() = accountId + + override val parentId: String + get() = associationId + +} diff --git a/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/UpdateStripeAccountInAssociationPayload.kt b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/UpdateStripeAccountInAssociationPayload.kt new file mode 100644 index 00000000..34b04b30 --- /dev/null +++ b/suitebde-commons/src/commonMain/kotlin/me/nathanfallet/suitebde/models/associations/UpdateStripeAccountInAssociationPayload.kt @@ -0,0 +1,8 @@ +package me.nathanfallet.suitebde.models.associations + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateStripeAccountInAssociationPayload( + val chargesEnabled: Boolean, +)