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>
148 lines
4.8 KiB
Go
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"
|
|
}
|