# Anonymous user

## 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 [Server to Server notifications (S2S) ](https://purchasely.gitbook.io/purchasely/2.8/stores-configuration/server-to-server-notifications)with anonymous purchases.

{% hint style="danger" %}
Consumable and non-consumables won't be transfered from an anonymous user to a authentified user
{% endhint %}

## Handling anonymous purchasing

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

![Anonymous Purchasing - General Process](https://3348776246-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHAzdlUVqKyZvwTnNIE%2F-MQCjXh1810U8l2a2HME%2F-MQCxfR3BZZmql11emGJ%2Fimage.png?alt=media\&token=60432762-4af5-429c-8105-c2c2275b2f14)

1. The anonymous user purchase a product → Purchasely SDK generates an `anonymous_id`
2. Once the store receipt validated, an event carrying this `anonymous_id` is sent on the webhook
3. The association between the `anonymous_id` and 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_id` from the SDK and check with the database that the user can access the content / service.

### 1. Proceeding with an anonymous Purchase

If no `userId` has 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_id` for the user. This `anonymous_id` is 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_id` generated by Purchasely.

```javascript
{
  "plan": "<plan vendorID defined in the Purchasely console>",
  "store": "APPLE_APP_STORE",
  "product": "<product vendorID define in the Purchasely console>",
  "anonymous_user_id": "<generated anonymous user id>",
  "event_name": "ACTIVATE",
  "offer_type": "NONE",
  "api_version": 3,
  "environment": "SANDBOX",
  "purchased_at": "2021-11-07T17:41:17.000Z",
  "store_country": "FR",
  "next_renewal_at": "2021-11-07T17:44:17.000Z",
  "purchased_at_ms": 1636306877000,
  "event_created_at": "2021-11-07T17:41:34.188Z",
  "is_family_shared": false,
  "store_product_id": "<store product id defined in the store console>",
  "next_renewal_at_ms": 1636307057000,
  "event_created_at_ms": 1636306894188,
  "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:44:17.000Z",
  "purchasely_subscription_id": "subs_XFJFJEBFFU757FUJH",
  "effective_next_renewal_at_ms": 1636307057000,
  "store_original_transaction_id": "10000009999999"
}
```

### 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\_id.

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

### 4. Getting user entitlements & anonymous\_id in the app

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

{% tabs %}
{% tab title="Swift" %}

```swift
Purchasely.anonymousUserId
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[Purchasely anonymousUserId];
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Purchasely.anonymousUserId
```

{% endtab %}

{% tab title="Java" %}

```java
Purchasely.getAnonymousUserId();
```

{% endtab %}

{% tab title="React Native" %}

```javascript
Purchasely.getAnonymousUserId();
```

{% endtab %}

{% tab title="Cordova" %}

```javascript
Purchasely.getAnonymousUserId((anonymousId) => {
	console.log("Purchasely anonymous Id: " + anonymousId);
});
```

{% endtab %}
{% endtabs %}

To ease customer support, we advise you to display both `anonymous_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.

{% hint style="info" %}
If you don't want to manage anonymous users you could use our mobile API to check [subscription status](https://purchasely.gitbook.io/purchasely/2.8/advanced-features/subscription-status) and unlock the content locally.
{% endhint %}

## 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.

{% tabs %}
{% tab title="Swift" %}

```kotlin
Purchasely.userLogin(with: "123456789") { (shouldRefreshCredentials) in
    if (shouldRefreshCredentials) {
        // You should call your backend to refresh user entitlements
    }
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
	[Purchasely userLoginWith:@"123456789" shouldRefresh:^(BOOL shouldRefreshCredentials) {
		if (shouldRefreshCredentials) {
			// You should call your backend to refresh user entitlements
		}
	}];
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Purchasely.userLogin("123456789") { refresh ->
    if (refresh) {
        // You should call your backend to refresh user entitlements
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
Purchasely.userLogin("123456789", refresh -> {
    if(refresh) {
        // You should call your backend to refresh user entitlements
    }
    return null;
});
```

{% endtab %}

{% tab title="React Native" %}

```javascript
Purchasely.userLogin('123456789').then((refresh) => {
  if (refresh) {
    // You should call your backend to refresh user entitlements
  }
});
```

{% endtab %}

{% tab title="Cordova" %}

```javascript
Purchasely.userLogin("123456789", (shouldRefresh) => {
	if (shouldRefresh) {
		// You should call your backend to refresh user entitlements
	}
});
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
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.
{% endhint %}

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 events, a `ACTIVATE` for the connected user and a `DEACTIVATE` for the anonymous user.

```javascript
{
  "plan": "<plan vendorID defined in the Purchasely console>",
  "store": "APPLE_APP_STORE",
  "product": "<product vendorID define in the Purchasely console>",
  "anonymous_user_id": "<generated anonymous user id>",
  "event_name": "DEACTIVATE",
  "offer_type": "NONE",
  "api_version": 3,
  "environment": "SANDBOX",
  "purchased_at": "2021-11-07T17:41:17.000Z",
  "store_country": "FR",
  "next_renewal_at": "2021-11-07T17:44:17.000Z",
  "purchased_at_ms": 1636306877000,
  "event_created_at": "2021-11-07T17:41:34.188Z",
  "is_family_shared": false,
  "store_product_id": "<store product id defined in the store console>",
  "next_renewal_at_ms": 1636307057000,
  "event_created_at_ms": 1636306894188,
  "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:44:17.000Z",
  "purchasely_subscription_id": "subs_XFJFJEBFFU757FUJH",
  "effective_next_renewal_at_ms": 1636307057000,
  "store_original_transaction_id": "10000009999999"
}
```

```javascript
{
  "plan": "<plan vendorID defined in the Purchasely console>",
  "store": "APPLE_APP_STORE",
  "product": "<product vendorID define in the Purchasely console>",
  "user_id": "<newly connected user id you provided through the sdk>",
  "event_name": "ACTIVATE",
  "offer_type": "NONE",
  "api_version": 3,
  "environment": "SANDBOX",
  "purchased_at": "2021-11-07T17:41:17.000Z",
  "store_country": "FR",
  "next_renewal_at": "2021-11-07T17:44:17.000Z",
  "purchased_at_ms": 1636306877000,
  "event_created_at": "2021-11-07T17:41:34.188Z",
  "is_family_shared": false,
  "store_product_id": "<store product id defined in the store console>",
  "next_renewal_at_ms": 1636307057000,
  "event_created_at_ms": 1636306894188,
  "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:44:17.000Z",
  "purchasely_subscription_id": "subs_XFJFJEBFFU757FUJH",
  "effective_next_renewal_at_ms": 1636307057000,
  "store_original_transaction_id": "10000009999999"
}
```

## Intercept paywall actions&#x20;

![](https://3348776246-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MHAzdlUVqKyZvwTnNIE%2F-MXT02gWmnEJ6AD8tlz4%2F-MXTFA8PUDG4eJhKXGIb%2FIMG_17A67C0C9589-1.jpeg?alt=media\&token=1b76ac61-7720-4c11-a61f-ac98d53f699e)

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 [SDK configuration](https://purchasely.gitbook.io/purchasely/2.8/quick-start/sdk-configuration#setting-up-the-user-id) for more information.

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`

{% tabs %}
{% tab title="Swift" %}

```swift
Purchasely.setLoginTappedHandler { (paywallController, isLoggedIn) in
	// Get your login controller
	loginCtrl = LoginViewController()
	
	// Configure the response to notify Purchasely that it needs to reload (if needed)
	loginCtrl.configure(with: isLoggedIn)
	
	paywallController.present(loginCtrl, animated: true, completion: nil)
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[Purchasely setLoginTappedHandler:^(UIViewController * paywallController, void (^ isLoggedIn)(BOOL)) {
	// Get your login controller
	LoginViewController *loginCtrl = LoginViewController()

	// Configure the response to notify Purchasely that it needs to reload (if needed)
	[loginCtrl configureWith: isLoggedIn];
	
	[paywallController presentViewController:loginCtrl animated:YES completion:nil];
}];
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
Purchasely.setLoginTappedHandler { activity, isLoggedIn ->
    // If there is no activity then there is nothing to display
    if (activity == null) return@setLoginTappedHandler

    // Call your method to display your view 
    // and return boolean result to userLoggedIn
    presentLogin(activity) { userLoggedIn ->
    		// Don't forget to notify the SDK by calling `isLoggedIn`
    		isLoggedIn(userLoggedIn)
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
Purchasely.setLoginTappedHandler((activity, listener) -> {
    //if there is no activity then there is nothing to display
    if (activity == null) return;
    
    /*  call your method to display your view
        and return boolean result to listener
        listener.userLoggedIn(userLoggedIn);
     */
    presentLogin(activity, listener);
});

//You can also use the method for kotlin with Function interface
```

{% endtab %}

{% tab title="ReactNative" %}

```typescript
Purchasely.setLoginTappedCallback(() => {
    //Present your own screen for user to log in
    console.log('Received callback from user tapped on sign in button');
    
    //Call this method with true to update Purchasely Paywall if user logged in
    Purchasely.onUserLoggedIn(true);
});
```

{% endtab %}

{% tab title="Cordova" %}

```javascript
Purchasely.setLoginTappedHandler(onLoginTapped => {
    //Present your own screen for user to log in
    
    //Call this method with true to update Purchasely Paywall if user logged in
    Purchasely.onUserLoggedIn(true);
});

```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
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.
{% endhint %}
