fix(native): Android offers TREK Cloud while orders are closed and says a refused purchase is refunded

The licence testers of the internal test buy from a release build, so the
plan screen no longer hides the purchase on Android before the opening.
Their purchases are Play test purchases, which the cloud serves. A real
purchase made before the opening is refused with ordering-closed and never
acknowledged, so Google Play refunds it after three days; the claim and the
restore now say that instead of a general error. The app still acknowledges
a purchase only after a redeem that returned an instance. iOS is unchanged.
This commit is contained in:
Maurice
2026-11-08 02:31:13 +00:00
parent 796f39fe1f
commit dedbf16f03
31 changed files with 123 additions and 7 deletions
+15 -7
View File
@@ -5,6 +5,7 @@ import { StatusBar, Style } from '@capacitor/status-bar';
import logoUrl from '../public/icons/icon-white.svg';
import logoBlack from '../public/logo-dark.svg'; // #000 wordmark, for light backgrounds
import logoWhite from '../public/logo-light.svg'; // #fff wordmark, for dark backgrounds
import { opensSoon, refundedBeforeOpening } from '../src/native/cloudOrders';
import { fakeIapClient, getIapClient, productIdFor, type Purchase } from '../src/native/iap';
import { CLOUD_TERMS_URL, CLOUD_URL, PRIVACY_URL, SERVER_DOCS_URL, STORE_TERMS_URL } from '../src/native/links';
import { probeDemo, probeServer, type ProbeFailure, type ProbeGet } from '../src/native/serverAddress';
@@ -429,12 +430,14 @@ function offerFor(offer: Offer, plan: CloudPlan, interval: 'month' | 'year'): {
async function loadOffer(list: CloudPlans): Promise<Offer> {
const available = await iapReady;
// Closed: a store purchase would be refused at the cloud, so only a test build may buy.
const opensSoon = available && list.closed && !(await iap.isSandbox().catch(() => false));
const canBuy = available && !opensSoon;
if (!canBuy || iap === fakeIapClient) return { canBuy, storePrices: null, opensSoon };
// Closed: a real purchase is refused at the cloud, so only a test build may buy on iOS;
// Android keeps the button for the licence testers (see cloudOrders.ts).
const sandbox = list.closed && !onPlay ? await iap.isSandbox().catch(() => false) : false;
const soon = opensSoon({ available, closed: list.closed, sandbox, onPlay });
const canBuy = available && !soon;
if (!canBuy || iap === fakeIapClient) return { canBuy, storePrices: null, opensSoon: soon };
const ids = list.plans.flatMap((p) => [productIdFor(p.id, 'month'), productIdFor(p.id, 'year')]);
return { canBuy, storePrices: await iap.prices(ids), opensSoon };
return { canBuy, storePrices: await iap.prices(ids), opensSoon: soon };
}
function planCard(plan: CloudPlan, interval: 'month' | 'year', offer: Offer): HTMLElement {
@@ -526,7 +529,10 @@ function planFooter(offer: Offer, plans: CloudPlan[]): Node[] {
void notify(NotificationType.Error);
restore.disabled = false;
status.className = 'hint bad';
status.textContent = t('native.plans.restoreFailed');
// Bought before the opening: refused, unacknowledged, and refunded by Google Play.
status.textContent = refundedBeforeOpening(redeemed.code, onPlay)
? t('native.claim.closedRefund')
: t('native.plans.restoreFailed');
});
// A payment that was pending (cash, some carrier billing) and has cleared since:
@@ -838,7 +844,9 @@ function showClaim(
slugNote.textContent = t('native.claim.taken');
slugNote.className = 'hint bad';
} else {
error.textContent = t('native.claim.redeemFailed');
error.textContent = refundedBeforeOpening(redeemed.code, onPlay)
? t('native.claim.closedRefund')
: t('native.claim.redeemFailed');
}
});
+35
View File
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest';
import { opensSoon, refundedBeforeOpening } from './cloudOrders';
describe('opensSoon', () => {
const base = { available: true, closed: true, sandbox: false, onPlay: false };
it('FE-NATIVE-ORDERS-001: closed orders hide the purchase from an App Store build', () => {
expect(opensSoon(base)).toBe(true);
});
it('FE-NATIVE-ORDERS-002: a TestFlight build may buy while orders are closed', () => {
expect(opensSoon({ ...base, sandbox: true })).toBe(false);
});
it('FE-NATIVE-ORDERS-003: an Android release build keeps the purchase while orders are closed', () => {
expect(opensSoon({ ...base, onPlay: true })).toBe(false);
});
it('FE-NATIVE-ORDERS-004: open orders and a binary without a store never say "opens soon"', () => {
expect(opensSoon({ ...base, closed: false })).toBe(false);
expect(opensSoon({ ...base, available: false })).toBe(false);
});
});
describe('refundedBeforeOpening', () => {
it('FE-NATIVE-ORDERS-005: a purchase refused as ordering-closed on Android is the refunded one', () => {
expect(refundedBeforeOpening('ordering-closed', true)).toBe(true);
});
it('FE-NATIVE-ORDERS-006: other refusals, and ordering-closed on iOS, keep the general message', () => {
expect(refundedBeforeOpening('ordering-closed', false)).toBe(false);
expect(refundedBeforeOpening('redeem-failed', true)).toBe(false);
expect(refundedBeforeOpening(undefined, true)).toBe(false);
});
});
+26
View File
@@ -0,0 +1,26 @@
/**
* What the app's plan screens do while TREK Cloud's orders are closed, apart
* from the screens themselves so it can be tested.
*
* On iOS only a test build (TestFlight, App Review) may buy then: the App Store
* charges it nothing and the cloud serves it. On Android the button shows in
* every build, so the licence testers of the internal test can buy from a
* release build; their purchases are Play test purchases, which the cloud
* serves. A real purchase made before the opening is refused at the cloud with
* `ordering-closed` and never acknowledged, so Google Play refunds it after
* three days, and the app says so instead of showing a general error.
*/
/** Whether the plans show without a way to buy: orders are closed and this build may not buy before the opening. */
export function opensSoon(opts: { available: boolean; closed: boolean; sandbox: boolean; onPlay: boolean }): boolean {
return opts.available && opts.closed && !opts.sandbox && !opts.onPlay;
}
/**
* Whether the cloud refused a purchase because it came before the opening, on
* Android, where Google Play then refunds it: the app says so rather than
* showing a general error. On iOS no real purchase gets this far.
*/
export function refundedBeforeOpening(code: string | undefined, onPlay: boolean): boolean {
return onPlay && code === 'ordering-closed';
}
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'تم إلغاء الشراء.',
'native.claim.failed': 'لم تتم عملية الشراء. حاول مرة أخرى.',
'native.claim.redeemFailed': 'تعذّر علينا إعداد TREK الخاص بك. حاول مرة أخرى.',
'native.claim.closedRefund': 'يفتح TREK Cloud قريبًا. سيرد Google Play ثمن هذا الشراء تلقائيًا خلال ثلاثة أيام.',
'native.plans.restore': 'استعادة المشتريات',
'native.plans.restoring': 'جارٍ التحقق من مشترياتك…',
'native.plans.restoreNone': 'لم يُعثر على اشتراك نشط لمعرّف Apple هذا.',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Alış ləğv edildi.',
'native.claim.failed': 'Alış baş tutmadı. Zəhmət olmasa yenidən cəhd edin.',
'native.claim.redeemFailed': 'TREK-inizi hazırlaya bilmədik. Zəhmət olmasa yenidən cəhd edin.',
'native.claim.closedRefund':
'TREK Cloud tezliklə açılır. Google Play bu alışın pulunu üç gün ərzində avtomatik qaytarır.',
'native.plans.restore': 'Alışları bərpa et',
'native.plans.restoring': 'Alışlarınız yoxlanılır…',
'native.plans.restoreNone': 'Bu Apple ID üçün aktiv abunə tapılmadı.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Compra cancelada.',
'native.claim.failed': 'A compra não foi concluída. Tente novamente.',
'native.claim.redeemFailed': 'Não conseguimos preparar seu TREK. Tente novamente.',
'native.claim.closedRefund':
'O TREK Cloud abre em breve. O Google Play reembolsa esta compra automaticamente em até três dias.',
'native.plans.restore': 'Restaurar compras',
'native.plans.restoring': 'Verificando suas compras…',
'native.plans.restoreNone': 'Nenhuma assinatura ativa encontrada neste ID Apple.',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Compra cancel·lada.',
'native.claim.failed': 'La compra no s’ha completat. Torna-ho a provar.',
'native.claim.redeemFailed': 'No hem pogut preparar el teu TREK. Torna-ho a provar.',
'native.claim.closedRefund':
'TREK Cloud obre aviat. Google Play et reemborsa aquesta compra automàticament en un termini de tres dies.',
'native.plans.restore': 'Restaura les compres',
'native.plans.restoring': 'Comprovant les teves compres…',
'native.plans.restoreNone': 'No s’ha trobat cap subscripció activa en aquest ID d’Apple.',
+1
View File
@@ -68,6 +68,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Nákup zrušen.',
'native.claim.failed': 'Nákup se nezdařil. Zkuste to znovu.',
'native.claim.redeemFailed': 'Nepodařilo se připravit váš TREK. Zkuste to znovu.',
'native.claim.closedRefund': 'TREK Cloud brzy otevírá. Google Play vám tento nákup automaticky vrátí do tří dnů.',
'native.plans.restore': 'Obnovit nákupy',
'native.plans.restoring': 'Kontrolujeme vaše nákupy…',
'native.plans.restoreNone': 'Pro toto Apple ID nebylo nalezeno žádné aktivní předplatné.',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Kauf abgebrochen.',
'native.claim.failed': 'Der Kauf hat nicht geklappt. Bitte versuch es erneut.',
'native.claim.redeemFailed': 'Wir konnten deine TREK nicht einrichten. Bitte versuch es erneut.',
'native.claim.closedRefund':
'TREK Cloud startet bald. Google Play erstattet dir diesen Kauf automatisch innerhalb von drei Tagen.',
'native.plans.restore': 'Käufe wiederherstellen',
'native.plans.restoring': 'Deine Käufe werden geprüft…',
'native.plans.restoreNone': 'Für diese Apple-ID wurde kein aktives Abo gefunden.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Purchase cancelled.',
'native.claim.failed': 'The purchase did not go through. Please try again.',
'native.claim.redeemFailed': 'We could not set up your TREK. Please try again.',
'native.claim.closedRefund':
'TREK Cloud opens soon. Google Play refunds this purchase automatically within three days.',
'native.plans.restore': 'Restore purchases',
'native.plans.restoring': 'Checking your purchases…',
'native.plans.restoreNone': 'No active subscription found on this Apple ID.',
+2
View File
@@ -70,6 +70,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Compra cancelada.',
'native.claim.failed': 'La compra no se completó. Inténtalo de nuevo.',
'native.claim.redeemFailed': 'No pudimos preparar tu TREK. Inténtalo de nuevo.',
'native.claim.closedRefund':
'TREK Cloud abre pronto. Google Play te reembolsa esta compra automáticamente en un plazo de tres días.',
'native.plans.restore': 'Restaurar compras',
'native.plans.restoring': 'Comprobando tus compras…',
'native.plans.restoreNone': 'No se encontró ninguna suscripción activa en este ID de Apple.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Ost tühistatud.',
'native.claim.failed': 'Ost ei õnnestunud. Proovi uuesti.',
'native.claim.redeemFailed': 'Me ei saanud sinu TREK-i seadistada. Proovi uuesti.',
'native.claim.closedRefund':
'TREK Cloud avatakse peagi. Google Play maksab selle ostu raha kolme päeva jooksul automaatselt tagasi.',
'native.plans.restore': 'Taasta ostud',
'native.plans.restoring': 'Kontrollime sinu oste…',
'native.plans.restoreNone': 'Selle Apple ID jaoks ei leitud aktiivset tellimust.',
+2
View File
@@ -70,6 +70,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Achat annulé.',
'native.claim.failed': 'L’achat n’a pas abouti. Réessayez.',
'native.claim.redeemFailed': 'Nous n’avons pas pu préparer votre TREK. Réessayez.',
'native.claim.closedRefund':
'TREK Cloud ouvre bientôt. Google Play vous rembourse cet achat automatiquement sous trois jours.',
'native.plans.restore': 'Restaurer les achats',
'native.plans.restoring': 'Vérification de vos achats…',
'native.plans.restoreNone': 'Aucun abonnement actif trouvé pour cet identifiant Apple.',
+2
View File
@@ -71,6 +71,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Η αγορά ακυρώθηκε.',
'native.claim.failed': 'Η αγορά δεν ολοκληρώθηκε. Δοκιμάστε ξανά.',
'native.claim.redeemFailed': 'Δεν μπορέσαμε να ετοιμάσουμε το TREK σας. Δοκιμάστε ξανά.',
'native.claim.closedRefund':
'Το TREK Cloud ανοίγει σύντομα. Το Google Play σας επιστρέφει αυτόματα τα χρήματα αυτής της αγοράς μέσα σε τρεις ημέρες.',
'native.plans.restore': 'Επαναφορά αγορών',
'native.plans.restoring': 'Έλεγχος των αγορών σας…',
'native.plans.restoreNone': 'Δεν βρέθηκε ενεργή συνδρομή για αυτό το Apple ID.',
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'הרכישה בוטלה.',
'native.claim.failed': 'הרכישה לא הושלמה. אנא נסו שוב.',
'native.claim.redeemFailed': 'לא הצלחנו להכין את ה-TREK שלכם. אנא נסו שוב.',
'native.claim.closedRefund': 'TREK Cloud ייפתח בקרוב. Google Play יחזיר לכם את הרכישה הזו אוטומטית תוך שלושה ימים.',
'native.plans.restore': 'שחזור רכישות',
'native.plans.restoring': 'בודקים את הרכישות שלך…',
'native.plans.restoreNone': 'לא נמצא מינוי פעיל עבור Apple ID זה.',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Vásárlás megszakítva.',
'native.claim.failed': 'A vásárlás nem sikerült. Próbáld újra.',
'native.claim.redeemFailed': 'Nem sikerült beállítani a TREK-edet. Próbáld újra.',
'native.claim.closedRefund':
'A TREK Cloud hamarosan nyit. A Google Play három napon belül automatikusan visszatéríti ezt a vásárlást.',
'native.plans.restore': 'Vásárlások visszaállítása',
'native.plans.restoring': 'Vásárlásaid ellenőrzése…',
'native.plans.restoreNone': 'Ehhez az Apple ID-hoz nem található aktív előfizetés.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Pembelian dibatalkan.',
'native.claim.failed': 'Pembelian tidak berhasil. Silakan coba lagi.',
'native.claim.redeemFailed': 'Kami tidak dapat menyiapkan TREK Anda. Silakan coba lagi.',
'native.claim.closedRefund':
'TREK Cloud segera dibuka. Google Play mengembalikan dana pembelian ini secara otomatis dalam tiga hari.',
'native.plans.restore': 'Pulihkan pembelian',
'native.plans.restoring': 'Memeriksa pembelian Anda…',
'native.plans.restoreNone': 'Tidak ada langganan aktif untuk Apple ID ini.',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Acquisto annullato.',
'native.claim.failed': 'L’acquisto non è andato a buon fine. Riprova.',
'native.claim.redeemFailed': 'Non siamo riusciti a preparare il tuo TREK. Riprova.',
'native.claim.closedRefund':
'TREK Cloud apre presto. Google Play ti rimborsa automaticamente questo acquisto entro tre giorni.',
'native.plans.restore': 'Ripristina acquisti',
'native.plans.restoring': 'Controllo dei tuoi acquisti…',
'native.plans.restoreNone': 'Nessun abbonamento attivo trovato per questo ID Apple.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': '購入をキャンセルしました。',
'native.claim.failed': '購入できませんでした。もう一度お試しください。',
'native.claim.redeemFailed': 'TREKを用意できませんでした。もう一度お試しください。',
'native.claim.closedRefund':
'TREK Cloudはまもなく開始します。この購入はGoogle Playにより3日以内に自動で返金されます。',
'native.plans.restore': '購入を復元',
'native.plans.restoring': '購入を確認しています…',
'native.plans.restoreNone': 'このApple IDに有効なサブスクリプションは見つかりませんでした。',
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': '구매가 취소되었습니다.',
'native.claim.failed': '구매가 완료되지 않았습니다. 다시 시도하세요.',
'native.claim.redeemFailed': 'TREK을 준비하지 못했습니다. 다시 시도하세요.',
'native.claim.closedRefund': 'TREK Cloud가 곧 문을 엽니다. Google Play에서 3일 이내에 이 구매를 자동으로 환불합니다.',
'native.plans.restore': '구입 내역 복원',
'native.plans.restoring': '구입 내역을 확인하는 중…',
'native.plans.restoreNone': '이 Apple ID에서 활성 구독을 찾을 수 없습니다.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Aankoop geannuleerd.',
'native.claim.failed': 'De aankoop is niet gelukt. Probeer het opnieuw.',
'native.claim.redeemFailed': 'We konden je TREK niet klaarzetten. Probeer het opnieuw.',
'native.claim.closedRefund':
'TREK Cloud opent binnenkort. Google Play betaalt deze aankoop binnen drie dagen automatisch terug.',
'native.plans.restore': 'Aankopen herstellen',
'native.plans.restoring': 'Je aankopen worden gecontroleerd…',
'native.plans.restoreNone': 'Geen actief abonnement gevonden voor deze Apple ID.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Zakup anulowany.',
'native.claim.failed': 'Zakup się nie powiódł. Spróbuj ponownie.',
'native.claim.redeemFailed': 'Nie udało się przygotować Twojego TREK. Spróbuj ponownie.',
'native.claim.closedRefund':
'TREK Cloud wkrótce startuje. Google Play automatycznie zwróci pieniądze za ten zakup w ciągu trzech dni.',
'native.plans.restore': 'Przywróć zakupy',
'native.plans.restoring': 'Sprawdzanie zakupów…',
'native.plans.restoreNone': 'Nie znaleziono aktywnej subskrypcji dla tego Apple ID.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Покупка отменена.',
'native.claim.failed': 'Покупка не прошла. Повторите попытку.',
'native.claim.redeemFailed': 'Не удалось настроить ваш TREK. Повторите попытку.',
'native.claim.closedRefund':
'TREK Cloud скоро откроется. Google Play автоматически вернёт деньги за эту покупку в течение трёх дней.',
'native.plans.restore': 'Восстановить покупки',
'native.plans.restoring': 'Проверяем ваши покупки…',
'native.plans.restoreNone': 'Для этого Apple ID не найдено активной подписки.',
+1
View File
@@ -68,6 +68,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Nákup zrušený.',
'native.claim.failed': 'Nákup sa nepodaril. Skúste to znova.',
'native.claim.redeemFailed': 'Nepodarilo sa pripraviť váš TREK. Skúste to znova.',
'native.claim.closedRefund': 'TREK Cloud čoskoro otvára. Google Play vám tento nákup automaticky vráti do troch dní.',
'native.plans.restore': 'Obnoviť nákupy',
'native.plans.restoring': 'Kontrolujeme vaše nákupy…',
'native.plans.restoreNone': 'Pre toto Apple ID sa nenašlo žiadne aktívne predplatné.',
+1
View File
@@ -69,6 +69,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Köpet avbröts.',
'native.claim.failed': 'Köpet gick inte igenom. Försök igen.',
'native.claim.redeemFailed': 'Vi kunde inte göra i ordning din TREK. Försök igen.',
'native.claim.closedRefund': 'TREK Cloud öppnar snart. Google Play återbetalar köpet automatiskt inom tre dagar.',
'native.plans.restore': 'Återställ köp',
'native.plans.restoring': 'Kontrollerar dina köp…',
'native.plans.restoreNone': 'Ingen aktiv prenumeration hittades för detta Apple-ID.',
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'ยกเลิกการซื้อแล้ว',
'native.claim.failed': 'การซื้อไม่สำเร็จ โปรดลองอีกครั้ง',
'native.claim.redeemFailed': 'เราตั้งค่า TREK ของคุณไม่สำเร็จ โปรดลองอีกครั้ง',
'native.claim.closedRefund': 'TREK Cloud จะเปิดเร็วๆ นี้ Google Play จะคืนเงินการซื้อนี้ให้โดยอัตโนมัติภายในสามวัน',
'native.plans.restore': 'กู้คืนการซื้อ',
'native.plans.restoring': 'กำลังตรวจสอบการซื้อของคุณ…',
'native.plans.restoreNone': 'ไม่พบการสมัครสมาชิกที่ใช้งานอยู่สำหรับ Apple ID นี้',
+2
View File
@@ -69,6 +69,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Satın alma iptal edildi.',
'native.claim.failed': 'Satın alma tamamlanamadı. Lütfen tekrar deneyin.',
'native.claim.redeemFailed': 'TREK’inizi hazırlayamadık. Lütfen tekrar deneyin.',
'native.claim.closedRefund':
'TREK Cloud yakında açılıyor. Google Play bu satın alımı üç gün içinde otomatik olarak iade eder.',
'native.plans.restore': 'Satın alımları geri yükle',
'native.plans.restoring': 'Satın alımlarınız kontrol ediliyor…',
'native.plans.restoreNone': 'Bu Apple Kimliği için etkin abonelik bulunamadı.',
+2
View File
@@ -68,6 +68,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Покупку скасовано.',
'native.claim.failed': 'Покупка не пройшла. Повторіть спробу.',
'native.claim.redeemFailed': 'Не вдалося налаштувати ваш TREK. Повторіть спробу.',
'native.claim.closedRefund':
'TREK Cloud скоро відкриється. Google Play автоматично поверне кошти за цю покупку протягом трьох днів.',
'native.plans.restore': 'Відновити покупки',
'native.plans.restoring': 'Перевіряємо ваші покупки…',
'native.plans.restoreNone': 'Для цього Apple ID не знайдено активної підписки.',
+2
View File
@@ -70,6 +70,8 @@ const native: TranslationStrings = {
'native.claim.cancelled': 'Đã hủy mua.',
'native.claim.failed': 'Giao dịch mua không thành công. Vui lòng thử lại.',
'native.claim.redeemFailed': 'Chúng tôi không thể thiết lập TREK của bạn. Vui lòng thử lại.',
'native.claim.closedRefund':
'TREK Cloud sắp ra mắt. Google Play sẽ tự động hoàn tiền giao dịch mua này trong vòng ba ngày.',
'native.plans.restore': 'Khôi phục giao dịch mua',
'native.plans.restoring': 'Đang kiểm tra giao dịch mua của bạn…',
'native.plans.restoreNone': 'Không tìm thấy gói đăng ký đang hoạt động cho Apple ID này.',
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': '已取消購買。',
'native.claim.failed': '購買未成功。請重試。',
'native.claim.redeemFailed': '我們無法為你設定 TREK。請重試。',
'native.claim.closedRefund': 'TREK Cloud 即將開放。Google Play 會在三天內自動退還這筆購買的款項。',
'native.plans.restore': '恢復購買項目',
'native.plans.restoring': '正在檢查你的購買項目…',
'native.plans.restoreNone': '此 Apple ID 沒有找到有效的訂閱。',
+1
View File
@@ -67,6 +67,7 @@ const native: TranslationStrings = {
'native.claim.cancelled': '购买已取消。',
'native.claim.failed': '购买未成功。请重试。',
'native.claim.redeemFailed': '我们无法为你设置 TREK。请重试。',
'native.claim.closedRefund': 'TREK Cloud 即将开放。Google Play 会在三天内自动退还这笔购买的款项。',
'native.plans.restore': '恢复购买',
'native.plans.restoring': '正在检查你的购买…',
'native.plans.restoreNone': '此 Apple ID 下没有找到有效订阅。',