Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cd94337
Add basic interface for customer center
facumenzella Oct 8, 2025
e9739bc
Merge branch 'main' into feat/customercenter-staticinterface
facumenzella Oct 8, 2025
6f3b3d8
Merge remote-tracking branch 'origin/main' into feat/customercenter-s…
facumenzella Oct 10, 2025
4cb892d
Merge branch 'feat/customercenter-staticinterface' of github.com:Reve…
facumenzella Oct 10, 2025
48a35fe
update deps
facumenzella Oct 10, 2025
3b54e0b
Merge branch 'main' into feat/customercenter-staticinterface
facumenzella Oct 13, 2025
6c90d5b
Add empty CustomerCenterCallbacks
facumenzella Oct 15, 2025
d846da5
Merge branch 'feat/customercenter-staticinterface' of github.com:Reve…
facumenzella Oct 15, 2025
8ad87e1
Remove result
facumenzella Oct 16, 2025
388aa64
remove extras
facumenzella Oct 16, 2025
9c01e8d
push missing piece
facumenzella Oct 16, 2025
15bc076
callbacks
vegaro Oct 16, 2025
893a33f
add callbacks to PurchasesListener
vegaro Oct 17, 2025
6574a3a
fix missing callback
vegaro Oct 17, 2025
f9e3a4b
remove catching to prevent catching devs exceptions
vegaro Oct 17, 2025
71a8b60
log clean up
vegaro Oct 17, 2025
a18dc9b
add internal back
vegaro Oct 17, 2025
01a351e
assign instead of copy
facumenzella Oct 17, 2025
e88bb72
fix indentation
vegaro Oct 17, 2025
5c97e94
NormalizeNullString
vegaro Oct 17, 2025
65f5025
private constructor
vegaro Oct 21, 2025
b14e0e5
fix );
vegaro Oct 21, 2025
9be5133
made events internal
vegaro Oct 21, 2025
6309e18
log cleanup
vegaro Oct 22, 2025
8a79fbd
fix whitespace
vegaro Oct 22, 2025
c530fc3
Revert "fix whitespace"
vegaro Oct 22, 2025
7695ad2
fix whitespace
vegaro Oct 22, 2025
49261da
replace with constants instead of enum
vegaro Oct 22, 2025
9911fdf
make CallbacksProxy internal to fix compilation
vegaro Oct 22, 2025
3c4795d
Merge branch 'main' into feat/customercenter-staticinterface
vegaro Oct 22, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:node="merge" />
<activity
android:name=".CustomerCenterTrampolineActivity"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:node="merge" />
</application>

<!-- No special permissions required -->

</manifest>


Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package com.revenuecat.purchasesunity.ui;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.revenuecat.purchases.Purchases;
import com.revenuecat.purchases.customercenter.CustomerCenterListener;
import com.revenuecat.purchases.hybridcommon.mappers.MappersHelpersKt;
import com.revenuecat.purchases.hybridcommon.ui.CustomerCenterListenerWrapper;
import com.revenuecat.purchases.ui.revenuecatui.customercenter.ShowCustomerCenter;

import java.util.Map;

import kotlin.Unit;

public class CustomerCenterTrampolineActivity extends ComponentActivity {
private static final String TAG = "PurchasesUnity";

private ActivityResultLauncher<Unit> launcher;
private CustomerCenterListener customerCenterListener;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

launcher = registerForActivityResult(
new ShowCustomerCenter(),
ignored -> {
RevenueCatUI.sendCustomerCenterDismissed();
finish();
}
);

if (!Purchases.isConfigured()) {
Log.e(TAG, "Purchases is not configured. Cannot launch Customer Center.");
RevenueCatUI.sendCustomerCenterError();
finish();
return;
}

customerCenterListener = createCustomerCenterListener();
Purchases.getSharedInstance().setCustomerCenterListener(customerCenterListener);

try {
launcher.launch(Unit.INSTANCE);
} catch (Throwable t) {
Log.e(TAG, "Error launching CustomerCenterActivity", t);
RevenueCatUI.sendCustomerCenterError();
finish();
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if (Purchases.isConfigured()) {
Purchases.getSharedInstance().setCustomerCenterListener(null);
}
}

private CustomerCenterListener createCustomerCenterListener() {
return new CustomerCenterListenerWrapper() {
@Override
public void onManagementOptionSelectedWrapper(@NonNull String s,
@Nullable String s1,
@Nullable String s2) {
// Ignored since it's deprecated
}

@Override
public void onFeedbackSurveyCompletedWrapper(@Nullable String feedbackSurveyOptionId) {
if (feedbackSurveyOptionId != null) {
RevenueCatUI.sendFeedbackSurveyCompleted(feedbackSurveyOptionId);
}
}

@Override
public void onManagementOptionSelectedWrapper(@Nullable String action,
@Nullable String url) {
if (action != null) {
RevenueCatUI.sendManagementOptionSelected(action, url);
}
}

@Override
public void onCustomActionSelectedWrapper(@Nullable String actionId,
@Nullable String purchaseIdentifier) {
if (actionId != null) {
RevenueCatUI.sendCustomActionSelected(actionId, purchaseIdentifier);
}
}

@Override
public void onShowingManageSubscriptionsWrapper() {
RevenueCatUI.sendShowingManageSubscriptions();
}

@Override
public void onRestoreCompletedWrapper(@Nullable Map<String, ?> customerInfo) {
if (customerInfo != null) {
String customerInfoJson = MappersHelpersKt.convertToJson(customerInfo).toString();
RevenueCatUI.sendRestoreCompleted(customerInfoJson);
}
}

@Override
public void onRestoreFailedWrapper(@Nullable Map<String, ?> error) {
if (error != null) {
String errorJson = MappersHelpersKt.convertToJson(error).toString();
RevenueCatUI.sendRestoreFailed(errorJson);
}
}

@Override
public void onRestoreStartedWrapper() {
RevenueCatUI.sendRestoreStarted();
}
};
}

public static void presentCustomerCenter(Activity activity) {
if (activity == null) {
Log.e(TAG, "Activity is null; cannot launch Customer Center");
RevenueCatUI.sendCustomerCenterError();
return;
}

Intent intent = new Intent(activity, CustomerCenterTrampolineActivity.class);

try {
activity.startActivity(intent);
} catch (Throwable t) {
Log.e(TAG, "Error launching CustomerCenterTrampolineActivity", t);
RevenueCatUI.sendCustomerCenterError();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,35 @@
import android.app.Activity;
import android.util.Log;

import androidx.annotation.Nullable;

public class RevenueCatUI {
public interface PaywallCallbacks { void onPaywallResult(String result); }

public interface CustomerCenterCallbacks {
void onCustomerCenterDismissed();
void onCustomerCenterError();
void onFeedbackSurveyCompleted(String feedbackSurveyOptionId);
void onShowingManageSubscriptions();
void onRestoreCompleted(String customerInfoJson);
void onRestoreFailed(String errorJson);
void onRestoreStarted();
void onRefundRequestStarted(String productIdentifier);
void onRefundRequestCompleted(String productIdentifier, String refundRequestStatus);
void onManagementOptionSelected(String option, @Nullable String url);
void onCustomActionSelected(String actionId, @Nullable String purchaseIdentifier);
}

private static final String TAG = "RevenueCatUI";
private static volatile PaywallCallbacks paywallCallbacks;
private static volatile CustomerCenterCallbacks customerCenterCallbacks;

public static void registerPaywallCallbacks(PaywallCallbacks cb) { paywallCallbacks = cb; }
public static void unregisterPaywallCallbacks() { paywallCallbacks = null; }

public static void registerCustomerCenterCallbacks(CustomerCenterCallbacks cb) { customerCenterCallbacks = cb; }
public static void unregisterCustomerCenterCallbacks() { customerCenterCallbacks = null; }

public static void presentPaywall(Activity activity, String offeringIdentifier, String presentedOfferingContextJson, boolean displayCloseButton) {
PaywallTrampolineActivity.presentPaywall(activity, offeringIdentifier, presentedOfferingContextJson, displayCloseButton);
}
Expand All @@ -20,6 +40,10 @@ public static void presentPaywallIfNeeded(Activity activity, String requiredEnti
PaywallTrampolineActivity.presentPaywallIfNeeded(activity, requiredEntitlementIdentifier, offeringIdentifier, presentedOfferingContextJson, displayCloseButton);
}

public static void presentCustomerCenter(Activity activity) {
CustomerCenterTrampolineActivity.presentCustomerCenter(activity);
}

public static void sendPaywallResult(String result) {
try {
PaywallCallbacks cb = paywallCallbacks;
Expand All @@ -32,4 +56,129 @@ public static void sendPaywallResult(String result) {
Log.e(TAG, "Error sending paywall result: " + e.getMessage());
}
}
}

public static void sendCustomerCenterDismissed() {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onCustomerCenterDismissed();
} else {
Log.w(TAG, "No callback registered to receive customer center dismissed");
}
} catch (Throwable e) {
Log.e(TAG, "Error sending customer center dismissed: " + e.getMessage());
}
}

public static void sendCustomerCenterError() {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onCustomerCenterError();
} else {
Log.w(TAG, "No callback registered to receive customer center error");
}
} catch (Throwable e) {
Log.e(TAG, "Error sending customer center error: " + e.getMessage());
}
}

public static void sendFeedbackSurveyCompleted(String feedbackSurveyOptionId) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onFeedbackSurveyCompleted(feedbackSurveyOptionId);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending feedback survey completed: " + e.getMessage());
}
}

public static void sendShowingManageSubscriptions() {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onShowingManageSubscriptions();
}
} catch (Throwable e) {
Log.e(TAG, "Error sending showing manage subscriptions: " + e.getMessage());
}
}

public static void sendRestoreCompleted(String customerInfoJson) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onRestoreCompleted(customerInfoJson);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending restore completed: " + e.getMessage());
}
}

public static void sendRestoreFailed(String errorJson) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onRestoreFailed(errorJson);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending restore failed: " + e.getMessage());
}
}

public static void sendRestoreStarted() {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onRestoreStarted();
}
} catch (Throwable e) {
Log.e(TAG, "Error sending restore started: " + e.getMessage());
}
}

public static void sendRefundRequestStarted(String productIdentifier) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onRefundRequestStarted(productIdentifier);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending refund request started: " + e.getMessage());
}
}

public static void sendRefundRequestCompleted(String productIdentifier, String refundRequestStatus) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onRefundRequestCompleted(productIdentifier, refundRequestStatus);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending refund request completed: " + e.getMessage());
}
}

public static void sendManagementOptionSelected(String option, @Nullable String url) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onManagementOptionSelected(option, url);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending management option selected: " + e.getMessage());
}
}

public static void sendCustomActionSelected(String actionId, @Nullable String purchaseIdentifier) {
try {
CustomerCenterCallbacks cb = customerCenterCallbacks;
if (cb != null) {
cb.onCustomActionSelected(actionId, purchaseIdentifier);
}
} catch (Throwable e) {
Log.e(TAG, "Error sending custom action selected: " + e.getMessage());
}
}
}
Loading