142 lines
4.9 KiB
Go
142 lines
4.9 KiB
Go
package grpc
|
|
|
|
import (
|
|
"context"
|
|
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
billingv1 "enkly/gen/billing/v1"
|
|
"enkly/pkg/grpcerr"
|
|
"enkly/services/billing/internal/application"
|
|
"enkly/services/billing/internal/domain"
|
|
)
|
|
|
|
type Server struct {
|
|
billingv1.UnimplementedBillingServiceServer
|
|
Svc *application.Service
|
|
PublishableKey string // STRIPE_PUBLISH_KEY, for GetPlanCatalogResponse
|
|
}
|
|
|
|
func New(svc *application.Service, publishableKey string) *Server {
|
|
return &Server{Svc: svc, PublishableKey: publishableKey}
|
|
}
|
|
|
|
func toProtoPlan(p domain.Plan) billingv1.Plan {
|
|
switch p {
|
|
case domain.PlanFree:
|
|
return billingv1.Plan_PLAN_FREE
|
|
case domain.PlanBasic:
|
|
return billingv1.Plan_PLAN_BASIC
|
|
case domain.PlanPro:
|
|
return billingv1.Plan_PLAN_PRO
|
|
case domain.PlanPremium:
|
|
return billingv1.Plan_PLAN_PREMIUM
|
|
default:
|
|
return billingv1.Plan_PLAN_UNSPECIFIED
|
|
}
|
|
}
|
|
|
|
func fromProtoPlan(p billingv1.Plan) domain.Plan {
|
|
switch p {
|
|
case billingv1.Plan_PLAN_FREE:
|
|
return domain.PlanFree
|
|
case billingv1.Plan_PLAN_BASIC:
|
|
return domain.PlanBasic
|
|
case billingv1.Plan_PLAN_PRO:
|
|
return domain.PlanPro
|
|
case billingv1.Plan_PLAN_PREMIUM:
|
|
return domain.PlanPremium
|
|
default:
|
|
return domain.PlanFree
|
|
}
|
|
}
|
|
|
|
func toProtoSubscription(s *domain.Subscription) *billingv1.Subscription {
|
|
out := &billingv1.Subscription{
|
|
UserId: s.UserID,
|
|
Plan: toProtoPlan(domain.Plan(s.Plan)),
|
|
Status: s.Status,
|
|
CancelAtPeriodEnd: s.CancelAtPeriodEnd,
|
|
HasBillingAccount: s.StripeCustomerID != "",
|
|
}
|
|
if !s.CurrentPeriodEnd.IsZero() {
|
|
out.CurrentPeriodEnd = timestamppb.New(s.CurrentPeriodEnd)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *Server) GetOrCreateSubscription(ctx context.Context, req *billingv1.GetOrCreateSubscriptionRequest) (*billingv1.Subscription, error) {
|
|
sub, err := s.Svc.GetOrCreateSubscription(ctx, req.GetUserId())
|
|
if err != nil {
|
|
return nil, grpcerr.Internal(err)
|
|
}
|
|
return toProtoSubscription(sub), nil
|
|
}
|
|
|
|
func (s *Server) GetPlanCatalog(ctx context.Context, req *billingv1.GetPlanCatalogRequest) (*billingv1.GetPlanCatalogResponse, error) {
|
|
entries := s.Svc.GetPlanCatalog(ctx)
|
|
resp := &billingv1.GetPlanCatalogResponse{PublishableKey: s.PublishableKey}
|
|
for _, e := range entries {
|
|
resp.Plans = append(resp.Plans, &billingv1.PlanInfo{
|
|
Plan: toProtoPlan(e.Plan),
|
|
Name: e.Name,
|
|
PriceEur: int32(e.PriceEUR),
|
|
DailyMessageLimit: int32(e.DailyMessageLimit),
|
|
Available: e.Available,
|
|
})
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *Server) CreateCheckoutSession(ctx context.Context, req *billingv1.CreateCheckoutSessionRequest) (*billingv1.CreateCheckoutSessionResponse, error) {
|
|
url, err := s.Svc.CreateCheckoutSession(ctx, req.GetUserId(), req.GetEmail(), fromProtoPlan(req.GetPlan()), req.GetSuccessUrl(), req.GetCancelUrl())
|
|
if err != nil {
|
|
return nil, grpcerr.FailedPrecondition(err.Error())
|
|
}
|
|
return &billingv1.CreateCheckoutSessionResponse{CheckoutUrl: url}, nil
|
|
}
|
|
|
|
func (s *Server) SyncCheckoutSession(ctx context.Context, req *billingv1.SyncCheckoutSessionRequest) (*billingv1.Subscription, error) {
|
|
sub, err := s.Svc.SyncCheckoutSession(ctx, req.GetUserId(), req.GetStripeSessionId())
|
|
if err != nil {
|
|
return nil, grpcerr.FailedPrecondition(err.Error())
|
|
}
|
|
return toProtoSubscription(sub), nil
|
|
}
|
|
|
|
func (s *Server) CreateBillingPortalSession(ctx context.Context, req *billingv1.CreateBillingPortalSessionRequest) (*billingv1.CreateBillingPortalSessionResponse, error) {
|
|
url, err := s.Svc.CreateBillingPortalSession(ctx, req.GetUserId(), req.GetReturnUrl())
|
|
if err != nil {
|
|
if application.IsNoBillingAccount(err) {
|
|
return nil, grpcerr.FailedPrecondition("no billing account yet — subscribe to a paid plan first")
|
|
}
|
|
return nil, grpcerr.FailedPrecondition(err.Error())
|
|
}
|
|
return &billingv1.CreateBillingPortalSessionResponse{PortalUrl: url}, nil
|
|
}
|
|
|
|
func (s *Server) IngestStripeWebhook(ctx context.Context, req *billingv1.IngestStripeWebhookRequest) (*billingv1.IngestStripeWebhookResponse, error) {
|
|
if err := s.Svc.IngestStripeWebhook(ctx, req.GetPayload(), req.GetSignatureHeader()); err != nil {
|
|
return nil, grpcerr.InvalidArgument(err.Error())
|
|
}
|
|
return &billingv1.IngestStripeWebhookResponse{Accepted: true}, nil
|
|
}
|
|
|
|
func (s *Server) CheckAndIncrementUsage(ctx context.Context, req *billingv1.CheckAndIncrementUsageRequest) (*billingv1.CheckAndIncrementUsageResponse, error) {
|
|
allowed, used, limit, planName, err := s.Svc.CheckAndIncrementUsage(ctx, req.GetUserId())
|
|
if err != nil {
|
|
return nil, grpcerr.Internal(err)
|
|
}
|
|
return &billingv1.CheckAndIncrementUsageResponse{
|
|
Allowed: allowed, Used: int32(used), Limit: int32(limit), PlanName: planName,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) GetDailyUsage(ctx context.Context, req *billingv1.GetDailyUsageRequest) (*billingv1.GetDailyUsageResponse, error) {
|
|
used, err := s.Svc.GetDailyUsage(ctx, req.GetUserId())
|
|
if err != nil {
|
|
return nil, grpcerr.Internal(err)
|
|
}
|
|
return &billingv1.GetDailyUsageResponse{Used: int32(used)}, nil
|
|
}
|