Skip to content

Pro backend 0.2 + libsession 1.7 compatibility updates#2144

Open
jagerman wants to merge 15 commits into
session-foundation:devfrom
jagerman:pro-wire-format-updates
Open

Pro backend 0.2 + libsession 1.7 compatibility updates#2144
jagerman wants to merge 15 commits into
session-foundation:devfrom
jagerman:pro-wire-format-updates

Conversation

@jagerman

Copy link
Copy Markdown
Member

Adopts the Pro backend response contract and reworks all client-owned Pro display strings to be data-driven off the provider slug, so new payment providers need only translations (no code changes). Relies on the updated libsession-util-android glue.

This is required for Android compatibility with session-foundation/session-pro-backend#2 and session-foundation/libsession-util#95.

Changes

  • ProApi consumes the new status enum + errorCode + single error; dropped the old ErrorStatus/errors[] handling and the already_redeemed special-case (a re-claim now returns ok + a proof).
  • get-details: consume/cache the clean GetProDetailsResponse glue struct (workers, ProDatabase, repository, display mapping).
  • Dynamic provider display: providerMetadata() resolves pro_provider_<slug>_<suffix> (with pro_provider_unknown_* → raw-slug fallback); removes the hardcoded Google/Apple isApple branching.
  • {pro_stores} store list: built at runtime from BackendRequests.visiblePlatforms() (own platform first, gated on the store translation existing) and substituted into the "purchase via …" messages; replaces the old two-store {platform_store}/{platform_store_other} enumeration. Deletes the dead DEFAULT_*_STORE constants and PLATFORM_STORE2_KEY.
  • error_code → user-facing message: failed Pro requests map error_code to a localized pro_error_<slug> string (falling back to the backend diagnostic); non-retryable add-payment failures now surface the specific reason.

Dependencies

jagerman and others added 15 commits July 18, 2026 14:02
Temporary save point — mid-refactor, intentionally not compiling. Will be
rebased/rewritten once libsession pins the response-parser structs (FINAL PIN
1f308535 moves response parsing into libsession; the kotlinx response schemas
below get deleted in favour of consuming libsession structs).

Durable (keep):
- Proof/ProProofInfo: gen_index_hash -> revocationTag, expiry ms -> seconds;
  protobuf accessor setRevocationTag; user-profile get/setProAccessExpiry.
- Revocation reshape: InstantAsSecondsSerializer; ProDatabase schema
  (revocation_tag/effective_ts/retain_until_ts, isRevoked >= effective_ts,
  retain_for aging) + lokiV61 migration; RevocationListPollingWorker caller.
- AddProPaymentApi request build: provider slug + Google token|order_id payment_id.

Superseded (to delete when libsession response structs land):
- GetProDetailsApi ProDetails/Item kotlinx schema, GetProRevocationsApi schema,
  ProProof response serializer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Save point (app does NOT compile yet — consumers/display still reference the
now-deleted response schemas).

- ProApi base: endpoint + buildJsonBody + kotlinx responseDeserializer ->
  buildProRequest(): ProRequest (endpoint+body from libsession) and
  parseResponse(json): Res (typed struct from libsession); check res.header.
- GetProDetailsApi -> GetProDetailsResponse (parsePaymentDetailsResponse).
- GenerateProProofApi / AddProPaymentApi -> ProProofResponse.
- GetProRevocationApi -> GetProRevocationsResponse.

Still TODO next: delete orphaned ProDetails/ProRevocations kotlinx schemas;
migrate ProDataMapper/ProDatabase/ProStatusManager/FetchProDetailsWorker/
RevocationListPollingWorker to the glue structs; provider display (names from
Crowdin, URLs from libsession provider_urls); .data now ProProofResponse.proof.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e source)

provideProBackendConfig() now sources the backend URL + Ed25519 signing pubkey
from the glue (BackendRequests.proBackendUrl() / proBackendPubKeyHex(), backed
by session::pro_backend::URL / PUBKEY) instead of the BuildConfig.PRO_BACKEND_DEV
copy — so a future URL/pubkey change happens only in libsession. x25519 is still
derived on the fly in ProBackendConfig. (The now-unused PRO_BACKEND_DEV
buildConfigField in app/build.gradle.kts can be removed separately.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ded)

The glue's ProRequest now carries contentType; relay it as the Content-Type header
instead of hardcoding "application/json", so the wire format stays a libsession<->
backend contract the app never assumes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
libsession no longer supplies provider display names (device/store/platform/
account) — they're client-owned i18n now. Hand-add the English values to
values/strings.xml so R.string.proProvider* compiles for the app-side pro
provider-metadata migration.

TODO (Jason): these MUST be added upstream on Crowdin (project 618696) +
approved + synced BEFORE the next translations regen, or the sync deletes them.
Marker: TODO(crowdin) comment in strings.xml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tract

Drop the ErrorStatus generic + convertErrorStatus(Int) mechanism: the header
is now a closed status enum + open error_code slug + diagnostic error. ProApi
is single-type-param; a Failure carries a typed ProApiError(status, errorCode,
error) with isRetryable (backend Error, or unknown_payment). Added ProErrorCode
slug constants (§5.1). All *Api subclasses drop the ErrorStatus arg; deleted
AddPaymentErrorStatus (int enum), GetProProofStatus, and the orphaned
GetProRevocations @serializable schema.

addProPayment: already-redeemed special-case deleted (ok now always carries a
proof — a re-claim succeeds), success saves data.proof, failures retry when
retryable else raise PaymentServerException.

Get-details consumers (ProDataMapper/workers/DB) still reference the old
ProDetails shape — migrated in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New app-side PaymentProviderMetadata (replaces the removed libsession type):
  display names from local proProvider* strings, URLs from the glue's
  providerUrls(slug).
- ProDataMapper.toProStatus now maps GetProDetailsResponse: userStatus slug
  (never/active/expired via ProUserStatus; no account-level revoked), plan slug
  -> ProSubscriptionDuration, provider slug -> metadata, Instant/Duration fields.
- ProStatus.providerData now uses the app-side PaymentProviderMetadata.

Plumbing (workers/DB/repository/ProStatusManager call sites, delete orphaned
ProDetails) + error_code->Crowdin follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the get-details plumbing off the deleted wire-vestige ProDetails onto
the clean @serializable glue struct:
- ProDatabase caches GetProDetailsResponse (tolerates a stale/incompatible blob
  by dropping it -> refetch); repository LoadState + workers use it.
- FetchProDetailsWorker / ProProofGenerationWorker: userStatus slug (ProUserStatus)
  instead of the int DETAILS_STATUS_*; ok-responses carry a proof (requireNotNull).
- ProStatusManager: toProStatus(now, context); debug mock states + non-originating
  UI build PaymentProviderMetadata via providerMetadata(slug, context).
- Delete the orphaned ProDetails / ServerProDetailsStatus / DETAILS_STATUS_*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e messages

Resolve provider display names from pro_provider_<slug>_<suffix> (with unknown/slug
fallback); build the {pro_stores} purchasable-store list from BackendRequests.visiblePlatforms()
(google_play first, gated on the store translation existing); map error_code slugs to localized
pro_error_<slug> strings. Removes the hardcoded DEFAULT_*_STORE constants and PLATFORM_STORE2_KEY.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…table schema

The pro_revocations CREATE TABLE still declared the old gen_index_hash/expiry_ms columns while
all queries used revocation_tag/effective_ts/retain_until_ts — inserts would have hit missing
columns. Fixed the schema to match. No migration (Session Pro has not shipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
My earlier fix edited ProDatabase.createTable (the lokiV57 migration step) to
the new revocation_tag/effective_ts/retain_until_ts schema. That is the wrong
place to change: createTable is replayed as the lokiV57 upgrade step for installs
coming from older versions, so it must keep the shape it originally shipped with.

The actual schema change is already handled correctly by
reshapeRevocationsForSeconds (lokiV61), which DROPs and recreates the table with
the seconds schema and runs for every upgrading and fresh install. Restore
createTable to the original gen_index_hash/expiry_ms shape so the migration
history is honest; reshape supersedes it. No functional change (reshape is
terminal and always runs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FetchProDetailsWorker cleared the pro config whenever userStatus != ACTIVE,
which also fires for any unknown/future status slug. removeProConfig() writes the
SYNCED user profile, so an older client that doesn't recognise a new status would
erase a still-valid proof and propagate the deletion to all the user's devices.

Only clear on the authoritative terminal statuses (EXPIRED/NEVER); leave the
proof untouched on an unrecognised status. Its own expiry governs entitlement,
and the backend won't refresh (or will revoke) a genuinely-lapsed account.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion-foundation#15)

Move the app onto the split-out get_pro_status endpoint (the light "am I Pro?"
call); the paginated get_payment_details history endpoint is library-only and is
not wired (no android UI uses payment history).

- api/GetProDetailsApi -> GetProStatusApi: buildGetProStatusRequest (drops the
  count=10 param, get_pro_status has no count) + parseProStatusResponse.
- ProDataMapper: GetProStatusResponse; the single latestPayment replaces
  items.firstOrNull()/items[0]. Unknown-status handling is unchanged (proof is
  only cleared on EXPIRED/NEVER, never on an unknown status).
- FetchProDetailsWorker -> FetchProStatusWorker, ProDetailsRepository ->
  ProStatusRepository, plus ProProofGenerationWorker / ProStatusManager /
  ProDatabase / settings view-models: full *ProDetails* -> *ProStatus*
  terminology purge (types, functions, workers, DB accessors).
- ProDatabase: the persisted pro-status cache key is renamed pro_details ->
  pro_status. This orphans any previously cached blob; it is re-fetched on next
  poll and the feature is pre-release + gated by forcePostPro(), so this is
  benign.

Pairs with the libsession-util-android glue commit that re-pins libsession to
3f8aace0 and reshapes GetProStatusResponse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render every plan period generically from libsession's parsed (count, unit)
instead of fixed month buckets, and carry (count, unit) throughout instead of a
re-parsed slug:

- ProStatus.Active.duration is a ProPlanPeriod(count, unit); ProDataMapper builds
  it from the glue's structured (planCount, planUnit) — no slug regex.
- SKU catalog (ProSubscriptionDuration) holds (count, unit) per opaque store slug.
  The annual SKU is (1, YEAR) to match the backend's "1y" code (ProPlan.TwelveMonth),
  never canonicalized, so current-plan matching works.
- Choose-plan cards are one generic pair of strings — "{plan_length} - {monthly_price}
  / month" and "{price} billed every {plan_length}" — replacing the six fixed
  proPrice*/proBilled* keys; {plan_length} comes from the ICU formatter.
- DateUtils: MONTH/YEAR added to the plan-length formatter; helper to resolve a
  not-yet-synced Crowdin key with an English fallback.

New Crowdin keys (to add upstream): proPlanLifetime, proPlanPricePerMonth,
proPlanBilledEvery. Retire: proPrice{One,Three,Twelve}Month(s),
proBilled{Monthly,Quarterly,Annually}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive the per-month discount baseline from the highest per-month price among the
available plans (the shortest-duration plan) instead of assuming the 1-month SKU
exists. If the monthly plan is ever dropped from the catalog, the discounts still
anchor correctly. The baseline plan itself gets 0% and no badge via discountBadge()
(already generic — no hardcoded period).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant