diff --git a/include/session/config/user_profile.h b/include/session/config/user_profile.h index 17e3a578..4729105f 100644 --- a/include/session/config/user_profile.h +++ b/include/session/config/user_profile.h @@ -399,6 +399,32 @@ LIBSESSION_EXPORT int64_t user_profile_get_pro_access_expiry(const config_object LIBSESSION_EXPORT void user_profile_set_pro_access_expiry( config_object* conf, int64_t access_expiry_ts); +/// API: user_profile/user_profile_get_pro_auto_renewing +/// +/// Returns whether the account's current Session Pro subscription is auto-renewing. Backend-derived +/// (the `auto_renewing` field on /get_pro_status); set alongside the access expiry. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `int` -- 1 if the subscription is known to be auto-renewing, otherwise 0 (terminal, unknown, +/// or not Pro). +LIBSESSION_EXPORT int user_profile_get_pro_auto_renewing(const config_object* conf); + +/// API: user_profile/user_profile_set_pro_auto_renewing +/// +/// Records whether the current Session Pro subscription is auto-renewing: nonzero stores the flag, +/// 0 clears it (which is also how it is cleared when the subscription lapses). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `auto_renewing` -- [in] nonzero if auto-renewing, 0 to clear +/// +/// Outputs: +/// - `void` +LIBSESSION_EXPORT void user_profile_set_pro_auto_renewing(config_object* conf, int auto_renewing); + /// API: user_profile/user_profile_get_refund_requested /// /// Retrieves the timestamp at which the user requested a refund of their current Session Pro diff --git a/include/session/config/user_profile.hpp b/include/session/config/user_profile.hpp index ea20306c..0fa2eb14 100644 --- a/include/session/config/user_profile.hpp +++ b/include/session/config/user_profile.hpp @@ -40,6 +40,10 @@ using namespace std::literals; /// flight"), so all the account's devices poll the backend to pull the entitlement through. /// Inserted only when not already pro; cleared automatically when entitlement lands; values /// more than a week in the past are ignored on read. +/// A - set to 1 when the current Session Pro subscription is auto-renewing; omitted when it is +/// terminal (will not renew), unknown, or the account isn't Pro. Backend-derived +/// (get_pro_status.auto_renewing) and synced across devices; the client sets it alongside `E` +/// and clears it (sets false) when the subscription lapses. /// P - user profile url after re-uploading (should take precedence over `p` when `T > t`). /// Q - user profile decryption key (binary) after re-uploading (should take precedence over `q` /// when `T > t`). @@ -339,6 +343,28 @@ class UserProfile : public ConfigBase { /// will expire, or nullopt to remove the value. void set_pro_access_expiry(std::optional access_expiry_ts); + /// API: user_profile/UserProfile::get_pro_auto_renewing + /// + /// Returns whether the account's current Session Pro subscription is auto-renewing (true) or + /// terminal/unknown (false). Backend-derived (the `auto_renewing` field on /get_pro_status); + /// the client sets it alongside `set_pro_access_expiry`. Only a `true` value is stored, so an + /// account that isn't Pro, or whose renewal status has not been learned, reads as false. + /// + /// Inputs: None + /// + /// Outputs: + /// - `bool` -- true iff the subscription is known to be auto-renewing. + bool get_pro_auto_renewing() const; + + /// API: user_profile/UserProfile::set_pro_auto_renewing + /// + /// Records whether the current Session Pro subscription is auto-renewing. `true` stores the + /// flag; `false` erases it -- which is also how it is cleared when the subscription lapses. + /// + /// Inputs: + /// - `auto_renewing` -- true if the subscription auto-renews; false to clear the flag. + void set_pro_auto_renewing(bool auto_renewing); + /// API: user_profile/UserProfile::get_refund_requested /// /// Retrieves the timestamp at which the user requested a refund of their current Session Pro diff --git a/src/config/user_profile.cpp b/src/config/user_profile.cpp index 5ca98b05..8bf367ed 100644 --- a/src/config/user_profile.cpp +++ b/src/config/user_profile.cpp @@ -241,6 +241,17 @@ void UserProfile::set_pro_access_expiry(std::optional } } +bool UserProfile::get_pro_auto_renewing() const { + return data["A"].integer_or(0) != 0; +} + +void UserProfile::set_pro_auto_renewing(bool auto_renewing) { + // Presence-only: store 1 when auto-renewing, erase otherwise (absent == terminal/unknown). No + // t/T bump -- this is backend-derived pro state (like E/I/R), not a user-initiated profile + // edit. + set_nonzero_int(data["A"], auto_renewing); +} + std::optional UserProfile::get_refund_requested() const { if (auto* R = data["R"].integer()) { std::chrono::sys_seconds when{std::chrono::seconds{*R}}; @@ -541,6 +552,14 @@ LIBSESSION_C_API void user_profile_set_pro_access_expiry( unbox(conf)->set_pro_access_expiry(as_sys_seconds(access_expiry_ts)); } +LIBSESSION_C_API int user_profile_get_pro_auto_renewing(const config_object* conf) { + return unbox(conf)->get_pro_auto_renewing() ? 1 : 0; +} + +LIBSESSION_C_API void user_profile_set_pro_auto_renewing(config_object* conf, int auto_renewing) { + unbox(conf)->set_pro_auto_renewing(auto_renewing != 0); +} + LIBSESSION_C_API int64_t user_profile_get_refund_requested(const config_object* conf) { if (auto when = unbox(conf)->get_refund_requested()) return epoch_seconds(*when); diff --git a/tests/test_config_userprofile.cpp b/tests/test_config_userprofile.cpp index 856386b9..b3bb3b21 100644 --- a/tests/test_config_userprofile.cpp +++ b/tests/test_config_userprofile.cpp @@ -336,6 +336,12 @@ TEST_CASE("user profile C API", "[config][user_profile][c]") { CHECK(user_profile_get_blinded_msgreqs(conf2) == -1); user_profile_set_blinded_msgreqs(conf2, 1); CHECK(user_profile_get_blinded_msgreqs(conf2) == 1); + + CHECK(user_profile_get_pro_auto_renewing(conf2) == 0); + user_profile_set_pro_auto_renewing(conf2, 1); + CHECK(user_profile_get_pro_auto_renewing(conf2) == 1); + user_profile_set_pro_auto_renewing(conf2, 0); + CHECK(user_profile_get_pro_auto_renewing(conf2) == 0); UserProfileTester::set_profile_updated(conf2, std::chrono::sys_seconds{124s}); // Both have changes, so push need a push @@ -654,6 +660,16 @@ TEST_CASE("UserProfile Pro Storage", "[config][user_profile][pro]") { profile.set_pro_access_expiry(access_expiry); CHECK(profile.get_pro_access_expiry() == access_expiry); + // Pro auto-renewing flag: presence-only, defaults to false, and (backend-derived state, not a + // user edit) does not stamp the profile-updated timestamp. + CHECK_FALSE(profile.get_pro_auto_renewing()); + UserProfileTester::set_profile_updated(profile, std::chrono::sys_seconds{456s}); + profile.set_pro_auto_renewing(true); + CHECK(profile.get_pro_auto_renewing()); + CHECK(profile.get_profile_updated().time_since_epoch().count() == 456); + profile.set_pro_auto_renewing(false); + CHECK_FALSE(profile.get_pro_auto_renewing()); + // Refund-requested flag (synced via config, not the Pro backend) CHECK_FALSE(profile.get_refund_requested().has_value());