Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ android {
}

dependencies {
// REES46 Android SDK (Maven Central).
// REES46 Android SDK (JitPack).
//
// Note: the 2.x line in Maven Central uses versions like 2.6.0 (not 2.28.0).
val rees46AndroidSdkVersion = "2.6.0"
// Published from github.com/rees46/android-sdk as `com.github.rees46:android-sdk:<tag>`.
// v2.33.0 is the first release that exposes the loyalty manager
// (`SDK.instance.loyaltyManager`).
val rees46AndroidSdkVersion = "v2.33.0"
add(
"rees46Implementation",
"com.rees46:rees46-sdk:$rees46AndroidSdkVersion",
"com.github.rees46:android-sdk:$rees46AndroidSdkVersion",
)

testImplementation("org.jetbrains.kotlin:kotlin-test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,55 @@ class Rees46FlutterSdkPlugin :
}
}

override fun joinLoyalty(
phone: String,
email: String?,
firstName: String?,
lastName: String?,
callback: (Result<String>) -> Unit,
) {
if (phone.isBlank()) {
callback(Result.failure(FlutterError("bad_args", "phone is required", null)))
return
}
try {
SDK.instance.loyaltyManager.join(
phone = phone,
email = email,
firstName = firstName,
lastName = lastName,
onSuccess = { response ->
callback(Result.success(Gson().toJson(response)))
},
onError = { code, message ->
callback(Result.failure(FlutterError("join_loyalty_failed", message ?: "error $code", null)))
},
)
} catch (t: Throwable) {
callback(Result.failure(FlutterError("join_loyalty_failed", t.message, null)))
}
}

override fun getLoyaltyStatus(identifier: String, callback: (Result<String>) -> Unit) {
if (identifier.isBlank()) {
callback(Result.failure(FlutterError("bad_args", "identifier is required", null)))
return
}
try {
SDK.instance.loyaltyManager.getStatus(
identifier = identifier,
onSuccess = { response ->
callback(Result.success(Gson().toJson(response)))
},
onError = { code, message ->
callback(Result.failure(FlutterError("loyalty_status_failed", message ?: "error $code", null)))
},
)
} catch (t: Throwable) {
callback(Result.failure(FlutterError("loyalty_status_failed", t.message, null)))
}
}

override fun getSid(): String = SDK.instance.getSid()

override fun getDid(): String? = SDK.instance.getDid()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,15 @@ data class InitConfig (
}

/**
* Wire format for one purchase line (maps to native [PurchaseItemRequest]; quantity not exposed).
* Wire format for one purchase line (maps to native `PurchaseItemRequest`).
* [amount] is the canonical line quantity; native's redundant `quantity` alias
* is intentionally not exposed.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class PurchaseLineItemWire (
val id: String,
/** Number of units of this product in the order (the line quantity). */
val amount: Long,
val price: Double,
val lineId: String? = null,
Expand Down Expand Up @@ -355,6 +358,20 @@ interface PersonalizationHostApi {
* Dart layer parses the result into [SearchFullResponse].
*/
fun searchFull(query: String, paramsJson: String?, callback: (Result<String>) -> Unit)
/**
* Joins the loyalty program (`loyalty/members/join`) and returns the
* response envelope as a JSON string `{ "status": ..., "payload": { ... } }`.
* The shop is identified by the SDK's configured `shop_id`; [phone] is required.
* Dart layer parses the result into [LoyaltyJoinResponse].
*/
fun joinLoyalty(phone: String, email: String?, firstName: String?, lastName: String?, callback: (Result<String>) -> Unit)
/**
* Returns the loyalty membership status (`loyalty/members/status`) as a JSON
* string `{ "status": ..., "payload": { "member": ..., "level": { ... } } }`.
* [identifier] is the member identifier (phone).
* Dart layer parses the result into [LoyaltyStatusResponse].
*/
fun getLoyaltyStatus(identifier: String, callback: (Result<String>) -> Unit)
/** [customJson] and [recommendedSourceJson] are JSON object strings or null. */
fun trackPurchase(orderId: String, orderPrice: Double, items: List<PurchaseLineItemWire>, deliveryType: String?, deliveryAddress: String?, paymentType: String?, isTaxFree: Boolean, promocode: String?, orderCash: Double?, orderBonuses: Double?, orderDelivery: Double?, orderDiscount: Double?, channel: String?, customJson: String?, recommendedSourceJson: String?, stream: String?, segment: String?, callback: (Result<Unit>) -> Unit)

Expand Down Expand Up @@ -610,6 +627,49 @@ interface PersonalizationHostApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.personalization_flutter_sdk.PersonalizationHostApi.joinLoyalty$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val phoneArg = args[0] as String
val emailArg = args[1] as String?
val firstNameArg = args[2] as String?
val lastNameArg = args[3] as String?
api.joinLoyalty(phoneArg, emailArg, firstNameArg, lastNameArg) { result: Result<String> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PersonalizationApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PersonalizationApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.personalization_flutter_sdk.PersonalizationHostApi.getLoyaltyStatus$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val identifierArg = args[0] as String
api.getLoyaltyStatus(identifierArg) { result: Result<String> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PersonalizationApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PersonalizationApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.personalization_flutter_sdk.PersonalizationHostApi.trackPurchase$separatedMessageChannelSuffix", codec)
if (api != null) {
Expand Down
63 changes: 63 additions & 0 deletions example/integration_test/loyalty_call_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:rees46_sdk/rees46_sdk.dart';

/// Real-network integration test for the loyalty methods.
///
/// Drives the actual native SDK (Android `loyaltyManager`, iOS
/// `joinLoyalty`/`getLoyaltyStatus`) against the live REES46 API using a
/// loyalty-enabled shop. Run on a booted simulator/emulator:
///
/// flutter test integration_test/loyalty_call_test.dart -d [device-id]
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

const shopId = 'c1140c8254976de297c3caf971701a';
const apiDomain = 'api.rees46.ru';
const phone = '79991234567';

Future<PersonalizationSdk> initSdk() async {
final sdk = PersonalizationSdk();
await sdk.initialize(
const SdkInitConfig(
shopId: shopId,
apiDomain: apiDomain,
stream: kIsWeb ? 'web' : 'ios',
enableLogs: true,
autoSendPushToken: false,
sendAdvertisingId: false,
enableAutoPopupPresentation: false,
needReInitialization: false,
),
);
// Wait until the native SDK has established a device id (did) — the status
// endpoint requires it, and on Android init resolves it asynchronously.
for (var i = 0; i < 40; i++) {
final did = await sdk.getDid();
if (did != null && did.isNotEmpty) break;
await Future<void>.delayed(const Duration(milliseconds: 250));
}
return sdk;
}

testWidgets('joinLoyalty — real call returns success', (tester) async {
final sdk = await initSdk();

final response = await sdk.joinLoyalty(phone: phone);
debugPrint(
'joinLoyalty -> status=${response.status} payload=${response.payload}',
);
expect(response.status, equals('success'));
});

testWidgets('getLoyaltyStatus — real call returns success', (tester) async {
final sdk = await initSdk();

final response = await sdk.getLoyaltyStatus(phone);
debugPrint(
'getLoyaltyStatus -> status=${response.status} member=${response.member} level=${response.level?.name}',
);
expect(response.status, equals('success'));
});
}
54 changes: 54 additions & 0 deletions example/integration_test/loyalty_sdk_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';

import 'package:rees46_sdk_example/main.dart' as app;

Future<void> _initializeSdk(PatrolIntegrationTester $) async {
await $.pumpWidgetAndSettle(const app.App());
await $(
'Status: Initialized',
).waitUntilExists(timeout: const Duration(seconds: 30));
await $('Status: Initialized').scrollTo(scrollDirection: AxisDirection.up);
}

String _labelText(PatrolIntegrationTester $, String key) {
final widget = $.tester.widget<Text>(find.byKey(Key(key)));
return widget.data ?? '';
}

void main() {
patrolTest('getLoyaltyStatus — real call returns success', ($) async {
await _initializeSdk($);

await $(const Key('btn_loyalty_status')).scrollTo();
await $(const Key('btn_loyalty_status')).tap();

await $(
const Key('lbl_loyalty_result'),
).waitUntilExists(timeout: const Duration(seconds: 30));

expect(find.byKey(const Key('lbl_loyalty_error')), findsNothing);

final result = _labelText($, 'lbl_loyalty_result');
expect(result, startsWith('status:'));
expect(result, contains('success'));
});

patrolTest('joinLoyalty — real call returns success', ($) async {
await _initializeSdk($);

await $(const Key('btn_join_loyalty')).scrollTo();
await $(const Key('btn_join_loyalty')).tap();

await $(
const Key('lbl_loyalty_result'),
).waitUntilExists(timeout: const Duration(seconds: 30));

expect(find.byKey(const Key('lbl_loyalty_error')), findsNothing);

final result = _labelText($, 'lbl_loyalty_result');
expect(result, startsWith('join:'));
expect(result, contains('success'));
});
}
22 changes: 8 additions & 14 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
PODS:
- CocoaAsyncSocket (7.6.5)
- Flutter (1.0.0)
- integration_test (0.0.1):
- Flutter
- patrol (0.0.1):
- CocoaAsyncSocket (~> 7.6)
- Flutter
- FlutterMacOS
- personalization_flutter_sdk (0.0.1):
- REES46 (3.28.0)
- rees46_sdk (0.0.1):
- Flutter
- REES46 (= 3.23.0)
- REES46 (3.23.0)
- REES46 (= 3.28.0)

DEPENDENCIES:
- Flutter (from `Flutter`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- patrol (from `.symlinks/plugins/patrol/darwin`)
- personalization_flutter_sdk (from `.symlinks/plugins/personalization_flutter_sdk/ios`)
- rees46_sdk (from `.symlinks/plugins/rees46_sdk/ios`)

SPEC REPOS:
trunk:
Expand All @@ -26,20 +23,17 @@ SPEC REPOS:
EXTERNAL SOURCES:
Flutter:
:path: Flutter
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
patrol:
:path: ".symlinks/plugins/patrol/darwin"
personalization_flutter_sdk:
:path: ".symlinks/plugins/personalization_flutter_sdk/ios"
rees46_sdk:
:path: ".symlinks/plugins/rees46_sdk/ios"

SPEC CHECKSUMS:
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
patrol: cea8074f183a2a4232d0ebd10569ae05149ada42
personalization_flutter_sdk: 296eed2fbff082c9697c96d2176b023c173bb375
REES46: 48ad09f18e2d75a730728c7e10f0328ffac69b08
REES46: ecba4cbbfd060636541beedff390564561a1a562
rees46_sdk: 58ed3f7511bca74a71702a41f7d37f757869837b

PODFILE CHECKSUM: b6e248ac4c1eff5807c8b044b39b8bc326af3f5f

Expand Down
Loading
Loading