Skip to content
This repository has been archived by the owner on Feb 19, 2025. It is now read-only.

Commit

Permalink
Merge pull request #57 from nathanfallet/feature/stripe-setup
Browse files Browse the repository at this point in the history
feat: stripe onboarding
  • Loading branch information
nathanfallet authored Feb 24, 2024
2 parents ebf0877 + f15a589 commit c0505c0
Show file tree
Hide file tree
Showing 30 changed files with 563 additions and 136 deletions.
9 changes: 9 additions & 0 deletions helm/suitebde/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions helm/suitebde/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
3 changes: 3 additions & 0 deletions helm/suitebde/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ cloudflare:
account: ''
token: ''

stripe:
key: ''

# MySQL configuration

mysql:
Expand Down
1 change: 1 addition & 0 deletions suitebde-backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}

}
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -12,11 +16,15 @@ class DashboardRouter(
translateUseCase: ITranslateUseCase,
requireUserForCallUseCase: IRequireUserForCallUseCase,
getAdminMenuForCallUseCase: IGetAdminMenuForCallUseCase,
) : AdminUnitRouter(
) : AdminModelRouter<Association, String, CreateAssociationPayload, UpdateAssociationPayload>(
typeInfo<Association>(),
typeInfo<CreateAssociationPayload>(),
typeInfo<UpdateAssociationPayload>(),
controller,
IDashboardController::class,
getLocaleForCallUseCase,
translateUseCase,
requireUserForCallUseCase,
getAdminMenuForCallUseCase
getAdminMenuForCallUseCase,
route = ""
)
Original file line number Diff line number Diff line change
@@ -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<Association, String, CreateAssociationPayload, UpdateAssociationPayload> {

@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

}
Original file line number Diff line number Diff line change
@@ -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]
)

}
Original file line number Diff line number Diff line change
@@ -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<StripeAccountInAssociation> =
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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.*
Expand Down Expand Up @@ -152,6 +148,11 @@ fun Application.configureKoin() {
environment.config.property("jwt.issuer").getString()
)
}
single<IStripeService> {
StripeService(
environment.config.property("stripe.key").getString()
)
}
single<ICloudflareClient> {
CloudflareClient(
environment.config.property("cloudflare.token").getString()
Expand All @@ -170,6 +171,7 @@ fun Application.configureKoin() {
single<ICodesInEmailsRepository> { CodesInEmailsDatabaseRepository(get()) }
single<IDomainsInAssociationsRepository> { DomainsInAssociationsDatabaseRepository(get()) }
single<ISubscriptionsInAssociationsRepository> { SubscriptionsInAssociationsDatabaseRepository(get()) }
single<IStripeAccountsInAssociationsRepository> { StripeAccountsInAssociationsDatabaseRepository(get()) }

// Users
single<IUsersRepository> { UsersDatabaseRepository(get()) }
Expand Down Expand Up @@ -293,6 +295,38 @@ fun Application.configureKoin() {
UpdateChildModelFromRepositorySuspendUseCase(get<ISubscriptionsInAssociationsRepository>())
}

// Stripe accounts in associations
single<IListChildModelSuspendUseCase<StripeAccountInAssociation, String>>(named<StripeAccountInAssociation>()) {
ListChildModelFromRepositorySuspendUseCase(get<IStripeAccountsInAssociationsRepository>())
}
single<IGetChildModelSuspendUseCase<StripeAccountInAssociation, String, String>>(named<StripeAccountInAssociation>()) {
GetChildModelFromRepositorySuspendUseCase(get<IStripeAccountsInAssociationsRepository>())
}
single<ICreateChildModelSuspendUseCase<StripeAccountInAssociation, CreateStripeAccountInAssociationPayload, String>>(
named<StripeAccountInAssociation>()
) {
CreateChildModelFromRepositorySuspendUseCase(get<IStripeAccountsInAssociationsRepository>())
}
single<IUpdateChildModelSuspendUseCase<StripeAccountInAssociation, String, UpdateStripeAccountInAssociationPayload, String>>(
named<StripeAccountInAssociation>()
) {
UpdateChildModelFromRepositorySuspendUseCase(get<IStripeAccountsInAssociationsRepository>())
}
single<IRefreshStripeAccountUseCase> {
RefreshStripeAccountUseCase(
get(),
get(named<StripeAccountInAssociation>()),
get(named<StripeAccountInAssociation>())
)
}
single<ICreateStripeAccountLinkUseCase> {
CreateStripeAccountLinkUseCase(
get(),
get(named<StripeAccountInAssociation>()),
get(named<StripeAccountInAssociation>())
)
}

// Auth
single<IHashPasswordUseCase> { HashPasswordUseCase() }
single<IVerifyPasswordUseCase> { VerifyPasswordUseCase() }
Expand Down Expand Up @@ -583,7 +617,7 @@ fun Application.configureKoin() {
)
}
single<IRootController> { RootController(get(), get()) }
single<IDashboardController> { DashboardController() }
single<IDashboardController> { DashboardController(get(), get(), get()) }

// Auth
single<IAuthController> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<StripeAccountInAssociation, String, CreateStripeAccountInAssociationPayload, UpdateStripeAccountInAssociationPayload, String>
Original file line number Diff line number Diff line change
@@ -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

}
Loading

0 comments on commit c0505c0

Please sign in to comment.