This website uses cookies to ensure you get the best experience
OK
API reference
Conventions. All Task-returning members are awaited on the Unity main thread, and all callbacks and events are raised there. Every productId parameter accepts a game-side product id or a catalog sku. Amounts are in minor units — the smallest whole denomination of the currency (kopecks for RUB, like cents for USD), so 199 ₽ is 19900; Price is the same amount as a decimal (AmountMinorUnits / 100m).
IBestBridgePaymentsService
Implemented by BestBridgePaymentsService.
Properties

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).

Events

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.

Lifecycle

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.

Purchasing

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);

Catalog and prices

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.

Ownership

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.

Subscriptions

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.

IBestBridgePurchaseListener
Task<bool> OnPurchase(BestBridgePurchaseInfo purchase);
Delivers one purchased or restored product, awaited before the SDK commits the delivery. Called for fresh purchases, restores and recovered orders.

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.

Rules: grant × purchase.Quantity, persist, then return true. Delivery is at-least-once, so the grant has to be idempotent by TransactionId — a reinstall, an identity change, or a crash between the true and its persistence re-delivers. This holds for consumables too: until the SDK has recorded the delivery and consumed the entitlement, a crash re-delivers it.
BestBridgePurchaseInfo (readonly struct)

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.

BestBridgePurchaseResult
Prefer the derived flags over Status: a status added in a later SDK version then cannot change what the game treats as a completed purchase.

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.

Other fields: BestBridgePurchaseStatus Status, int Quantity (units purchased), string TransactionId (the delivery key OnPurchase received for this order; set only for a delivered purchase, else null), string ErrorText (raw diagnostic text for logs — never display it), int RewardIndex (not set by the purchase flow, always 0).
The SDK shows the message to the player; the game only reacts. With showPurchaseMessages: true (the default) the SDK itself shows a popup for every outcome that needs one. Use the result only to change game state — grant the product, redraw the shop — and do not show a second popup with the same information. Some outcomes get no popup, and none is needed there: the player closed the popup, a second Purchase call arrived while one was already running, or the product id is not registered in this build.
BestBridgePurchaseStatus
The last column is the text the SDK shows, unless showPurchaseMessages is off.

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

Models
BestBridgeProduct (serializable struct)
BestBridgeProduct(string productId, string sku, BestBridgeProductType type) → ProductId, Sku, Type. productId is game-side and never leaves the game; sku is the catalog id. They may be identical.
BestBridgeProductType
Consumable · NonConsumable · Subscription
BestBridgeProductInfo (struct)

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.

BestBridgeInitializationStatus (struct)
IsSuccess, IsError, IsConnectionError, IsCancelled, Exception. Factories: Success(), Error(exception), ConnectionError(), Cancelled().
A connection error sets both IsConnectionError and IsError and carries no Exception, so check IsConnectionError and IsCancelled first.
BestBridgeSubscriptionState

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.

BestBridgeSubscriptionActionKind
Loading (disabled) · Subscribe · Unsubscribe · Resume · Expiring (disabled; show the end date — the plan is buyable again only after it expires) · PaymentProblem (disabled; fixable only outside the game) · Switch (another plan of the group is live — offer to switch to this one; disabled while that plan is paused or cancelled) · Scheduled (disabled; a switch to this plan already takes effect at the end of the current period)
BestBridgeSubscriptionAction (readonly struct)
Kind, ProductId, IsEnabled, ShowsExpiration, HasPaymentProblem, State, Data, CurrentPlanProductId, SwitchTiming. Carries no player-facing copy: wording and localization stay with the game. Drive the control from IsEnabled, not from Kind: besides Loading / PaymentProblem / Scheduled / Expiring it is also false for a Switch the platform will not accept yet.
BestBridgeSubscriptionSwitchTiming
Unknown (not a switch, or catalog prices missing — the charge may still be immediate, so show the immediate-charge copy, never "nothing is charged now") · Immediate (an upgrade: applied and charged at once, minus a prorated credit for the unused remainder) · AtPeriodEnd (costs nothing now; takes over when the current period ends). Decided by price: strictly dearer is an upgrade, equal or cheaper is scheduled.
State describes the product itself, Kind what the button should offer — so for Switch / Scheduled the state reads NotSubscribed while CurrentPlanProductId names the plan the player is on and Data carries that plan’s record (its period is what the dates come from).
BestBridgeSubscriptionData (struct)
ProductId, Sku, SubscriptionId, SubscriptionGroup, Status, AutoRenew, ExpirationDate (end of the paid period), TimeToEndInSeconds (clamped to 0), NextBillingDate (null when auto-renew is off or paused), PendingProductId (scheduled downgrade, else null).
BestBridgeSubscriptionStatus
Active · Grace · Paused · Unknown. A terminal subscription never reaches the cache, so there is no canceled or expired value; anything the SDK does not recognize maps to Unknown.
Popup contracts
The game implements these with its own UI; the service decides when each is shown. A controller is mandatory — the constructor throws ArgumentNullException without one.
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
}
ShowError receives the resolved text and is the single path for every failure the SDK reports. The other two read their copy from service.Messages. The wait popup’s CancellationToken cancels order creation and polling.
BestBridgeConfirmationRequest (readonly struct)

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.

BestBridgeConfirmationResult (readonly struct)
Confirmed, Quantity. Factories: Canceled, Confirm(quantity). The quantity is validated, not clamped: return exactly what the player picked, and 1 when AllowQuantitySelection is false.
Texts: IBestBridgeMessages
Pass an instance as the last constructor argument, or derive from BestBridgeMessagesDefaults (all members virtual) and override a subset. BestBridgeMessages holds the static Russian defaults.

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

Не удалось возобновить подписку. Проверьте способ оплаты и попробуйте позже.

Не удалось возобновить подписку. Проверьте способ оплаты и попробуйте позже.

Подписка изменилась во время оплаты, поэтому операция не выполнена. Списанные средства вернутся автоматически.

BestBridgeConfig
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.

BestBridgeEnvironment
Which built-in environment to use, independently of the build type. Both are compiled into the SDK. The API key is per environment, so a forced environment needs the key that belongs to it.

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.

Constructors of BestBridgePaymentsService
// 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);
Identity

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.

The identity set is hashed (SHA-256, order-independent) and the hash is persisted. Pass each identity once: a duplicate changes the hash. A changed hash means a different user: the SDK resets its local ledger and cached access, then runs a full sync that auto-restores the new identity’s entitlements. A re-auth for the same identity resets nothing.
Utilities

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.

BestBridgeLog
The SDK’s single logger.

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.

Custom transport: IBestBridgeClient
The layer the service runs on, implemented by BestBridgeClient (UnityWebRequest + JsonUtility). Game code normally never touches it; the second service constructor accepts one for tests or a custom implementation.

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.

Failure policy. 10 s per attempt. Network errors, timeouts and HTTP 408 / 429 / 5xx are retried up to 3 times with a 2 s → 4 s → 6 s backoff. Any other HTTP error without a platform error body (a firewall's 403, a proxy's 404) fails at once. An auth failure re-issues the token and repeats the request once. Anything the policy cannot resolve surfaces as BestBridgeException.
Result types (readonly structs)

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

Exceptions
BestBridgeException
Thrown for a request failure the retry and re-auth policy could not resolve.

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

Thrown by the SDK

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.

Every argument exception above is an integration error, not a runtime failure: a player cannot trigger one, and payment or connection failures arrive as a BestBridgePurchaseResult status instead. Purchase therefore needs no try/catch; such an exception is fixed at the call site, or in the confirmation popup that returned a bad quantity.