Authenticated user changes the billing email address used on Stripe invoices and payment receipts. This is independent of the account email (users.email). Requires a valid JWT access token.
- Billing email is stored on the Stripe Customer object (
customer.email) — not in a TalkIDE DB column. - On first billing action, Stripe Customer is created with the user’s account email as billing email. The user can then override it independently.
- The new billing email must be a valid email format. No uniqueness check across users (multiple users may share a billing entity, e.g. company billing email).
- If the user has no
stripe_customer_idyet, this endpoint lazily creates the Stripe Customer (same as UC-10001) and sets the provided email on it. - Related: UC-10002 —
billingEmailfield in the GET response reflects the current Stripe Customer email.
sequenceDiagram
actor User
User->>+FE: edits billing email field and clicks "Save"
FE->>FE: validate email format
alt email format invalid
FE-->>User: show "Please enter a valid email address" error
end
FE->>+BE: PUT /api/v1/users/me/billing/email <br> Authorization: Bearer {accessToken} <br> UpdateBillingEmailRequest
BE->>BE: validate JWT access token
alt access token invalid or missing
BE-->>FE: 401 Unauthorized <br> ErrorResponse
end
BE->>BE: validate request (email format)
alt request is invalid
BE-->>FE: 400 Bad Request <br> ErrorResponse
end
BE->>BE: resolve stripe_customer_id (ensureCustomer)
alt stripe_customer_id is null
BE->>Stripe: stripe.customers.create({ email: newEmail, metadata: { userId } })
Stripe-->>BE: Customer { id: "cus_..." }
BE->>DB: UPDATE users SET stripe_customer_id = "cus_..." WHERE id = userId
end
BE->>Stripe: stripe.customers.update(customerId, { email: newEmail })
Stripe-->>BE: Customer (updated)
BE->>-FE: 204 No Content
FE->>-User: show "Billing email updated" success toast, update displayed billing email
PUT /api/v1/users/me/billing/email UpdateBillingEmailRequest:
{
"billingEmail": "billing@mycompany.com"
}
204 No Content (success, no body)
400 Bad Request (validation) ErrorResponse:
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"errors": [
{ "field": "billingEmail", "message": "must be a valid email address" }
]
}
401 Unauthorized ErrorResponse:
{
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Access token is missing or invalid"
}
502 Bad Gateway (Stripe API unreachable) ErrorResponse:
{
"status": 502,
"code": "STRIPE_UNAVAILABLE",
"message": "Payment provider is temporarily unavailable. Please try again."
}
Frontend
Validations
| Field | Constraints | Size | Pattern | Note |
|---|---|---|---|---|
| billingEmail | not_blank, email | 5 - 254 | ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ | Validated before submit |
UX Guidelines
Billing email row in BillingSection.vue:
- Displayed as an inline editable field. Default value populated from
GET /payment-methodresponse (billingEmailfield). - “Save” button visible only when the value differs from the loaded value.
- On 204: toast (green, 3 s): “Billing email updated.”
- On 502: toast (rose, 5 s): “Could not update billing email. Please try again.”
- On 400: inline error below field.
Backend
Validations
| Field | Constraints | Note |
|---|---|---|
JWT Authorization header | not_blank, valid signature, not expired | 401 if missing/invalid |
billingEmail | not_blank, valid email format | 400 VALIDATION_ERROR if blank or invalid format |
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| Authenticated user with existing stripe_customer_id | PUT /billing/email is called with valid email | 204 No Content; Stripe Customer email updated; subsequent GET /payment-method returns new billingEmail |
| Authenticated user with no stripe_customer_id | PUT /billing/email is called with valid email | Stripe Customer lazily created with new email; stripe_customer_id persisted to users table; 204 No Content |
billingEmail is blank | PUT /billing/email is called | 400 VALIDATION_ERROR returned |
billingEmail is not a valid email (e.g. “notanemail”) | PUT /billing/email is called | 400 VALIDATION_ERROR returned |
| No Authorization header | PUT /billing/email is called | 401 AUTHENTICATION_FAILED returned |
| Stripe API unreachable | PUT /billing/email is called | 502 STRIPE_UNAVAILABLE returned; no Customer created/updated |
Was this page helpful?
Thanks for the feedback.