Purchasely
4.4
4.4
  • Welcome page
  • General
    • Presentation
    • Release notes
  • Quick start
    • Console Configuration
    • SDK Implementation
    • Testing
    • Sample
  • Basic configuration
    • Console Configuration
      • Creating a new application
      • Creating your Products
        • App Store
        • Play Store
        • Huawei App Gallery
        • Amazon App Store
        • Products & Plans
      • Design your Paywalls
      • Design your Paywalls-Latest
        • Carousel
        • Carousel Flow
        • Features List
        • Features List & Plan Picker with 2 & 3 columns
        • Feature list overlay
        • Plan picker horizontal
        • Plan picker with 2 and 3 Column
    • SDK installation
      • iOS SDK
      • Android SDK
      • React Native SDK
      • Cordova SDK
      • Flutter SDK
      • Unity SDK
    • SDK configurations
      • Paywall observer mode
      • Full mode
      • StoreKit 2
      • Appendices
        • Start the SDK
        • Set User Id
        • Notify when the app is ready
        • Present paywalls
        • Unlock content / service
        • Close SDK (Android only)
    • Stripe
    • Purchasely with RevenueCat
  • S2S notifications
    • Server-to-server notifications ?
    • Apple App Store
    • Google Play Store
    • Huawei App Gallery
  • Analytics
    • Dashboards
      • Introduction
      • Live
      • Subscriptions
      • Cohorts
      • Trials
      • Events
    • Events
      • Webhook events
        • Subscription events
        • Subscription events attributes
      • SDK events
        • UI events
        • UI attributes
  • Integrations
    • Webhook
      • Receiving and understanding messages
      • Managing entitlements
      • Detailed sequence diagrams
    • Airship
    • Amplitude
    • AppsFlyer
    • Adjust
    • Piano analytics(ex AT Internet)
    • Batch
    • Branch
    • Braze
    • Clevertap
    • Customer.io
    • Firebase
    • Iterable
    • Mixpanel
    • MoEngage
    • OneSignal
    • Segment
    • Brevo(ex Sendinblue)
  • Advanced Features
    • Asynchronous paywalls
    • NEW: Promotional offers
    • Anonymous user
    • Associating content
    • Audiences
    • Customising UI
      • Errors & alerts
      • Controllers (iOS) / Fragments (Android)
    • Deeplinks automations
    • Disable placements
    • Displaying users subscriptions
    • Localization
    • Lottie animations
    • Non-subscription products
    • Paywall action interceptor
    • Promoting your products
      • Self-promotion
      • Promoting In-App Purchases
    • Purchase manually
    • Subscription status
    • Use your own paywall
  • Others
    • Frequently Asked Questions
    • Migration guides
      • Migrate to Purchasely
      • Webhook
        • Migrate to Webhook v3.0
      • SDK
        • Migrate to SDK v3.0
          • v2.2.0
          • v2.1.3
        • Migrate to SDK v3.1
        • Migrate to SDK v3.2
        • Migrate to SDK v4.0.0
  • TESTING
    • Testing Cycle Durations
Powered by GitBook

© Purchasely 2020-2023

On this page
  • Why anonymous ?
  • Handling anonymous purchasing
  • 1. Proceeding with an anonymous Purchase
  • 2. Getting the user anonymous_id from the webhook
  • 3. Attaching the purchase to the anonymous user
  • 4. Getting user entitlements & anonymous_id in the app
  • Authentication of an anonymous user
  • Intercept paywall actions

Was this helpful?

Edit on GitHub
  1. Advanced Features

Anonymous user

PreviousNEW: Promotional offersNextAssociating content

Last updated 1 year ago

Was this helpful?

Why anonymous ?

If your app can be used without requiring the user to register (like news app) and you want to offer purchases to unlock content, Apple will refuse that you force the user to register to subscribe.

In that case you will have to handle anonymous purchases (aka unregistered user purchases).

Purchasely helps you combine the power of with anonymous purchases.

Consumable and non-consumables won't be transfered from an anonymous user to a authentified user

Handling anonymous purchasing

To handle anonymous users, Purchasely SDK automatically generates an anonymous_user_id for the user. This anonymous_id remains consistent as long as the user keeps the app on its device.

  1. The anonymous user purchase a product → Purchasely SDK generates ananonymous_user_id

  2. Once the store receipt validated, an event carrying this anonymous_user_id is sent on the webhook

  3. The association between the anonymous_user_idand the Product & Plan purchased must be stored in the developer database.

  4. The app can use a dedicated method of the SDK to get the anonymous_user_idfrom the SDK and check with the database that the user can access the content / service.

1. Proceeding with an anonymous Purchase

If no UserIdhas been attached to the user by the app to Purchasely SDK, the user will be treated as an anonymous user.

When an anonymous user makes an In-App Purchase, the platform will automatically associate it to the anonymous_user_idfor the user. This anonymous_user_idis tied to this particular device and remains consistent as long as the app remains installed on the device and will be the key for your backend to authorize the purchase on the device.

2. Getting the user anonymous_id from the webhook

When an event occurs on a subscription made by an anonymous user, the webhook message carries the user anonymous_user_idgenerated by Purchasely.

{
  "api_version": 3,
  "content_id": "<content id you provided through the sdk>",
  "environment": "SANDBOX",
  "event_created_at": "2021-11-22T09:23:38.559Z",
  "event_created_at_ms": 1637573018559,
  "event_name": "ACTIVATE",
  "is_family_shared": false,
  "offer_type": "NONE",
  "original_purchased_at": "2021-11-22T09:23:36.000Z",
  "original_purchased_at_ms": 1637573016000,
  "plan": "<plan vendorID defined in the Purchasely console>",
  "product": "<product vendorID define in the Purchasely console>",
  "purchased_at": "2021-11-22T09:23:36.000Z",
  "purchased_at_ms": 1637573016000,
  "purchasely_one_time_purchase_id": "otp_XXXXXXXFFFFFFFFF",
  "store": "APPLE_APP_STORE",
  "store_app_bundle_id": "<app bundle id defined in the store console>",
  "store_country": "US",
  "store_original_transaction_id": "100000099999999",
  "store_product_id": "<store product id defined in the store console>",
  "store_transaction_id": "100000099999999",
  "anonymous_user_id": "<anonymous user id generated by Purchasely>"
}

3. Attaching the purchase to the anonymous user

Purchases and corresponding entitlements must be attached to the anonymous user and stored in your database, using the anonymous_user_id.

Every subsequent purchase or event for this particular user will carry the same anonymous_user_id. In other words, when a particular event (renewal / cancellation / expiration etc...) happens on an anonymous user subscription, it will carry the same anonymous_user_id.

4. Getting user entitlements & anonymous_id in the app

Entitlements are managed by the developer backend and directly attached to the anonymous_user_id. The app can retrieve the anonymous_user_id from the SDK using the following method :

Purchasely.anonymousUserId
[Purchasely anonymousUserId];
Purchasely.anonymousUserId
Purchasely.getAnonymousUserId();
Purchasely.getAnonymousUserId();
Purchasely.getAnonymousUserId((anonymousId) => {
	console.log("Purchasely anonymous Id: " + anonymousId);
});
Purchasely.anonymousUserId;
private PurchaselyRuntime.Purchasely _purchasely;

_purchasely.GetAnonymousUserId();

To ease customer support, we advise you to display both anonymous_user_id and user_id inside your app (e.g. in the section "My Subscriptions", in the settings or in the contact-us mail). This will help you identify the user in the Purchasely Console or in your own logs.

Authentication of an anonymous user

To authenticate an anonymous user, just set the appUserId. If your user had a subscription we will transfer it to the connected user. That implies sending events to your backend and wait for it to confirm before you try to refresh your entitlements. This is why you have a closure that passes a boolean telling you if the entitlements of the logged in user should be refreshed.

Purchasely.userLogin(with: "123456789") { (shouldRefreshCredentials) in
    if (shouldRefreshCredentials) {
        // You should call your backend to refresh user entitlements
    }
}
	[Purchasely userLoginWith:@"123456789" shouldRefresh:^(BOOL shouldRefreshCredentials) {
		if (shouldRefreshCredentials) {
			// You should call your backend to refresh user entitlements
		}
	}];
Purchasely.userLogin("123456789") { refresh ->
    if (refresh) {
        // You should call your backend to refresh user entitlements
    }
}
Purchasely.userLogin("123456789", refresh -> {
    if(refresh) {
        // You should call your backend to refresh user entitlements
    }
    return null;
});
Purchasely.userLogin('123456789').then((refresh) => {
  if (refresh) {
    // You should call your backend to refresh user entitlements
  }
});
Purchasely.userLogin("123456789", (shouldRefresh) => {
	if (shouldRefresh) {
		// You should call your backend to refresh user entitlements
	}
});
Purchasely.userLogin('123456789').then((refresh) => {
  if (refresh) {
    //call your backend to refresh user information
  }
});
private PurchaselyRuntime.Purchasely _purchasely;

_purchasely.UserLogin("userId", OnUserLoginCompleted);

Do not forget to call Purchasely.userLogin("YOUR_USER_ID")before returning the result to Purchasely to properly update the presentation without the sign-in button.

Once the user has been connected all the purchased subscriptions are transferred from the anonymous user, to the connected user. User entitlements shall thus be refreshed by the App.

The Webhook will receive 2 Transactional events, a ACTIVATE for the connected user and a DEACTIVATE for the anonymous user. The webhook and integrations you activated will also receive 2 events, a SUBSCRIPTION_RECEIVEDfor the connected user and a SUBSCRIPTION_TRANSFERREDfor the anonymous user.

{
  "plan": "<plan vendorID defined in the Purchasely console>",
  "store": "APPLE_APP_STORE",
  "product": "<product vendorID define in the Purchasely console>",
  "anonymous_user_id": "<anonymous user id originally generated by Purchasely>",
  "event_name": "DEACTIVATE",
  "offer_type": "NONE",
  "api_version": 3,
  "environment": "SANDBOX",
  "purchased_at": "2021-11-07T17:44:17.000Z",
  "store_country": "FR",
  "next_renewal_at": "2021-11-07T17:47:17.000Z",
  "purchased_at_ms": 1636307057000,
  "event_created_at": "2021-11-07T17:43:35.225Z",
  "is_family_shared": false,
  "store_product_id": "<store product id defined in the store console>",
  "next_renewal_at_ms": 1636307237000,
  "event_created_at_ms": 1636307015225,
  "previous_offer_type": "NONE",
  "store_app_bundle_id": "<app bundle id defined in the store console>",
  "subscription_status": "AUTO_RENEWING",
  "store_transaction_id": "100000099999999",
  "original_purchased_at": "2021-11-07T17:41:18.000Z",
  "original_purchased_at_ms": 1636306878000,
  "effective_next_renewal_at": "2021-11-07T17:47:17.000Z",
  "purchasely_subscription_id": "subs_XFJFJEBFFU757FUJH",
  "effective_next_renewal_at_ms": 1636307237000,
  "store_original_transaction_id": "10000009999999"
}
{
  "plan": "<plan vendorID defined in the Purchasely console>",
  "store": "APPLE_APP_STORE",
  "product": "<product vendorID define in the Purchasely console>",
  "user_id": "<user you provided through the SDK when the user logged in>",
  "event_name": "ACTIVATE",
  "offer_type": "NONE",
  "api_version": 3,
  "environment": "SANDBOX",
  "purchased_at": "2021-11-07T17:44:17.000Z",
  "store_country": "FR",
  "next_renewal_at": "2021-11-07T17:47:17.000Z",
  "purchased_at_ms": 1636307057000,
  "event_created_at": "2021-11-07T17:43:35.225Z",
  "is_family_shared": false,
  "store_product_id": "<store product id defined in the store console>",
  "next_renewal_at_ms": 1636307237000,
  "event_created_at_ms": 1636307015225,
  "previous_offer_type": "NONE",
  "store_app_bundle_id": "<app bundle id defined in the store console>",
  "subscription_status": "AUTO_RENEWING",
  "store_transaction_id": "100000099999999",
  "original_purchased_at": "2021-11-07T17:41:18.000Z",
  "original_purchased_at_ms": 1636306878000,
  "effective_next_renewal_at": "2021-11-07T17:47:17.000Z",
  "purchasely_subscription_id": "subs_XFJFJEBFFU757FUJH",
  "effective_next_renewal_at_ms": 1636307237000,
  "store_original_transaction_id": "10000009999999"
}

Intercept paywall actions

To intercept this event, you can setup a global handler that passes you the source paywall controller / fragment above which to display login.

  • IF the user signs in AND has a subscription, you can dismiss the paywall controller

  • IF the user signs in AND doesn't have a subscription, dismiss your login controller and notify the SDK by calling the isLoggedIn closure with true

  • IF the user cancels sign-in, dismiss your login controller and notify the SDK by calling the isLoggedIn closure with false

Purchasely.setPaywallActionsInterceptor { [weak self] (action, parameters, presentationInfo, proceed) in

	switch action {

	// Intercept the tap on login
	case .login:
		// When the user has completed the process
		// Pass true to reload the paywall or dismiss the paywall if the user already has an active subscription
		self?.presentLogin(above: presentationInfo?.controller) { (loggedIn) in
			proceed(loggedIn)
		}
	default:
		proceed(true)
		break
	}
}
[Purchasely setPaywallActionsInterceptor:^(enum PLYPresentationAction action, PLYPresentationActionParameters *parameters, PLYPresentationInfo *info, void (^ proceed)(BOOL)) {
        switch (action) {
            // Intercept the tap on purchase to display the terms and condition
            case PLYPresentationActionPurchase:{
                [self presentTermsAndConditionsAbove:info.controller completion:^(BOOL userAcceptedTerms) {
                    proceed(userAcceptedTerms);
                }];
                break;
            }
            default:
                proceed(YES);
                break;
        }
    }];
Purchasely.setPaywallActionsInterceptor { info, action, parameters, processAction ->
    if (info?.activity == null) return@setPaywallActionsInterceptor

    when(action) {
        PLYPresentationAction.LOGIN -> {
            // Call your method to display your view 
            // and return boolean result to userLoggedIn
            presentLogin(info.activity) { userLoggedIn ->
    		// Don't forget to notify the SDK by calling `processAction`
    		processAction(userLoggedIn)
            }
        }
        else -> {
            Log.d("PLYActionInterceptor", action.value + " " + parameters)
            processAction(true)
        }
    }
}
Purchasely.setPaywallActionsInterceptor((info, action, parameters, listener) -> {
    //if there is no activity then there is nothing to display
    if (info == null || info.getActivity() == null) return;

    switch (action) {
        case LOGIN:
            // call your method to display your view
            // and return boolean result to listener
            // listener.processAction(true);
            presentLogin(info.getActivity(), listener);
            break;
        default:
            listener.processAction(true);
    }
});
Purchasely.setPaywallActionInterceptorCallback((result) => {
    if (result.action === PLYPaywallAction.LOGIN) {
      console.log('User wants to login');
      //Present your own screen for user to log in
      Purchasely.closePaywall();
      Purchasely.userLogin('MY_USER_ID');
      //Call this method to update Purchasely Paywall
      Purchasely.onProcessAction(true);
    } else {
      Purchasely.onProcessAction(true);
    }
});
Purchasely.setPaywallActionInterceptorCallback((result) => {
   if (result.action === PLYPaywallAction.LOGIN) {
      console.log('User wants to login');
      //Present your own screen for user to log in
      Purchasely.closePaywall();
      Purchasely.userLogin('MY_USER_ID');
      //Call this method to update Purchasely Paywall
      Purchasely.onProcessAction(true);
    } else {
      Purchasely.onProcessAction(true);
    }
  });
Purchasely.setPaywallActionInterceptorCallback(
          (PaywallActionInterceptorResult result) {
    if (result.action == PLYPaywallAction.login) {
      print('User wants to login');
      //Present your own screen for user to log in
      Purchasely.closePaywall();
      Purchasely.userLogin('MY_USER_ID');
      //Call this method to update Purchasely Paywall
      Purchasely.onProcessAction(true);
    } else {
      Purchasely.onProcessAction(true);
    }
 });
private PurchaselyRuntime.Purchasely _purchasely;

...
_purchasely.SetPaywallActionInterceptor(OnPaywallActionIntercepted);
...

private void OnPaywallActionIntercepted(PaywallAction action)
{
    Log($"Purchasely Paywall Action Intercepted. Action: {action.action}.");
}

Do not forget to call Purchasely.userLogin("YOUR_USER_ID")before returning the result to Purchasely to properly update the presentation without the sign-in button.

If you don't want to manage anonymous users you could use our mobile API to check and unlock the content locally.

Every presentation, has a Already subscribed? Sign-in button to let your customers connect to unlock a feature / access a content. This button is displayed if you did not set a user id with Purchasely.userLogin() see for more information.

subscription status
Server to Server notifications (S2S)
Anonymous Purchasing - General Process
SDK configuration