Member | Description |
bool IsInitialized | true after Initialize() succeeded. Set before the first state and subscriptions sync completes, so it does not mean the caches are filled. |
string UserId | Public player id: stable across launches, persisted locally, refreshed on every state sync. Empty until the first successful sync. Show it in-game for support lookups. |
IBestBridgeMessages Messages | The active player-facing texts: the instance passed at construction, or BestBridgeMessagesDefaults. |
IReadOnlyList<BestBridgeProduct> PendingSubscriptionRenewals | Products whose renewal charge is pending (subscription status Grace). |
Member | Raised |
event Action<BestBridgeInitializationStatus> OnInitialized | Once per Initialize() call, on success and on failure. Subscribe before calling; for the already-initialized case read IsInitialized. |
event Action OnSubscriptionsChanged | Only when the subscription picture changed: access gained or lost, or a difference in status, auto-renew, period or pending plan. Re-read GetSubscriptionState in the handler. Handler exceptions are caught and logged. |
event Action OnNonConsumablesChanged | When a state sync changed the set of owned non-consumables: a grant from another device or a restore, or a revocation after a refund. Re-read IsNonConsumablePurchased and lock what is no longer owned; OnPurchase fires for grants only. Also raised by the first state sync of every session. Handler exceptions are caught and logged. |
Member | Description |
Task<BestBridgeInitializationStatus> Initialize() | Authenticates the player, loads the catalog, syncs the purchase state. Repeated and concurrent calls share one in-flight task. 45 s budget; exceeding it reports ConnectionError. On success it hooks auto-recovery; on a connection failure it schedules background retries (5 s / 15 s / 45 s). |
void Dispose() | On BestBridgePaymentsService (IDisposable). Releases the app-lifecycle hook used by auto-recovery and ends a purchase or subscription action waiting on its confirmation popup as declined (nothing is sent). Needed only for a second instance, or when one is dropped while the app runs. |
Member | Description |
Task<BestBridgePurchaseResult> Purchase(string productId, Action<bool> callback = null, int quantity = 1) | Runs the checkout: confirmation popup → order → payment page in the external browser → polling → grant from the purchase state. callback receives true only on delivered success. quantity must be 1..99, and above 1 only for consumables, otherwise it throws; for a consumable it is the value preselected in the confirmation popup. Still pending after ~60 s of foreground polling → Error, and recovery delivers it once it settles. A call made while another purchase is in flight → Error (“Purchase already in progress”). A live subscription in the product's group is refused: the same plan → AlreadyPurchased, another plan → SubscriptionPlanChangeRequired. Usually before any order; if the SDK's state was stale, the platform refuses the order and the SDK returns the same result. |
Task RestorePurchases(bool showPopupOnResult = false) | Re-reads the purchase state and delivers through OnPurchase everything owned that this install has not delivered yet. |
Task<bool> TryProcessUnfinishedPurchaseAsync() | Delivers purchases that settled while the app was gone. Idempotent; true when at least one product was delivered. Runs automatically with autoRecoverPurchases (default) after init and on a foreground return (at most once per 5 s, not while a purchase is waiting for its own order); |
Member | Description |
Task<IReadOnlyList<BestBridgeProductInfo>> GetProductsAsync() | Loads the catalog, seeds the price cache, returns one entry per sku. Needs no signed-in player, so it works before Initialize(). Throws on failure. |
Task RefreshCatalogAsync() | The same reload; discards the list and swallows errors (logged). For shop open and anywhere prices are shown. Re-read the getters afterwards. |
Task RefreshProductsAsync() | On BestBridgePaymentsService only. Like RefreshCatalogAsync(), but propagates the exception. |
BestBridgeProductInfo GetProductInfo(string productId) | Synchronous cache read; default on a miss. |
bool TryGetProductInfo(string productId, out BestBridgeProductInfo info) | The same, but separates a miss from a genuine zero price. |
string GetLocalizedItemPrice(string productId) | Display price from the cache; "" when not cached. |
bool TryFindProduct(string productId, out BestBridgeProduct product) | Resolves a registered mapping by product id or sku. |
Member | Description |
bool IsNonConsumablePurchased(string productId) | Whether the player owns this non-consumable per the latest synced purchase state. Always false for consumables. Turns false again after a refund; OnNonConsumablesChanged says when to re-read. |
bool IsSubscriptionActive(string productId) | Whether a subscription entitlement in the purchase state grants access, independent of auto-renew. |
Member | Description |
BestBridgeSubscriptionState GetSubscriptionState(string productId, out BestBridgeSubscriptionData data) | Joins access from the purchase state with the cached renewal metadata into one value for a subscription screen. Synchronous cache read; Unknown until the first successful sync. data is default when no live subscription is cached. |
BestBridgeSubscriptionAction GetSubscriptionAction(string productId) | The same state resolved into a button: which control, whether it may be pressed, whether to show the end date, whether to warn. Group-aware: when another plan of the same subscription group is live, the offer is Switch (or Scheduled) rather than Subscribe, and CurrentPlanProductId names the live plan. |
Task<bool> InvokeSubscriptionActionAsync(string productId) | Performs what GetSubscriptionAction currently offers (subscribe / unsubscribe / resume / switch plan). A disabled action is a logged no-op returning false. For a purchase, true means delivered, so paid-but-pending reports false. |
Task<bool> Unsubscribe(string productId) | Turns off auto-renew; access lasts until period end, then expires. Irreversible, idempotent. false when no live subscription matches or the request failed. |
Task<bool> PauseSubscription(string productId) | Schedules a pause: Paused (access removed) from the end of the current period. |
Task<bool> ResumeSubscription(string productId, bool showPopups = true) | Immediate charge and new period from Paused, or cancels a scheduled pause. When it charges, the SDK shows confirmation (Kind = Resume), a wait popup and ResumeSubscriptionError on failure; showPopups: false keeps it silent. false when the player declines, on a declined charge or a missing card. A charge the bank has not confirmed yet is waited out as for ChangeSubscriptionPlan. Before deciding whether the call charges, the SDK refreshes the subscription state, so a subscription paused since the last refresh is still confirmed with the player. |
Task<bool> ChangeSubscriptionPlan(string currentProductId, string newProductId, bool showPopups = true) | Within one subscription group — this, not a second Purchase, is how a player moves between tariffs. An upgrade applies immediately (prorated); a downgrade takes effect next period and shows up as PendingProductId until then. Refused locally (false, logged, no request) when the new product is not a registered subscription or the two are known to be in different groups. Shows the SDK popups (confirmation with Kind = PlanChange and SwitchTiming, wait, PlanChangeError); showPopups: false keeps it silent. A charge the bank has not confirmed yet (CHARGE_PENDING) is waited out for about 30 s; if still open, false with SubscriptionChargeOutcomeUnknown, and the next refresh picks it up. If the charge went through but the subscription had changed meanwhile, the platform refunds it: false with SubscriptionChargeRefunded. Repeating never charges twice. |
BestBridgeSubscriptionData[] GetUserSubscriptions() | Synchronous snapshot of the subscription cache. |
bool TryGetSubscription(string productId, out BestBridgeSubscriptionData data) | First cached live subscription for the product; false on a miss. |
Task UpdateSubscriptions() | Refreshes the subscription cache (renewal and billing metadata). Also runs on init and after a purchase or management call. Errors are swallowed and logged. |
Task RefreshSubscriptionStatusAsync() | Full purchase-state sync plus a subscriptions refresh, so a change made on another device is picked up. Errors are swallowed and logged. Delivers no purchases. Raises OnSubscriptionsChanged on a change and reports a Grace renewal problem to the player. |
Task<bool> OnPurchase(BestBridgePurchaseInfo purchase); Return | Effect |
true | The SDK records TransactionId in its ledger (persisted immediately) and commits, consuming a consumable. OnPurchase is not called again for that transaction. |
false or a thrown exception | Nothing is committed. The entitlement stays in the purchase state and is retried on the next recovery sync or restore. |
Field | Meaning |
BestBridgeProduct Product | The granted product. |
string TransactionId | Dedup key: grantedByOrderId for one-time products, subscriptionId (stable across renewals) for subscriptions. |
int Quantity | Units to grant (multi-buy). 1 for non-consumables and subscriptions. |
bool IsRestored | true for a restore, recovery or auto-restore; false for an interactive purchase. Use it only to skip celebration UI, never to decide whether to grant. |
Property | True when |
bool IsDelivered | The reward reached the player. The only condition under which the product may be treated as owned. |
bool IsSuccess | Same as IsDelivered (source compatibility). |
bool IsPaidDeliveryPending | Charged, not delivered yet — do not grant. |
bool IsAlreadyOwned | Already owned, or a plan change is required; nothing failed, nothing charged. |
bool IsPlanChangeRequired | Another plan of the same group is live: offer ChangeSubscriptionPlan. Nothing charged. |
bool IsCanceledByPlayer | The player backed out. |
bool IsFailure | Nothing new is owned: a request or processing error, a cancel or refund on the payment side, or a failed init. Excludes IsPaidDeliveryPending. |
Value | Meaning | Message shown |
Success | Order succeeded and the entitlement was delivered. | PurchaseSuccess |
PaidDeliveryPending | Charged, not delivered yet: the follow-up state read failed, OnPurchase deferred, or the granted sku is not registered in this build. Other entitlements delivered by the same sync do not count. Before reporting it the SDK re-reads the state for a few seconds, since the grant can lag the payment. The next recovery sync delivers it. | PaymentStillProcessing |
Canceled | Order ended canceled or refunded on the payment side, or was paid but compensated (refunded by the platform). | PaymentNotCompleted |
CanceledByUser | The confirmation or wait popup was dismissed, or the service was disposed while the confirmation popup was open. | — |
Error | Request or processing error (see ErrorText), including the poll timeout. | ConnectionProblem / PurchaseError, or PaymentStillProcessing on the timeout; none for an unknown product id or a re-entrant call |
NotInitialized | The service could not initialize; no order was created. | PurchaseUnavailable |
NonConsumableAlreadyPurchased | A non-consumable the player already owns; no order created. | ProductAlreadyOwned |
AlreadyPurchased | A live subscription on this product — any status, including cancelled, paused and grace; no order created. | SubscriptionAlreadyActive |
SubscriptionPlanChangeRequired | A live subscription on another plan of the same group; no order created. One live subscription per group is a platform invariant enforced only after payment, so the order would be charged, grant nothing and be refunded (reported as Canceled). Offer ChangeSubscriptionPlan instead. | SubscriptionAlreadyActive |
Field | Meaning |
string Title | Catalog title. |
string LocalizedPrice | Display string. Default formatting: 0.00 ₽ for RUB and an empty currency, otherwise 0.00 <CURRENCY>. "" when the price is 0. |
string Sku | Catalog sku. |
BestBridgeProductType Type | From the catalog. |
long AmountMinorUnits | Price in minor units: 199 ₽ is 19900. |
decimal Price | AmountMinorUnits / 100. |
string BillingPeriodUnit, int BillingPeriodCount, string SubscriptionGroup | Subscriptions only; null / 0 otherwise. |
Value | Meaning |
Unknown | Nothing synced yet. Render loading or disabled, never “Subscribe”. |
NotSubscribed | No live subscription. |
Active | Subscribed, auto-renew on. |
CanceledUntilPeriodEnd | Auto-renew off; access until ExpirationDate, then expires. Auto-renew cannot be turned back on, and the plan is buyable again only once it has expired — until then the subscription is still live and an order would be charged, grant nothing and be refunded (reported as Canceled). |
Paused | Paused; access removed until resumed. |
Grace | A renewal charge failed and is being retried while access is kept. |
public interface IBestBridgePopupController
{
Task<IBestBridgeWaitPopup> ShowWaitPopup(bool canCancel);
Task<IBestBridgeConfirmationPopup> ShowConfirmationPopup();
Task<IBestBridgeInfoPopup> ShowInfoPopup();
}
public interface IBestBridgeWaitPopup { CancellationToken CancellationToken { get; } void Close(); }
public interface IBestBridgeConfirmationPopup
{
Task<BestBridgeConfirmationResult> RequestConfirmation(BestBridgeConfirmationRequest request);
}
public interface IBestBridgeInfoPopup
{
void ShowRestoreResult(bool wasAnyRestored);
void ShowSuccess(string productId);
void ShowError(string productId, string message); // message is resolved copy — render it
} Field | Meaning |
ProductId, ProductType | What is being bought. |
BestBridgeProductInfo ProductInfo | Cached per-unit price and title. May be default when the catalog has not loaded. |
bool AllowQuantitySelection | true only for consumables — render the quantity selector. |
int MinQuantity, MaxQuantity, InitialQuantity | 1, 99 (MaxPurchaseQuantity), and the preselected value. |
BestBridgeConfirmationKind Kind | Purchase, PlanChange or Resume: what the popup asks. · string CurrentPlanProductId — for PlanChange: the plan being left. |
BestBridgeSubscriptionSwitchTiming SwitchTiming | For PlanChange: Immediate, AtPeriodEnd or Unknown. |
Member | Default (ru) |
InfoHeader | Информация |
ErrorHeader | Ошибка |
PleaseWait | Пожалуйста, подождите… |
ConfirmPaymentPrompt | Вам необходимо перейти на страницу оплаты. |
QuantityLabel | Количество |
PurchaseTotalFormat | Итого: {0} — {0} is the total (unit price × quantity) |
PurchaseSuccess | Покупка выполнена успешно. |
PurchasesRestored | Покупки восстановлены. |
NoPurchasesToRestore | Нет покупок для восстановления. |
ConnectionProblem | Проблема с подключением. Попробуйте восстановить покупку позже. |
PurchaseError | Не удалось завершить покупку. Попробуйте позже. |
PaymentStillProcessing | Платёж ещё обрабатывается. Покупка будет начислена автоматически после подтверждения. |
PurchaseUnavailable | Покупки сейчас недоступны. Проверьте соединение и попробуйте позже. |
ProductAlreadyOwned | Этот товар у вас уже есть. |
PaymentNotCompleted | Оплата не была завершена. |
SubscriptionRenewalProblem | Не удалось продлить подписку. Проверьте способ оплаты, иначе доступ будет отключён. |
SubscriptionAlreadyActive | Подписка уже активна и продлевается автоматически. Повторная оплата не требуется. |
ConfirmPlanChangeImmediate | Новый тариф начнёт действовать сразу, оплата спишется сейчас. Продолжить? |
ConfirmPlanChangeAtPeriodEnd | Новый тариф начнёт действовать после окончания оплаченного периода. Сейчас ничего не списывается. Продолжить? |
SubscriptionChargeOutcomeUnknown | Результат оплаты пока неизвестен. Если платёж пройдёт, подписка обновится автоматически. |
PlanChangeError | Не удалось сменить тариф. Текущий тариф сохранён, попробуйте позже. |
ConfirmResumeSubscription | Подписка возобновится сразу, оплата за новый период спишется сейчас. Продолжить |
ResumeSubscriptionError | Не удалось возобновить подписку. Проверьте способ оплаты и попробуйте позже. |
Не удалось возобновить подписку. Проверьте способ оплаты и попробуйте позже. | Подписка изменилась во время оплаты, поэтому операция не выполнена. Списанные средства вернутся автоматически. |
new BestBridgeConfig(
string apiKey,
bool isDevBuild,
IReadOnlyList<UserIdentity> identities = null,
BestBridgeLogLevel? logLevel = null,
bool notifySubscriptionPaymentProblems = true,
bool autoRecoverPurchases = true,
bool autoRetryFailedInitialization = true,
bool showPurchaseMessages = true,
BestBridgeEnvironment environment = BestBridgeEnvironment.Auto); Member | Description |
ApiKey | Project API key. Throws ArgumentException when null or blank. |
IsDevBuild | Selects the built-in development or production environment, unless environment forces one. |
Environment | The override as passed in. Auto (default) means no override. |
ResolvedEnvironment | The environment actually in use — never Auto. |
IsEnvironmentOverridden | true when the override contradicts IsDevBuild. Logged as a warning, which a release build does not print, so read this property to catch a leftover override. |
BaseUrl | The resolved base URL. Validated as an absolute http(s) URL, else InvalidOperationException. |
Identities | Stable identities for resolve-or-create. null or empty falls back to the device id. |
LogLevel | Default Info in dev builds, Error in release. Honoured as passed in a development build; a release build caps it at Error and reduces every message. |
ShowPurchaseMessages | Whether the SDK reports purchase outcomes to the player itself. Default true, covering every outcome that needs a message. false shows no result popup at all; the confirmation and wait popups, the opt-in restore result and the renewal notice are unaffected. |
NotifySubscriptionPaymentProblems | Whether the SDK shows its own Grace error popup. |
AutoRecoverPurchases | Whether the SDK runs recovery itself, after init and on every foreground return. |
AutoRetryFailedInitialization | Whether a connection-failed start is retried in the background (5 s / 15 s / 45 s), off the caller’s path. Each attempt raises OnInitialized. |
Value | Meaning |
Auto | Default. Follows isDevBuild: dev build → development, release build → production. |
Development | Always development. For a QA or release-candidate build that must not touch real money. |
Production | Always production. For verifying a real payment from a development build. |
// Production: builds a BestBridgeClient from the config.
new BestBridgePaymentsService(products, purchaseListener, popupController, config, messages = null);
// Testing / custom transport: takes an IBestBridgeClient; the config-derived flags become parameters.
new BestBridgePaymentsService(products, purchaseListener, popupController, client, messages = null,
notifySubscriptionPaymentProblems = true, autoRecoverPurchases = true, autoRetryFailedInitialization = true,
showPurchaseMessages = true); Type | Description |
UserIdentityType | DeviceId · GooglePlay · AppleId · Facebook · Custom |
UserIdentity (readonly struct) | UserIdentity(UserIdentityType type, string value) → Type, Value. Empty values are skipped in the request. The platform rejects the whole sign-in when a value is whitespace only or longer than 256 characters, or when more than 20 identities are sent. |
IIdentityProvider | UserIdentity GetIdentity(). One implementation per identity source. |
DeviceIdIdentityProvider | SystemInfo.deviceUniqueIdentifier, with a persistent random per-install id where the platform has none. |
IdentitiesListCreator.Create(params IIdentityProvider[]) | Builds the list for BestBridgeConfig; null providers are skipped. |
Member | Description |
BestBridgeStoreUtility.IsStoreInRuRegion(string currencyCode, bool forceTrueInEditor = true) | true for "RUB"; true in the Editor unless disabled. |
BestBridgeStoreUtilityIAP.IsStoreInRuRegion(Product product, bool forceTrueInEditor = true) | The same, reading product.metadata.isoCurrencyCode. Assembly BestBridge.Payments.UnityIAP. A null product is non-RU outside the Editor. |
BestBridgeTaskExtensions.Forget(this Task) / Forget<T>(this Task<T>) | Fire-and-forget that observes and logs faults. |
Member | Description |
Level | Requested verbosity (Off / Error / Warning / Info), applied from BestBridgeConfig.LogLevel when the transport is created. Capped at Error in a production build. |
IsDevelopmentBuild | Set by the SDK from BestBridgeConfig.IsDevBuild. Defaults to false, so the testing constructor follows the production rules unless it is set explicitly. |
ForceLogging(bool enabled) | Overrides the build type. true = full unreduced logs at Info in any build, including ids; false = complete silence. |
ClearForcedLogging() | Drops the override and returns to build-driven behaviour. |
IsForced / EffectiveLevel / IsRedacting | Current state, for a debug screen. |
Member | Does |
bool IsAuthenticated / string UserId | Auth state, and the public player id once known. |
Task<bool> EnsureAuthenticatedAsync(ct) | Issues a token when missing or when the identity set changed. true means the identity changed, and the caller must discard player-scoped local state. |
Task AuthenticateAsync(ct) | Issues a token unconditionally. |
Task<CreateOrderResult> CreateOrderAsync(sku, idempotencyKey, quantity = 1, ct) | Creates an order for one catalog sku. Repeating a call with the same idempotencyKey never charges twice. |
Task<OrderStatusResult> GetOrderAsync(orderId, ct) | Current status of one order with Compensated. A subscription-charge order is readable by its owner with any shop key of the project; a checkout order only with its own. |
Task<StateResult> GetStateAsync(long? knownVersion, ct) | Reads the purchase state; knownVersion asks for changes only. |
Task ConsumeAsync(grantedByOrderId, ct) | Consumes a delivered consumable entitlement. |
Task<IReadOnlyList<CatalogProduct>> GetProductsAsync(ct) | The product catalog with prices. Needs no signed-in player. |
Task<IReadOnlyList<SubscriptionInfo>> GetSubscriptionsAsync(ct) | Renewal and billing metadata for the player’s subscriptions. |
Task CancelSubscriptionAsync(subscriptionId, ct) | Turns off auto-renew. |
Task PauseSubscriptionAsync(subscriptionId, ct) | Schedules a pause. |
Task ResumeSubscriptionAsync(subscriptionId, ct) | Resumes a paused subscription, or cancels a scheduled pause. Throws CHARGE_PENDING with OrderId when the bank has not confirmed the charge yet: poll that order with GetOrderAsync; repeating creates no new charge. |
Task ChangeSubscriptionPlanAsync(subscriptionId, sku, ct) | Moves the subscription to another plan in the same group. An upgrade charge may end in CHARGE_PENDING like a resume. |
Type | Fields |
CreateOrderResult | OrderId, Status, ConfirmationUrl, Amount (order total), Currency, Quantity |
OrderStatusResult | OrderId, Status, PaidAtEpochMs, Amount, Currency, Quantity, Compensated (Succeeded but refunded by the platform: no period was granted) |
StateResult | EntitlementVersion, UpToDate, Entitlements (null when up to date), CarriedEntitlementsArray (whether an entitlement list really arrived, which separates an authoritative empty state from an unusable answer); false also for "entitlements": null) |
EntitlementInfo | Sku, Type, Quantity, GrantedByOrderId, GrantedAtEpochMs, ExpiresAtEpochMs, SubscriptionId |
CatalogProduct | Sku, Type, Title, PriceMinor, Currency, BillingPeriodUnit, BillingPeriodCount, SubscriptionGroup |
SubscriptionInfo | SubscriptionId, Sku, SubscriptionGroup, Status, AutoRenew, CurrentPeriodEndEpochMs, NextBillingAtEpochMs, PendingSku |
OrderStatus (enum) | Pending · Succeeded · Canceled · Refunded · Unknown |
Member | Meaning |
Message | Failure description. Diagnostic — not for display. |
Code | Machine-readable cause: a payment-platform code such as QUANTITY_NOT_ALLOWED or QUANTITY_OUT_OF_RANGE, CHARGE_FAILED or CHARGE_PENDING, CHARGE_COMPENSATED or SUBSCRIPTION_ALREADY_EXISTS or one of NETWORK_ERROR / HTTP_<status> / INVALID_RESPONSE / UNEXPECTED_RESPONSE / INVALID_AUTH_RESPONSE. |
Action | BestBridgeErrorAction: Retry · Reauth · Info (unknown values map to Info). |
RequestId | Correlation id of the failed request (may be null). Quote it in support tickets. |
UserId | Public player id reported with the error (may be null). |
StatusCode | Status code of the failed request. |
OrderId | |
The order the error refers to, when the platform sends one (may be null). Set by CHARGE_PENDING; poll it with GetOrderAsync |
Exception | When |
ArgumentNullException | popupController is null. |
ArgumentException | apiKey null or blank; quantity != 1 for a non-consumable or subscription (argument or popup result). |
ArgumentOutOfRangeException | quantity outside 1..99 (argument or popup result). |
InvalidOperationException | The resolved BaseUrl is not a valid absolute http(s) URL. |
BestBridgeException | An unresolved request failure. Only GetProductsAsync and RefreshProductsAsync propagate it; Purchase, the refresh helpers, UpdateSubscriptions, RestorePurchases and TryProcessUnfinishedPurchaseAsync catch and log it. |