diff --git a/content/docs/android/guides/web-checkout/using-revenuecat.mdx b/content/docs/android/guides/web-checkout/using-revenuecat.mdx index a7f6e961..9002e7de 100644 --- a/content/docs/android/guides/web-checkout/using-revenuecat.mdx +++ b/content/docs/android/guides/web-checkout/using-revenuecat.mdx @@ -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() @@ -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) @@ -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) } @@ -120,6 +122,11 @@ class SWDelegate : SuperwallDelegate { } ``` + + 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. + + 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 diff --git a/content/docs/expo/guides/web-checkout/using-revenuecat.mdx b/content/docs/expo/guides/web-checkout/using-revenuecat.mdx index f5efbb3a..ca0a867b 100644 --- a/content/docs/expo/guides/web-checkout/using-revenuecat.mdx +++ b/content/docs/expo/guides/web-checkout/using-revenuecat.mdx @@ -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 @@ -100,6 +103,11 @@ export class SWDelegate extends SuperwallDelegate { } ``` + + 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. + + 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 diff --git a/content/docs/flutter/guides/web-checkout/using-revenuecat.mdx b/content/docs/flutter/guides/web-checkout/using-revenuecat.mdx index d74d236a..1d268dd6 100644 --- a/content/docs/flutter/guides/web-checkout/using-revenuecat.mdx +++ b/content/docs/flutter/guides/web-checkout/using-revenuecat.mdx @@ -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 @@ -100,6 +102,11 @@ class MySuperwallDelegate extends SuperwallDelegate { } ``` + + 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. + + Set up the delegate when configuring Superwall: ```dart @@ -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'); } } ``` diff --git a/content/shared/web-checkout/using-revenuecat.mdx b/content/shared/web-checkout/using-revenuecat.mdx index f1651d45..a1dbee5c 100644 --- a/content/shared/web-checkout/using-revenuecat.mdx +++ b/content/shared/web-checkout/using-revenuecat.mdx @@ -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 @@ -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) + } } } } @@ -93,6 +106,11 @@ final class Delegate: SuperwallDelegate { } ``` + + 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. + + 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