Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ require (
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stripe/stripe-go/v84 v84.3.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stripe/stripe-go/v84 v84.3.0 h1:77HH+ro7yzmyyF7Xkbkj6y5QtnU1WWHC6t2y4mq0Wvk=
github.com/stripe/stripe-go/v84 v84.3.0/go.mod h1:Z4gcKw1zl4geDG2+cjpSaJES9jaohGX6n7FP8/kHIqw=
github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU=
github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY=
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk=
Expand Down
35 changes: 16 additions & 19 deletions backend/internal/models/event_occurrence.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,22 @@ const (
EventOccurrenceStatusCancelled EventOccurrenceStatus = "cancelled"
)

// database model for a specific instance of an event
// stores full type information for Event and Location
type EventOccurrence struct {
ID uuid.UUID `json:"id" db:"id"`
ManagerId *uuid.UUID `json:"manager_id" db:"manager_id"`
Event Event `json:"event" db:"-"`
Location Location `json:"location" db:"-"`
StartTime time.Time `json:"start_time" db:"start_time"`
EndTime time.Time `json:"end_time" db:"end_time"`
MaxAttendees int `json:"max_attendees" db:"max_attendees"`
Language string `json:"language" db:"language"`
CurrEnrolled int `json:"curr_enrolled" db:"curr_enrolled"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
Status RegistrationStatus `json:"status" db:"status" doc:"Current status of the event occurrence" enum:"scheduled,cancelled"`
ID uuid.UUID `json:"id" db:"id"`
ManagerId *uuid.UUID `json:"manager_id" db:"manager_id"`
Event Event `json:"event" db:"-"`
Location Location `json:"location" db:"-"`
StartTime time.Time `json:"start_time" db:"start_time"`
EndTime time.Time `json:"end_time" db:"end_time"`
MaxAttendees int `json:"max_attendees" db:"max_attendees"`
Language string `json:"language" db:"language"`
CurrEnrolled int `json:"curr_enrolled" db:"curr_enrolled"`
Price int `json:"price" db:"price" doc:"Price in cents (e.g., 10000 = ฿100)"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
Status EventOccurrenceStatus `json:"status" db:"status" doc:"Current status of the event occurrence" enum:"scheduled,cancelled"`
}

// get all
type GetAllEventOccurrencesInput struct {
Page int `query:"page" minimum:"1" default:"1"`
Limit int `query:"limit" minimum:"1" maximum:"100" default:"100"`
Expand All @@ -40,7 +38,6 @@ type GetAllEventOccurrencesOutput struct {
Body []EventOccurrence `json:"body" doc:"List of all event occurrences in the database"`
}

// get by event occurrence id
type GetEventOccurrenceByIDInput struct {
ID uuid.UUID `path:"id" doc:"ID of an event occurrence"`
}
Expand All @@ -49,7 +46,6 @@ type GetEventOccurrenceByIDOutput struct {
Body *EventOccurrence `json:"body" doc:"Event occurrence in the database that matches the ID"`
}

// post
type CreateEventOccurrenceInput struct {
Body struct {
ManagerId *uuid.UUID `json:"manager_id,omitempty" doc:"ID of a manager in the database"`
Expand All @@ -59,14 +55,14 @@ type CreateEventOccurrenceInput struct {
EndTime time.Time `json:"end_time" doc:"End time of the event occurrence"`
MaxAttendees int `json:"max_attendees" doc:"Maximum number of attendees" minimum:"1" maximum:"100"`
Language string `json:"language" doc:"Primary language used for the event occurrence" minLength:"2" maxLength:"30"`
Price int `json:"price" doc:"Price in cents (e.g., 10000 = ฿100)" minimum:"0"`
} `json:"body" doc:"New event occurrence to add"`
}

type CreateEventOccurrenceOutput struct {
Body *EventOccurrence `json:"body" doc:"Created event occurrence"`
}

// patch
type UpdateEventOccurrenceInput struct {
ID uuid.UUID `path:"id" doc:"ID of the event occurrence to update"`
Body struct {
Expand All @@ -78,6 +74,7 @@ type UpdateEventOccurrenceInput struct {
MaxAttendees *int `json:"max_attendees,omitempty" doc:"Maximum number of attendees" minimum:"1" maximum:"100"`
Language *string `json:"language,omitempty" doc:"Primary language used for the event occurrence" minLength:"2" maxLength:"30"`
CurrEnrolled *int `json:"curr_enrolled,omitempty" doc:"Number of students currently enrolled in the event occurrence" minimum:"0" maximum:"100"`
Price *int `json:"price,omitempty" doc:"Price in cents" minimum:"0"`
} `json:"body" doc:"Event occurrence fields to update"`
}

Expand All @@ -93,4 +90,4 @@ type CancelEventOccurrenceOutput struct {
Body struct {
Message string `json:"message" doc:"Success message"`
} `json:"body"`
}
}
19 changes: 14 additions & 5 deletions backend/internal/models/guardian.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Guardian struct {
Username string `json:"username" db:"username"`
ProfilePictureS3Key *string `json:"profile_picture_s3_key" db:"profile_picture_s3_key"`
LanguagePreference string `json:"language_preference" db:"language_preference"`
StripeCustomerID *string `json:"stripe_customer_id,omitempty" db:"stripe_customer_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
Expand All @@ -36,11 +37,11 @@ type GetGuardianByChildIDInput struct {

type CreateGuardianInput struct {
Body struct {
Name string `json:"name" doc:"Name of the guardian"`
Email string `json:"email" doc:"Email of the guardian"`
Username string `json:"username" doc:"Username of the guardian"`
ProfilePictureS3Key *string `json:"profile_picture_s3_key,omitempty" doc:"S3 key for profile picture" required:"false"`
LanguagePreference string `json:"language_preference" doc:"Language preference"`
Name string `json:"name" doc:"Name of the guardian"`
Email string `json:"email" doc:"Email of the guardian"`
Username string `json:"username" doc:"Username of the guardian"`
ProfilePictureS3Key *string `json:"profile_picture_s3_key,omitempty" doc:"S3 key for profile picture" required:"false"`
LanguagePreference string `json:"language_preference" doc:"Language preference"`
AuthID *uuid.UUID `json:"auth_id,omitempty" db:"auth_id" doc:"auth id of the guardian being created" required:"false"`
}
}
Expand Down Expand Up @@ -71,3 +72,11 @@ type GetGuardianByChildIDOutput struct {
type GetGuardianByIDOutput struct {
Body *Guardian `json:"body"`
}

type CreateStripeCustomerInput struct {
GuardianID uuid.UUID `path:"guardian_id" doc:"Guardian ID"`
}

type CreateStripeCustomerOutput struct {
Body Guardian `json:"body" doc:"Updated guardian with Stripe customer ID"`
}
63 changes: 63 additions & 0 deletions backend/internal/models/guardian_payment_method.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package models

import (
"time"

"github.com/google/uuid"
)

type GuardianPaymentMethod struct {
ID uuid.UUID `json:"id" db:"id"`
GuardianID uuid.UUID `json:"guardian_id" db:"guardian_id"`
StripePaymentMethodID string `json:"stripe_payment_method_id" db:"stripe_payment_method_id"`
CardBrand *string `json:"card_brand,omitempty" db:"card_brand"`
CardLast4 *string `json:"card_last4,omitempty" db:"card_last4"`
CardExpMonth *int `json:"card_exp_month,omitempty" db:"card_exp_month"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to store any of this card info or can it be pulled from stripe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice to have to show on the frontend and reduce calls to the API

CardExpYear *int `json:"card_exp_year,omitempty" db:"card_exp_year"`
IsDefault bool `json:"is_default" db:"is_default"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}

type CreateGuardianPaymentMethodInput struct {
Body struct {
GuardianID uuid.UUID `json:"guardian_id" doc:"Guardian ID"`
StripePaymentMethodID string `json:"stripe_payment_method_id" doc:"Stripe payment method ID (pm_...)"`
CardBrand *string `json:"card_brand,omitempty" doc:"Card brand (visa, mastercard, etc.)"`
CardLast4 *string `json:"card_last4,omitempty" doc:"Last 4 digits of card"`
CardExpMonth *int `json:"card_exp_month,omitempty" doc:"Card expiration month" minimum:"1" maximum:"12"`
CardExpYear *int `json:"card_exp_year,omitempty" doc:"Card expiration year" minimum:"2026"`
IsDefault bool `json:"is_default" doc:"Whether this is the default payment method"`
}
}

type CreateGuardianPaymentMethodOutput struct {
Body GuardianPaymentMethod `json:"body" doc:"Created payment method"`
}

type GetGuardianPaymentMethodsByGuardianIDInput struct {
GuardianID uuid.UUID `path:"guardian_id" doc:"Guardian ID"`
}

type GetGuardianPaymentMethodsByGuardianIDOutput struct {
Body []GuardianPaymentMethod `json:"body" doc:"List of guardian's payment methods"`
}

type DeleteGuardianPaymentMethodInput struct {
ID uuid.UUID `path:"id" doc:"Payment method ID"`
}

type DeleteGuardianPaymentMethodOutput struct {
Body struct {
Message string `json:"message" doc:"Success message"`
}
}

type SetDefaultPaymentMethodInput struct {
GuardianID uuid.UUID `path:"guardian_id" doc:"Guardian ID"`
PaymentMethodID uuid.UUID `path:"payment_method_id" doc:"Payment method ID to set as default"`
}

type SetDefaultPaymentMethodOutput struct {
Body GuardianPaymentMethod `json:"body" doc:"Updated payment method"`
}
16 changes: 9 additions & 7 deletions backend/internal/models/organization.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (
)

type Organization struct {
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Active bool `json:"active" db:"active"`
PfpS3Key *string `json:"pfp_s3_key,omitempty" db:"pfp_s3_key"`
ID uuid.UUID `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Active bool `json:"active" db:"active"`
PfpS3Key *string `json:"pfp_s3_key,omitempty" db:"pfp_s3_key"`
PresignedURL *string `json:"presigned_url"`
LocationID *uuid.UUID `json:"location_id,omitempty" db:"location_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
LocationID *uuid.UUID `json:"location_id,omitempty" db:"location_id"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
StripeAccountID *string `json:"stripe_account_id" db:"stripe_account_id"`
StripeAccountActivated bool `json:"stripe_account_activated" db:"stripe_account_activated" default:"false"`
}

// CreateOrganizationRouteInput is the multipart form input for creating an organization with an image
Expand Down
109 changes: 109 additions & 0 deletions backend/internal/models/payment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package models

import (
"time"

"github.com/google/uuid"
"github.com/stripe/stripe-go/v84"
)

type CreateOrgStripeAccountInput struct {
Body struct {
OrganizationID uuid.UUID `json:"organization_id" doc:"UUID of the existing organization"`
}
}

type CreateOrgStripeAccountOutput struct {
Body struct {
Account stripe.V2CoreAccount `json:"account" doc:"Stripe account details"`
}
}

type CreateStripeOnboardingLinkInput struct {
Body struct {
AccountID string `json:"account_id" doc:"Stripe account ID (e.g., acct_123)"`
RefreshURL string `json:"refresh_url" doc:"URL to redirect if onboarding is exited early"`
ReturnURL string `json:"return_url" doc:"URL to redirect after successful onboarding"`
}
}

type CreateStripeOnboardingLinkOutput struct {
Body struct {
OnboardingURL string `json:"onboarding_url" doc:"Stripe-hosted onboarding page URL"`
}
}

type CreateSetupIntentInput struct {
GuardianID uuid.UUID `path:"guardian_id" doc:"Guardian ID"`
}

type CreateSetupIntentOutput struct {
Body struct {
ClientSecret string `json:"client_secret" doc:"Stripe SetupIntent client_secret for frontend"`
}
}

type CreateOrgLoginLinkInput struct {
OrganizationID uuid.UUID `path:"organization_id" doc:"Organization ID"`
}

type CreateOrgLoginLinkOutput struct {
Body struct {
LoginURL string `json:"login_url" doc:"Stripe Express dashboard login URL"`
}
}

type CreatePaymentIntentInput struct {
Body struct {
RegistrationID uuid.UUID `json:"registration_id" doc:"Registration/booking ID"`
GuardianID uuid.UUID `json:"guardian_id" doc:"Guardian ID"`
ProviderOrgID uuid.UUID `json:"provider_org_id" doc:"Provider organization ID"`
Amount int64 `json:"amount" doc:"Total amount in cents" minimum:"1"`
Currency string `json:"currency" doc:"Currency code (e.g., thb, usd)" pattern:"^[a-z]{3}$"`
EventDate time.Time `json:"event_date" doc:"Event date and time"`
PaymentMethodID *string `json:"payment_method_id,omitempty" doc:"Stripe payment method ID (required for bookings)"`

GuardianStripeID string
OrgStripeID string
}
}

type CreatePaymentIntentOutput struct {
Body struct {
PaymentIntentID string `json:"payment_intent_id" doc:"Stripe payment intent ID"`
ClientSecret string `json:"client_secret" doc:"Client secret for frontend to confirm payment"`
Status string `json:"status" doc:"Payment intent status"`
TotalAmount int `json:"total_amount" doc:"Total amount in cents"`
ProviderAmount int `json:"provider_amount" doc:"Amount provider receives in cents"`
PlatformFeeAmount int `json:"platform_fee_amount" doc:"Platform fee in cents"`
Currency string `json:"currency" doc:"Currency code"`
}
}

type CancelPaymentIntentInput struct {
PaymentIntentID string `json:"payment_intent_id" doc:"Stripe payment intent ID to cancel/refund"`
StripeAccountID string `json:"stripe_account_id" doc:"Organization's Stripe account ID"`
}

type CancelPaymentIntentOutput struct {
Body struct {
PaymentIntentID string `json:"payment_intent_id" doc:"Cancelled payment intent ID"`
Status string `json:"status" doc:"Payment intent status after cancellation"`
Amount int64 `json:"amount" doc:"Amount that was cancelled/refunded in cents"`
Currency string `json:"currency" doc:"Currency code"`
} `json:"body" doc:"Cancellation result"`
}

type CapturePaymentIntentInput struct {
PaymentIntentID string `json:"payment_intent_id" doc:"Stripe payment intent ID to capture"`
StripeAccountID string `json:"stripe_account_id" doc:"Organization's Stripe account ID"`
}

type CapturePaymentIntentOutput struct {
Body struct {
PaymentIntentID string `json:"payment_intent_id" doc:"Captured payment intent ID"`
Status string `json:"status" doc:"Payment intent status (should be 'succeeded')"`
Amount int64 `json:"amount" doc:"Amount captured in cents"`
Currency string `json:"currency" doc:"Currency code"`
} `json:"body" doc:"Capture result"`
}
Loading