All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m10s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 5m13s
Build and Release / Lint (push) Successful in 5m25s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 3m13s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 8h5m42s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 7m30s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 7m55s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m36s
Implement complete subscription monetization system for repositories with Stripe and PayPal integration. Includes: - Database models and migrations for monetization settings, subscription products, and user subscriptions - Payment provider abstraction layer with Stripe and PayPal implementations - Admin UI for configuring payment providers and viewing subscriptions - Repository settings UI for managing subscription products and tiers - Subscription checkout flow and webhook handlers for payment events - Access control to gate repository code behind active subscriptions
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
monetize_model "code.gitcaddy.com/server/v3/models/monetize"
|
|
repo_model "code.gitcaddy.com/server/v3/models/repo"
|
|
"code.gitcaddy.com/server/v3/modules/templates"
|
|
"code.gitcaddy.com/server/v3/modules/web"
|
|
"code.gitcaddy.com/server/v3/services/context"
|
|
"code.gitcaddy.com/server/v3/services/forms"
|
|
)
|
|
|
|
const tplSubscriptions templates.TplName = "repo/settings/subscriptions"
|
|
|
|
// SubscriptionsGeneral shows the subscriptions enable/disable toggle.
|
|
func SubscriptionsGeneral(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.subscriptions")
|
|
ctx.Data["PageIsSettingsSubscriptions"] = true
|
|
ctx.Data["PageIsSettingsSubscriptionsGeneral"] = true
|
|
ctx.Data["PageType"] = "general"
|
|
ctx.Data["SubscriptionsEnabled"] = ctx.Repo.Repository.SubscriptionsEnabled
|
|
ctx.HTML(http.StatusOK, tplSubscriptions)
|
|
}
|
|
|
|
// SubscriptionsGeneralPost toggles the subscriptions enabled flag.
|
|
func SubscriptionsGeneralPost(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.subscriptions")
|
|
ctx.Data["PageIsSettingsSubscriptions"] = true
|
|
ctx.Data["PageIsSettingsSubscriptionsGeneral"] = true
|
|
ctx.Data["PageType"] = "general"
|
|
|
|
enabled := ctx.FormBool("subscriptions_enabled")
|
|
ctx.Repo.Repository.SubscriptionsEnabled = enabled
|
|
|
|
if err := repo_model.UpdateRepositoryColsWithAutoTime(ctx, ctx.Repo.Repository, "subscriptions_enabled"); err != nil {
|
|
ctx.ServerError("UpdateRepositoryCols", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.settings.subscriptions.saved"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/subscriptions")
|
|
}
|
|
|
|
// SubscriptionsProducts shows the product list and create form.
|
|
func SubscriptionsProducts(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.subscriptions.products")
|
|
ctx.Data["PageIsSettingsSubscriptions"] = true
|
|
ctx.Data["PageIsSettingsSubscriptionsProducts"] = true
|
|
ctx.Data["PageType"] = "products"
|
|
|
|
products, err := monetize_model.GetProductsByRepoID(ctx, ctx.Repo.Repository.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetProductsByRepoID", err)
|
|
return
|
|
}
|
|
ctx.Data["Products"] = products
|
|
ctx.HTML(http.StatusOK, tplSubscriptions)
|
|
}
|
|
|
|
// SubscriptionsProductsPost creates or updates a product.
|
|
func SubscriptionsProductsPost(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.subscriptions.products")
|
|
ctx.Data["PageIsSettingsSubscriptions"] = true
|
|
ctx.Data["PageIsSettingsSubscriptionsProducts"] = true
|
|
ctx.Data["PageType"] = "products"
|
|
|
|
form := web.GetForm(ctx).(*forms.SubscriptionProductForm)
|
|
|
|
p := &monetize_model.RepoSubscriptionProduct{
|
|
RepoID: ctx.Repo.Repository.ID,
|
|
Name: form.Name,
|
|
Type: monetize_model.ProductType(form.Type),
|
|
PriceCents: form.PriceCents,
|
|
Currency: form.Currency,
|
|
IsActive: form.IsActive,
|
|
}
|
|
|
|
editID := ctx.FormInt64("id")
|
|
if editID > 0 {
|
|
p.ID = editID
|
|
if err := monetize_model.UpdateProduct(ctx, p); err != nil {
|
|
ctx.ServerError("UpdateProduct", err)
|
|
return
|
|
}
|
|
} else {
|
|
if err := monetize_model.CreateProduct(ctx, p); err != nil {
|
|
ctx.ServerError("CreateProduct", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.settings.subscriptions.product_saved"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/subscriptions/products")
|
|
}
|
|
|
|
// SubscriptionsProductDelete deletes a product.
|
|
func SubscriptionsProductDelete(ctx *context.Context) {
|
|
id := ctx.FormInt64("id")
|
|
if id > 0 {
|
|
if err := monetize_model.DeleteProduct(ctx, id); err != nil {
|
|
ctx.ServerError("DeleteProduct", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("repo.settings.subscriptions.product_deleted"))
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/subscriptions/products")
|
|
}
|
|
|
|
// SubscriptionsClients shows the subscriber list for this repo.
|
|
func SubscriptionsClients(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("repo.settings.subscriptions.clients")
|
|
ctx.Data["PageIsSettingsSubscriptions"] = true
|
|
ctx.Data["PageIsSettingsSubscriptionsClients"] = true
|
|
ctx.Data["PageType"] = "clients"
|
|
|
|
page := ctx.FormInt("page")
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
pageSize := 20
|
|
|
|
subs, count, err := monetize_model.GetSubscriptionsByRepoID(ctx, ctx.Repo.Repository.ID, page, pageSize)
|
|
if err != nil {
|
|
ctx.ServerError("GetSubscriptionsByRepoID", err)
|
|
return
|
|
}
|
|
|
|
for _, sub := range subs {
|
|
_ = sub.LoadUser(ctx)
|
|
_ = sub.LoadProduct(ctx)
|
|
}
|
|
|
|
ctx.Data["Subscriptions"] = subs
|
|
ctx.Data["Total"] = count
|
|
|
|
pager := context.NewPagination(int(count), pageSize, page, 5)
|
|
pager.AddParamFromRequest(ctx.Req)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.HTML(http.StatusOK, tplSubscriptions)
|
|
}
|