From 42c2825e90c1a59c452753cf14cd6a24d0ebaebe Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sat, 26 Jul 2025 01:08:53 +0800 Subject: [PATCH] feat: enable IAP for upgrading to Readest Premium on iOS (#1678) --- Cargo.lock | 107 ++++++++++++++- Cargo.toml | 9 +- .../public/locales/ar/translation.json | 9 +- .../public/locales/de/translation.json | 9 +- .../public/locales/el/translation.json | 9 +- .../public/locales/es/translation.json | 9 +- .../public/locales/fr/translation.json | 9 +- .../public/locales/hi/translation.json | 9 +- .../public/locales/id/translation.json | 9 +- .../public/locales/it/translation.json | 9 +- .../public/locales/ja/translation.json | 9 +- .../public/locales/ko/translation.json | 9 +- .../public/locales/nl/translation.json | 9 +- .../public/locales/pl/translation.json | 9 +- .../public/locales/pt/translation.json | 9 +- .../public/locales/ru/translation.json | 9 +- .../public/locales/th/translation.json | 9 +- .../public/locales/tr/translation.json | 9 +- .../public/locales/uk/translation.json | 9 +- .../public/locales/vi/translation.json | 9 +- .../public/locales/zh-CN/translation.json | 9 +- .../public/locales/zh-TW/translation.json | 9 +- .../src/app/api/apple/iap-verify/route.ts | 37 ++++-- apps/readest-app/src/app/library/page.tsx | 3 +- .../app/user/components/AccountActions.tsx | 21 ++- .../src/app/user/components/PlanCard.tsx | 7 +- .../app/user/components/PlansComparison.tsx | 4 +- apps/readest-app/src/app/user/page.tsx | 123 ++++++++++++++++-- .../app/user/subscription/success/page.tsx | 89 +++++++++++-- apps/readest-app/src/app/user/utils/plan.ts | 16 ++- apps/readest-app/src/components/Toast.tsx | 14 +- .../src/libs/iap/apple/verifier.ts | 7 +- apps/readest-app/src/types/error.ts | 9 ++ packages/tauri | 2 +- packages/tauri-plugins | 2 +- 35 files changed, 519 insertions(+), 111 deletions(-) create mode 100644 apps/readest-app/src/types/error.ts diff --git a/Cargo.lock b/Cargo.lock index 07ff689e..9e84758a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1507,6 +1507,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-id" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "filetime" version = "0.2.25" @@ -1608,6 +1617,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -2447,6 +2465,26 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" @@ -2613,6 +2651,26 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -2850,6 +2908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -2948,6 +3007,46 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "notify" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" +dependencies = [ + "bitflags 2.9.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +dependencies = [ + "serde", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -5393,7 +5492,7 @@ dependencies = [ [[package]] name = "tauri-plugin-deep-link" -version = "2.4.0" +version = "2.4.1" dependencies = [ "dunce", "rust-ini", @@ -5412,8 +5511,6 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bedd4c3cf6f7aa97918a8814a736bd3695c9ddf3ede2d50eda6069c3290edc" dependencies = [ "log", "raw-window-handle", @@ -5430,12 +5527,12 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99" dependencies = [ "anyhow", "dunce", "glob", + "notify", + "notify-debouncer-full", "percent-encoding", "schemars 0.8.22", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1874591a..86953461 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ members = [ "packages/tauri/crates/tauri", "packages/tauri/crates/tauri-utils", "packages/tauri/crates/tauri-build", + "packages/tauri-plugins/plugins/fs", + "packages/tauri-plugins/plugins/dialog", "packages/tauri-plugins/plugins/deep-link", ] resolver = "2" @@ -16,8 +18,11 @@ tauri = { version = "2", default-features = false } tauri-build = "2" tauri-plugin = "2" tauri-utils = "2" +schemars = "0.8" serde_json = "1" thiserror = "2" +glob = "0.3" +dunce = "1" url = "2" [workspace.package] @@ -33,4 +38,6 @@ rust-version = "1.77.2" tauri = { path = "packages/tauri/crates/tauri" } tauri-utils = { path = "packages/tauri/crates/tauri-utils" } tauri-build = { path = "packages/tauri/crates/tauri-build" } -tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" } \ No newline at end of file +tauri-plugin-fs = { path = "packages/tauri-plugins/plugins/fs" } +tauri-plugin-dialog = { path = "packages/tauri-plugins/plugins/dialog" } +tauri-plugin-deep-link = { path = "packages/tauri-plugins/plugins/deep-link" } diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 6b32f350..57fae79e 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -372,7 +372,6 @@ "Upgrade to Plus or Pro": "الترقية إلى Plus أو Pro", "Current Plan": "الخطة الحالية", "Plan Limits": "حدود الخطة", - "Failed to manage subscription. Please try again later.": "فشل في إدارة الاشتراك. يرجى المحاولة مرة أخرى لاحقًا.", "Processing your payment...": "جارٍ معالجة الدفع...", "Please wait while we confirm your subscription.": "يرجى الانتظار بينما نقوم بتأكيد اشتراكك.", "Payment Processing": "معالجة الدفع", @@ -469,5 +468,11 @@ "Open Book in New Window": "فتح الكتاب في نافذة جديدة", "Voices for {{lang}}": "الأصوات لـ {{lang}}", "Yandex Translate": "ترجمة Yandex", - "YYYY or YYYY-MM-DD": "YYYY أو YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY أو YYYY-MM-DD", + "Restore Purchase": "استعادة الشراء", + "No purchases found to restore.": "لا توجد مشتريات لاستعادتها.", + "Failed to restore purchases.": "فشل في استعادة المشتريات.", + "Failed to manage subscription.": "فشل في إدارة الاشتراك.", + "Failed to load subscription plans.": "فشل في تحميل خطط الاشتراك.", + "year": "سنة" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 840e6c03..83503b71 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -356,7 +356,6 @@ "Upgrade to Plus or Pro": "Upgrade auf Plus oder Pro", "Current Plan": "Aktueller Tarif", "Plan Limits": "Tariflimits", - "Failed to manage subscription. Please try again later.": "Verwaltung des Abos fehlgeschlagen. Bitte versuchen Sie es später erneut.", "Processing your payment...": "Zahlung wird verarbeitet...", "Please wait while we confirm your subscription.": "Bitte warten Sie, während wir Ihr Abo bestätigen.", "Payment Processing": "Zahlung wird verarbeitet", @@ -453,5 +452,11 @@ "Open Book in New Window": "Buch in neuem Fenster öffnen", "Voices for {{lang}}": "Stimmen für {{lang}}", "Yandex Translate": "Yandex Übersetzer", - "YYYY or YYYY-MM-DD": "YYYY oder YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY oder YYYY-MM-DD", + "Restore Purchase": "Kauf wiederherstellen", + "No purchases found to restore.": "Keine Käufe zum Wiederherstellen gefunden.", + "Failed to restore purchases.": "Wiederherstellung der Käufe fehlgeschlagen.", + "Failed to manage subscription.": "Verwaltung des Abonnements fehlgeschlagen.", + "Failed to load subscription plans.": "Laden der Abonnementpläne fehlgeschlagen.", + "year": "Jahr" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index eeb85864..08b748e3 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -357,7 +357,6 @@ "Upgrade to Plus or Pro": "Αναβάθμιση σε Plus ή Pro", "Current Plan": "Τρέχον πρόγραμμα", "Plan Limits": "Όρια προγράμματος", - "Failed to manage subscription. Please try again later.": "Αποτυχία διαχείρισης της συνδρομής. Παρακαλώ δοκιμάστε ξανά αργότερα.", "Processing your payment...": "Επεξεργασία πληρωμής...", "Please wait while we confirm your subscription.": "Παρακαλώ περιμένετε όσο επιβεβαιώνουμε τη συνδρομή σας.", "Payment Processing": "Επεξεργασία πληρωμής", @@ -453,5 +452,11 @@ "Open Book in New Window": "Άνοιγμα βιβλίου σε νέο παράθυρο", "Voices for {{lang}}": "Φωνές για {{lang}}", "Yandex Translate": "Μετάφραση Yandex", - "YYYY or YYYY-MM-DD": "YYYY ή YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY ή YYYY-MM-DD", + "Restore Purchase": "Επαναφορά αγοράς", + "No purchases found to restore.": "Δεν βρέθηκαν αγορές για επαναφορά.", + "Failed to restore purchases.": "Αποτυχία επαναφοράς αγορών.", + "Failed to manage subscription.": "Αποτυχία διαχείρισης συνδρομής.", + "Failed to load subscription plans.": "Αποτυχία φόρτωσης σχεδίων συνδρομής.", + "year": "έτος" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 15b2d13e..4592bef4 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -361,7 +361,6 @@ "Upgrade to Plus or Pro": "Actualizar a Plus o Pro", "Current Plan": "Plan actual", "Plan Limits": "Límites del plan", - "Failed to manage subscription. Please try again later.": "No se pudo gestionar la suscripción. Por favor, inténtalo de nuevo más tarde.", "Processing your payment...": "Procesando tu pago...", "Please wait while we confirm your subscription.": "Espera mientras confirmamos tu suscripción.", "Payment Processing": "Procesando pago", @@ -457,5 +456,11 @@ "Open Book in New Window": "Abre el libro en una nueva ventana", "Voices for {{lang}}": "Voces para {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "YYYY o YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY o YYYY-MM-DD", + "Restore Purchase": "Restaurar compra", + "No purchases found to restore.": "No se encontraron compras para restaurar.", + "Failed to restore purchases.": "Error al restaurar compras.", + "Failed to manage subscription.": "Error al gestionar la suscripción.", + "Failed to load subscription plans.": "Error al cargar los planes de suscripción.", + "year": "año" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 8824dc89..1a9ee080 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -360,7 +360,6 @@ "Upgrade to Plus or Pro": "Passer à Plus ou Pro", "Current Plan": "Abonnement actuel", "Plan Limits": "Limites de l’abonnement", - "Failed to manage subscription. Please try again later.": "Échec de la gestion de l’abonnement. Veuillez réessayer plus tard.", "Processing your payment...": "Traitement de votre paiement...", "Please wait while we confirm your subscription.": "Veuillez patienter pendant la confirmation de votre abonnement.", "Payment Processing": "Traitement du paiement", @@ -457,5 +456,11 @@ "Open Book in New Window": "Ouvrir dans une nouvelle fenêtre", "Voices for {{lang}}": "Voix pour {{lang}}", "Yandex Translate": "Yandex Traduction", - "YYYY or YYYY-MM-DD": "YYYY ou YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY ou YYYY-MM-DD", + "Restore Purchase": "Restaurer l'achat", + "No purchases found to restore.": "Aucun achat trouvé à restaurer.", + "Failed to restore purchases.": "Échec de la restauration des achats.", + "Failed to manage subscription.": "Échec de la gestion de l'abonnement.", + "Failed to load subscription plans.": "Échec du chargement des plans d'abonnement.", + "year": "année" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index f9bbd01a..2268e477 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -357,7 +357,6 @@ "Upgrade to Plus or Pro": "Plus या Pro में अपग्रेड करें", "Current Plan": "वर्तमान योजना", "Plan Limits": "योजना की सीमाएँ", - "Failed to manage subscription. Please try again later.": "सदस्यता प्रबंधित करने में विफल। कृपया बाद में पुनः प्रयास करें।", "Processing your payment...": "आपका भुगतान संसाधित हो रहा है...", "Please wait while we confirm your subscription.": "कृपया प्रतीक्षा करें, हम आपकी सदस्यता की पुष्टि कर रहे हैं।", "Payment Processing": "भुगतान संसाधित हो रहा है", @@ -453,5 +452,11 @@ "Open Book in New Window": "नई विंडो में पुस्तक खोलें", "Voices for {{lang}}": "{{lang}} के लिए आवाज़ें", "Yandex Translate": "यांडेक्स अनुवाद", - "YYYY or YYYY-MM-DD": "YYYY या YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY या YYYY-MM-DD", + "Restore Purchase": "खरीदारी पुनर्स्थापित करें", + "No purchases found to restore.": "पुनर्स्थापित करने के लिए कोई खरीदारी नहीं मिली।", + "Failed to restore purchases.": "खरीदारी पुनर्स्थापित करने में विफल।", + "Failed to manage subscription.": "सदस्यता प्रबंधित करने में विफल।", + "Failed to load subscription plans.": "सदस्यता योजनाएँ लोड करने में विफल।", + "year": "वर्ष" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 20d60e38..c803bc89 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "Tingkatkan ke Plus atau Pro", "Current Plan": "Paket Saat Ini", "Plan Limits": "Batas Paket", - "Failed to manage subscription. Please try again later.": "Gagal mengelola langganan. Silakan coba lagi nanti.", "Processing your payment...": "Memproses pembayaran Anda...", "Please wait while we confirm your subscription.": "Mohon tunggu saat kami mengonfirmasi langganan Anda.", "Payment Processing": "Memproses Pembayaran", @@ -449,5 +448,11 @@ "Open Book in New Window": "Buka Buku di Jendela Baru", "Voices for {{lang}}": "Suara untuk {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "YYYY atau YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY atau YYYY-MM-DD", + "Restore Purchase": "Pulihkan Pembelian", + "No purchases found to restore.": "Tidak ada pembelian yang ditemukan untuk dipulihkan.", + "Failed to restore purchases.": "Gagal memulihkan pembelian.", + "Failed to manage subscription.": "Gagal mengelola langganan.", + "Failed to load subscription plans.": "Gagal memuat rencana langganan.", + "year": "tahun" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index ab64aff6..db25788b 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -361,7 +361,6 @@ "Upgrade to Plus or Pro": "Aggiorna a Plus o Pro", "Current Plan": "Piano Attuale", "Plan Limits": "Limiti del Piano", - "Failed to manage subscription. Please try again later.": "Impossibile gestire l'abbonamento. Riprova più tardi.", "Processing your payment...": "Elaborazione del pagamento...", "Please wait while we confirm your subscription.": "Attendi mentre confermiamo il tuo abbonamento.", "Payment Processing": "Elaborazione Pagamento", @@ -457,5 +456,11 @@ "Open Book in New Window": "Apri libro in una nuova finestra", "Voices for {{lang}}": "Voci per {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "AAAA o AAAA-MM-GG" + "YYYY or YYYY-MM-DD": "AAAA o AAAA-MM-GG", + "Restore Purchase": "Ripristina acquisto", + "No purchases found to restore.": "Nessun acquisto trovato da ripristinare.", + "Failed to restore purchases.": "Impossibile ripristinare gli acquisti.", + "Failed to manage subscription.": "Impossibile gestire l'abbonamento.", + "Failed to load subscription plans.": "Impossibile caricare i piani di abbonamento.", + "year": "anno" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 9120045b..dfcf2fbc 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "Plus または Pro にアップグレード", "Current Plan": "現在のプラン", "Plan Limits": "プランの上限", - "Failed to manage subscription. Please try again later.": "サブスクリプションの管理に失敗しました。後でもう一度お試しください。", "Processing your payment...": "お支払いを処理中...", "Please wait while we confirm your subscription.": "サブスクリプションを確認しています。しばらくお待ちください。", "Payment Processing": "支払いを処理中", @@ -449,5 +448,11 @@ "Open Book in New Window": "新しいウィンドウで本を開く", "Voices for {{lang}}": "{{lang}}の音声", "Yandex Translate": "Yandex翻訳", - "YYYY or YYYY-MM-DD": "YYYYまたはYYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYYまたはYYYY-MM-DD", + "Restore Purchase": "購入を復元", + "No purchases found to restore.": "復元する購入が見つかりません。", + "Failed to restore purchases.": "購入の復元に失敗しました。", + "Failed to manage subscription.": "サブスクリプションの管理に失敗しました。", + "Failed to load subscription plans.": "サブスクリプションプランの読み込みに失敗しました。", + "year": "年" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 0008a05f..060a1f77 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "Plus 또는 Pro로 업그레이드", "Current Plan": "현재 요금제", "Plan Limits": "요금제 한도", - "Failed to manage subscription. Please try again later.": "구독 관리에 실패했습니다. 나중에 다시 시도해주세요.", "Processing your payment...": "결제를 처리 중입니다...", "Please wait while we confirm your subscription.": "구독을 확인하는 중입니다. 잠시만 기다려주세요.", "Payment Processing": "결제 처리 중", @@ -449,5 +448,11 @@ "Open Book in New Window": "새 창에서 책 열기", "Voices for {{lang}}": "{{lang}}의 음성", "Yandex Translate": "Yandex 번역", - "YYYY or YYYY-MM-DD": "YYYY 또는 YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY 또는 YYYY-MM-DD", + "Restore Purchase": "구매 복원", + "No purchases found to restore.": "복원할 구매가 없습니다.", + "Failed to restore purchases.": "구매 복원에 실패했습니다.", + "Failed to manage subscription.": "구독 관리에 실패했습니다.", + "Failed to load subscription plans.": "구독 플랜 로드에 실패했습니다.", + "year": "년" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 57135404..14af5d4a 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -357,7 +357,6 @@ "Upgrade to Plus or Pro": "Upgrade naar Plus of Pro", "Current Plan": "Huidig abonnement", "Plan Limits": "Abonnementslimieten", - "Failed to manage subscription. Please try again later.": "Abonnement beheren is mislukt. Probeer het later opnieuw.", "Processing your payment...": "Je betaling wordt verwerkt...", "Please wait while we confirm your subscription.": "Even geduld terwijl we je abonnement bevestigen.", "Payment Processing": "Betaling verwerken", @@ -453,5 +452,11 @@ "Open Book in New Window": "Open boek in nieuw venster", "Voices for {{lang}}": "Stemmen voor {{lang}}", "Yandex Translate": "Yandex Vertalen", - "YYYY or YYYY-MM-DD": "YYYY of YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY of YYYY-MM-DD", + "Restore Purchase": "Herstel aankoop", + "No purchases found to restore.": "Geen aankopen gevonden om te herstellen.", + "Failed to restore purchases.": "Herstellen van aankopen is mislukt.", + "Failed to manage subscription.": "Beheren van abonnement is mislukt.", + "Failed to load subscription plans.": "Laden van abonnement plannen is mislukt.", + "year": "jaar" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 3db86e5f..aeeb3cc1 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -365,7 +365,6 @@ "Upgrade to Plus or Pro": "Uaktualnij do Plus lub Pro", "Current Plan": "Obecny plan", "Plan Limits": "Limity planu", - "Failed to manage subscription. Please try again later.": "Nie udało się zarządzać subskrypcją. Spróbuj ponownie później.", "Processing your payment...": "Przetwarzanie płatności...", "Please wait while we confirm your subscription.": "Proszę czekać, potwierdzamy subskrypcję.", "Payment Processing": "Przetwarzanie płatności", @@ -461,5 +460,11 @@ "Open Book in New Window": "Otwórz książkę w nowym oknie", "Voices for {{lang}}": "{{lang}} Głosy", "Yandex Translate": "Yandex Tłumacz", - "YYYY or YYYY-MM-DD": "YYYY lub YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY lub YYYY-MM-DD", + "Restore Purchase": "Przywróć zakup", + "No purchases found to restore.": "Nie znaleziono zakupów do przywrócenia.", + "Failed to restore purchases.": "Nie udało się przywrócić zakupów.", + "Failed to manage subscription.": "Nie udało się zarządzać subskrypcją.", + "Failed to load subscription plans.": "Nie udało się załadować planów subskrypcyjnych.", + "year": "rok" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index a9a08531..4a63efba 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -361,7 +361,6 @@ "Upgrade to Plus or Pro": "Atualizar para Plus ou Pro", "Current Plan": "Plano atual", "Plan Limits": "Limites do plano", - "Failed to manage subscription. Please try again later.": "Não foi possível gerenciar a assinatura. Tente novamente mais tarde.", "Processing your payment...": "Processando seu pagamento...", "Please wait while we confirm your subscription.": "Por favor, aguarde enquanto confirmamos sua assinatura.", "Payment Processing": "Processando pagamento", @@ -457,5 +456,11 @@ "Open Book in New Window": "Abrir livro em nova janela", "Voices for {{lang}}": "Vozes para {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "YYYY ou YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY ou YYYY-MM-DD", + "Restore Purchase": "Restaurar Compra", + "No purchases found to restore.": "Nenhuma compra encontrada para restaurar.", + "Failed to restore purchases.": "Falha ao restaurar compras.", + "Failed to manage subscription.": "Falha ao gerenciar assinatura.", + "Failed to load subscription plans.": "Falha ao carregar planos de assinatura.", + "year": "ano" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 811b1101..d3ffb3b3 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -365,7 +365,6 @@ "Upgrade to Plus or Pro": "Обновить до Plus или Pro", "Current Plan": "Текущий план", "Plan Limits": "Лимиты плана", - "Failed to manage subscription. Please try again later.": "Не удалось управлять подпиской. Пожалуйста, попробуйте позже.", "Processing your payment...": "Обработка платежа...", "Please wait while we confirm your subscription.": "Пожалуйста, подождите, пока мы подтверждаем вашу подписку.", "Payment Processing": "Обработка платежа", @@ -461,5 +460,11 @@ "Open Book in New Window": "Открыть книгу в новом окне", "Voices for {{lang}}": "Голоса для {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "YYYY или YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY или YYYY-MM-DD", + "Restore Purchase": "Восстановить покупку", + "No purchases found to restore.": "Не найдено покупок для восстановления.", + "Failed to restore purchases.": "Не удалось восстановить покупки.", + "Failed to manage subscription.": "Не удалось управлять подпиской.", + "Failed to load subscription plans.": "Не удалось загрузить планы подписки.", + "year": "год" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 099d13ca..11d378bc 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -306,7 +306,6 @@ "Current Plan": "แผนปัจจุบัน", "Plan Limits": "ข้อจำกัดแผน", "Failed to delete user. Please try again later.": "ไม่สามารถลบผู้ใช้ ลองใหม่ภายหลัง", - "Failed to manage subscription. Please try again later.": "ไม่สามารถจัดการสมาชิก ลองใหม่ภายหลัง", "Loading profile...": "กำลังโหลดโปรไฟล์...", "Processing your payment...": "กำลังประมวลผลการชำระเงิน...", "Please wait while we confirm your subscription.": "กรุณารอสักครู่เพื่อยืนยันการสมัครสมาชิก", @@ -449,5 +448,11 @@ "Open Book in New Window": "เปิดหนังสือในหน้าต่างใหม่", "Voices for {{lang}}": "เสียงสำหรับ {{lang}}", "Yandex Translate": "Yandex Translate", - "YYYY or YYYY-MM-DD": "YYYY หรือ YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY หรือ YYYY-MM-DD", + "Restore Purchase": "กู้คืนการซื้อ", + "No purchases found to restore.": "ไม่พบการซื้อเพื่อกู้คืน", + "Failed to restore purchases.": "ไม่สามารถกู้คืนการซื้อได้", + "Failed to manage subscription.": "ไม่สามารถจัดการการสมัครสมาชิกได้", + "Failed to load subscription plans.": "ไม่สามารถโหลดแผนการสมัครสมาชิกได้", + "year": "ปี" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 89112e50..c8675720 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -357,7 +357,6 @@ "Upgrade to Plus or Pro": "Plus veya Pro'ya yükselt", "Current Plan": "Mevcut Plan", "Plan Limits": "Plan Limitleri", - "Failed to manage subscription. Please try again later.": "Abonelik yönetimi başarısız oldu. Lütfen daha sonra tekrar deneyin.", "Processing your payment...": "Ödemeniz işleniyor...", "Please wait while we confirm your subscription.": "Aboneliğinizi onaylıyoruz, lütfen bekleyin.", "Payment Processing": "Ödeme İşleniyor", @@ -453,5 +452,11 @@ "Open Book in New Window": "Kitabı Yeni Pencerede Aç", "Voices for {{lang}}": "{{lang}} için Sesler", "Yandex Translate": "Yandex Çeviri", - "YYYY or YYYY-MM-DD": "YYYY veya YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY veya YYYY-MM-DD", + "Restore Purchase": "Satın Almayı Geri Yükle", + "No purchases found to restore.": "Geri yüklemek için satın alma bulunamadı.", + "Failed to restore purchases.": "Satın alımları geri yükleme başarısız oldu.", + "Failed to manage subscription.": "Aboneliği yönetme başarısız oldu.", + "Failed to load subscription plans.": "Abonelik planlarını yükleme başarısız oldu.", + "year": "yıl" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 4efd183b..8eda56e9 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -365,7 +365,6 @@ "Upgrade to Plus or Pro": "Перейти на Plus або Pro", "Current Plan": "Поточний план", "Plan Limits": "Обмеження плану", - "Failed to manage subscription. Please try again later.": "Не вдалося керувати підпискою. Спробуйте ще раз пізніше.", "Processing your payment...": "Обробка вашого платежу...", "Please wait while we confirm your subscription.": "Зачекайте, ми підтверджуємо вашу підписку.", "Payment Processing": "Обробка платежу", @@ -461,5 +460,11 @@ "Open Book in New Window": "Відкрити книгу в новому вікні", "Voices for {{lang}}": "Голоси для {{lang}}", "Yandex Translate": "Yandex Перекладач", - "YYYY or YYYY-MM-DD": "YYYY або YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY або YYYY-MM-DD", + "Restore Purchase": "Відновити покупку", + "No purchases found to restore.": "Не знайдено покупок для відновлення.", + "Failed to restore purchases.": "Не вдалося відновити покупки.", + "Failed to manage subscription.": "Не вдалося керувати підпискою.", + "Failed to load subscription plans.": "Не вдалося завантажити плани підписки.", + "year": "рік" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index a8b43de8..5419936f 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "Nâng cấp lên Plus hoặc Pro", "Current Plan": "Gói hiện tại", "Plan Limits": "Giới hạn gói", - "Failed to manage subscription. Please try again later.": "Không thể quản lý đăng ký. Vui lòng thử lại sau.", "Processing your payment...": "Đang xử lý thanh toán...", "Please wait while we confirm your subscription.": "Vui lòng chờ trong khi chúng tôi xác nhận đăng ký của bạn.", "Payment Processing": "Đang xử lý thanh toán", @@ -449,5 +448,11 @@ "Open Book in New Window": "Mở Sách trong Cửa sổ Mới", "Voices for {{lang}}": "Giọng nói cho {{lang}}", "Yandex Translate": "Yandex Dịch", - "YYYY or YYYY-MM-DD": "YYYY hoặc YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY hoặc YYYY-MM-DD", + "Restore Purchase": "Khôi phục mua hàng", + "No purchases found to restore.": "Không tìm thấy giao dịch mua nào để khôi phục.", + "Failed to restore purchases.": "Khôi phục giao dịch mua không thành công.", + "Failed to manage subscription.": "Quản lý đăng ký không thành công.", + "Failed to load subscription plans.": "Tải kế hoạch đăng ký không thành công.", + "year": "năm" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 5a461cfe..e0b911f8 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "升级到 Plus 或 Pro", "Current Plan": "当前套餐", "Plan Limits": "套餐限制", - "Failed to manage subscription. Please try again later.": "无法管理订阅,请稍后再试。", "Processing your payment...": "正在处理付款...", "Please wait while we confirm your subscription.": "请稍候,我们正在确认您的订阅。", "Payment Processing": "付款处理中", @@ -449,5 +448,11 @@ "Open Book in New Window": "在新窗口中打开书籍", "Voices for {{lang}}": "{{lang}} 的语音", "Yandex Translate": "Yandex 翻译", - "YYYY or YYYY-MM-DD": "YYYY 或 YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY 或 YYYY-MM-DD", + "Restore Purchase": "恢复购买", + "No purchases found to restore.": "未找到可恢复的购买。", + "Failed to restore purchases.": "恢复购买失败。", + "Failed to manage subscription.": "管理订阅失败。", + "Failed to load subscription plans.": "加载订阅计划失败。", + "year": "年" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index f96e4250..678bd4d1 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -353,7 +353,6 @@ "Upgrade to Plus or Pro": "升級到 Plus 或 Pro", "Current Plan": "當前方案", "Plan Limits": "方案限制", - "Failed to manage subscription. Please try again later.": "無法管理訂閱,請稍後再試。", "Processing your payment...": "正在處理付款...", "Please wait while we confirm your subscription.": "請稍候,我們正在確認您的訂閱。", "Payment Processing": "付款處理中", @@ -449,5 +448,11 @@ "Open Book in New Window": "在新視窗中打開書籍", "Voices for {{lang}}": "{{lang}} 的語音", "Yandex Translate": "Yandex 翻譯", - "YYYY or YYYY-MM-DD": "YYYY 或 YYYY-MM-DD" + "YYYY or YYYY-MM-DD": "YYYY 或 YYYY-MM-DD", + "Restore Purchase": "恢復購買", + "No purchases found to restore.": "未找到可恢復的購買。", + "Failed to restore purchases.": "恢復購買失敗。", + "Failed to manage subscription.": "管理訂閱失敗。", + "Failed to load subscription plans.": "加載訂閱計劃失敗。", + "year": "年" } diff --git a/apps/readest-app/src/app/api/apple/iap-verify/route.ts b/apps/readest-app/src/app/api/apple/iap-verify/route.ts index 19e17bb8..9d2bf023 100644 --- a/apps/readest-app/src/app/api/apple/iap-verify/route.ts +++ b/apps/readest-app/src/app/api/apple/iap-verify/route.ts @@ -3,6 +3,7 @@ import { NextResponse } from 'next/server'; import { defaultIAPVerifier } from '@/libs/iap/apple/verifier'; import { createSupabaseAdminClient } from '@/utils/supabase'; import { validateUserAndToken } from '@/utils/access'; +import { IAPError } from '@/types/error'; const iapVerificationSchema = z.object({ transactionId: z.string().min(1, 'Transaction ID is required'), @@ -10,10 +11,10 @@ const iapVerificationSchema = z.object({ }); const PRODUCT_MAP: Record = { - 'com.bilingify.readest.monthly.plus': 'Monthly Plus Subscription', - 'com.bilingify.readest.yearly.plus': 'Yearly Plus Subscription', - 'com.bilingify.readest.monthly.pro': 'Monthly Pro Subscription', - 'com.bilingify.readest.yearly.pro': 'Yearly Pro Subscription', + 'com.bilingify.readest.monthly.plus': 'Plus', + 'com.bilingify.readest.yearly.plus': 'Plus', + 'com.bilingify.readest.monthly.pro': 'Pro', + 'com.bilingify.readest.yearly.pro': 'Pro', }; const getProductName = (productId: string) => { @@ -118,7 +119,7 @@ export async function POST(request: Request) { const { user, token } = await validateUserAndToken(request.headers.get('authorization')); if (!user || !token) { - return NextResponse.json({ error: 'Not authenticated' }, { status: 403 }); + return NextResponse.json({ error: IAPError.NOT_AUTHENTICATED }, { status: 403 }); } try { @@ -126,12 +127,22 @@ export async function POST(request: Request) { const { data: existingSubscription } = await supabase .from('apple_iap_subscriptions') .select('*') - .eq('transaction_id', transactionId) - .eq('user_id', user.id) + .eq('original_transaction_id', originalTransactionId) .single(); console.log('Existing subscription:', existingSubscription); - if (existingSubscription && existingSubscription.status === 'active') { + // Should not restore purchase for another account + if (existingSubscription && existingSubscription.user_id !== user.id) { + return NextResponse.json( + { error: IAPError.TRANSACTION_BELONGS_TO_ANOTHER_USER }, + { status: 403 }, + ); + } + if ( + existingSubscription && + existingSubscription.transactionId === transactionId && + existingSubscription.status === 'active' + ) { console.log('Transaction already verified and active'); const purchase = { @@ -140,7 +151,7 @@ export async function POST(request: Request) { subscriptionId: existingSubscription.web_order_line_item_id || existingSubscription.original_transaction_id, - planName: await getProductName(existingSubscription.product_id), + planName: getProductName(existingSubscription.product_id), productId: existingSubscription.product_id, platform: existingSubscription.platform, transactionId: existingSubscription.transaction_id, @@ -163,7 +174,7 @@ export async function POST(request: Request) { console.error('Apple verification failed:', verificationResult.error); return NextResponse.json( { - error: verificationResult.error || 'Transaction verification failed with Apple', + error: verificationResult.error || IAPError.UNKNOWN_ERROR, purchase: null, }, { status: 400 }, @@ -184,7 +195,7 @@ export async function POST(request: Request) { status: verificationResult.status!, customerEmail: user.email!, subscriptionId: transaction.webOrderLineItemId || transaction.originalTransactionId, - planName: await getProductName(transaction.productId), + planName: getProductName(transaction.productId), productId: transaction.productId, platform: 'ios', transactionId: transaction.transactionId, @@ -208,7 +219,7 @@ export async function POST(request: Request) { return NextResponse.json( { - error: 'Transaction failed to update database. Please contact support.', + error: IAPError.TRANSACTION_SERVICE_UNAVAILABLE, purchase: null, }, { status: 500 }, @@ -222,7 +233,7 @@ export async function POST(request: Request) { } catch (error) { console.error('IAP verification error:', error); return NextResponse.json( - { error: error instanceof Error ? error.message : 'Unknown error' }, + { error: error instanceof Error ? error.message : IAPError.UNKNOWN_ERROR }, { status: 500 }, ); } diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index a5608591..0a86680f 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -436,7 +436,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP }; const selectFilesTauri = async () => { - const exts = appService?.isMobileApp ? [] : SUPPORTED_FILE_EXTS; + const exts = appService?.isIOSApp ? [] : SUPPORTED_FILE_EXTS; const files = (await appService?.selectFiles(_('Select Books'), exts)) || []; if (appService?.isIOSApp) { return files.filter((file) => { @@ -444,7 +444,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP return SUPPORTED_FILE_EXTS.includes(fileExt); }); } - // Cannot filter out files on Android since some content providers may not return the file name return files; }; diff --git a/apps/readest-app/src/app/user/components/AccountActions.tsx b/apps/readest-app/src/app/user/components/AccountActions.tsx index 5f9c5640..bf27a654 100644 --- a/apps/readest-app/src/app/user/components/AccountActions.tsx +++ b/apps/readest-app/src/app/user/components/AccountActions.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; +import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { UserPlan } from '@/types/user'; @@ -48,16 +49,19 @@ interface AccountActionsProps { userPlan: UserPlan; onLogout: () => void; onConfirmDelete: () => void; - onManageSubscription: () => void; + onRestorePurchase?: () => void; + onManageSubscription?: () => void; } const AccountActions: React.FC = ({ userPlan, onLogout, onConfirmDelete, + onRestorePurchase, onManageSubscription, }) => { const _ = useTranslation(); + const { appService } = useEnv(); const [showConfirmDelete, setShowConfirmDelete] = useState(false); const handleDeleteRequest = () => { @@ -79,13 +83,22 @@ const AccountActions: React.FC = ({ }} />
- {userPlan !== 'free' && ( + {appService?.isIOSApp ? ( + ) : ( + userPlan !== 'free' && ( + + ) )}