FLIN product guide

This is the product view of FLIN for the FLIN product team, and for engineers who want to know what the app does before they read the code. It covers what is live in Indonesia, what is built but switched off, where the Philippines stands, which vendor powers each feature, where the copy lives, and what FLIN still owes the build. Terms follow the glossary in CONTEXT.md (Home country, Test user, Internal user, Emailed report, Debt Discipline Program and so on). The binding multi-country rules are in multi-country.md. This guide links to its section numbers rather than repeating them.

Last verified: 2026-10-12 against main @ a270f1d (live infra/EAS/Trigger.dev checks on the same date).

Contents

  1. What FLIN is
  2. One app, two countries
  3. Status board
  4. Feature inventory
  5. User journeys
  6. Notifications
  7. Analytics and CRM
  8. Test users and internal users
  9. Languages and copy
  10. App Store and Google Play
  11. Known product-visible gaps
  12. Pending inputs from FLIN
  13. Roadmap signals in the repo

1. What FLIN is

FLIN is a credit-wellness app. A user signs up with a WhatsApp-enabled phone number and verifies their identity with an ID-card photo and a liveness selfie. FLIN then shows a credit score and a list of their loans. From there FLIN offers ways out of debt:

  • Free credit score: an AFPI (fintech-lender association) report pulled through Finfra once, during onboarding. Indonesia only.
  • Paid credit report: the CLIK credit-bureau report, bought per pull. It unlocks every debt category, an Emailed report and a Downloaded report.
  • Recommended debt solution: Dana Talangan, a bridging or consolidation loan (wire name bridgingLoan; the planned Philippine copy calls it "Debt Consolidation"), or Mediasi Utang, debt mediation and negotiation. The user applies in the app and FLIN's team works the application in the CRM.
  • Debt Discipline Program: a paid repayment-coaching membership. It includes a dashboard, a payment calendar, AI analysis, consultations with a credit expert and a monthly bureau refresh.

Who it is for: consumers with several loans (cash loans, pay later, credit cards). The FAQ describes Dana Talangan as "best suited for salaried employees who have 2 or more active loans, whose total monthly instalments exceed 30–40% of their salary" (en.json, faq → Dana Talangan). The consultation form asks for a service region in Indonesia: Jabodetabek, Bandung, Surabaya, elsewhere in Java, or elsewhere in Indonesia (constants.ts SERVICE_REGIONS).

Names you will see:

  • The app's display name is "Flin". The store listing is "FLIN - Credit Wellness App", and the App Store seller is Maybright Asia Pacific pte ltd (verified live 2026-10-12).
  • The Terms name PT FLIN Consultancy Indonesia as the contracting entity.
  • Bundle id and package: com.flin.flinapp.

2. One app, two countries

One store binary serves every country. In product terms:

  • Home country comes from the device's Region setting: Philippines when the region is PH, Indonesia for anything else. Nobody picks it, and the app never uses location or the SIM card. It is frozen when the user signs in and holds until they sign out. A user who signed in before the PH change stays on Indonesia even on a PH-region phone. See ADR-0009 and multi-country.md §8.3.
  • The Home country decides which FLIN server the app talks to. The Indonesia and Philippines servers are separate, each with its own database in its own region. It also decides the dial code (+62 / +63), currency, languages, onboarding steps and products. The dial code and languages come from the app's own country table before sign-in; after that the app follows the server's public config.get response.
  • A country is offered only when its server URL is baked into the build. The Philippines URL variable EXPO_PUBLIC_SERVER_URL_PH is not set in any EAS environment, so no shipped binary offers the Philippines. A PH-region phone running the live 1.1.0 build gets Indonesia (verified live 2026-10-12).
  • Country differences go through data: a country profile, a copy overlay or a vendor adapter. Code never carries a country === "PH" branch. An ID-only screen is hidden by the feature that owns it, never deleted (multi-country.md §8.4).

3. Status board

Live values were read on 2026-10-12: the public config.get of both servers, the running ECS task definitions (ID flin-prod:282, PH flin-ph-prod:4), and the EAS environment that built the live 1.1.0 binary. Indonesia's store app 1.1.0 has been live since 2026-10-07.

How a feature is switched on. There are four kinds of switch:

  • Build flag (EXPO_PUBLIC_*): frozen into each binary. Changing it needs a new store build. Defined in apps/native/lib/phase1.ts.
  • Server env: set per deployment. It changes with a redeploy and needs no app release.
  • Profile feature fact: features.* in constants.ts. A fact about the country, changed in code.
  • Onboarding step: onboardingSteps in the same file.

Paid features need both the build flag and the server env on.

Feature Indonesia (live) Philippines Controlled by
Country offered to users Yes (https://appapi.flin.co.id) No. The server is up at https://appapi.flin.ph as APP_ENV=staging, but no binary has its URL Build: EXPO_PUBLIC_SERVER_URL / EXPO_PUBLIC_SERVER_URL_PH (EAS)
Phone sign-up, WhatsApp OTP Live (CampaignHQ) Wired, same adapter; PH account and template not confirmed Profile adapter (otp)
Identity check (ID card + liveness selfie) Live: KTP read by AWS Bedrock, AsliRI liveness and biometric match (pass ≥ 70) Mock adapters, always pass. Onfido is pending Profile adapters (idDocOcr, biometric)
Consultation form (financial details) Live, onboarding step Not in the flow Onboarding step financial_details
Loan verification screen Off (retired 2026-09-16, FLIN-208) Off Feature fact loanVerification=false
Free credit score (AFPI) Live Off (no vendor) Feature fact freeCreditScore (ID true, PH false)
Paid credit report (CLIK) Live at Rp 25.000. Built with EXPO_PUBLIC_PAID_REPORT=1; server PAID_REPORT_ENABLED=1, REPORT_PRICE_IDR=25000 Off: PAID_REPORT_ENABLED=0, CIBI adapter not built. Placeholder price ₱199.00 (REPORT_PRICE_MINOR=19900) Build flag + server env + price env
Emailed report / Downloaded report Live (follows the paid report) Off Follows the paid report
CLIK's own score as the headline score Off (CLIK_BUREAU_SCORE_ENABLED unset, defaults to 0), so FLIN's combined score shows n/a Server env
AI loan classification of CLIK contracts On (CLIK_CLASSIFY_ENABLED defaults to 1) n/a Server env
Debt Discipline Program Server on, store app off. DEBT_DISCIPLINE_ENABLED=1, DDP_REFRESH_ENABLED=1, DDP_DUE_NOTIFS_ENABLED=1, but 1.1.0 was built with EXPO_PUBLIC_DEBT_DISCIPLINE=0, so store users never see it. Internal preview builds (flag 1) see it against production. The due-notification job reported 3 members Off (0/0/0). Placeholder prices ₱99.00 / ₱249.00 Build flag + server env
Consultations (coach calls) Part of Debt Discipline (same state) Off Debt Discipline
Dana Talangan application Live (FLIN One tab, when recommended) Code shared. The PH recommendation may only offer Debt Consolidation or Debt Discipline; no PH copy yet Recommendation service
Mediasi Utang application Live Off (routes return NOT_FOUND) Feature fact mediation
Portfolio Insight (free AI card, Report tab and Discipline dashboard) Live (Bedrock) Would likely fail: the Bedrock model agreement for GPT-5.6 Luna is not accepted in the PH account (verified live) Always on
Inbox Live Live (no traffic) —
Lifecycle push / WhatsApp nudges On (NOTIF_TICK_ENABLED=1) Off (0), and there is no PH Trigger.dev project Server env + Trigger.dev
Due-date reminders (inbox) On (NOTIF_DUE_REMINDERS_ENABLED=1) Off (0) Server env
Application-status updates (from CRM stage) Live GHL secrets are probably placeholders GHL webhook + server
CRM sync to GoHighLevel Live GHL secrets are probably placeholders (still at version 1) Server env
Support chat Live (LeadConnector widget) Same Indonesia widget, which is hardcoded in the app App code
Account deletion Request through support chat only. No backend Same —
Languages Indonesian (default) + English, with a language switcher English only, no switcher Profile locales + app country table
Adjust in-app funnel events Live (production environment baked into 1.1.0) Same SDK Build: EXPO_PUBLIC_ADJUST_ENV
Adjust server-side purchase event Not sent. ADJUST_S2S_TOKEN is missing from the live task definition Placeholder secret ECS task-def secret
Forced or optional update prompt Not configured (MIN_SUPPORTED_APP_VERSION, LATEST_APP_VERSION, IOS_APP_STORE_ID unset) Not configured Server env
Store-review test login Live in production Live (its own +63 test list, as set in Terraform) Server env TEST_PHONE_NUMBERS, TEST_OTP_CODE
Internal-user free purchases Verified @flin.co.id email Nobody (no staff domain yet) Profile internalEmailDomain

⚠️ Unverified: whether the PH vendor secrets (Xendit, GHL, Adjust, Sentry) are still placeholders. They are at SSM version 1, written in the same second as the Terraform apply that seeds https://replace.me placeholders. Nobody read the values (live metadata 2026-10-12).

4. Feature inventory

Screens are files under apps/native/app/. Prices are the live values from §3.

4.1 Sign-up, onboarding and identity

  • What the user sees:
  • Rules:
    • The steps come from the server (config.get.onboardingSteps; see §5.1).
    • Identity has two records: the Scanned KTP (immutable evidence) and the Confirmed personal details (user-correctable). The NIK always comes from the scan.
    • A failed biometric match lets the user retake the photo (kyc.resetKtp).
  • Vendors: CampaignHQ (WhatsApp OTP); AWS Bedrock (KTP reading, runbook); AsliRI (liveness SDK + verify); GoHighLevel (every step syncs to the CRM, §7.2).

4.2 Home, loans and the free credit score (Indonesia)

  • What the user sees:
    • Home (home.tsx): a score hero, status counts and total outstanding, the loan list (FLIN-211), the recommendation card, an add-loan entry, promo cards and an eye toggle that hides figures.
    • Report tab (report.tsx): the full report. It is locked until the paid report is bought.
    • loan-detail.tsx: Overview / Amount & Balance / Terms / Repay history tabs. AFPI and manual loans show Overview and Balance only.
    • paylater-lender.tsx: a Lender card for closed pay-later draws.
    • add-loan.tsx and edit-debt.tsx: manual loans. Only manual loans can be deleted.
  • Rules:
    • The free AFPI report is pulled once per user, when the consultation form is submitted (a cost guard). Only Debt Discipline members get a monthly AFPI refresh.
    • FLIN's own score runs 300–900, in bands excellent / good / fair / subprime / high risk.
    • Free tier categories are cash loan and pay later. Every other Debt category is a Locked category until the paid report is bought.
    • Cash loan merged KTA and Pinjol on 2026-09-21 (ADR-0010).
    • Each loan has a Loan status (active / overdue / closed) and a Status line ("Due on / Overdue since / Paid on / Written off on").
    • Loans are listed by Loan amount. A credit card is listed by its outstanding instead.
    • The headline score is CLIK's own bureau score (only if that flag is on), then FLIN's combined score, then the CLIK-derived score, then the AFPI-derived score (headline-score.ts).
  • Vendor: Finfra (AFPI). The free score is a country fact, server-enforced by requireFeature("freeCreditScore").

4.3 Paid credit report (CLIK), Emailed report, Downloaded report

  • What the user sees:
    • A purchase card inside the unpaid loan list and locked category rows (FLIN-241), plus "Pay Once for {price}" buttons (FLIN-243/244).
    • confirm-details-paid.tsx: confirm name and address (the NIK is not editable) and verify an email by code.
    • activate-program.tsx: CLIK consent, then Xendit hosted checkout in the browser, then a success screen.
    • Home's "Refresh Data" goes back through the same checkout.
  • Rules:
    • Price: Rp 25.000 in Indonesia (REPORT_PRICE_IDR). The report is bought per pull: "Refresh" is a new purchase, and the server has no "already paid" guard on purpose.
    • A Verified email is required before buying. The code lasts 10 minutes, with a 30-second resend cooldown and 5 attempts.
    • The consent text the user accepted must hash-match a published wording (consent-registry.ts).
    • A Xendit session lasts 60 minutes, and a pending one is reused for up to 50 minutes.
    • After paying, the app polls for about 45 seconds, then waits up to 60 seconds for the CLIK loans before opening Home.
    • The Emailed report is a CLIK-only PDF, encrypted with the date of birth (DDMMYYYY) as password. It is sent after every bureau pull with up to 6 attempts (swept every 15 min) and kept for 12 months.
    • The Downloaded report covers CLIK + AFPI, is unencrypted, is rendered live on each tap and opens through a 5-minute link. See ADR-0006.
    • CLIK contracts are classified once by an AI model (ADR-0007). A Duplicate loan reported by both bureaus keeps CLIK's record (ADR-0008).
  • Vendors: CLIK (SOAP enquiry, plus a nightly consent-evidence upload over FTPS, a regulatory obligation); Xendit; AWS Bedrock (classification); SMTP email (Amazon SES; for Indonesia this is a strong inference from live AWS metadata, see operations.md); Adjust (server-side purchase event, currently not sent: §7.1).
  • Switches: both halves must be on. Build EXPO_PUBLIC_PAID_REPORT hides the UI. Server PAID_REPORT_ENABLED strips CLIK rows from every response when off.

4.4 Recommendation, Dana Talangan and Mediasi Utang (FLIN One tab)

  • What the user sees:
    • The FLIN One tab shows only when there is a recommendation or a submitted application.
    • Dana Talangan: dana-talangan.tsx (pitch with monthly repayment), then a 3-step application (personal → employment and finance → documents and bank), then review and submit, then a status timeline backed by the CRM.
    • Mediation: mediation.tsx (pitch), then financial data with secured and unsecured lists, manual loans and uploads (bank statement, salary slip), then review and submit.
  • Rules:
    • The recommendation chooses Dana Talangan or Mediasi by FOIR with a 0.65 threshold, using the FLIN instalment: PMT at 4%/month over 36 months on the Effective outstanding × 1.05.
    • A submitted application gets a number FL-YYMM-NNNNN, an inbox entry and a CRM update.
    • The status screen shows the CRM pipeline stage (§7.2).
  • Vendor: GoHighLevel (pipelines, stage webhook).

4.5 Debt Discipline Program (built; hidden in the store app)

  • What the user sees:
  • Rules:
    • Discipline plans: monthly, 1 month, Rp 35.000; quarterly, 3 months, Rp 99.000. These are the schema defaults; the live config.get matches.
    • A First-time buyer of the quarterly plan gets a Bonus month (pay 3, covered 4). Nothing auto-renews.
    • The Renewal window opens 7 days before the end of a monthly plan and 14 days before the end of a quarterly one. After coverage ends, a "plan ended" prompt shows for 30 days. An early renewal appends a new period starting at the old end.
    • Discipline membership is derived from purchases and the clock; it is never stored.
    • A member gets one CLIK + AFPI refresh per calendar month (daily job at 09:00 WIB) and a monthly AI analysis (Bedrock, cached for the month). The score projection is display-only.
    • Rules live in debt-discipline-helpers.ts.
  • Vendors: Xendit, CLIK, Finfra, AWS Bedrock, GoHighLevel (coach calendar).
  • Note: the server gates only the entry points (config.get.features.debtDiscipline). Its purchase procedures carry no server-side flag check. That is deliberate: internal builds buy against the same production server. Whether to add a server gate is an open question (handover.md).

4.6 Consultations (coach calls)

  • Rules:
    • A Consultation is a video call with a credit expert, booked on the GHL coach calendar through the in-app widget. Its URL is server-held in DDP_COACH_BOOKING_URL and prefilled with the member's name and phone.
    • A Consultation credit is granted per paid month (the bonus month is excluded), all upfront.
    • A member holds at most one Upcoming or In Progress consultation at a time.
    • A consultation allows up to 2 Reschedules (3 slots in all). Cancelling or missing a call does not refund the credit.
    • Reminders go out 72 h, 24 h and 1 h before the slot (inbox + push). They are checked every 15 minutes while DDP_DUE_NOTIFS_ENABLED=1.
    • Status (Upcoming / In Progress / Completed / Cancelled) follows the clock and the calendar (routers/consultations.ts).
  • Inbox: notification-center.tsx, with All / Payments / Score tabs, mark read and per-type deep links (§6.2).
  • Profile: profile.tsx.
    • Rows: FAQ, Chat, Terms, Privacy, Language (hidden when the country has one locale), a theme toggle, logout and Delete account. The app version label shows under Terms.
    • edit-profile.tsx edits email, salary and avatar. Changing the email resets its verification.
    • Hidden "future-phase" sections: Security & Access (change PIN), Payments (method, history), Subscription (status, renewal) and an "Unlock Debt Discipline" upsell card. They are commented out in profile.tsx.
  • Support chat: support-chat.tsx embeds the LeadConnector (GoHighLevel) chat widget in a WebView with base URL https://flin.co.id. That domain must stay on the widget's allowed-domain list. There is no server code for it.
  • Delete account: delete-account.tsx explains a hand-off to support chat. Nothing is deleted in-app and there is no deletion backend. The Privacy Policy promises processing within 7 business days.
  • Legal: terms.tsx, privacy.tsx and faq.tsx render structured locale blocks. The FAQ has 13 categories and 145 items. See §9.

5. User journeys

5.1 Sign-up and onboarding (Indonesia vs Philippines)

Step lists come from the live config.get (verified 2026-10-12):

  • ID: consent, verify_id, verify_identity, credit_report, financial_details, confirm_details, notifications, completed.
  • PH: the same, without financial_details.

verify_id and confirm_details are alias steps with no screen of their own. They exist so that older builds and mid-flow users keep working (onboarding-steps.ts).

flowchart TD A["Launch: phone number, dial code from Home country"] --> B["WhatsApp OTP via CampaignHQ"] B --> C["Account created, Home country frozen"] C --> D["consent: app consent"] D --> E["verify_identity: ID card photo, then liveness selfie"] E --> F{"Biometric match passes?"} F -- "no" --> E F -- "yes" --> G["Identity verified"] G --> H["credit_report: create credit profile intro"] H --> I{"Home country"} I -- "ID" --> J["financial_details: consultation form, free AFPI pull, FLIN score"] I -- "PH" --> K["No consultation form in the PH flow"] J --> L["notifications: push permission"] K --> L L --> M["completed: Home, push series enrolled, welcome inbox entry"]

Country differences in this journey:

Step Indonesia Philippines (not offered yet)
ID document KTP, read by AWS Bedrock Mock reader returning synthetic KTP fields. Onfido is planned
Liveness / match AsliRI SDK + verify, pass ≥ 70 AsliRI mock: always passes
Consultation form Yes; it triggers the free AFPI pull Step absent. But the app's credit-report screen still opens /financial-details directly (gap, §11)
Copy en.json / id.json en.json (Indonesia's English) until the en-PH overlay merges

5.2 Buying the paid report (Indonesia)

flowchart TD A["Home or Report: locked categories and purchase card"] --> B["Confirm details: name and address, NIK from the scan"] B --> C{"Verified email on the account?"} C -- "no" --> D["Email code sheet"] D --> E["CLIK consent screen"] C -- "yes" --> E E --> F{"Internal user or Test user?"} F -- "yes" --> G["Recorded as paid, no charge"] F -- "no" --> H["Xendit hosted checkout, 60 min session"] H --> I["Xendit webhook: paid"] G --> J["Settle: inbox and push, CLIK pull, classification, dedup, scores"] I --> J J --> K["Emailed report sent to the verified email"] J --> L["Home and Report unlocked, Downloaded report available"] L --> M["Refresh Data later = buy again"]

5.3 Debt Discipline purchase and renewal

flowchart TD A["Discipline tab intro or promo card"] --> B["Plan choice: monthly or quarterly, prices from config.get"] B --> C{"First-time buyer?"} C -- "yes" --> D["Quarterly shows the bonus month"] C -- "no" --> E["Returning-buyer pricing"] D --> F["Program consent"] E --> F F --> G["Xendit checkout, free for internal and test users"] G --> H["Webhook settles: coverage window, welcome push series, first bureau pull"] H --> I["Member: dashboard, calendar, AI analysis, consultations, monthly refresh"] I --> J{"Inside renewal window?"} J -- "yes, 7 or 14 days left" --> K["Renewal banner countdown"] K --> B I --> L["Coverage ends"] L --> N["Plan-ended prompt for 30 days"] N --> B

5.4 Consultation booking

sequenceDiagram participant U as Member app participant S as FLIN server participant G as GoHighLevel coach calendar U->>S: debtDiscipline.getStatus S-->>U: membership, booking URL, consultations U->>G: Book in the widget, prefilled name and phone G->>S: Appointment webhook S->>S: Store booking, spend one credit S-->>U: Consultation shows as Upcoming Note over S: Every 15 min, reminders 72h, 24h and 1h before, inbox and push U->>G: Reschedule, at most twice G->>S: Appointment webhook, same consultation

6. Notifications

The word covers two separate systems (ADR-0004):

  • Outbound notifications are lifecycle nudges to the user, organised as Series, Enrollments and the Send-gate.
  • The Inbox is the in-app list.

6.1 Outbound lifecycle series

Defined in packages/notifications/src/series.ts. One arbiter tick runs every 30 minutes (ADR-0002).

Series Channel Who (Persona) Anchor event Steps (offset from anchor)
ktp WhatsApp P1b: phone verified, no KTP phone verified +2 h, +1 d at 10:00, +3 d at 11:00
photo WhatsApp P1b: KTP uploaded, no selfie KTP uploaded +2 h, +1 d, +3 d
detail WhatsApp P1c: identity done, form not done biometric passed +2 h, +1 d at 10:00, +3 d at 11:00
upgrade Push P2a: onboarded, not paid. Recurring until paid reached Home day 0, +3 d, +7 d, +14 d
prod Push Onboarded and eligible for a recommendation. Branches on application state reached Home (not started) / application start (started, incomplete) not started: +2 d, +6 d. Started: +2 d, +4 d
ddp Push Debt Discipline member (replaces upgrade and prod while covered) plan activated 0 h, +1 d at 09:00, +3 d at 10:00
  • Channel split (ADR-0001): early onboarding users have no push token yet (it is captured near the end of onboarding), so they get WhatsApp through CampaignHQ templates. After onboarding they get Expo push.
  • Send-gate defaults:
    • quiet hours 22:00–07:00
    • daily cap 1 for P1 and 2 for P2
    • cooling period 5 days
    • minimum 2 days between steps in recurring and prod series
    • (NOTIF_* env, packages/env/src/server.ts)
  • Time zone: hardcoded to WIB (Asia/Jakarta) in the notification engine (config.ts), in the jobs' "today" calculation, and in every Trigger.dev cron. A Philippine stack would send at Jakarta times (one hour behind Manila) until this is read from the country profile (multi-country.md §7).
  • Live state (2026-10-12):
    • ID has NOTIF_TICK_ENABLED=1; production tick runs report about 86 active enrollments and 0–1 sends per run.
    • PH has 0, and no PH Trigger.dev project exists.

⚠️ Unverified: whether Meta has approved the WhatsApp templates in every language. The template ids are registered in templates.ts, and the code treats a template as "inert until approved". Earlier local notes said the handoff was blocked on Meta approval.

6.2 Event-driven notifications and the Inbox

The inbox stores a type plus params, and the app renders the text in the user's language (ADR-0003). There are 13 types:

Type Written when Also pushed? Inbox deep link
payment_success Paid report or Debt Discipline plan settled Yes Report
report_ready, report_refreshed, score_changed, recommendation_available After a CLIK pull No (inbox only) Report / FLIN One
application_submitted Dana Talangan or mediation submitted No FLIN One
application_status_changed CRM stage changes (§7.2) Push and WhatsApp on milestones, if the user kept "updates" on FLIN One
due_date_reminder Daily 08:00 WIB, open loans due today (NOTIF_DUE_REMINDERS_ENABLED) No (inbox only) —
welcome Onboarding completed No Home
ddp_due_stage Member loan due at T-7/5/3/1, due, D+1/3/7 (daily 08:00 WIB) Yes Discipline calendar
ddp_streak, ddp_milestone Member streaks; debt reduced 10/25/50/75/100% Yes Discipline
ddp_consultation_reminder 72 h / 24 h / 1 h before a call Yes Discipline
  • Push taps do not route. The app registers no notification-tap handler, so tapping a push just opens the app (§11). Deep links work only from inbox rows.
  • An app build that does not know a newer type still shows it, using the server's fallback text.

Where notification copy lives is listed in §9.2.

7. Analytics and CRM

7.1 Adjust (attribution and funnel)

  • The Adjust SDK runs in the app (lib/adjust.ts). The store build uses the production Adjust environment.
  • ATT is disabled and IDFA is never used. The app syncs Adjust's install id (adid) to the server and passes the push token to Adjust for uninstall tracking.
  • Funnel events (keys are the exact Adjust dashboard names):
    • Onboarding: sign_up_number_verified, app_consent_completed, kyc_completed, free_credit_score_viewed, onboarding_consultation_form_completed, onboarding_loan_data_confirmation_completed (screen retired), registration_complete.
    • Dana Talangan: dana_talangan_app_from_started (sic), dana_talangan_app_form_documents_uploaded, dana_talangan_app_form_filled.
    • Mediation: m&n_app_form_started, m&n_app_form_documents_uploaded, m&n_app_form_filled.
    • Paid report: paid_report_payment_initiated, paid_credit_report_checked, credit_report_download, refresh_credit_report.
    • Engagement: loan_details_viewed, my_profile_viewed, customer_support_reached_out, account_deletion_request_sent.
  • Milestones are Unique events (once per device). Repeatable actions count every time.
  • paid_report_purchased is server-to-server only, and it is not reaching Adjust from Indonesia production.
    • The SSM parameter /flin/prod/ADJUST_S2S_TOKEN was filled on 2026-08-22, but no live task-definition revision ever injects it, so the server skips the event silently (verified live 2026-10-12, task def flin-prod:282).
    • Revenue attribution in Adjust is missing for every real purchase until the secret is added to the running task definition. See deployment.md for the clone-the-service-revision procedure.

⚠️ Unverified: Adjust iOS SKAN app verification. KumoHQ's local setup checklist still had it unchecked; see "Deferred / open" in specs/adjust-integration.md.

7.2 GoHighLevel CRM

  • What is synced (crm-sync.ts, field map field-map.ts):

    • The app pushes the whole current picture of a contact at every step: OTP, KTP, selfie, biometric, consultation form, onboarding completion, application drafts and submits.
    • That picture covers identity and address from the KTP, employment, income and debt totals, Dana Talangan bank details, mediation data and timestamps.
    • It includes a revocable 90-day link to the KTP image for CRM reps. Opening it gives a 5-minute download (ADR-0005).
    • Test users are never synced.
  • "FLIN App Status" field (numeric): 1 number submitted, 2 OTP verified, 3 KTP, 4 selfie, 5 eKYC done, 6 consultation form, 7/8 Dana Talangan draft/submitted, 9/10 mediation draft/submitted. The app never lowers it. Values 11 (not eligible) and 12 (not responding) are set only by the CRM team (app-status.ts).

  • Pipelines → what the user sees (pipeline-map.ts, keyed by stage id):

    • "FLIN App" (Dana Talangan):

      Stage User sees
      DT / M&N App Form Filled Submitted
      Credit Interview Done, Credit Process Credit assessment
      IPA Granted Approved (milestone)
      Dana Talangan Disbursed Disbursed (milestone)
      Rejected by FLIN Rejected (milestone)
      Rejected by Customer Closed (no push)
      Not Eligible Not eligible (milestone)
      Not Responding On hold (milestone)
    • "M&N Program" (mediation):

      Stage User sees
      M&N App Form Filled Submitted
      M&N (Credit Funnel) Recommended for mediation (milestone)
      Leads Contacted Under review
      Meeting Completed Consultation completed
      Quotation Sent Quotation sent (milestone)
      Mediation, Negotiation In progress
      Rejected / Not Eligible / Customer Not Responding As above
    • A Dana Talangan applicant moved to the mediation pipeline is shown the mediation journey.

    • Renaming or reordering stages in GHL breaks the mapping, and a pinned test fails. Tell engineering before changing the pipeline.

  • Inbound: a stage-changed webhook and a coach-appointment webhook.

7.3 Metabase

  • Metabase (https://metabase.flin.co.id) reads the Indonesia production database through a read-only role that can see every column, PII included. The Philippines has no Metabase.
  • Analyst gotchas are in data-model.md:
    • Exclude synthetic report_payment rows from revenue: ddp-refresh-… (amount 0), internal-… and test-….
    • Exclude Test users.
    • Read the latest CLIK report only when the pull completed.
  • Access is currently not restricted. The WAF allowlist that should gate Metabase admits every IPv4 address (verified live 2026-10-12). Restoring the allowlist is an operations fix (operations.md).
  • A manual database snapshot named flin-prod-pre-user-purge-20261006 exists (verified live).

⚠️ Unverified (earlier maintainer notes): production was purged to the test users on 2026-10-06, so Metabase history before that date is not representative.

8. Test users and internal users

Test user Internal user
Who A phone number on the deployment's TEST_PHONE_NUMBERS list (9 numbers in ID production). Used by app-store reviewers and QA against production A member of staff whose verified email is exactly on the company domain: flin.co.id in Indonesia. The Philippines has none yet (pending FLIN-174)
Sign-in Fixed code (TEST_OTP_CODE) instead of a WhatsApp OTP Normal OTP
Vendors Identity, AFPI and CLIK are simulated (a synthetic "TEST USER" KTP, mock liveness, a dummy CLIK report even in production). One designated number in code exercises CLIK's published UAT subject for a real bureau answer Everything real: bureau pulls, identity checks, CRM
Money Purchases recorded as paid, no charge (test- prefix) Purchases recorded as paid, no charge (internal- prefix)
CRM Never synced Synced
  • The store-review login is live in Indonesia production (verified live 2026-10-12): the test-number list and fixed code are on the running task definition. The PH stack carries its own list of Philippine test numbers, matching Terraform.
  • A synthetic sign-up address (…@temp.flin.co.id) never makes anyone internal.
  • Internal preview builds talk to the production API (verified live, EAS preview environment). Anything a tester does there that is not on a test number creates real production data, CRM contacts and charges.

TODO(handover): Which test numbers and which login instructions were given to Apple and Google for review, and who keeps them current?

9. Languages and copy

9.1 Languages

  • On main:
    • en.json (Indonesia's English, and the complete fallback), id.json and fil.json.
    • The app registers en, id, fil and the en-PH overlay (lib/i18n.ts, lib/locale.ts); which a user can pick is the country's set. Filipino was withdrawn from Indonesia's set before the first release (DEV-44 / QA-67).
    • Indonesia opens in Indonesian whatever the phone's language (FLIN-4), and offers English through the language switcher.
    • The user's choice is sent to the server, which uses it for push, WhatsApp and report-email language.
  • Philippines: English (en-PH) and Filipino, with the same switcher. The en-PH overlay holds only the keys PH words differently (on main since PR #127, 2026-10-13); its consent is a placeholder until FLIN sends the Philippine text (FLIN-154). The remaining planned replacements (₱, Debt Consolidation, CIBI, DPD, PHT…) are specified in multi-country.md §8.5.
  • Legal screen language status:
    • The terms, privacy and faq blocks exist in all three files with the same structure (13 FAQ categories, 145 items each).
    • The Indonesian and Filipino blocks were added on 2026-07-08 (commit 9c7af55).
    • A code comment says the server's Filipino push copy is "an AI translation pending review, matching how the legal screens were handled" (application-status-copy.ts).

⚠️ Unverified: that the Indonesian legal text has had no human legal review. Earlier maintainer notes say the id/fil Terms, Privacy and FAQ are AI translations that still need Indonesian and Filipino legal review before they are treated as binding.

TODO(handover): Has FLIN's legal team reviewed the Indonesian Terms, Privacy Policy, FAQ and consent text shown in the live app?

9.2 Where to change copy

Copy Where Ships how
App UI text apps/native/locales/{en,id,fil}.json (every string goes through i18next) New store build (there are no over-the-air updates)
Terms / Privacy / FAQ Structured blocks terms, privacy, faq in the same files, rendered by legal-screen.tsx and faq.tsx. Keep the structure identical across the three files; earlier edits were scripted for that reason New store build
CLIK consent (paid report) activateProgram.consentBody. Also bump CLIK_CONSENT_VERSION in activate-program.tsx and add the new hashes to consent-registry.ts, keeping the old ones. A test fails if the copy and the hashes drift Server + store build
Debt Discipline consent Locale keys used by discipline-consent.tsx. Bump DISCIPLINE_CONSENT_VERSION Store build
Inbox text App: notificationCenter.types.<type> in the locale files. Fallback for old builds: inbox-copy.ts Store build / server
Lifecycle push (upgrade, prod, ddp) English in series.ts; id/fil in copy.ts (AI translations pending native review) Server deploy
Debt Discipline event pushes Server fallback text in inbox-copy.ts Server deploy
Application-status pushes application-status-copy.ts Server deploy
WhatsApp messages Templates approved by Meta in CampaignHQ; ids per language in templates.ts CampaignHQ + server
Emailed report email and PDF text report/copy.ts Server deploy
Email verification code email email-otp.ts (English only) Server deploy
PH wording apps/native/locales/en-PH.json overlay (not on main yet). Never edit PH wording into en.json / id.json Store build

Prices are not hardcoded in the app UI; it formats config.get prices. Two exceptions:

  • The legal and FAQ copy states "Rp25,000" in all three locale files. A price change needs a copy change and a store build. The live price matches it. A comment in infra/terraform/envs/prod/terraform.tfvars saying the FAQ reads Rp20.000 is stale.
  • The upgrade push series says "Rp29,000" / "Rp29.000" and "Today only", but the price is Rp 25.000 and no discount exists (§11).

10. App Store and Google Play

Fact Value (verified live 2026-10-12)
Live version 1.1.0 on both stores. iOS released 2026-10-07 (first iOS release 2026-09-03). Play updated 2026-10-06 (first released 2026-08-22, "10+" downloads)
iOS App Store id 6781172200
App Store storefronts Indonesia only. Lookups in the PH and US storefronts return nothing
Google Play The listing page loads for ID and PH. Per-country availability was not verified
Builds behind 1.1.0 Commit 2ac4279 (merge of PR #135): iOS build 31, Android versionCode 4
Next version 1.2.0 is set in app.json. Only two internal Android preview builds exist; there is no store build
Over-the-air updates None. Every app change needs a store build and review
Forced or optional update prompt Built but not used. The server sends no minimum or latest version, so nobody is prompted. The iOS "update" button would open the generic App Store because IOS_APP_STORE_ID is unset; it should be 6781172200. Raising the minimum version is an ops decision; never bump it as a side effect
  • Privacy labels:
    • The paid report is the app's only purchase, and it is on in 1.1.0, so "Purchases → Purchase History" should be declared.
    • The earlier rule was to drop Purchases only while the paid-report build flag is off.
    • Adjust adds identifiers (IDFV) and usage data (analytics / third-party advertising) to the Apple and Play declarations (gitignored spec notes).
    • No store metadata or privacy declarations are kept in the repo.

TODO(handover): What do the current App Store privacy label and Play Data Safety form declare, and does it include Purchases now that the paid report is live?

The release process is in mobile-release.md.

11. Known product-visible gaps

Engineering detail and fixes are consolidated in handover.md → Known issues.

Gap Impact Fix direction
Tapping a push notification opens the app without going anywhere (no tap handler; pushes carry only an id) Users must find the item themselves; push engagement is lower Add a tap handler that routes like the inbox (store build)
/r/verify-id server link redirects to flin://verify-id, which has had no screen since 2026-08-13 Anyone following it sees an English "Page Not Found" Point the server redirect at flin://verify-identity (server-only fix). Check whether any CRM or WhatsApp message uses the link
upgrade push copy advertises "Rp29,000" and a "Today only" offer Live ID users see the wrong price and a discount that does not exist Correct the copy in series.ts / copy.ts (server deploy)
FAQ promises a monthly report refresh for paid-report buyers and a "Profile → Settings → Notifications" screen Neither exists: the monthly refresh is Debt Discipline-only, and there is no notification-settings screen Correct the FAQ (store build)
No in-app account deletion and no deletion backend; the Privacy Policy promises processing within 7 business days Requests arrive through support chat with no defined back-office process Define the ops process (database, files, CRM, Xendit, Adjust); build deletion later
Adjust server-side purchase event not sent from ID production No purchase revenue in Adjust Inject ADJUST_S2S_TOKEN into the running task definition
The old Trigger.dev project (KumoHQ org) still runs notification crons against another database; it sent 1 notification on 2026-10-11. The Trigger.dev staging environment sends real Debt Discipline notifications to 2 members Stray or duplicate notifications to real people Disable the old project's schedules (or delete it); check staging's target
Internal preview builds use the production API Test activity pollutes production data and CRM Use test numbers, or point previews at UAT
Developer screens asliri-test and modal are reachable by deep link in store builds Odd screens if someone guesses the link Remove or gate them
Unknown links land on a hardcoded-English "Page Not Found" Untranslated screen Localise it
Version gating unset No way to push users off old builds until configured Set IOS_APP_STORE_ID; decide minimum and latest versions
Email verification code email is English only Indonesian users get an English email Localise email-otp.ts
PH only: money renders only as rupiah (lib/money.ts has no PHP; the price hook hides non-IDR prices instead) A PH launch would show amounts wrongly or blank Phase 1b money hydration (multi-country.md §5)
PH only: the app does not read features.freeCreditScore or features.mediation (the server enforces them), and the PH "locked Home" (FLIN-171) is not built PH users would see ID-shaped Home surfaces whose calls fail PH screen tickets
PH only: the credit-report screen opens the consultation form directly, although PH has no such step A PH user would hit a step outside their flow Route from the country's step list
PH only: identity verification is mocked (always passes) and the bureau adapter throws PH must not take real users until Onfido and CIBI land Adapters; the PH server refuses APP_ENV=prod until then
PH only: notification and job times use WIB; there is no PH Trigger.dev project No PH reminders, retries or nudges; times off by an hour Country time zone + PH Trigger.dev project
PH only: support chat uses Indonesia's widget (https://flin.co.id) PH chats land in the ID inbox Widget per country (needs a mechanism, §8.2)
PH only: AI features need the Bedrock GPT-5.6 Luna agreement accepted in the PH account Portfolio Insight and Debt Discipline AI would fail in PH Accept the offer in the console, or use an already-accepted model

12. Pending inputs from FLIN

These block work. The rule is: build on mock adapters and do not guess (multi-country.md §8.6, CLAUDE.md). Code markers: TODO(FLIN-174) in profiles/ph.ts, constants.ts and infra/terraform/envs/ph-prod/main.tf.

Input Ticket What it blocks
CIBI sample response + UAT credentials FLIN-174 The PH credit-bureau adapter, the PH paid report, and the KOL→DPD loan-status shape and Pinjol/KTA → "Personal Loan" mapping (profiles/ph.ts). ⚠️ Unverified: earlier maintainer notes record CIBI UAT credentials received on 2026-09-25 and the UAT API answering; no adapter exists on main
Onfido contract (classic API access, PhilID/UMID coverage, pricing) FLIN-174 PH identity verification (both ports), and flipping the PH server to APP_ENV=prod
PH prices: paid report, Debt Discipline monthly and quarterly FLIN-174 Live placeholders ₱199.00 / ₱99.00 / ₱249.00 (envs/ph-prod/main.tf)
PH staff email domain FLIN-174 Internal users in PH (constants.ts)
PH app consent text (English and Filipino) FLIN-154 The consent screen (placeholder on branch ph-languages)
PH credit-report consent text (names PT CRIF/CLIK today) FLIN-207 The PH paid-report consent
PH Terms, Privacy, FAQ §8.6 PH legal screens
Portfolio Insight prompt (PH_Portfolio_Summary_AI_Prompt_and_Inputs), Debt Discipline AI prompt changes §8.6 PH AI features
Score projection logic, estimated score range (FLIN APP_Estimated Credit Score) §8.6 PH score surfaces
Debt Consolidation eligibility (PH Criteria for Product Recommendation) §8.6 The PH recommendation
KOL/DPD explainer content FLIN-174 "What is DPD?" copy
Replacements for OJK / KEMENKEU / Komdigi references (pending designer) FLIN-174 PH copy
Nav bar: 4 or 5 tabs (pending Isha) FLIN-174 PH tab set
Xendit PH account confirmation; CampaignHQ PH account and templates; whether GHL PH reuses ID's field and pipeline ids §8.6 PH payments, OTP / WhatsApp, CRM
Missing PH icons and illustrations (tag Isha + Akshay) §8.5 PH screens

Open Indonesia decisions were raised on the Kumo tickets.

⚠️ Unverified (earlier maintainer notes, not in the repo):

  • FLIN-211 loan-list label sign-offs: "Loan Amount" labels, "Written off/Macet" on KOL 5, manual loans in totals, year filter.
  • FLIN-241…249 deviations awaiting FLIN.
  • Replacement bank logos from PR #122.

TODO(handover): Give FLIN's product team access to the Kumo tracker (work.kumohq.in/flin, FLIN-nnn tickets) and transfer the product specs that are still local-only (e.g. .scratch/debt-discipline/product-questions.md, marked "Blocking Phase 2"). The specs that code cites are now in specs/; the rest are listed in handover.md#not-in-git.

13. Roadmap signals in the repo

  • Unmerged branches (none of this is shipped):
    • onfido-poc, 2 commits ahead and 436 behind: the Onfido KYC trial from 2026-07-30 (verdict "go"). Marked never to merge; its config plugin is meant to be reused.
  • Multi-country roadmap (multi-country.md §5):
    • Phase 1b open items: money hydrated from config.get, onboarding routing fully from config.get, and removal of the deprecated API fields once old builds age out.
    • Phase 2 (PH): copy overlay, real adapters, and the open infra items.
  • PH go-live checklist:
    • Accept the Bedrock agreement.
    • Raise the EC2 quota for the DB tunnel.
    • Create the PH Trigger.dev project.
    • Replace the placeholder secrets.
    • Register the PH Xendit and GHL webhooks.
    • Flip APP_ENV=prod once CIBI and Onfido land.
    • Set EXPO_PUBLIC_SERVER_URL_PH in the production EAS profile and ship a store build.
    • Make the app available in the Philippine storefronts.
    • The full list is in deployment.md and mobile-release.md.
  • Hidden future-phase UI in Profile: change PIN, payment methods and history, subscription status, and the Debt Discipline upsell.
  • Store release: 1.2.0 is bumped but not built for the stores. It carries the app changes merged since 1.1.0 (for example FLIN-250 and FLIN-136 consultations), and Debt Discipline stays hidden in it unless the production EXPO_PUBLIC_DEBT_DISCIPLINE changes.

This page was last edited on 2026-10-13 09:04

Powered by Wiki|Docs

This page was last edited on 2026-10-13 09:04

FLIN
PT FLIN Consultancy Indonesia

Powered by Wiki|Docs