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
3 changes: 3 additions & 0 deletions demo-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ android {
localProperties.load(new FileInputStream(localPropertiesFile))
}
def shopId = localProperties.getProperty('shop.id', '357382bf66ac0ce2f1722677c59511')
// Server-side shop secret (orders/by_user). Kept out of source — set shop.secret in local.properties.
def shopSecret = localProperties.getProperty('shop.secret', 'demo-shop-secret')

defaultConfig {
applicationId "com.personalization.demo"
Expand All @@ -32,6 +34,7 @@ android {

// Add shop ID to BuildConfig
buildConfigField "String", "SHOP_ID", "\"${shopId}\""
buildConfigField "String", "SHOP_SECRET", "\"${shopSecret}\""
}

productFlavors {
Expand Down
8 changes: 8 additions & 0 deletions demo-app/local.properties.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Template for demo-app credentials. Copy the relevant lines into your local.properties
# (root local.properties is read first, then demo-app/local.properties). Never commit real values.

# Public shop identifier (sent on every request).
shop.id=YOUR_SHOP_ID

# Server-side shop secret — required only for orders/by_user. Keep it local, never commit.
shop.secret=YOUR_SHOP_SECRET
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.personalization.demo

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class OrdersDemoE2ETest {

@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)

@Test
fun getLastOrderProducts_tapButton_noCrash() {
onView(withId(R.id.btnGetLastOrderProducts)).perform(click())
Thread.sleep(1500)
}

@Test
fun getUserOrders_tapButton_noCrash() {
onView(withId(R.id.btnGetUserOrders)).perform(click())
Thread.sleep(1500)
}
}
63 changes: 63 additions & 0 deletions demo-app/src/main/java/com/personalization/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class MainActivity : AppCompatActivity() {
const val COLLISION_PLACEHOLDER_VALUE = "collision_demo"
}

private object DemoOrdersConstants {
/**
* Server-side shop secret for `orders/by_user`. Sourced from `shop.secret` in local.properties
* (gitignored) via BuildConfig — never hardcode a real secret here. Falls back to a placeholder.
*/
val SHOP_SECRET = BuildConfig.SHOP_SECRET
}

private object DemoProductViewConstants {
const val PRODUCT_ID = "demo-product-view-001"
const val DEMO_PRICE = 2499.99
Expand Down Expand Up @@ -121,6 +129,61 @@ class MainActivity : AppCompatActivity() {
findViewById<Button>(R.id.btnTrackPurchaseFull).setOnClickListener {
trackPurchaseFull()
}

findViewById<Button>(R.id.btnGetLastOrderProducts).setOnClickListener {
getLastOrderProducts()
}

findViewById<Button>(R.id.btnGetUserOrders).setOnClickListener {
getUserOrders()
}
}

private fun getUserOrders() {
sdk.ordersManager.getUserOrders(
shopSecret = DemoOrdersConstants.SHOP_SECRET,
onSuccess = { orders ->
runOnUiThread {
Toast.makeText(
this,
getString(R.string.get_user_orders_ok, orders.size),
Toast.LENGTH_LONG,
).show()
}
},
onError = { code, msg ->
runOnUiThread {
Toast.makeText(
this,
"${getString(R.string.get_user_orders_fail)}: $code $msg",
Toast.LENGTH_LONG,
).show()
}
},
)
}

private fun getLastOrderProducts() {
sdk.ordersManager.getLastOrderProducts(
onGetLastOrderProducts = { response ->
runOnUiThread {
Toast.makeText(
this,
getString(R.string.get_last_order_products_ok, response.products.size),
Toast.LENGTH_LONG,
).show()
}
},
onError = { code, msg ->
runOnUiThread {
Toast.makeText(
this,
"${getString(R.string.get_last_order_products_fail)}: $code $msg",
Toast.LENGTH_LONG,
).show()
}
},
)
}

private fun trackPurchaseMinimal() {
Expand Down
14 changes: 14 additions & 0 deletions demo-app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@
android:text="@string/track_purchase_full"
android:layout_marginTop="16dp" />

<Button
android:id="@+id/btnGetLastOrderProducts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/get_last_order_products"
android:layout_marginTop="24dp" />

<Button
android:id="@+id/btnGetUserOrders"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/get_user_orders"
android:layout_marginTop="16dp" />

</LinearLayout>


6 changes: 6 additions & 0 deletions demo-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<string name="track_purchase_full">Track purchase (full)</string>
<string name="track_purchase_ok">trackPurchase: sent</string>
<string name="track_purchase_fail">trackPurchase failed</string>
<string name="get_last_order_products">Get last order products</string>
<string name="get_last_order_products_ok">getLastOrderProducts: %1$d product(s)</string>
<string name="get_last_order_products_fail">getLastOrderProducts failed</string>
<string name="get_user_orders">Get user orders</string>
<string name="get_user_orders_ok">getUserOrders: %1$d order(s)</string>
<string name="get_user_orders_fail">getUserOrders failed</string>
</resources>


Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.personalization.api.OnApiCallbackListener
import com.personalization.api.models.purchase.PurchaseTrackingRequest
import com.personalization.api.managers.CartManager
import com.personalization.api.managers.InAppNotificationManager
import com.personalization.api.managers.OrdersManager
import com.personalization.api.managers.ProductsManager
import com.personalization.api.managers.PredictManager
import com.personalization.api.managers.RecommendationManager
Expand Down Expand Up @@ -84,6 +85,9 @@ open class SDK {
@Inject
lateinit var predictManager: PredictManager

@Inject
lateinit var ordersManager: OrdersManager

@Inject
lateinit var inAppNotificationManager: InAppNotificationManager

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.personalization.api.managers

import com.personalization.Params
import com.personalization.api.responses.orders.GetLastOrderProductsResponse
import com.personalization.api.responses.orders.Order

interface OrdersManager {

/**
* Request the products of the user's last order
*
* @param params Parameters for the request
* @param onGetLastOrderProducts Callback for get last order products
* @param onError Callback for error
*/
fun getLastOrderProducts(
params: Params = Params(),
onGetLastOrderProducts: (GetLastOrderProductsResponse) -> Unit,
onError: (Int, String?) -> Unit = { _: Int, _: String? -> }
)

/**
* Request the list of the user's orders (`orders/by_user`), ascending by date and internal id.
*
* The user is identified by [did] (the SDK's current device id is used when omitted) or by
* [email] / [phone] / [loyaltyId] / [externalId]. [dateFrom] (YYYY-MM-DD) limits orders from a date.
*
* NOTE: [shopSecret] is a server-side secret key; shipping it inside a mobile app exposes it.
* Use only in trusted contexts.
*
* @param shopSecret API secret key (required)
* @param onSuccess Callback with the list of orders
* @param onError Callback for error
*/
fun getUserOrders(
shopSecret: String,
did: String? = null,
email: String? = null,
phone: String? = null,
loyaltyId: String? = null,
externalId: String? = null,
dateFrom: String? = null,
onSuccess: (List<Order>) -> Unit,
onError: (Int, String?) -> Unit = { _: Int, _: String? -> }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.personalization.api.responses.orders

import com.personalization.api.responses.product.Product

/**
* Response for the `orders/last_for_user` request.
*
* The endpoint returns a top-level JSON array of products that belong to the
* user's last order, so [products] is mapped directly from that array.
*/
data class GetLastOrderProductsResponse(
val products: List<Product>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.personalization.api.responses.orders

import com.google.gson.annotations.SerializedName

/**
* Envelope of the `orders/by_user` response: `{ "status": ..., "data": { "orders": [...] } }`.
*/
internal data class GetUserOrdersResponse(
@SerializedName("status")
val status: String? = null,
@SerializedName("data")
val data: UserOrdersData? = null
)

internal data class UserOrdersData(
@SerializedName("orders")
val orders: List<Order> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.personalization.api.responses.orders

import com.google.gson.annotations.SerializedName

/**
* A single user order returned by `orders/by_user`.
*
* Monetary fields (`value`, `cashValue`, ...) are returned by the API as strings.
*/
data class Order(
@SerializedName("_id")
val internalId: Long? = null,
@SerializedName("id")
val id: String? = null,
@SerializedName("date")
val date: String? = null,
@SerializedName("value")
val value: String? = null,
@SerializedName("cash_value")
val cashValue: String? = null,
@SerializedName("bonuses_value")
val bonusesValue: String? = null,
@SerializedName("delivery_value")
val deliveryValue: String? = null,
@SerializedName("promocode")
val promocode: String? = null,
@SerializedName("delivery_date")
val deliveryDate: String? = null,
@SerializedName("internal_status")
val internalStatus: String? = null,
@SerializedName("stream")
val stream: String? = null,
@SerializedName("channel")
val channel: String? = null,
@SerializedName("tax_free")
val taxFree: Boolean? = null,
@SerializedName("delivery_type")
val deliveryType: String? = null,
@SerializedName("delivery_address")
val deliveryAddress: String? = null,
@SerializedName("order_status")
val orderStatus: String? = null,
@SerializedName("payment_type")
val paymentType: String? = null,
@SerializedName("items")
val items: List<OrderItem> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.personalization.api.responses.orders

import com.google.gson.annotations.SerializedName
import com.personalization.api.responses.product.Product

/**
* A line item of a user [Order]. [item] is the catalog product (same shape as other product responses).
*
* [price] / [originalPrice] are returned by the API as strings.
*/
data class OrderItem(
@SerializedName("amount")
val amount: Int? = null,
@SerializedName("price")
val price: String? = null,
@SerializedName("status")
val status: String? = null,
@SerializedName("original_price")
val originalPrice: String? = null,
@SerializedName("barcode")
val barcode: String? = null,
@SerializedName("line_id")
val lineId: String? = null,
@SerializedName("cancel_reason")
val cancelReason: String? = null,
@SerializedName("item")
val item: Product? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import com.personalization.RegisterManager
import com.personalization.api.managers.CartManager
import com.personalization.api.managers.InAppNotificationManager
import com.personalization.api.managers.OrdersManager
import com.personalization.api.managers.ProductsManager
import com.personalization.api.managers.RecommendationManager
import com.personalization.api.managers.PredictManager
Expand All @@ -12,6 +13,7 @@ import com.personalization.api.managers.TrackEventManager
import com.personalization.features.cart.CartManagerImpl
import com.personalization.features.inAppNotification.impl.InAppNotificationManagerImpl
import com.personalization.features.notification.domain.data.NotificationDataExtractor
import com.personalization.features.orders.impl.OrdersManagerImpl
import com.personalization.features.predict.impl.PredictManagerImpl
import com.personalization.features.products.impl.ProductsManagerImpl
import com.personalization.features.recommendation.impl.RecommendationManagerImpl
Expand Down Expand Up @@ -119,6 +121,14 @@ class SdkModule {
sendNetworkMethodUseCase = sendNetworkMethodUseCase
)

@Singleton
@Provides
fun provideOrdersManager(
sendNetworkMethodUseCase: SendNetworkMethodUseCase
): OrdersManager = OrdersManagerImpl(
sendNetworkMethodUseCase = sendNetworkMethodUseCase
)

@Singleton
@Provides
fun provideInAppNotificationManager(
Expand Down
Loading
Loading