Subscriptions only: no question credits anywhere; Bazaar bridge is subscribeProduct only (bazaarBuy(sku, orderId, token), bazaarPrices(skusJson)), no consume
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,12 +8,11 @@ import { store } from './storage';
|
||||
* جایی است که با آن حرف میزند. در مرورگرِ معمولی آن شیء نیست و خرید با زرینپال است.
|
||||
*
|
||||
* قرارداد (سمتِ اندروید: `android/` در همین ریپو):
|
||||
* JS → اندروید TadrisinoAndroid.bazaarBuy(sku, orderId, productType, dynamicPriceToken)
|
||||
* productType «inapp» (بسته، purchaseProduct) یا «subscription» (subscribeProduct)؛
|
||||
* orderId همان payloadِ خرید است؛ dynamicPriceToken رشتهی خالی یعنی قیمتِ پیشخان.
|
||||
* JS → اندروید TadrisinoAndroid.bazaarConsume(purchaseToken) فقط بسته، بعد از تأییدِ سرور.
|
||||
* JS → اندروید TadrisinoAndroid.bazaarPrices(skusJson, productType) قیمتِ پیشخان؛ جواب با رویدادِ
|
||||
* «tadrisino:bazaar-prices» و detailِ { productType, prices: { sku: «۷۹۸,۰۰۰ تومان» } } یا { productType, error }
|
||||
* JS → اندروید TadrisinoAndroid.bazaarBuy(sku, orderId, dynamicPriceToken)
|
||||
* خریدِ اشتراک با subscribeProduct (فقط اشتراک فروخته میشود)؛ orderId همان payloadِ خرید است؛
|
||||
* dynamicPriceToken رشتهی خالی یعنی قیمتِ پیشخان.
|
||||
* JS → اندروید TadrisinoAndroid.bazaarPrices(skusJson) قیمتِ پیشخان؛ جواب با رویدادِ «tadrisino:bazaar-prices»
|
||||
* و detailِ { prices: { sku: «۸۹۸,۰۰۰ تومان» } } یا { error }
|
||||
* اندروید → JS رویدادِ window به نامِ «tadrisino:bazaar» با detail:
|
||||
* { orderId, purchaseToken } خرید انجام شد
|
||||
* { orderId, error: 'canceled' | 'failed', message? }
|
||||
@@ -23,14 +22,11 @@ import { store } from './storage';
|
||||
* هیچکدام از اینها هنوز روی گوشیِ واقعی اجرا نشده (۲۰۲۶-۱۰-۱۲).
|
||||
*/
|
||||
|
||||
export type BazaarProductType = 'inapp' | 'subscription';
|
||||
|
||||
interface AndroidBridge {
|
||||
bazaarBuy?: (sku: string, orderId: string, productType: BazaarProductType, dynamicPriceToken: string) => void;
|
||||
bazaarBuy?: (sku: string, orderId: string, dynamicPriceToken: string) => void;
|
||||
/** APKِ قدیمی؛ فقط برای اینکه بدانیم داخلِ اپیم ولی خرید از بازار در آن ممکن نیست. */
|
||||
bazaarPurchase?: (sku: string, orderId: string) => void;
|
||||
bazaarConsume?: (purchaseToken: string) => void;
|
||||
bazaarPrices?: (skusJson: string, productType: BazaarProductType) => void;
|
||||
bazaarPrices?: (skusJson: string) => void;
|
||||
/** اولین صفحهی واقعی کشیده شد؛ اسپلشِ سیستم میتواند برود (APKهای قدیمیتر این را ندارند). */
|
||||
appReady?: () => void;
|
||||
}
|
||||
@@ -56,7 +52,7 @@ export function inAndroidApp(): boolean {
|
||||
return typeof window.TadrisinoAndroid === 'object' && window.TadrisinoAndroid !== null;
|
||||
}
|
||||
|
||||
/** اپی که میتواند از بازار بخرد (بسته و اشتراک). */
|
||||
/** اپی که میتواند از بازار اشتراک بخرد. */
|
||||
export function canBuyFromBazaar(): boolean {
|
||||
return typeof window.TadrisinoAndroid?.bazaarBuy === 'function';
|
||||
}
|
||||
@@ -65,7 +61,7 @@ export function canBuyFromBazaar(): boolean {
|
||||
const PURCHASE_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
/** خرید را به اپِ اندروید میسپارد و purchaseToken را برمیگرداند. */
|
||||
export function bazaarBuy(sku: string, orderId: string, productType: BazaarProductType, dynamicPriceToken: string | null): Promise<string> {
|
||||
export function bazaarBuy(sku: string, orderId: string, dynamicPriceToken: string | null): Promise<string> {
|
||||
const bridge = window.TadrisinoAndroid;
|
||||
if (!bridge?.bazaarBuy) return Promise.reject(new BazaarPurchaseError('failed', 'not in the Android app'));
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -83,16 +79,16 @@ export function bazaarBuy(sku: string, orderId: string, productType: BazaarProdu
|
||||
}
|
||||
window.addEventListener('tadrisino:bazaar', onResult);
|
||||
try {
|
||||
bridge.bazaarBuy!(sku, orderId, productType, dynamicPriceToken ?? '');
|
||||
bridge.bazaarBuy!(sku, orderId, dynamicPriceToken ?? '');
|
||||
} catch (err) {
|
||||
finish(() => reject(new BazaarPurchaseError('failed', String(err))));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
type PricesResult = { productType: BazaarProductType; prices?: Record<string, string>; error?: string };
|
||||
type PricesResult = { prices?: Record<string, string>; error?: string };
|
||||
|
||||
function askPrices(skus: string[], productType: BazaarProductType): Promise<Record<string, string>> {
|
||||
function askPrices(skus: string[]): Promise<Record<string, string>> {
|
||||
const bridge = window.TadrisinoAndroid;
|
||||
if (!bridge?.bazaarPrices || !skus.length) return Promise.resolve({});
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -104,12 +100,12 @@ function askPrices(skus: string[], productType: BazaarProductType): Promise<Reco
|
||||
}
|
||||
function onResult(event: Event) {
|
||||
const detail = (event as CustomEvent<PricesResult>).detail;
|
||||
if (!detail || detail.productType !== productType) return;
|
||||
if (!detail) return;
|
||||
if (detail.prices) done(() => resolve(detail.prices!));
|
||||
else done(() => reject(new Error(detail.error || 'failed')));
|
||||
}
|
||||
window.addEventListener('tadrisino:bazaar-prices', onResult);
|
||||
bridge.bazaarPrices!(JSON.stringify(skus), productType);
|
||||
bridge.bazaarPrices!(JSON.stringify(skus));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,13 +113,13 @@ function askPrices(skus: string[], productType: BazaarProductType): Promise<Reco
|
||||
* قیمتِ ثبتشده در پیشخانِ بازار برای هر SKU، از خودِ بازار روی گوشی (API سرور قیمت نمیدهد). اتصالِ Poolakey
|
||||
* چند لحظه بعد از باز شدنِ اپ برقرار میشود، پس یک بار دوباره امتحان میشود. خطا یعنی قیمتی نشان داده نمیشود.
|
||||
*/
|
||||
export async function bazaarPrices(skus: string[], productType: BazaarProductType): Promise<Record<string, string>> {
|
||||
export async function bazaarPrices(skus: string[]): Promise<Record<string, string>> {
|
||||
try {
|
||||
return await askPrices(skus, productType);
|
||||
return await askPrices(skus);
|
||||
} catch {
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
try {
|
||||
return await askPrices(skus, productType);
|
||||
return await askPrices(skus);
|
||||
} catch (err) {
|
||||
console.error('bazaarPrices failed', err);
|
||||
return {};
|
||||
@@ -131,15 +127,6 @@ export async function bazaarPrices(skus: string[], productType: BazaarProductTyp
|
||||
}
|
||||
}
|
||||
|
||||
export function bazaarConsume(purchaseToken: string) {
|
||||
try {
|
||||
window.TadrisinoAndroid?.bazaarConsume?.(purchaseToken);
|
||||
} catch (err) {
|
||||
// مصرف نشدن یعنی خریدِ دوبارهی همین بسته تا مصرفِ بعدی گیر میکند؛ اعتبارِ کاربر سالم است.
|
||||
console.error('bazaarConsume failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotency-Keyِ خریدِ نیمهتمامِ هر SKU. اگر بازار پول را گرفت ولی تأییدِ سرور نرسید (قطعِ
|
||||
* اینترنت)، تلاشِ بعدی باید همان سفارش را بگیرد؛ سفارشِ تازه با توکنِ قبلی تأیید نمیشد چون هر
|
||||
|
||||
Reference in New Issue
Block a user