Birthday Add-On — Book-Bite API Implementation Plan
Scope: Book-Bite partner-portal integration only. The hh-server backend plan and Pegasus consumer plan are separate documents.
Implementation target repository: hungryhub-team/book-bite.
Goal: Implement and verify the Book-Bite client for birthday add-on list, detail, create, update, and delete flows against the partner API contract.
Status: The hh-server contract for these fields is implemented and documented; the Book-Bite FE implementation remains a pending checklist in this plan.
Canonical wire contract: src/BookBite/superpowers/specs/2026-08-06-birthday-add-on-api-contract.md.
Backend dependency: hh-server owns authorization, validation, persistence, and response shape. Book-Bite must consume the response as implemented and must not recreate backend birthday rules locally as an authority.
Scope
- Call
GET /birthday_add_onswith the selected restaurant’srestaurant_id, pagination, filters, and sorting. - Consume the list’s flat restaurant-group response (
free,premium, andmeta) and the JSON:API detail response. - Submit multipart create/update payloads using
add_on[...]and the correct nested attributes. - Use the authenticated restaurant context for create/update authorization. For create, the server resolves an explicit request-level
restaurant_idthroughdefault_restaurantwhen present, or falls back to the staff’s default restaurant; do not nest it underadd_on[...]. - For Partner booking create/update, the portal does not need to send
dining_occasion_id; hh-server assigns the Birthday occasion automatically when a birthday add-on is selected. If the booking payload containsreservation[restaurant_id]=null, hh-server normalizes it to the authenticated staff member’s default restaurant. - Map
itemTypeto the two backend modes:items_listuses birthday item rows, whileitems_imageuses menu image rows. - Treat
isFreeBirthdayAddOnas the authoritative free-row indicator. Price inference remains only as temporary compatibility fallback. - Render uploader responses as already-resolved URLs. Do not prepend a local base URL to an absolute image URL.
- Surface
409 BIRTHDAY_FREE_ADDON_MAX_ONE_REACHEDas the free-add-on conflict state and preserve backend validation errors for field-level display.
Payment settings parity with Admin
The birthday add-on form must implement the same payment settings contract as the Admin Add-On Settings screen. This is part of the v1 create/edit flow, not a later UI enhancement.
FE state and wire mapping
| FE state | API field | Behavior |
|---|---|---|
requirePrepayment | add_on[require_prepayment] | Checkbox; default false. |
paymentTypes | add_on[add_on_payment_types_attributes] | Array of selected integer payment-type IDs; multiple values are allowed. |
loadedPaymentTypeRows | nested row id / payment_type_id | Keep the persisted junction ID while editing so removed rows can be marked _destroy. |
paymentTypeOptions | GET /restaurant_packages/payment_types response data | Options shaped as { id, name }; the partner API applies currency filtering. Do not hardcode gateway names or IDs. |
minBookingTimeInAdvance | add_on[minimum_booking_time_in_advance] | Integer minutes; 0 is Follow Restaurant Level. |
Create must keep restaurant_id at the top level and place these settings under add_on[...]:
{
restaurant_id: 42,
add_on: {
require_prepayment: true,
minimum_booking_time_in_advance: 30,
add_on_payment_types_attributes: [
{ payment_type_id: 1 },
{ payment_type_id: 4 }
]
}
}
For update, retain each existing junction row’s id. A removed row is sent as { id, payment_type_id, _destroy: true }; a newly selected row is sent as { payment_type_id }. If prepayment is toggled off, the FE must clear the selection and mark all previously persisted rows for destruction.
UI and validation behavior
- Render a checkbox labeled by the existing Add-On Settings copy for
requirePrepayment. - Only show the payment-type multi-select when prepayment is enabled.
- Fetch payment options from the existing
GET /restaurant_packages/payment_typespartner endpoint. It returns{ id, name }options and applies the restaurant-currency filtering on the server; do not duplicate the Admin payment catalog or hardcode gateway IDs in Birthday Add-On. - Block save when prepayment is enabled and no payment type is selected. Show the same validation state as Admin: “Select at least one payment type before saving.”
- Permit multiple selections; do not reduce the value to a single payment type.
- Render the time-in-advance selector using the Admin option builder:
0(Follow Restaurant Level), 15/30/45 minutes, then hourly values in 15-minute increments. Do not add the Admin-only1000000(Unlimited) option unless that display mode is explicitly enabled.
File ownership
| Area | Files |
|---|---|
| API methods | src/api/birthdayAddOn.js |
| List normalization | src/pages/package/birthday-add-on/helpers/list.js, BirthdayAddOnList.vue |
| Form mapping and payloads | src/pages/package/birthday-add-on/helpers/form.js |
| Create/edit screens | src/pages/package/birthday-add-on/CreateBirthdayAddOn.vue, EditBirthdayAddOn.vue, components/FormBirthdayAddOn.vue |
| Contract tests | tests/birthdayAddOnList.test.js |
Implementation checklist
- Confirm list requests send only populated filters and use
restaurant_idonly for list selection. - Confirm detail reads
response.data.attributesand usesresponse.data.idforrestaurant_add_ons_attributes.id. - Confirm create/update build
birthdayAddOnItemsAttributesoraddOnMenusAttributesaccording toitemType. - Confirm free create forces
priceCents = 0and premium create leaves price editable. - Confirm image fields are sent as files and displayed from the full URL returned by the API.
- Confirm active-state updates use the nested restaurant-add-on ID.
- Confirm delete handles the API success shape
{ success: true, data: null }. - Confirm the explicit free indicator is preferred over price inference in list grouping.
- Add
requirePrepayment,paymentTypes,loadedPaymentTypeRows,paymentTypeOptions, andminBookingTimeInAdvanceto create/edit form state. - Submit multiple
add_on_payment_types_attributesrows using integerpayment_type_idvalues. - Preserve existing payment junction IDs and submit
_destroy=truefor removed rows or when prepayment is disabled. - Match Admin’s currency-specific payment option filtering and its
0/15-minute time-in-advance option values. - Block save when prepayment is enabled with zero selected payment types.
- Load payment options from
GET /restaurant_packages/payment_typesand use the returned IDs in the nested payment-type payload.
Verification
node --test tests/birthdayAddOnList.test.js
Also run the repository lint/build commands before merging. Manual verification must cover list, detail, premium create, free create, edit, delete, validation 422, and free-conflict 409. For payment settings, verify create with zero/one/multiple types, edit add/remove without duplicate junctions, toggling prepayment off, the no-type validation error, currency-specific options, and time values 0, 15, 30, 45, and an hourly value.
Out of scope
- hh-server migrations, serializers, reservation validation, or feature flags.
- Pegasus V5 list/detail/availability consumption.
- A new client-side birthday API contract that diverges from the canonical spec.