ابرووا
شروع رایگان
به‌زودی. این راهنما از Abrova A/B Testing استفاده می‌کند که به‌زودی فعال می‌شود. گام‌هایی که با تریس است از امروز کار می‌کند.

ساخت صفحه‌ی محصول با قیمت A/B

در پایان: سه نسخه‌ی قیمت با تخصیص پایدار، نرخ تبدیل خرید برای هر نسخه و خطاهای پرداخت با برچسب نسخه.
Abrova A/B Testingابرووا تریسFlutter · iOS · Next.jsپیشرفته · ۴۵ دقیقه

پیش از شروع

کاربرنسخه: کنترل، ‎−۱۰٪، ‎−۱۵٪خرید و خطا برای هر نسخه
این راهنما هنوز ترجمه نشده — نسخه‌ی انگلیسی نمایش داده می‌شود. English

Price tests pay off only when they are measured correctly and the page keeps working while they run. This guide wires the whole loop: Abrova A/B Testing assigns each user a stable price variant and counts purchases per variant, and Abrova Trace labels every checkout error with the variant the user saw, so you can answer "did the new price break checkout?".

Both products use the same Abrova project: one public key (ab_live_…) for every SDK on every platform, and a server secret (ab_srv_…) that stays on your backend. Both are in Abrova Console under Project settings → Keys.

Coming soon. Abrova A/B Testing is coming soon. Its code below is the planned API from the A/B Testing docs. The Trace calls (setExtra, captureException) work today.

How the pieces fit together#

  1. 1The product page asks for the variantgetVariant and getVariantConfig return the user’s price variant and its discount. The service decides the variant and the SDK caches it, so the same user always sees the same price.
  2. 2Trace remembers the variantsetExtra puts the variant on every error Trace reports from then on.
  3. 3Checkout completestrackConversion credits the purchase, with its amount, to the variant.
  4. 4You compareA/B Testing shows conversion and revenue per variant; Trace shows the checkout errors and the variant each one carries.

Set up the experiment in Abrova Console#

Before writing code, define the experiment in Abrova Console → A/B Testing:

  • Experiment key: price-test-product-x (one per product, or one for the whole shop).
  • Variants: control (50%), discount-10 (25%), discount-15 (25%). Mark control as the control variant.
  • Configuration per variant, the data your code reads:
    • control: { "discountPercent": 0 }
    • discount-10: { "discountPercent": 10 }
    • discount-15: { "discountPercent": 15 }
  • Traffic allocation: the share of users who enter the experiment. Users outside it get the control variant.
  • Goal: a conversion goal for the purchase.

Each variant has a name and a key; the key is made from the name (lower case, anything other than letters and digits becomes -). Naming the variants in that form, as above, keeps name and key identical. That matters because getVariant returns the variant name on Flutter, React Native, Next.js and Node.js, and the variant key on Android and iOS.

Initialize A/B Testing and Trace at app start#

Package names: abrova_trace_flutter_sdk (from Abrova Center today, see the Flutter SDK) and abrova_ab_testing (when A/B Testing launches).

dart
import 'package:flutter/material.dart';
import 'package:abrova_trace_flutter_sdk/abrova_trace_flutter_sdk.dart';
import 'package:abrova_ab_testing/abrova_ab_testing.dart';

const abrovaApiKey = 'ab_live_your_api_key'; // one key for every SDK

void main() async {
  await AbrovaTrace.initWithZone(
    AbrovaTraceConfig(apiKey: abrovaApiKey, release: '1.0.0'),
    () async {
      WidgetsFlutterBinding.ensureInitialized();

      await AbrovaAbTesting.init(AbrovaAbTestingConfig(
        apiKey: abrovaApiKey,
        tokenProvider: fetchAbrovaToken, // your code: the user token your server mints, or null while signed out
      ));

      runApp(const MyApp());
    },
  );
}

Assigning variants and tracking events act for a user, so the A/B Testing SDK needs an Abrova user token. Your backend mints it with the server secret (POST https://auth.abrova.ir/users/token) and tokenProvider returns it; see User tokens. If you use Abrova Auth, pass AbrovaAuth.instance.tokenProvider instead and you need no backend for this. You can call init before anyone is signed in: until there is a token, the SDK waits and getVariant returns the default variant.

When the user signs in, give both SDKs the same id. Your server must mint the token for that id:

dart
const userId = 'user_42';
AbrovaTrace.setUserId(userId);
await AbrovaAbTesting.instance.setUserId(userId);  // the id your server puts in the token
await AbrovaAbTesting.instance.refreshUserToken(); // asks tokenProvider again now

On sign-out, call AbrovaAbTesting.instance.clearUserId(). It clears that user's variants, attributes and unsent events.

Read the variant on the product page#

When the product page opens, ask for the variant, then read its configuration. The first call asks the service for an assignment; after that the SDK returns the cached one, so one call per page load is enough. If the service can't be reached and nothing is cached, you get the default variant and no configuration, so the page shows the base price.

Flutter

dart
class ProductPage extends StatefulWidget {
  const ProductPage({super.key, required this.basePriceToman});
  final int basePriceToman;

  @override
  State<ProductPage> createState() => _ProductPageState();
}

class _ProductPageState extends State<ProductPage> {
  static const priceTest = 'price-test-product-x';
  late final Future<int> _discount = _loadDiscount();

  Future<int> _loadDiscount() async {
    final variant = await AbrovaAbTesting.instance.getVariant(priceTest);
    final config = await AbrovaAbTesting.instance.getVariantConfig(priceTest);

    // Every Trace error from now on carries the variant in its extra data.
    AbrovaTrace.setExtra('priceVariant', variant);

    return int.tryParse('${config?['discountPercent'] ?? 0}') ?? 0;
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<int>(
      future: _discount,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return const CircularProgressIndicator();
        final discount = snapshot.data!;
        final priceToman = widget.basePriceToman * (100 - discount) ~/ 100;

        return PriceCard( // your widget
          priceToman: priceToman,
          originalPriceToman: discount > 0 ? widget.basePriceToman : null,
        );
      },
    );
  }
}

getVariant never throws. Without a user, a user token or a connection it returns the cached variant, or the default when there is none.

iOS

Use the completion form of getVariant: it waits for the service's answer. The synchronous form returns the default until the assignment has loaded, so the user would briefly see the control price.

swift
AbrovaAbTesting.shared.getVariant(experimentKey: "price-test-product-x") { variant in
    let config = AbrovaAbTesting.shared.getVariantConfig(experimentKey: "price-test-product-x")
    let discount = config?["discountPercent"] as? Int ?? 0
    let priceToman = basePriceToman * (100 - discount) / 100
    AbrovaTrace.shared.setExtra("priceVariant", value: variant)
    self.renderPriceCard(priceToman: priceToman)
}

Web (Next.js)

Package names: @abrova-trace/nextjs-sdk (today) and @abrova/ab-testing-nextjs (when A/B Testing launches). Run this in a Client Component.

typescript
import { AbrovaAbTesting } from '@abrova/ab-testing-nextjs';
import { AbrovaTrace } from '@abrova-trace/nextjs-sdk';

// After AbrovaAbTesting.init({ apiKey, tokenProvider }) and sign-in:
const variant = await AbrovaAbTesting.getVariant('price-test-product-x');
const config = await AbrovaAbTesting.getVariantConfig('price-test-product-x');
const discount = Number(config?.discountPercent ?? 0);
const priceToman = Math.round((basePriceToman * (100 - discount)) / 100);

// Every Trace error from now on carries the variant in its extra data.
AbrovaTrace.getInstance()?.setExtra('priceVariant', variant);

renderPriceCard({ priceToman });

Track the conversion when the user buys#

When checkout completes, record the conversion. The SDK attaches the variant from the assignment it cached in step 3, so call getVariant before trackConversion on the same device. The value is what A/B Testing adds up as revenue per variant; use the amount the user paid, in Toman.

Flutter

dart
// In your checkout-complete handler:
await AbrovaAbTesting.instance.trackConversion(
  'price-test-product-x',
  value: orderTotalToman.toDouble(),
);

Web (Next.js)

typescript
import { AbrovaAbTesting } from '@abrova/ab-testing-nextjs';

await AbrovaAbTesting.trackConversion('price-test-product-x', orderTotalToman);

Capture checkout errors with the variant attached#

Because step 3 called setExtra('priceVariant', …), every error Trace reports from then on carries the variant. That is how you notice a "winning" discount that also breaks checkout for some users. Uncaught errors are reported on their own; report the ones you catch yourself:

dart
try {
  await processPayment(priceToman); // your code
  await AbrovaAbTesting.instance.trackConversion(
    'price-test-product-x',
    value: priceToman.toDouble(),
  );
} catch (err, stack) {
  await AbrovaTrace.captureException(
    err,
    stackTrace: stack,
    extra: {'flow': 'checkout'}, // priceVariant is already attached by setExtra
  );
  showErrorBanner('We could not process your payment.'); // your code
}

Use setExtra or extra, not tags, for the variant. To check it worked, open Abrova Console → Trace → Issues and open the payment error: the Context card shows the extra data, including priceVariant. Repeats of the same error are grouped into one issue, which keeps the extra data of the latest occurrence, so compare the payment errors per variant by opening them rather than expecting a per-variant filter.

Read the experiment results#

Once enough users have bought, open Abrova Console → A/B Testing → price-test-product-x. For each variant it shows participants, conversion rate, the revenue from the values you tracked, and whether the difference to control is statistically significant. Aim for 95% significance before you decide.

A/B Testing and Trace are separate pages: check the payment errors in Trace, and the priceVariant each one carries, before you call a winner. Pick the variant with the best conversion and an acceptable error rate, complete the experiment, and ship the winning price as the regular price.

Going further#

  • Scope the experiment to new customers or a region with targeting rules before rolling it out to everyone (A/B Testing quick start).
  • Put several price experiments on the same page in a mutual exclusion group, so no user is in two of them.
  • Drop expected errors such as "card declined" before they become issues with ignore rules.
دام‌ها
  • نتیجه را فقط پس از رسیدن به حجم نمونه بخوانید.
  • نسخه را همیشه از getVariant بگیرید، نه از عدد تصادفی، تا با تازه‌سازی صفحه قیمت عوض نشود.
  • پیش از نمایش قیمت منتظر نسخه بمانید، وگرنه قیمت کنترل لحظه‌ای دیده می‌شود.
  • setExtra را پیش از اجرای کد پرداخت صدا بزنید، وگرنه خطاهایش نسخه را ندارند.
  • getVariant در Flutter، React Native، Next.js و Node.js نام و در Android و iOS کلید را برمی‌گرداند: نسخه‌ها را به شکل کلید نام‌گذاری کنید.
  • دو آزمایش قیمت را روی یک صفحه اجرا نکنید؛ از گروه انحصار متقابل استفاده کنید.

بعدی: Abrova A/B Testing