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
33 changes: 20 additions & 13 deletions content/docs/android/guides/web-checkout/using-revenuecat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException

class SWDelegate : SuperwallDelegate {
private val client = OkHttpClient()
Expand Down Expand Up @@ -60,8 +61,8 @@ class SWDelegate : SuperwallDelegate {

// In the background using coroutines...
coroutineScope.launch {
// For each subscription id, link it to the user in RevenueCat
stripeSubscriptionIds.forEach { stripeSubscriptionId ->
// For each subscription id, link it to the user in RevenueCat
stripeSubscriptionIds.forEach { stripeSubscriptionId ->
try {
val json = JSONObject().apply {
put("app_user_id", appUserId)
Expand All @@ -80,14 +81,15 @@ class SWDelegate : SuperwallDelegate {
.addHeader("Authorization", "Bearer $revenueCatStripePublicAPIKey")
.build()

val response = client.newCall(request).execute()
val responseBody = response.body?.string()

if (response.isSuccessful) {
Log.d("Superwall", "[!] Success: linked $stripeSubscriptionId to user $appUserId: $responseBody")
} else {
Log.e("Superwall", "[!] Error: unable to link $stripeSubscriptionId to user $appUserId. Response: $responseBody")
}
client.newCall(request).execute().use { response ->
val responseBody = response.body?.string().orEmpty()

if (!response.isSuccessful) {
throw IOException("RevenueCat responded with ${response.code}: $responseBody")
}

Log.d("Superwall", "[!] Success: linked $stripeSubscriptionId to user $appUserId: $responseBody")
}
} catch (e: Exception) {
Log.e("Superwall", "[!] Error: unable to link $stripeSubscriptionId to user $appUserId", e)
}
Expand Down Expand Up @@ -120,6 +122,11 @@ class SWDelegate : SuperwallDelegate {
}
```

<Note>
The example surfaces non-200 responses and network exceptions so you can add retries, user messaging,
or monitoring. Customize the error handling to fit your production logging and UX.
</Note>

<Warning>
If you call `logIn` from RevenueCat's SDK, then you need to call the logic you've implemented
inside `didRedeemLink(result:)` again. For example, that means if `logIn` was invoked from
Expand Down
58 changes: 33 additions & 25 deletions content/docs/expo/guides/web-checkout/using-revenuecat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,36 @@ export class SWDelegate extends SuperwallDelegate {

// In the background, process all subscription IDs
await Promise.all(
stripeSubscriptionIds.map(async (stripeSubscriptionId) => {
try {
const response = await fetch('https://api.revenuecat.com/v1/receipts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': `Bearer ${revenueCatStripePublicAPIKey}`,
},
body: JSON.stringify({
app_user_id: appUserId,
fetch_token: stripeSubscriptionId,
}),
});

const data = await response.json();

if (response.ok) {
stripeSubscriptionIds.map(async (stripeSubscriptionId) => {
try {
const response = await fetch('https://api.revenuecat.com/v1/receipts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': `Bearer ${revenueCatStripePublicAPIKey}`,
},
body: JSON.stringify({
app_user_id: appUserId,
fetch_token: stripeSubscriptionId,
}),
});

const responseText = await response.text();

if (!response.ok) {
throw new Error(
`RevenueCat responded with ${response.status}: ${responseText || 'No body'}`
);
}

const data = responseText ? JSON.parse(responseText) : {};
console.log(`[!] Success: linked ${stripeSubscriptionId} to user ${appUserId}`, data);
} else {
console.error(`[!] Error: unable to link ${stripeSubscriptionId} to user ${appUserId}. Response:`, data);
} catch (error) {
console.error(`[!] Error: unable to link ${stripeSubscriptionId} to user ${appUserId}`, error);
}
} catch (error) {
console.error(`[!] Error: unable to link ${stripeSubscriptionId} to user ${appUserId}`, error);
}
})
})
);

/// After all network calls complete, invalidate the cache
Expand All @@ -100,6 +103,11 @@ export class SWDelegate extends SuperwallDelegate {
}
```

<Note>
The example explicitly checks HTTP status codes and throws on failures so you can plug in retries,
alerting, or user messaging. Tailor the error handling to your networking stack.
</Note>

<Warning>
If you call `logIn` from RevenueCat's SDK, then you need to call the logic you've implemented
inside `didRedeemLink(result)` again. For example, that means if `logIn` was invoked from
Expand Down
61 changes: 35 additions & 26 deletions content/docs/flutter/guides/web-checkout/using-revenuecat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,33 @@ class MySuperwallDelegate extends SuperwallDelegate {

// In the background, send requests to RevenueCat
for (final stripeSubscriptionId in stripeSubscriptionIds) {
try {
final url = Uri.parse('https://api.revenuecat.com/v1/receipts');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': 'Bearer $revenueCatStripePublicAPIKey',
},
body: jsonEncode({
'app_user_id': appUserId,
'fetch_token': stripeSubscriptionId,
}),
);

if (response.statusCode == 200) {
try {
final url = Uri.parse('https://api.revenuecat.com/v1/receipts');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': 'Bearer $revenueCatStripePublicAPIKey',
},
body: jsonEncode({
'app_user_id': appUserId,
'fetch_token': stripeSubscriptionId,
}),
);

if (response.statusCode != 200) {
throw Exception(
'RevenueCat responded with ${response.statusCode}: ${response.body}',
);
}

final json = jsonDecode(response.body);
print('[!] Success: linked $stripeSubscriptionId to user $appUserId: $json');
} else {
print('[!] Error: unable to link $stripeSubscriptionId to user $appUserId. Status: ${response.statusCode}');
} catch (error) {
print('[!] Error: unable to link $stripeSubscriptionId to user $appUserId: $error');
}
} catch (error) {
print('[!] Error: unable to link $stripeSubscriptionId to user $appUserId: $error');
}
}

// After all network calls complete, invalidate the cache
Expand All @@ -100,6 +102,11 @@ class MySuperwallDelegate extends SuperwallDelegate {
}
```

<Note>
The example throws when RevenueCat responds with an error so you can add retries, alerts, or custom
UI. Adapt the error-handling strategy to your networking and logging requirements.
</Note>

Set up the delegate when configuring Superwall:

```dart
Expand Down Expand Up @@ -198,11 +205,13 @@ class MySuperwallDelegate extends SuperwallDelegate {
}),
);

if (response.statusCode == 200) {
print('[!] Successfully linked $stripeSubscriptionId to $appUserId');
} else {
throw Exception('Failed to link subscription: ${response.statusCode}');
if (response.statusCode != 200) {
throw Exception(
'RevenueCat responded with ${response.statusCode}: ${response.body}',
);
}

print('[!] Successfully linked $stripeSubscriptionId to $appUserId');
}
}
```
Expand Down
44 changes: 31 additions & 13 deletions content/shared/web-checkout/using-revenuecat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class Delegate: SuperwallDelegate {
let revenueCatStripePublicAPIKey = "strp....." // replace with your RevenueCat Stripe Public API Key
let appUserId = Purchases.shared.appUserID

// In the background...
// In the background...
Task.detached {
await withTaskGroup(of: Void.self) { group in
// For each subscription id, link it to the user in RevenueCat
Expand All @@ -55,18 +55,31 @@ final class Delegate: SuperwallDelegate {
request.setValue("stripe", forHTTPHeaderField: "X-Platform")
request.setValue("Bearer \(revenueCatStripePublicAPIKey)", forHTTPHeaderField: "Authorization")

do {
request.httpBody = try JSONEncoder().encode([
"app_user_id": appUserId,
"fetch_token": stripeSubscriptionId
])

let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("[!] Success: linked \(stripeSubscriptionId) to user \(appUserId)", json)
} catch {
print("[!] Error: unable to link \(stripeSubscriptionId) to user \(appUserId)", error)
}
do {
request.httpBody = try JSONEncoder().encode([
"app_user_id": appUserId,
"fetch_token": stripeSubscriptionId
])

let (data, response) = try await URLSession.shared.data(for: request)

guard let httpResponse = response as? HTTPURLResponse else {
print("[!] Error: Received an invalid response for \(stripeSubscriptionId)")
return
}

guard (200..<300).contains(httpResponse.statusCode) else {
let body = String(data: data, encoding: .utf8) ?? ""
print("[!] Error: RevenueCat responded with \(httpResponse.statusCode) for \(stripeSubscriptionId). Body: \(body)")
return
}

let json = try JSONSerialization.jsonObject(with: data, options: [])
print("[!] Success: linked \(stripeSubscriptionId) to user \(appUserId)", json)
} catch {
// Surface network errors so you can retry or notify the user.
print("[!] Error: unable to link \(stripeSubscriptionId) to user \(appUserId)", error)
}
}
}
}
Expand All @@ -93,6 +106,11 @@ final class Delegate: SuperwallDelegate {
}
```

<Note>
The snippet logs HTTP failures and propagates network errors so you can build retries, show UI,
or report the issue. Be sure to adapt the error handling to match your monitoring and UX needs.
</Note>

<Warning>
If you call `logIn` from RevenueCat's SDK, then you need to call the logic you've implemented
inside `didRedeemLink(result:)` again. For example, that means if `logIn` was invoked from
Expand Down