customerio / customerio-android

This is the official Customer.io SDK for Android.
MIT License
11 stars 9 forks source link

Support for merge customers endpoint #255

Open voghDev opened 10 months ago

voghDev commented 10 months ago

Is your feature request related to a problem? Please describe. Hello!

We're trying to merge duplicated users in our App. According to the documentation you provide, this can be done via web management, or calling the /api/v1/merge_customers endpoint. I see there is no function to merge users in customer.io sdk for Android, nor iOS.

Are you planning to include a function to merge two users in the sdk?

Thanks in advance!

Describe the solution you'd like An idea to user as starting point could be

Android (CustomerIO.kt)

fun merge(identifier: String, newIdentifier: String) {
    ... // call to /api/v1/merge_customers
}

iOS (CustomerIO.swift)

    func merge(
        identifier: String,
        newIdentifier: String
    )

Describe alternatives you've considered

Additional context Having it in a newer version of the library would be awesome!

Shahroz16 commented 10 months ago

Hi @voghDev,

Thank you for your feature request for "merge customer's endpoints". We've added it to our backlog for consideration in future releases.

We appreciate your feedback and suggestions.

voghDev commented 9 months ago

Hi!

we've done our first successful merge requests. I'll post the Retrofit code we're using for calling the endpoint. I think it's ok posting it as any AI can generate a similar code snippet in seconds.

interface CustomerIoService {
  @POST("/api/v1/merge_customers")
  fun mergeCustomers(@Body paramsRequest: CustomerIoParamsRequest): Single<Unit>
}

data class CustomerIoParamsRequest(
  @SerializedName("primary")
  val primary: CustomerIoUserData,
  @SerializedName("secondary")
  val secondary: CustomerIoUserData,
)

data class CustomerIoUserData(
  @SerializedName("email")
  val email: String
)

class CustomerIoAuthHeaderInterceptor : Interceptor {
  private val siteId = "this is the siteId"
  private val apiKey = "this is the apiKey"
  override fun intercept(chain: Interceptor.Chain): Response = chain.run {
    val auth = buildAuthHeader(siteId, apiKey)
    proceed(
      request()
        .newBuilder()
        .header("Authorization", "Basic $auth")
        .build()
    )
  }

  private fun buildAuthHeader(siteId: String, apiKey: String) =
    Base64.encodeToString("$siteId:$apiKey".toByteArray(charset("UTF-8")), Base64.NO_WRAP)
}

// DI Module

private fun provideCustomerIoRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
  .baseUrl("https://track.customer.io/")
  .addConverterFactory(GsonConverterFactory.create())
  .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
  .client(okHttpClient)
  .build()

private fun provideCustomerIoHttpClient(): OkHttpClient {
  val logging = HttpLoggingInterceptor()
  val authHeaderInterceptor = CustomerIoAuthHeaderInterceptor()
  return OkHttpClient.Builder()
    .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
    .readTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .addInterceptor(authHeaderInterceptor)
    .addInterceptor(logging)
    .build()
}

@Shahroz16 If you find something wrong on it, please let us know 🙏 we're guessing how the api works and apparently having concurrency issues between the sdk and our http layer

Shahroz16 commented 9 months ago

Hey @voghDev, thank you for posting this, can you explain the concurrency issue? because that shouldn't happen as SDKs network layer is going to be different than your app network layer, and I believe you are adding this in your own apps network layer.

voghDev commented 9 months ago

Hi! Thanks for your response. The issue is that we are getting a correct 200 OK HTTP response from the /api/v1/merge_customers endpoint, but we don't see the users merged in customer.io web panel. After various attempts, it seems to end up merging the users correctly, but not the first time we execute it. That's why I thought about a concurrency issue, as we call identify() in the SDK and our merge Retrofit implementation in consecutive lines of code 🤔

Shahroz16 commented 9 months ago

I think it is because the merge request isn't blocking, it gets queued and then it gets performed when picked on the backend side of things. So as long as you got 200, and you had the right profiles, you should be okay.

voghDev commented 9 months ago

Thanks for your help and support 🙌 We have fresh information that is helpful. It seems that when we merge the user immediately after the identify call, the answer is 200 as the Auth headers are correct, but the user is not being merged because the account has not been created yet. This happens for users identified for the first time in customer.io. Seems like it takes a few millis/seconds for the account creation to complete. Is there any way to query if an account exists (has been created) or not? by email, by id, by cio_id or any field? I don't see it in the SDK nor in the public Api, but would like to ask it, in case we can implement the query and share the code here 🙂

Shahroz16 commented 9 months ago

Hey @voghDev, I don't think there is any endpoint for this, unfortunately, so not an elegant solution but maybe add a delay? when you call the identify method of the CIO SDK, maybe add a delay of X second and then call merge?

That X seconds doesn't have to be a random value but X could be the according to your config of background queue plus some buffer?

voghDev commented 9 months ago

That's one of the solutions we thought about; Adding a delay. Another solution would be a retry logic that performs a maximum of N attempts. Once the maximum attempts have been reached, it won't try to merge anymore. We'll let you know what we finally do 🙂 Thank you so much for the information!