Thank-you Page & Rich Menu Switching After Submission
Overview
These are the side effects that fire after answers have been saved successfully — the mechanism that turns a form into a tool for converting casual visitors into members: switching the user's rich menu and/or changing line_user.user_type from guest to member.
The critical design decision: the rich menu switch is triggered server-side on successful submit, not by a button press on the thank-you page. If the client issued the call itself, anyone holding the form URL could POST to change their own rich menu.
Business Flow
Two independent mechanisms can fire during a single submission, producing two messages on the same queue. Whichever is processed last wins the user's menu — that ordering is not guaranteed, and the CMS documents the limitation for whoever configures the form.
Mechanism 1 — the form-level convert_to_member flag
-
Publish the payload
{lineOaId, type:"setRichMenuMemberByLineUserId", userType:"member", userId}to theline_change_richmenuqueue. -
Errors from this publish are propagated, failing the request — unlike mechanism 2, which is purely best-effort.
-
Then issue
UPDATE line_user SET user_type='member'when the user is not already a member. This is best-effort; failures are only logged.Why the explicit UPDATE is needed: the
userTypefield in the payload is never read by any worker, on either the NestJS or the Go side. The result was a rich menu that changed whileuser_typestayedguest— a QA defect fixed by updating the column here.
Mechanism 2 — a thank_you.action of type rich_menu
Configured per form on the CMS thank-you page.
- Read the
form_builder.thank_youjsonb defensively: it must havemode == "custom",action.type == "rich_menu", andaction.rich_menu_idas an integer of 1 or greater. Anything missing logs a warning and skips, because the column is raw JSON that may have been written before the CMS had a validator. - A
lineUserIdis required. The CMS already enforcesrequire_line_loginat save time; this is the runtime safety belt. - If
fb.LineOaIDis 0 or less, log a warning and skip. - Re-check ownership at publish time. This is the only gate on this path, because the worker-side handler looks up the rich menu without filtering on
line_oa_idand then binds it using the token of whichever OA the payload names. The check isSELECT 1 FROM rich_menu WHERE id=$1 AND line_oa_id=$2 AND deleted_date IS NULL AND status='active', where$2comes from the form's own OA, never from the request. - Publish
{type:"setRichMenuByTriggerRule", lineOaId, userId, richMenuId}to the same queue.setRichMenuByTriggerRuleis used rather thansetRichMenuMemberByLineUserIdbecause the latter handler ignores the id it is given and picks the OA's member-type menu with a query of its own. - Every failure is a warning only. The response has already been recorded, the user did nothing wrong, and the menu switch is a bonus.
- This mechanism never touches
line_user.user_type, and must not: its job is to change the menu, not membership status.
What the web thank-you page uses
The entire thank_you object is already returned in the formBuilderInfo section of the Submit response and from GET /form-builder/:hash, so the web app renders the close, oa_chat, and url buttons plus the auto-close countdown on its own. The botBasicId used by the oa_chat button comes from the LINE OA lookup step.
Key Files & Functions
This feature has no route of its own — it is step 10 of the Submit pipeline.
| File | Functions |
|---|---|
internal/formsubmission/thankyou.go | thankYouRichMenuID(raw), (*Service).publishThankYouRichMenu, (*Service).warn |
internal/formsubmission/service.go | The convertToMember(fb) block inside Submit (publish plus UPDATE line_user), the convertToMember helper, ptrStringOrNil |
internal/formsubmission/register.go | Defines the richMenuQueue name, defaulting to line_change_richmenu |
Connections to Other Services
- RabbitMQ — the
line_change_richmenuqueue, consumed by line-management-worker-go via thesetRichMenuMemberByLineUserIdandsetRichMenuByTriggerRulehandlers. - Database —
rich_menu(ownership check),line_user(theuser_typecolumn), andform_builder(thethank_youjsonb plus theconvert_to_memberandrequire_line_loginflags). - Related features — invoked from the form submission pipeline; all configuration originates in the CMS Form Builder screen.
- client-web — corresponds to the
form-thank-youfeature, and relates toform-fill.