diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index 0a91fa33..e377979e 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -378,7 +378,6 @@ "Email:": "البريد الإلكتروني:", "Plan:": "الخطة:", "Amount:": "المبلغ:", - "Subscription ID:": "رقم الاشتراك:", "Go to Library": "الانتقال إلى المكتبة", "Need help? Contact our support team at support@readest.com": "تحتاج مساعدة؟ تواصل مع فريق الدعم على support@readest.com", "Free Plan": "الخطة المجانية", @@ -651,5 +650,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "فتح سمات إضافية، خطوط، خيارات تخطيط، وخدمات قراءة بصوت عالٍ، ومترجمين، وخدمات تخزين سحابية.", "Purchase Successful!": "تمت عملية الشراء بنجاح!", "lifetime": "مدى الحياة", - "Thank you for your purchase! Your payment has been processed successfully.": "شكرًا لشرائك! تم معالجة الدفع بنجاح." + "Thank you for your purchase! Your payment has been processed successfully.": "شكرًا لشرائك! تم معالجة الدفع بنجاح.", + "Order ID:": "معرف الطلب:" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index 28e65814..dd185a6a 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -376,7 +376,6 @@ "Email:": "ইমেইল:", "Plan:": "প্ল্যান:", "Amount:": "পরিমাণ:", - "Subscription ID:": "সাবস্ক্রিপশন ID:", "Go to Library": "লাইব্রেরিতে যান", "Need help? Contact our support team at support@readest.com": "সাহায্য দরকার? support@readest.com এ আমাদের সাপোর্ট টিমের সাথে যোগাযোগ করুন", "Free Plan": "ফ্রি প্ল্যান", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।", "Purchase Successful!": "ক্রয় সফল!", "lifetime": "জীবনকাল", - "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়া করা হয়েছে।" + "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়া করা হয়েছে।", + "Order ID:": "অর্ডার আইডি:" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 00c3f0ec..2fb5fbf5 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -358,7 +358,6 @@ "Email:": "གློག་རྡུལ་ཡིག་ཟམ།", "Plan:": "ཐུམ་སྒྲིལ།", "Amount:": "དངུལ་གྲངས།", - "Subscription ID:": "མཚམས་སྦྱོར་ ID །", "Go to Library": "དཔེ་མཛོད་ལ་འགྲོ།", "Need help? Contact our support team at support@readest.com": "རོགས་རམ་དགོས་སམ། རྒྱབ་སྐྱོར་ཚོགས་པར་འབྲེལ་བ་གནང་རོགས། support@readest.com", "Free Plan": "རིན་མེད་ཐུམ་སྒྲིལ།", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "অতিরিক্ত থিম, ফন্ট, লেআউট বিকল্প এবং উচ্চস্বরে পড়া, অনুবাদক, ক্লাউড স্টোরেজ পরিষেবাগুলি আনলক করুন।", "Purchase Successful!": "ক্রয় সফল!", "lifetime": "জীবনকাল", - "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়াকৃত হয়েছে।" + "Thank you for your purchase! Your payment has been processed successfully.": "আপনার ক্রয়ের জন্য ধন্যবাদ! আপনার পেমেন্ট সফলভাবে প্রক্রিয়াকৃত হয়েছে।", + "Order ID:": "অর্ডার আইডি:" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index 7c419707..c1cee685 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -362,7 +362,6 @@ "Email:": "E-Mail:", "Plan:": "Tarif:", "Amount:": "Betrag:", - "Subscription ID:": "Abo-ID:", "Go to Library": "Zur Bibliothek", "Need help? Contact our support team at support@readest.com": "Brauchen Sie Hilfe? Kontaktieren Sie unser Support-Team unter support@readest.com", "Free Plan": "Gratis-Tarif", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Zusätzliche Themen, Schriftarten, Layoutoptionen und Vorlesefunktionen, Übersetzer, Cloud-Speicherdienste freischalten.", "Purchase Successful!": "Kauf erfolgreich!", "lifetime": "lebenslang", - "Thank you for your purchase! Your payment has been processed successfully.": "Danke für Ihren Kauf! Ihre Zahlung wurde erfolgreich verarbeitet." + "Thank you for your purchase! Your payment has been processed successfully.": "Danke für Ihren Kauf! Ihre Zahlung wurde erfolgreich verarbeitet.", + "Order ID:": "Bestell-ID:" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 20de4ca7..7f9dfe0f 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -363,7 +363,6 @@ "Email:": "Email:", "Plan:": "Πρόγραμμα:", "Amount:": "Ποσό:", - "Subscription ID:": "ID συνδρομής:", "Go to Library": "Μετάβαση στη βιβλιοθήκη", "Need help? Contact our support team at support@readest.com": "Χρειάζεστε βοήθεια; Επικοινωνήστε με την υποστήριξη στο support@readest.com", "Free Plan": "Δωρεάν πρόγραμμα", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ξεκλειδώστε επιπλέον θέματα, γραμματοσειρές, επιλογές διάταξης και δυνατότητες ανάγνωσης, μεταφραστές, υπηρεσίες αποθήκευσης cloud.", "Purchase Successful!": "Η αγορά ήταν επιτυχής!", "lifetime": "διαρκής", - "Thank you for your purchase! Your payment has been processed successfully.": "Σας ευχαριστούμε για την αγορά σας! Η πληρωμή σας έχει επεξεργαστεί με επιτυχία." + "Thank you for your purchase! Your payment has been processed successfully.": "Σας ευχαριστούμε για την αγορά σας! Η πληρωμή σας έχει επεξεργαστεί με επιτυχία.", + "Order ID:": "Αριθμός παραγγελίας:" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index 3562d0c6..6983ef3d 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -393,7 +393,6 @@ "Email:": "Correo electrónico:", "Plan:": "Plan:", "Amount:": "Importe:", - "Subscription ID:": "ID de suscripción:", "Go to Library": "Ir a la biblioteca", "Need help? Contact our support team at support@readest.com": "¿Necesitas ayuda? Contacta con nuestro equipo de soporte en support@readest.com", "Free Plan": "Plan gratuito", @@ -639,5 +638,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Desbloquear temas adicionales, fuentes, opciones de diseño y funciones de lectura en voz alta, traductores, servicios de almacenamiento en la nube.", "Purchase Successful!": "¡Compra Exitosa!", "lifetime": "de por vida", - "Thank you for your purchase! Your payment has been processed successfully.": "¡Gracias por tu compra! Tu pago se ha procesado correctamente." + "Thank you for your purchase! Your payment has been processed successfully.": "¡Gracias por tu compra! Tu pago se ha procesado correctamente.", + "Order ID:": "ID de pedido:" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 400bc888..0add8ef5 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -362,7 +362,6 @@ "Email:": "ایمیل:", "Plan:": "طرح:", "Amount:": "مبلغ:", - "Subscription ID:": "شناسه اشتراک:", "Go to Library": "رفتن به کتابخانه", "Need help? Contact our support team at support@readest.com": "نیاز به کمک دارید؟ با تیم پشتیبانی ما به آدرس support@readest.com تماس بگیرید", "Free Plan": "طرح رایگان", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "باز کردن تم‌ها، فونت‌ها، گزینه‌های چیدمان و خواندن با صدای بلند، مترجم‌ها، خدمات ذخیره‌سازی ابری اضافی.", "Purchase Successful!": "خرید با موفقیت انجام شد!", "lifetime": "مادام‌العمر", - "Thank you for your purchase! Your payment has been processed successfully.": "از خرید شما متشکریم! پرداخت شما با موفقیت انجام شد." -} \ No newline at end of file + "Thank you for your purchase! Your payment has been processed successfully.": "از خرید شما متشکریم! پرداخت شما با موفقیت انجام شد.", + "Order ID:": "شناسه سفارش:" +} diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index 85769563..ed7cd311 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -366,7 +366,6 @@ "Email:": "E-mail :", "Plan:": "Abonnement :", "Amount:": "Montant :", - "Subscription ID:": "ID d’abonnement :", "Go to Library": "Aller à la bibliothèque", "Need help? Contact our support team at support@readest.com": "Besoin d’aide ? Contactez notre équipe à support@readest.com", "Free Plan": "Abonnement gratuit", @@ -639,5 +638,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Débloquer des thèmes supplémentaires, des polices, des options de mise en page et des fonctions de lecture à haute voix, des traducteurs, des services de stockage cloud.", "Purchase Successful!": "Achat Réussi!", "lifetime": "à vie", - "Thank you for your purchase! Your payment has been processed successfully.": "Merci pour votre achat ! Votre paiement a été traité avec succès." + "Thank you for your purchase! Your payment has been processed successfully.": "Merci pour votre achat ! Votre paiement a été traité avec succès.", + "Order ID:": "ID de commande :" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index 21bc0bef..6168a896 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -363,7 +363,6 @@ "Email:": "ईमेल:", "Plan:": "योजना:", "Amount:": "राशि:", - "Subscription ID:": "सदस्यता आईडी:", "Go to Library": "लाइब्रेरी पर जाएँ", "Need help? Contact our support team at support@readest.com": "मदद चाहिए? हमारी सहायता टीम से संपर्क करें: support@readest.com", "Free Plan": "मुफ्त योजना", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "अतिरिक्त थीम, फ़ॉन्ट, लेआउट विकल्प और पढ़ने के लिए, अनुवादक, क्लाउड स्टोरेज सेवाएँ अनलॉक करें।", "Purchase Successful!": "खरीदारी सफल!", "lifetime": "जीवनकाल", - "Thank you for your purchase! Your payment has been processed successfully.": "आपकी खरीदारी के लिए धन्यवाद! आपका भुगतान सफलतापूर्वक संसाधित हो गया है।" + "Thank you for your purchase! Your payment has been processed successfully.": "आपकी खरीदारी के लिए धन्यवाद! आपका भुगतान सफलतापूर्वक संसाधित हो गया है।", + "Order ID:": "ऑर्डर आईडी:" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index 0a4fd67f..2d2cef6d 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -359,7 +359,6 @@ "Email:": "Email:", "Plan:": "Paket:", "Amount:": "Jumlah:", - "Subscription ID:": "ID Langganan:", "Go to Library": "Pergi ke Perpustakaan", "Need help? Contact our support team at support@readest.com": "Perlu bantuan? Hubungi tim dukungan kami di support@readest.com", "Free Plan": "Paket Gratis", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Buka tema tambahan, font, opsi tata letak, dan baca nyaring, penerjemah, layanan penyimpanan cloud.", "Purchase Successful!": "Pembelian Berhasil!", "lifetime": "seumur hidup", - "Thank you for your purchase! Your payment has been processed successfully.": "Terima kasih atas pembelian Anda! Pembayaran Anda telah berhasil diproses." + "Thank you for your purchase! Your payment has been processed successfully.": "Terima kasih atas pembelian Anda! Pembayaran Anda telah berhasil diproses.", + "Order ID:": "ID Pesanan:" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index fe406210..d0887b4a 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -367,7 +367,6 @@ "Email:": "Email:", "Plan:": "Piano:", "Amount:": "Importo:", - "Subscription ID:": "ID Abbonamento:", "Go to Library": "Vai alla Libreria", "Need help? Contact our support team at support@readest.com": "Hai bisogno di aiuto? Contatta il nostro supporto a support@readest.com", "Free Plan": "Piano Gratuito", @@ -639,5 +638,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Sblocca temi aggiuntivi, font, opzioni di layout e lettura ad alta voce, traduttori, servizi di archiviazione cloud.", "Purchase Successful!": "Acquisto Riuscito!", "lifetime": "a vita", - "Thank you for your purchase! Your payment has been processed successfully.": "Grazie per il tuo acquisto! Il pagamento è stato elaborato con successo." + "Thank you for your purchase! Your payment has been processed successfully.": "Grazie per il tuo acquisto! Il pagamento è stato elaborato con successo.", + "Order ID:": "ID Ordine:" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 2cbf6f13..26a90942 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -359,7 +359,6 @@ "Email:": "メール:", "Plan:": "プラン:", "Amount:": "金額:", - "Subscription ID:": "サブスクリプションID:", "Go to Library": "ライブラリへ移動", "Need help? Contact our support team at support@readest.com": "サポートが必要ですか? support@readest.com までお問い合わせください。", "Free Plan": "無料プラン", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "追加のテーマ、フォント、レイアウトオプション、読み上げ、翻訳者、クラウドストレージサービスを解除します。", "Purchase Successful!": "購入成功!", "lifetime": "ライフタイム", - "Thank you for your purchase! Your payment has been processed successfully.": "ご購入ありがとうございます!お支払いが正常に処理されました。" + "Thank you for your purchase! Your payment has been processed successfully.": "ご購入ありがとうございます!お支払いが正常に処理されました。", + "Order ID:": "購入ID:" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index 28850cc5..de6b5771 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -359,7 +359,6 @@ "Email:": "이메일:", "Plan:": "요금제:", "Amount:": "금액:", - "Subscription ID:": "구독 ID:", "Go to Library": "라이브러리로 이동", "Need help? Contact our support team at support@readest.com": "도움이 필요하시면 support@readest.com으로 문의해주세요.", "Free Plan": "무료 요금제", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "추가 테마, 글꼴, 레이아웃 옵션 및 읽기, 번역기, 클라우드 스토리지 서비스를 잠금 해제합니다.", "Purchase Successful!": "구매 성공!", "lifetime": "평생", - "Thank you for your purchase! Your payment has been processed successfully.": "구매해주셔서 감사합니다! 결제가 성공적으로 처리되었습니다." + "Thank you for your purchase! Your payment has been processed successfully.": "구매해주셔서 감사합니다! 결제가 성공적으로 처리되었습니다.", + "Order ID:": "주문 ID:" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index 5565d2cf..86a1be0f 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -361,7 +361,6 @@ "Email:": "E-mail:", "Plan:": "Abonnement:", "Amount:": "Bedrag:", - "Subscription ID:": "Abonnements-ID:", "Go to Library": "Ga naar bibliotheek", "Need help? Contact our support team at support@readest.com": "Hulp nodig? Neem contact op met support via support@readest.com", "Free Plan": "Gratis abonnement", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ontgrendel extra thema's, lettertypen, lay-outopties en voorlezen, vertalers, cloudopslagdiensten.", "Purchase Successful!": "Aankoop Succesvol!", "lifetime": "levenslang", - "Thank you for your purchase! Your payment has been processed successfully.": "Bedankt voor je aankoop! Je betaling is succesvol verwerkt." + "Thank you for your purchase! Your payment has been processed successfully.": "Bedankt voor je aankoop! Je betaling is succesvol verwerkt.", + "Order ID:": "Bestel-ID:" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 122005fb..815724b6 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -371,7 +371,6 @@ "Email:": "E-mail:", "Plan:": "Plan:", "Amount:": "Kwota:", - "Subscription ID:": "ID subskrypcji:", "Go to Library": "Przejdź do biblioteki", "Need help? Contact our support team at support@readest.com": "Potrzebujesz pomocy? Skontaktuj się z naszym zespołem wsparcia: support@readest.com", "Free Plan": "Bezpłatny plan", @@ -643,5 +642,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Odblokuj dodatkowe motywy, czcionki, opcje układu oraz funkcje czytania na głos, tłumacze, usługi przechowywania w chmurze.", "Purchase Successful!": "Zakup Udany!", "lifetime": "dożywotnio", - "Thank you for your purchase! Your payment has been processed successfully.": "Dziękujemy za zakup! Twoja płatność została pomyślnie przetworzona." + "Thank you for your purchase! Your payment has been processed successfully.": "Dziękujemy za zakup! Twoja płatność została pomyślnie przetworzona.", + "Order ID:": "ID zamówienia:" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 619d4329..3bf3d827 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -367,7 +367,6 @@ "Email:": "E-mail:", "Plan:": "Plano:", "Amount:": "Valor:", - "Subscription ID:": "ID da assinatura:", "Go to Library": "Ir para a biblioteca", "Need help? Contact our support team at support@readest.com": "Precisa de ajuda? Entre em contato com nossa equipe de suporte: support@readest.com", "Free Plan": "Plano gratuito", @@ -639,5 +638,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Desbloquear temas adicionais, fontes, opções de layout e leitura em voz alta, tradutores, serviços de armazenamento em nuvem.", "Purchase Successful!": "Compra Bem-Sucedida!", "lifetime": "vitalício", - "Thank you for your purchase! Your payment has been processed successfully.": "Obrigado pela sua compra! Seu pagamento foi processado com sucesso." + "Thank you for your purchase! Your payment has been processed successfully.": "Obrigado pela sua compra! Seu pagamento foi processado com sucesso.", + "Order ID:": "ID do Pedido:" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index a2c5b405..0907b1b7 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -371,7 +371,6 @@ "Email:": "Электронная почта:", "Plan:": "План:", "Amount:": "Сумма:", - "Subscription ID:": "ID подписки:", "Go to Library": "Перейти в библиотеку", "Need help? Contact our support team at support@readest.com": "Нужна помощь? Свяжитесь с нашей поддержкой: support@readest.com", "Free Plan": "Бесплатный план", @@ -643,5 +642,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Разблокировать дополнительные темы, шрифты, параметры макета и чтения вслух, переводчики, услуги облачного хранилища.", "Purchase Successful!": "Покупка Успешна!", "lifetime": "пожизненный", - "Thank you for your purchase! Your payment has been processed successfully.": "Спасибо за вашу покупку! Ваш платеж был успешно обработан." + "Thank you for your purchase! Your payment has been processed successfully.": "Спасибо за вашу покупку! Ваш платеж был успешно обработан.", + "Order ID:": "ID заказа:" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 4a22224a..92a39a5e 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -376,7 +376,6 @@ "Email:": "ඊමේල්:", "Plan:": "සැලැස්ම:", "Amount:": "මුදල:", - "Subscription ID:": "දායකත්ව ID:", "Go to Library": "පුස්තකාලයට යන්න", "Need help? Contact our support team at support@readest.com": "උදව් අවශ්‍යද? support@readest.com හි අපගේ සහාය කණ්ඩායමට සම්බන්ධ වන්න", "Free Plan": "නොමිලේ සැලැස්ම", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "අමතර තේමාවන්, ෆොන්ට්, ආකෘති විකල්ප සහ පවසා කියවීම, පරිවර්තක, කලාප ගබඩා සේවා විවෘත කරන්න.", "Purchase Successful!": "මිලදී ගැනීම සාර්ථකයි!", "lifetime": "පොදුකාලීන", - "Thank you for your purchase! Your payment has been processed successfully.": "ඔබේ මිලදී ගැනීමට ස්තූතියි! ඔබේ ගෙවීම සාර්ථකව සැකසිණි." + "Thank you for your purchase! Your payment has been processed successfully.": "ඔබේ මිලදී ගැනීමට ස්තූතියි! ඔබේ ගෙවීම සාර්ථකව සැකසිණි.", + "Order ID:": "ඇණවුම් හැඳුනුම්පත:" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index 766908b3..b8483216 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -347,7 +347,6 @@ "Email:": "E-post:", "Plan:": "Plan:", "Amount:": "Belopp:", - "Subscription ID:": "Prenumerations-ID:", "Need help? Contact our support team at support@readest.com": "Behöver du hjälp? Kontakta vår support på support@readest.com", "Free Plan": "Gratisplan", "month": "månad", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Lås upp ytterligare teman, typsnitt, layoutalternativ och uppläsning, översättare, molnlagringstjänster.", "Purchase Successful!": "Köp lyckades!", "lifetime": "livstid", - "Thank you for your purchase! Your payment has been processed successfully.": "Tack för ditt köp! Din betalning har behandlats." + "Thank you for your purchase! Your payment has been processed successfully.": "Tack för ditt köp! Din betalning har behandlats.", + "Order ID:": "Order-ID:" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 49990ad5..abf13194 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -376,7 +376,6 @@ "Email:": "மின்னஞ்சல்:", "Plan:": "திட்டம்:", "Amount:": "தொகை:", - "Subscription ID:": "சந்தா ID:", "Go to Library": "நூலகத்திற்கு செல்லவும்", "Need help? Contact our support team at support@readest.com": "உதவி தேவையா? support@readest.com இல் எங்கள் ஆதரவு குழுவைத் தொடர்பு கொள்ளவும்", "Free Plan": "இலவச திட்டம்", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "கூடுதல் தீமைகள், எழுத்துருக்கள், வடிவமைப்பு விருப்பங்கள் மற்றும் வாசிக்க, மொழிபெயர்க்க, மேக சேமிப்பு சேவைகளைத் திறக்கவும்.", "Purchase Successful!": "கொள்முதல் வெற்றிகரமாக முடிந்தது!", "lifetime": "ஆயுள்திருத்தம்", - "Thank you for your purchase! Your payment has been processed successfully.": "உங்கள் வாங்குதலுக்கு நன்றி! உங்கள் கட்டணம் வெற்றிகரமாக செயல்படுத்தப்பட்டது." + "Thank you for your purchase! Your payment has been processed successfully.": "உங்கள் வாங்குதலுக்கு நன்றி! உங்கள் கட்டணம் வெற்றிகரமாக செயல்படுத்தப்பட்டது.", + "Order ID:": "ஆர்டர் ஐடி:" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 734f6318..743ff710 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -312,7 +312,6 @@ "Email:": "อีเมล:", "Plan:": "แผน:", "Amount:": "จำนวนเงิน:", - "Subscription ID:": "รหัสสมาชิก:", "Go to Library": "ไปที่คลังหนังสือ", "Need help? Contact our support team at support@readest.com": "ต้องการความช่วยเหลือ? ติดต่อทีมสนับสนุนที่ support@readest.com", "Free Plan": "แผนฟรี", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "ปลดล็อกธีมเพิ่มเติม ฟอนต์ ตัวเลือกการจัดรูปแบบ และฟีเจอร์การอ่านออกเสียง ตัวแปล และบริการพื้นที่เก็บข้อมูลคลาวด์", "Purchase Successful!": "การซื้อสำเร็จ!", "lifetime": "ตลอดชีพ", - "Thank you for your purchase! Your payment has been processed successfully.": "ขอบคุณสำหรับการซื้อ! การชำระเงินของคุณได้รับการประมวลผลเรียบร้อยแล้ว" + "Thank you for your purchase! Your payment has been processed successfully.": "ขอบคุณสำหรับการซื้อ! การชำระเงินของคุณได้รับการประมวลผลเรียบร้อยแล้ว", + "Order ID:": "รหัสคำสั่งซื้อ:" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 7c2a1ca1..95d411fb 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -363,7 +363,6 @@ "Email:": "E-posta:", "Plan:": "Plan:", "Amount:": "Tutar:", - "Subscription ID:": "Abonelik ID'si:", "Go to Library": "Kütüphaneye Git", "Need help? Contact our support team at support@readest.com": "Yardım mı gerekiyor? Destek ekibimizle iletişime geçin: support@readest.com", "Free Plan": "Ücretsiz Plan", @@ -635,5 +634,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Ek tema, yazı tipi, düzen seçeneklerini ve sesli okuma, çevirmenler, bulut depolama hizmetlerini açın.", "Purchase Successful!": "Satın Alma Başarılı!", "lifetime": "ömür boyu", - "Thank you for your purchase! Your payment has been processed successfully.": "Satın alma işleminiz için teşekkürler! Ödemeniz başarıyla işlendi." + "Thank you for your purchase! Your payment has been processed successfully.": "Satın alma işleminiz için teşekkürler! Ödemeniz başarıyla işlendi.", + "Order ID:": "Sipariş Kimliği:" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 03e312bb..812687f5 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -371,7 +371,6 @@ "Email:": "Електронна пошта:", "Plan:": "План:", "Amount:": "Сума:", - "Subscription ID:": "ID підписки:", "Go to Library": "Перейти до бібліотеки", "Need help? Contact our support team at support@readest.com": "Потрібна допомога? Зверніться до служби підтримки: support@readest.com", "Free Plan": "Безкоштовний план", @@ -643,5 +642,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Відкрийте додаткові теми, шрифти, параметри макета та функції читання вголос, перекладачі, послуги хмарного зберігання.", "Purchase Successful!": "Покупка успішна!", "lifetime": "довічний", - "Thank you for your purchase! Your payment has been processed successfully.": "Дякуємо за вашу покупку! Ваш платіж успішно оброблено." + "Thank you for your purchase! Your payment has been processed successfully.": "Дякуємо за вашу покупку! Ваш платіж успішно оброблено.", + "Order ID:": "ID замовлення:" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index e37a4a75..d5400f61 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -359,7 +359,6 @@ "Email:": "Email:", "Plan:": "Gói:", "Amount:": "Số tiền:", - "Subscription ID:": "ID đăng ký:", "Go to Library": "Đi đến Thư viện", "Need help? Contact our support team at support@readest.com": "Cần hỗ trợ? Liên hệ nhóm hỗ trợ tại support@readest.com", "Free Plan": "Gói miễn phí", @@ -631,5 +630,6 @@ "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "Mở khóa các chủ đề, phông chữ, tùy chọn bố cục và đọc to, dịch giả, dịch vụ lưu trữ đám mây bổ sung.", "Purchase Successful!": "Mua hàng thành công!", "lifetime": "trọn đời", - "Thank you for your purchase! Your payment has been processed successfully.": "Cảm ơn bạn đã mua hàng! Thanh toán của bạn đã được xử lý thành công." + "Thank you for your purchase! Your payment has been processed successfully.": "Cảm ơn bạn đã mua hàng! Thanh toán của bạn đã được xử lý thành công.", + "Order ID:": "Mã đơn hàng:" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index f69ac962..38ed5379 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -359,7 +359,6 @@ "Email:": "邮箱:", "Plan:": "套餐:", "Amount:": "金额:", - "Subscription ID:": "订阅 ID:", "Go to Library": "前往书库", "Need help? Contact our support team at support@readest.com": "需要帮助?请联系支持团队:support@readest.com", "Free Plan": "免费套餐", @@ -622,7 +621,7 @@ "Failed to open the book file": "无法打开书籍文件", "On-Demand Purchase": "按需购买", "Full Customization": "完全自定义", - "Lifetime Plan": "终身计划", + "Lifetime Plan": "终身套餐", "One-Time Payment": "一次性付款", "Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "进行一次性付款以享受在所有设备上对特定功能的终身访问。仅在需要时购买特定功能或服务。", "Expand Cloud Sync Storage": "扩展云同步存储", @@ -630,6 +629,7 @@ "Unlock All Customization Options": "解锁所有自定义选项", "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "解锁其他主题、字体、布局选项以及朗读、翻译、云存储服务。", "Purchase Successful!": "购买成功!", - "lifetime": "终身", - "Thank you for your purchase! Your payment has been processed successfully.": "感谢您的购买!您的付款已成功处理。" + "lifetime": "永久", + "Thank you for your purchase! Your payment has been processed successfully.": "感谢您的购买!您的付款已成功处理。", + "Order ID:": "订单号:" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index f92a25c7..6f0719e9 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -359,7 +359,6 @@ "Email:": "電子郵件:", "Plan:": "方案:", "Amount:": "金額:", - "Subscription ID:": "訂閱 ID:", "Go to Library": "前往書庫", "Need help? Contact our support team at support@readest.com": "需要協助?請聯絡支援團隊:support@readest.com", "Free Plan": "免費方案", @@ -622,7 +621,7 @@ "Failed to open the book file": "無法打開書籍文件", "On-Demand Purchase": "按需購買", "Full Customization": "完全自定義", - "Lifetime Plan": "終身計劃", + "Lifetime Plan": "終身方案", "One-Time Payment": "一次性付款", "Make a single payment to enjoy lifetime access to specific features on all devices. Purchase specific features or services only when you need them.": "進行一次性付款以享受在所有設備上對特定功能的終身訪問。僅在需要時購買特定功能或服務。", "Expand Cloud Sync Storage": "擴展雲同步存儲", @@ -630,6 +629,7 @@ "Unlock All Customization Options": "解鎖所有自定義選項", "Unlock additional themes, fonts, layout options and read aloud, translators, cloud storage services.": "解鎖其他主題、字體、佈局選項以及朗讀、翻譯、雲存儲服務。", "Purchase Successful!": "購買成功!", - "lifetime": "終身", - "Thank you for your purchase! Your payment has been processed successfully.": "感謝您的購買!您的付款已成功處理。" + "lifetime": "永久", + "Thank you for your purchase! Your payment has been processed successfully.": "感謝您的購買!您的付款已成功處理。", + "Order ID:": "訂單編號:" } diff --git a/apps/readest-app/scripts/release-google-play.sh b/apps/readest-app/scripts/release-google-play.sh index d77a21a9..087c6533 100755 --- a/apps/readest-app/scripts/release-google-play.sh +++ b/apps/readest-app/scripts/release-google-play.sh @@ -39,7 +39,7 @@ if grep -q 'MANAGE_EXTERNAL_STORAGE' "$MANIFEST"; then fi echo "🚀 Running: pnpm tauri android build" -pnpm tauri android build +pnpm tauri android build --config src-tauri/tauri.playstore.conf.json # --- ADD PERMISSION BACK AFTER BUILD --- if ! grep -q 'REQUEST_INSTALL_PACKAGES' "$MANIFEST"; then diff --git a/apps/readest-app/src-tauri/tauri.conf.json b/apps/readest-app/src-tauri/tauri.conf.json index e4a7eed7..b6a11243 100644 --- a/apps/readest-app/src-tauri/tauri.conf.json +++ b/apps/readest-app/src-tauri/tauri.conf.json @@ -15,7 +15,7 @@ "security": { "csp": { "default-src": "'self' 'unsafe-inline' blob: data: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost", - "connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com", + "connect-src": "'self' blob: data: asset: http://asset.localhost ipc: http://ipc.localhost http://*:* https://*:* https://*.sentry.io https://*.posthog.com https://*.deepl.com https://*.wikipedia.org https://*.wiktionary.org https://*.supabase.co https://*.readest.com wss://speech.platform.bing.com https://*.cloudflarestorage.com https://translate.googleapis.com https://translate.toil.cc https://*.microsofttranslator.com https://edge.microsoft.com https://*.googleusercontent.com", "img-src": "'self' blob: data: asset: http://asset.localhost https://*", "style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost https://cdn.jsdelivr.net https://fonts.googleapis.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com", "font-src": "'self' blob: data: asset: http://asset.localhost tauri: https://db.onlinewebfonts.com https://cdn.jsdelivr.net https://fonts.gstatic.com https://chinese-fonts-cdn.netlify.app https://cdnjs.cloudflare.com", @@ -64,8 +64,7 @@ } }, "android": { - "minSdkVersion": 26, - "autoIncrementVersionCode": true + "minSdkVersion": 26 }, "iOS": { "developmentTeam": "J5W48D69VR", diff --git a/apps/readest-app/src/__tests__/integration/apple-iap.test.ts b/apps/readest-app/src/__tests__/integration/apple-iap.test.ts index aaf988a9..dd6d2796 100644 --- a/apps/readest-app/src/__tests__/integration/apple-iap.test.ts +++ b/apps/readest-app/src/__tests__/integration/apple-iap.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll } from 'vitest'; -import { AppleIAPVerifier, createAppleIAPVerifier } from '@/libs/payment/apple/verifier'; +import { AppleIAPVerifier, createAppleIAPVerifier } from '@/libs/payment/iap/apple/verifier'; const SKIP_IAP_API_TESTS = !process.env['ENABLE_IAP_API_TESTS']; const REAL_TEST_DATA = { 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 5580452d..a1f62073 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 @@ -1,104 +1,15 @@ import { z } from 'zod'; import { NextResponse } from 'next/server'; -import { getAppleIAPVerifier } from '@/libs/payment/apple/verifier'; -import { createSupabaseAdminClient } from '@/utils/supabase'; +import { IAPError } from '@/libs/payment/iap/types'; import { validateUserAndToken } from '@/utils/access'; -import { IAPError } from '@/types/error'; +import { getAppleIAPVerifier } from '@/libs/payment/iap/apple/verifier'; +import { processPurchaseData, VerifiedPurchase } from '@/libs/payment/iap/apple/server'; const iapVerificationSchema = z.object({ transactionId: z.string().min(1, 'Transaction ID is required'), originalTransactionId: z.string().min(1, 'Original Transaction ID is required'), }); -const PRODUCT_MAP: Record = { - '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) => { - return PRODUCT_MAP[productId] || productId; -}; - -const getProductPlan = (productId: string) => { - if (productId.includes('plus')) { - return 'plus'; - } else if (productId.includes('pro')) { - return 'pro'; - } - return 'free'; -}; - -interface Purchase { - status: string; - customerEmail: string; - subscriptionId: string; - planName: string; - productId: string; - platform: string; - transactionId: string; - originalTransactionId: string; - purchaseDate?: string; - expiresDate?: string | null; - quantity: number; - environment: string; - bundleId: string; - webOrderLineItemId?: string; - subscriptionGroupIdentifier?: string; - type?: string; - revocationDate?: string | null; - revocationReason?: number | null; -} - -async function updateUserSubscription(userId: string, purchase: Purchase) { - try { - const supabase = createSupabaseAdminClient(); - const { data, error } = await supabase.from('apple_iap_subscriptions').upsert( - { - user_id: userId, - platform: purchase.platform, - product_id: purchase.productId, - transaction_id: purchase.transactionId, - original_transaction_id: purchase.originalTransactionId, - status: purchase.status === 'active' ? 'active' : 'expired', - purchase_date: purchase.purchaseDate, - expires_date: purchase.expiresDate, - environment: purchase.environment, - bundle_id: purchase.bundleId, - quantity: purchase.quantity || 1, - auto_renew_status: true, - web_order_line_item_id: purchase.webOrderLineItemId, - subscription_group_identifier: purchase.subscriptionGroupIdentifier, - created_at: new Date(), - updated_at: new Date(), - }, - { - onConflict: 'user_id,original_transaction_id', - }, - ); - - if (error) { - console.error('Database update error:', error); - throw new Error(`Database update failed: ${error.message}`); - } - - const plan = await getProductPlan(purchase.productId); - await supabase - .from('plans') - .update({ - plan: ['active', 'trialing'].includes(purchase.status) ? plan : 'free', - status: purchase.status, - }) - .eq('id', userId); - - return data; - } catch (error) { - console.error('Failed to update user subscription:', error); - throw error; - } -} - export async function POST(request: Request) { const body = await request.json(); let validatedInput; @@ -123,29 +34,13 @@ export async function POST(request: Request) { } try { - const supabase = createSupabaseAdminClient(); - const { data: existingSubscription } = await supabase - .from('apple_iap_subscriptions') - .select('*') - .eq('original_transaction_id', originalTransactionId) - .single(); - - console.log('Existing subscription:', existingSubscription); - // 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 }, - ); - } - const defaultIAPVerifier = getAppleIAPVerifier(); const verificationResult = await defaultIAPVerifier.verifyTransaction(originalTransactionId); if (!verificationResult.success) { console.error('Apple verification failed:', verificationResult.error); return NextResponse.json( { - error: verificationResult.error || IAPError.UNKNOWN_ERROR, + error: verificationResult.error || IAPError.TRANSACTION_CANNOT_BE_VERIFIED, purchase: null, }, { status: 400 }, @@ -158,37 +53,15 @@ export async function POST(request: Request) { productId: transaction.productId, environment: transaction.environment, }); - if (transaction.environment === 'Sandbox' && process.env.NODE_ENV === 'production') { - console.warn('Sandbox transaction in production environment'); - } - - const purchase = { - status: verificationResult.status!, - customerEmail: user.email!, - subscriptionId: transaction.webOrderLineItemId || transaction.originalTransactionId, - planName: getProductName(transaction.productId), - planType: verificationResult.planType, - productId: transaction.productId, - platform: 'ios', - transactionId: transaction.transactionId, - originalTransactionId: transaction.originalTransactionId, - purchaseDate: verificationResult.purchaseDate?.toISOString(), - expiresDate: verificationResult.expiresDate?.toISOString() || null, - quantity: transaction.quantity, - environment: transaction.environment.toLowerCase(), - bundleId: transaction.bundleId, - webOrderLineItemId: transaction.webOrderLineItemId, - subscriptionGroupIdentifier: transaction.subscriptionGroupIdentifier, - type: transaction.type, - revocationDate: verificationResult.revocationDate?.toISOString() || null, - revocationReason: verificationResult.revocationReason, - }; try { - await updateUserSubscription(user.id, purchase); + const purchase: VerifiedPurchase = await processPurchaseData(user, verificationResult); + return NextResponse.json({ + purchase, + error: null, + }); } catch (dbError) { console.error('Database update failed:', dbError); - return NextResponse.json( { error: IAPError.TRANSACTION_SERVICE_UNAVAILABLE, @@ -197,11 +70,6 @@ export async function POST(request: Request) { { status: 500 }, ); } - - return NextResponse.json({ - purchase, - error: null, - }); } catch (error) { console.error('IAP verification error:', error); return NextResponse.json( diff --git a/apps/readest-app/src/app/api/google/iap-verify/route.ts b/apps/readest-app/src/app/api/google/iap-verify/route.ts index c7b013f9..158d9d86 100644 --- a/apps/readest-app/src/app/api/google/iap-verify/route.ts +++ b/apps/readest-app/src/app/api/google/iap-verify/route.ts @@ -1,14 +1,9 @@ import { z } from 'zod'; import { NextResponse } from 'next/server'; -import { - getGoogleIAPVerifier, - ProductPurchase, - SubscriptionPurchase, -} from '@/libs/payment/google/verifier'; -import { createSupabaseAdminClient } from '@/utils/supabase'; import { validateUserAndToken } from '@/utils/access'; -import { IAPError } from '@/types/error'; -import { PlanType } from '@/types/quota'; +import { getGoogleIAPVerifier, VerifyPurchaseParams } from '@/libs/payment/iap/google/verifier'; +import { processPurchaseData, VerifiedPurchase } from '@/libs/payment/iap/google/server'; +import { IAPError } from '@/libs/payment/iap/types'; const iapVerificationSchema = z.object({ packageName: z.string().min(1, 'Package name is required'), @@ -17,112 +12,6 @@ const iapVerificationSchema = z.object({ purchaseToken: z.string().min(1, 'Purchase token is required'), }); -const PRODUCT_MAP: Record = { - '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) => { - return PRODUCT_MAP[productId] || productId; -}; - -const getProductPlan = (productId: string) => { - if (productId.includes('plus')) { - return 'plus'; - } else if (productId.includes('pro')) { - return 'pro'; - } - return 'free'; -}; - -interface Purchase { - status: string; - customerEmail: string; - subscriptionId: string; - planName: string; - planType: PlanType; - productId: string; - platform: string; - purchaseToken: string; - orderId: string; - purchaseDate?: string; - expiresDate?: string | null; - quantity: number; - environment: string; - packageName: string; - purchaseState?: number | null; - acknowledgementState?: number | null; - autoRenewing?: boolean | null; - priceAmountMicros?: string | null; - priceCurrencyCode?: string | null; - countryCode?: string | null; - developerPayload?: string | null; - linkedPurchaseToken?: string | null; - obfuscatedExternalAccountId?: string | null; - obfuscatedExternalProfileId?: string | null; - cancelReason?: number | null; - userCancellationTimeMillis?: string | null; -} - -async function updateUserSubscription(userId: string, purchase: Purchase) { - try { - const supabase = createSupabaseAdminClient(); - const { data, error } = await supabase.from('google_iap_subscriptions').upsert( - { - user_id: userId, - platform: purchase.platform, - product_id: purchase.productId, - purchase_token: purchase.purchaseToken, - order_id: purchase.orderId, - status: purchase.status === 'active' ? 'active' : 'expired', - purchase_date: purchase.purchaseDate, - expires_date: purchase.expiresDate, - environment: purchase.environment, - package_name: purchase.packageName, - quantity: purchase.quantity || 1, - auto_renew_status: purchase.autoRenewing || false, - purchase_state: purchase.purchaseState, - acknowledgement_state: purchase.acknowledgementState, - price_amount_micros: purchase.priceAmountMicros, - price_currency_code: purchase.priceCurrencyCode, - country_code: purchase.countryCode, - developer_payload: purchase.developerPayload, - linked_purchase_token: purchase.linkedPurchaseToken, - obfuscated_external_account_id: purchase.obfuscatedExternalAccountId, - obfuscated_external_profile_id: purchase.obfuscatedExternalProfileId, - cancel_reason: purchase.cancelReason, - user_cancellation_time_millis: purchase.userCancellationTimeMillis, - created_at: new Date(), - updated_at: new Date(), - }, - { - onConflict: 'user_id,order_id', - }, - ); - - if (error) { - console.error('Database update error:', error); - throw new Error(`Database update failed: ${error.message}`); - } - - const plan = getProductPlan(purchase.productId); - await supabase - .from('plans') - .update({ - plan: ['active', 'trialing'].includes(purchase.status) ? plan : 'free', - status: purchase.status, - }) - .eq('id', userId); - - return data; - } catch (error) { - console.error('Failed to update user subscription:', error); - throw error; - } -} - export async function POST(request: Request) { const body = await request.json(); let validatedInput; @@ -139,7 +28,7 @@ export async function POST(request: Request) { ); } } - const { purchaseToken, productId, packageName } = validatedInput!; + const { orderId, purchaseToken, productId, packageName } = validatedInput!; const { user, token } = await validateUserAndToken(request.headers.get('authorization')); if (!user || !token) { @@ -147,39 +36,14 @@ export async function POST(request: Request) { } try { - const supabase = createSupabaseAdminClient(); - - // Check if this purchase already exists - if (purchaseToken) { - const { data: existingSubscription } = await supabase - .from('google_iap_subscriptions') - .select('*') - .eq('purchase_token', purchaseToken) - .single(); - - console.log('Existing subscription:', existingSubscription); - - // 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 }, - ); - } - } - const googleIAPVerifier = getGoogleIAPVerifier(); - const verificationResult = await googleIAPVerifier.verifyPurchase({ - purchaseToken, - productId, - packageName, - }); - + const verifyParams: VerifyPurchaseParams = { orderId, purchaseToken, productId, packageName }; + const verificationResult = await googleIAPVerifier.verifyPurchase(verifyParams); if (!verificationResult.success) { console.error('Google verification failed:', verificationResult.error); return NextResponse.json( { - error: verificationResult.error || IAPError.UNKNOWN_ERROR, + error: verificationResult.error || IAPError.TRANSACTION_CANNOT_BE_VERIFIED, purchase: null, }, { status: 400 }, @@ -187,103 +51,29 @@ export async function POST(request: Request) { } const purchaseData = verificationResult.purchaseData!; - const isSubscription = verificationResult.purchaseType === 'subscription'; console.log('Google verification successful:', { orderId: purchaseData.orderId, productId: productId, purchaseState: purchaseData.purchaseState, }); - // Check environment (test purchases have specific patterns in orderId) - const isTestPurchase = purchaseData.purchaseType === 0; // 0 = Test, 1 = Promo, undefined = Real - if (isTestPurchase && process.env.NODE_ENV === 'production') { - console.warn('Test purchase in production environment'); - } - - let purchase: Purchase; - - if (isSubscription) { - const subData = purchaseData as SubscriptionPurchase; - purchase = { - status: verificationResult.status!, - customerEmail: user.email!, - subscriptionId: subData.orderId || purchaseToken, - planName: getProductName(productId), - planType: 'subscription', - productId: productId, - platform: 'android', - purchaseToken: purchaseToken, - orderId: subData.orderId || '', - purchaseDate: verificationResult.purchaseDate?.toISOString(), - expiresDate: verificationResult.expiresDate?.toISOString() || null, - quantity: subData.quantity || 1, - environment: isTestPurchase ? 'sandbox' : 'production', - packageName: packageName, - purchaseState: subData.purchaseState, - acknowledgementState: subData.acknowledgementState, - autoRenewing: subData.autoRenewing, - priceAmountMicros: subData.priceAmountMicros, - priceCurrencyCode: subData.priceCurrencyCode, - countryCode: subData.countryCode, - developerPayload: subData.developerPayload, - linkedPurchaseToken: subData.linkedPurchaseToken, - obfuscatedExternalAccountId: subData.obfuscatedExternalAccountId, - obfuscatedExternalProfileId: subData.obfuscatedExternalProfileId, - cancelReason: subData.cancelReason, - userCancellationTimeMillis: subData.userCancellationTimeMillis, - }; - } else { - const prodData = purchaseData as ProductPurchase; - purchase = { - status: verificationResult.status!, - customerEmail: user.email!, - subscriptionId: prodData.orderId || purchaseToken, - planName: getProductName(productId), - planType: 'purchase', - productId: productId, - platform: 'android', - purchaseToken: purchaseToken, - orderId: prodData.orderId || '', - purchaseDate: verificationResult.purchaseDate?.toISOString(), - expiresDate: null, // One-time purchases don't expire - quantity: prodData.quantity || 1, - environment: isTestPurchase ? 'sandbox' : 'production', - packageName: packageName, - purchaseState: prodData.purchaseState, - acknowledgementState: prodData.acknowledgementState, - autoRenewing: false, // Not applicable for one-time purchases - priceAmountMicros: undefined, - priceCurrencyCode: prodData.regionCode, - countryCode: prodData.regionCode, - developerPayload: prodData.developerPayload, - linkedPurchaseToken: undefined, - obfuscatedExternalAccountId: prodData.obfuscatedExternalAccountId, - obfuscatedExternalProfileId: prodData.obfuscatedExternalProfileId, - cancelReason: null, - userCancellationTimeMillis: null, - }; - } - - // Acknowledge the purchase if needed - if (purchaseData.acknowledgementState === 0) { - try { - await googleIAPVerifier.acknowledgePurchase({ - purchaseToken, - productId, - packageName, - }); - purchase.acknowledgementState = 1; - } catch (ackError) { - console.error('Failed to acknowledge purchase:', ackError); - // Continue even if acknowledgement fails - } - } - + let purchase: VerifiedPurchase; try { - await updateUserSubscription(user.id, purchase); + purchase = await processPurchaseData(user, verifyParams, verificationResult); + if (verificationResult.purchaseData?.acknowledgementState === 0) { + try { + await googleIAPVerifier.acknowledgePurchase(verifyParams); + purchase.acknowledgementState = 1; + } catch (ackError) { + console.error('Failed to acknowledge purchase:', ackError); + } + } + return NextResponse.json({ + purchase, + error: null, + }); } catch (dbError) { console.error('Database update failed:', dbError); - return NextResponse.json( { error: IAPError.TRANSACTION_SERVICE_UNAVAILABLE, @@ -292,11 +82,6 @@ export async function POST(request: Request) { { status: 500 }, ); } - - return NextResponse.json({ - purchase, - error: null, - }); } catch (error) { console.error('IAP verification error:', error); return NextResponse.json( diff --git a/apps/readest-app/src/app/user/components/PlanNavigation.tsx b/apps/readest-app/src/app/user/components/PlanNavigation.tsx index 61302a7b..33119b6d 100644 --- a/apps/readest-app/src/app/user/components/PlanNavigation.tsx +++ b/apps/readest-app/src/app/user/components/PlanNavigation.tsx @@ -1,17 +1,14 @@ import { useTranslation } from '@/hooks/useTranslation'; +import { UserPlan } from '@/types/quota'; import { PlanDetails } from '../utils/plan'; interface PlanNavigationProps { allPlans: PlanDetails[]; - currentPlanIndex: number; - onSelectPlan: (index: number) => void; + currentPlan: UserPlan; + onSelectPlan: (plan: UserPlan) => void; } -const PlanNavigation: React.FC = ({ - allPlans, - currentPlanIndex, - onSelectPlan, -}) => { +const PlanNavigation: React.FC = ({ allPlans, currentPlan, onSelectPlan }) => { const _ = useTranslation(); return ( @@ -24,9 +21,9 @@ const PlanNavigation: React.FC = ({ {allPlans.map((plan, index) => (