Skip to content

Commit 08d4681

Browse files
committed
Merge remote-tracking branch 'origin/main' into atavism/android-auto-update
# Conflicts: # lib/features/setting/setting.dart
2 parents 2052de5 + ab0c541 commit 08d4681

25 files changed

Lines changed: 835 additions & 159 deletions

File tree

android/app/src/main/kotlin/org/getlantern/lantern/handler/MethodHandler.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ enum class Methods(val method: String) {
4747
StripeBillingPortal("stripeBillingPortal"),
4848
Plans("plans"),
4949
AcknowledgeInAppPurchase("acknowledgeInAppPurchase"),
50+
RestoreInAppPurchase("restoreInAppPurchase"),
5051
PaymentRedirect("paymentRedirect"),
5152
ReportIssue("reportIssue"),
5253

@@ -468,6 +469,26 @@ class MethodHandler : FlutterPlugin,
468469
}
469470
}
470471

472+
Methods.RestoreInAppPurchase.method -> {
473+
scope.launch {
474+
result.runCatching {
475+
val map = call.arguments as Map<*, *>
476+
val restoreData = Mobile.restoreGooglePlayPurchase(
477+
map["purchaseToken"] as String,
478+
)
479+
withContext(Dispatchers.Main) {
480+
success(restoreData.toByteArray(Charsets.UTF_8))
481+
}
482+
}.onFailure { e ->
483+
result.error(
484+
"restore_in_app_purchase",
485+
e.localizedMessage ?: "Please try again",
486+
e
487+
)
488+
}
489+
}
490+
}
491+
471492
Methods.PaymentRedirect.method -> {
472493
scope.launch {
473494
result.runCatching {

assets/locales/en.po

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,21 @@ msgstr "Welcome to Lantern!"
509509
msgid "lantern_pro_description"
510510
msgstr "Your Pro features are unlocked—enjoy unlimited data, faster speeds, and premium servers."
511511

512+
msgid "purchase_restored_title"
513+
msgstr "Purchase Restored"
514+
515+
msgid "purchase_restored_description"
516+
msgstr "Your Lantern Pro subscription is now active on this device."
517+
518+
msgid "no_purchase_found_title"
519+
msgstr "No purchase found"
520+
521+
msgid "no_purchase_found_body_ios"
522+
msgstr "No active subscription is tied to this Apple ID."
523+
524+
msgid "no_purchase_found_body_android"
525+
msgstr "No active subscription is tied to this Google account."
526+
512527
msgid "plans_fetch_error"
513528
msgstr "Error fetching plans. Please try again later."
514529

@@ -1065,6 +1080,15 @@ msgstr "Enter an Activation Code"
10651080
msgid "restore_purchase"
10661081
msgstr "Restore Purchase"
10671082

1083+
msgid "already_purchased"
1084+
msgstr "Already purchased?"
1085+
1086+
msgid "restore_purchase_success"
1087+
msgstr "Purchase restored successfully."
1088+
1089+
msgid "restore_purchase_no_purchases"
1090+
msgstr "No purchases were found to restore."
1091+
10681092

10691093
msgid "do_private_server_setup"
10701094
msgstr "DO Private Server Setup"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ replace github.com/quic-go/qpack => github.com/quic-go/qpack v0.5.1
2525
require (
2626
github.com/alecthomas/assert/v2 v2.3.0
2727
github.com/getlantern/lantern-server-provisioner v0.0.0-20251031121934-8ea031fccfa9
28-
github.com/getlantern/radiance v0.0.0-20260511200900-51be53e10add
28+
github.com/getlantern/radiance v0.0.0-20260513120333-8fd1b0335917
2929
github.com/sagernet/sing-box v1.12.22
3030
golang.org/x/mobile v0.0.0-20250711185624-d5bb5ecc55c0
3131
golang.org/x/sys v0.41.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535 h1:rtDmW8YL
261261
github.com/getlantern/pluriconfig v0.0.0-20251126214241-8cc8bc561535/go.mod h1:WKJEdjMOD4IuTRYwjQHjT4bmqDl5J82RShMLxPAvi0Q=
262262
github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b h1:gMYJzEhLrmIqQ+JnjiYNm+UyUDalK3WUmVyecFwmV5g=
263263
github.com/getlantern/publicip v0.0.0-20260328175246-2c460fe80c6b/go.mod h1:NpfXdK4ldEKkjQ4P1R+DBF4ua5VFOlxmgHROTnYrApg=
264-
github.com/getlantern/radiance v0.0.0-20260511200900-51be53e10add h1:HuMeJUhU+IdoJ53rU6wy3Z+CTUynIgo7BCu9UVrCG+Q=
265-
github.com/getlantern/radiance v0.0.0-20260511200900-51be53e10add/go.mod h1:OWxfQRKKT/W13sKURvdLrgU/fYgUfWbmkgrREMzAEtk=
264+
github.com/getlantern/radiance v0.0.0-20260513120333-8fd1b0335917 h1:7rJL4TXNfaIqxBigae5T4hHiVLCz4EEP2PkyDrLCtmw=
265+
github.com/getlantern/radiance v0.0.0-20260513120333-8fd1b0335917/go.mod h1:oBXKRBE6qxdBmxnjV9NI3CSOSy4zDlm1f7haUVWpwBQ=
266266
github.com/getlantern/samizdat v0.0.3-0.20260327203406-ef7323341974 h1:k+/qNo5YNO+8M8LVUp6G5Evm1OQdEs3Z4ye8top4AhI=
267267
github.com/getlantern/samizdat v0.0.3-0.20260327203406-ef7323341974/go.mod h1:uEeykQSW2/6rTjfPlj3MTTo59poSHXfAHTGgzYDkbr0=
268268
github.com/getlantern/semconv v0.0.0-20260327040646-21845dda05cb h1:c5YM7b3a4r2J8Eh89KkI6M/iTFe6Bi+b8AJlfkKdFq4=

ios/Podfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ PODS:
33
- Flutter
44
- device_info_plus (0.0.1):
55
- Flutter
6+
- file_selector_ios (0.0.1):
7+
- Flutter
68
- Flutter (1.0.0)
79
- flutter_inappwebview_ios (0.0.1):
810
- Flutter
@@ -99,6 +101,7 @@ PODS:
99101
DEPENDENCIES:
100102
- app_links (from `.symlinks/plugins/app_links/ios`)
101103
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
104+
- file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`)
102105
- Flutter (from `Flutter`)
103106
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
104107
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
@@ -131,6 +134,8 @@ EXTERNAL SOURCES:
131134
:path: ".symlinks/plugins/app_links/ios"
132135
device_info_plus:
133136
:path: ".symlinks/plugins/device_info_plus/ios"
137+
file_selector_ios:
138+
:path: ".symlinks/plugins/file_selector_ios/ios"
134139
Flutter:
135140
:path: Flutter
136141
flutter_inappwebview_ios:
@@ -161,6 +166,7 @@ EXTERNAL SOURCES:
161166
SPEC CHECKSUMS:
162167
app_links: 6d01271b3907b0ee7325c5297c75d697c4226c4d
163168
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
169+
file_selector_ios: 80c12e90ad3f2045ed6819d03742f1a4c5ec3f93
164170
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
165171
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
166172
flutter_local_notifications: eda81afddbf18f8589f6ec069ce593c4c6378769

ios/Runner/Handlers/MethodHandler.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ class MethodHandler {
9292
}
9393
self.acknowledgeInAppPurchase(token: token, planId: planId, result: result)
9494

95+
case "restoreInAppPurchase":
96+
guard
97+
let map = call.arguments as? [String: Any],
98+
let token = map["purchaseToken"] as? String
99+
else {
100+
result(
101+
FlutterError(
102+
code: "INVALID_ARGUMENTS",
103+
message: "Missing or invalid purchaseToken",
104+
details: nil
105+
)
106+
)
107+
return
108+
}
109+
self.restoreInAppPurchase(token: token, result: result)
110+
95111
// user management
96112
case "startRecoveryByEmail":
97113
let map = (call.arguments as? [String: Any]) ?? [:]
@@ -553,6 +569,20 @@ class MethodHandler {
553569
}
554570
}
555571

572+
func restoreInAppPurchase(token: String, result: @escaping FlutterResult) {
573+
Task {
574+
var error: NSError?
575+
let json = MobileRestoreApplePurchase(token, &error)
576+
if let error {
577+
await self.handleFlutterError(error, result: result, code: "RESTORE_FAILED")
578+
return
579+
}
580+
await MainActor.run {
581+
result(json.data(using: .utf8))
582+
}
583+
}
584+
}
585+
556586
// MARK: - User management
557587

558588
func startRecoveryByEmail(result: @escaping FlutterResult, email: String) {

lantern-core/core.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ type Payment interface {
122122
StripeBillingPortalUrl() (string, error)
123123
AcknowledgeGooglePurchase(purchaseToken, planId string) (string, error)
124124
AcknowledgeApplePurchase(receipt, planII string) (string, error)
125+
RestoreGooglePlayPurchase(purchaseToken string) (string, error)
126+
RestoreApplePurchase(receipt string) (string, error)
125127
PaymentRedirect(provider, planID, email, idempotencyKey string) (string, error)
126128
ActivationCode(email, resellerCode string) error
127129
SubscriptionPaymentRedirectURL(redirectBody account.PaymentRedirectData) (string, error)
@@ -871,6 +873,38 @@ func (lc *LanternCore) AcknowledgeApplePurchase(receipt, planII string) (string,
871873
return lc.client.VerifySubscription(lc.ctx, account.AppleService, params)
872874
}
873875

876+
func (lc *LanternCore) RestoreGooglePlayPurchase(purchaseToken string) (string, error) {
877+
params := map[string]string{
878+
"purchaseToken": purchaseToken,
879+
"idempotencyKey": strconv.FormatInt(time.Now().UnixNano(), 10),
880+
}
881+
resp, err := lc.client.RestoreSubscription(lc.ctx, account.GoogleService, params)
882+
if err != nil {
883+
return "", err
884+
}
885+
data, err := json.Marshal(resp)
886+
if err != nil {
887+
return "", fmt.Errorf("error marshalling restore google play purchase response: %w", err)
888+
}
889+
return string(data), nil
890+
}
891+
892+
func (lc *LanternCore) RestoreApplePurchase(receipt string) (string, error) {
893+
params := map[string]string{
894+
"receipt": receipt,
895+
"idempotencyKey": strconv.FormatInt(time.Now().UnixNano(), 10),
896+
}
897+
resp, err := lc.client.RestoreSubscription(lc.ctx, account.AppleService, params)
898+
if err != nil {
899+
return "", err
900+
}
901+
data, err := json.Marshal(resp)
902+
if err != nil {
903+
return "", fmt.Errorf("error marshalling restore apple purchase response: %w", err)
904+
}
905+
return string(data), nil
906+
}
907+
874908
func (lc *LanternCore) SubscriptionPaymentRedirectURL(redirectBody account.PaymentRedirectData) (string, error) {
875909
return lc.client.SubscriptionPaymentRedirectURL(lc.ctx, redirectBody)
876910
}

lantern-core/mobile/mobile.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,39 @@ func AcknowledgeApplePurchase(receipt, planII string) (string, error) {
565565
})
566566
}
567567

568+
func RestoreGooglePlayPurchase(purchaseToken string) (string, error) {
569+
return withCoreR(func(c lanterncore.Core) (string, error) {
570+
return restoreSubscription(c, c.RestoreGooglePlayPurchase, purchaseToken)
571+
})
572+
}
573+
574+
func RestoreApplePurchase(receipt string) (string, error) {
575+
return withCoreR(func(c lanterncore.Core) (string, error) {
576+
return restoreSubscription(c, c.RestoreApplePurchase, receipt)
577+
})
578+
}
579+
580+
func restoreSubscription(c lanterncore.Core, fn func(string) (string, error), token string) (string, error) {
581+
data, err := fn(token)
582+
if err != nil {
583+
return "", err
584+
}
585+
var resp account.RestoreSubscriptionResponse
586+
if err := json.Unmarshal([]byte(data), &resp); err != nil {
587+
return "", fmt.Errorf("error unmarshalling restore subscription response: %v", err)
588+
}
589+
if resp.ActualUserID != 0 && resp.ActualUserToken != "" {
590+
slog.Info("Restore made on a different account, switching accounts", "actualUserId", resp.ActualUserID)
591+
if err := c.PatchSettings(settings.Settings{
592+
settings.UserIDKey: fmt.Sprintf("%d", resp.ActualUserID),
593+
settings.TokenKey: resp.ActualUserToken,
594+
}); err != nil {
595+
return "", fmt.Errorf("error updating settings after account switch: %v", err)
596+
}
597+
}
598+
return data, nil
599+
}
600+
568601
func PaymentRedirect(provider, planId, email, idempotencyKey string) (string, error) {
569602
return withCoreR(func(c lanterncore.Core) (string, error) {
570603
return c.PaymentRedirect(provider, planId, email, idempotencyKey)

0 commit comments

Comments
 (0)