Files
apskel-pos-backend/internal/contract/customer_auth_contract.go
T
efrilmandClaude Opus 5.5 a3cb7dd5fd fix(customer-auth): register customers into the app's organization
Registration put every new customer into a hardcoded organization id,
which does not exist in staging, so set-password failed on the
fk_customers_organization foreign key with a 500.

POST /customer-auth/register/start now takes organization_id, the
organization the app is built for. It must be a UUID of an existing
organization, checked before the OTP is sent; it is kept in the OTP
session and set-password creates the customer there. A registration
started before this change has no organization in its session and is asked
to start again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:12:26 +07:00

148 lines
4.8 KiB
Go

package contract
import (
"time"
"github.com/google/uuid"
)
// Request Contracts
type CheckPhoneRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Password string `json:"password,omitempty"` // Optional - only required if user exists
}
type RegisterStartRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Name string `json:"name" binding:"required"`
BirthDate string `json:"birth_date" binding:"required"`
// The organization (brand) the customer registers with. A customer belongs to one
// organization; the app sends the one it is built for.
OrganizationID string `json:"organization_id" binding:"required"`
}
type RegisterVerifyOtpRequest struct {
RegistrationToken string `json:"registration_token" binding:"required"`
OtpCode string `json:"otp_code" binding:"required"`
}
type RegisterSetPasswordRequest struct {
RegistrationToken string `json:"registration_token" binding:"required"`
Password string `json:"password" binding:"required,min=8"`
ConfirmPassword string `json:"confirm_password" binding:"required"`
}
type CustomerLoginRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Password string `json:"password" binding:"required"`
}
type ResendOtpRequest struct {
PhoneNumber string `json:"phone_number" binding:"required"`
Purpose string `json:"purpose" binding:"required,oneof=login registration"`
}
// Response Contracts
type CheckPhoneResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *CheckPhoneResponseData `json:"data,omitempty"`
}
type CheckPhoneResponseData struct {
// For NOT_REGISTERED status
PhoneNumber string `json:"phone_number,omitempty"`
// For PASSWORD_REQUIRED status
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
User *CustomerUserData `json:"user,omitempty"`
// For OTP_REQUIRED status (if password doesn't exist)
OtpToken string `json:"otp_token,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
}
type RegisterStartResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterStartResponseData `json:"data,omitempty"`
}
type RegisterStartResponseData struct {
RegistrationToken string `json:"registration_token"`
OtpToken string `json:"otp_token"`
ExpiresIn int `json:"expires_in"`
}
type RegisterVerifyOtpResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterVerifyOtpResponseData `json:"data,omitempty"`
}
type RegisterVerifyOtpResponseData struct {
RegistrationToken string `json:"registration_token"`
}
type RegisterSetPasswordResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *RegisterSetPasswordResponseData `json:"data,omitempty"`
}
type RegisterSetPasswordResponseData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
User *CustomerUserData `json:"user"`
}
type CustomerUserData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
PhoneNumber string `json:"phone_number"`
BirthDate string `json:"birth_date"`
}
type CustomerLoginResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *CustomerLoginResponseData `json:"data,omitempty"`
}
type CustomerLoginResponseData struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
User *CustomerUserData `json:"user"`
}
type ResendOtpResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data *ResendOtpResponseData `json:"data,omitempty"`
}
type ResendOtpResponseData struct {
OtpToken string `json:"otp_token"`
ExpiresIn int `json:"expires_in"`
NextResendIn int `json:"next_resend_in"` // Seconds until next resend is allowed
}
// Internal structures for OTP and registration tokens
type OtpSession struct {
Token string `json:"token"`
Code string `json:"code"`
PhoneNumber string `json:"phone_number"`
ExpiresAt time.Time `json:"expires_at"`
Purpose string `json:"purpose"` // "login" or "registration"
}
type RegistrationSession struct {
Token string `json:"token"`
PhoneNumber string `json:"phone_number"`
Name string `json:"name"`
BirthDate string `json:"birth_date"`
ExpiresAt time.Time `json:"expires_at"`
Step string `json:"step"` // "otp_sent", "otp_verified", "password_set"
}