Skip to content

Commit

Permalink
d
Browse files Browse the repository at this point in the history
  • Loading branch information
Ldoppea committed Nov 26, 2023
1 parent dac5ef1 commit 8bfac6a
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 152 deletions.
200 changes: 96 additions & 104 deletions src/app/domain/iap/services/clouderyOffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,75 +52,93 @@ export const isClouderyOfferUrl = (
return url.startsWith(`${manager_url}/cozy/instances/${uuid}/premium`)
}

export const isStartIapUrl = (url: string): boolean => {
if (Platform.OS === 'android')
return url.startsWith('https://play.google.com/store/account/subscriptions')
export const interceptNavigation =
(
instanceInfo: InstanceInfo,
subscriptions: Subscription[],
setIsBuying: (isBuying: boolean) => void,
subscribed: () => void
) =>
(request: WebViewNavigation): boolean => {
log.debug('Navigating to', request.url)
try {
if (isOsStoreUrl(request.url)) {
void Linking.openURL(request.url)
return false
}

if (isStartIapUrl(request.url)) {
const url = new URL(request.url)
const productId = url.searchParams.get('productId') ?? 'test_comfort'

if (!productId) {
log.error('The IAP url do not contain any product ID')
return false
}

log.debug('Should start IAP on navigation with productId', productId)
void buySubscription(
productId,
instanceInfo,
subscriptions,
setIsBuying,
subscribed
)
return false
}

return true
} catch (error) {
const errorMessage = getErrorMessage(error)
log.error(
`Error while analysing WebView navigation. Intercept it anyway to prevent unexpected behavior: ${errorMessage}`
)

return false
}
}

const isStartIapUrl = (url: string): boolean => {
return url.startsWith(START_IAP_URL)
}

export const isOsStoreUrl = (url: string): boolean => {
const isOsStoreUrl = (url: string): boolean => {
return (
url.startsWith('https://apps.apple.com/account/subscriptions') ||
url.startsWith('https://play.google.com/store/account/subscriptions')
)
}

const getSubscriptionOffers = (
productId: string,
subscriptions: Subscription[]
): SubscriptionOffer[] => {
if (Platform.OS !== 'android') {
return []
}

log.debug('Get Subscription offers')

const subscriptionsAndroid = subscriptions as SubscriptionAndroid[]

const offers = subscriptionsAndroid
.filter(subscription => subscription.productId === productId)
.map(subscription => {
return {
sku: productId,
offerToken: subscription.subscriptionOfferDetails[0].offerToken
}
})
// .flatMap(subscription => subscription.subscriptionOfferDetails)
// .map(subscriptionOfferDetail => {
// return {
// sku: productId,
// basePlanId: subscriptionOfferDetail.basePlanId,
// offerToken: subscriptionOfferDetail.offerToken
// }
// })

console.log('🟠 OFFER', offers)
return offers
}

const buySubscription = async (
productId: string,
itemId: string,
instanceInfo: InstanceInfo,
subscriptions: Subscription[],
setIsBuying: (isBuying: boolean) => void,
subscribed: () => void
): Promise<void> => {
log.debug('Buy subscription', productId)
console.log('🟠 subscriptions', JSON.stringify(subscriptions))
log.debug('Buy subscription', itemId)

try {
setIsBuying(true)
if (Platform.OS === 'ios') {
log.debug('Clear iOS transactions')
const productId = itemId
await clearTransactionIOS()
await requestSubscription({
sku: productId,
appAccountToken: instanceInfo.instance.data.uuid
})
} else {
const { productId, offers } = getSubscriptionOffers(itemId, subscriptions)

await requestSubscription({
sku: productId,
appAccountToken: instanceInfo.instance.data.uuid,
obfuscatedAccountIdAndroid: instanceInfo.instance.data.uuid,
subscriptionOffers: offers
})
}

await requestSubscription({
sku: productId,
appAccountToken: instanceInfo.instance.data.uuid,
obfuscatedAccountIdAndroid: instanceInfo.instance.data.uuid,
subscriptionOffers: getSubscriptionOffers(productId, subscriptions)
})
subscribed()
} catch (error: unknown) {
if (
Expand All @@ -133,7 +151,7 @@ const buySubscription = async (
setIsBuying(false)
return
}
console.log('🟠 error', JSON.stringify(error))

const errorMessage = getErrorMessage(error)
log.error(
`Error while analysing WebView navigation. Intercept it anyway to prevent unexpected behavior: ${errorMessage}`
Expand All @@ -142,70 +160,44 @@ const buySubscription = async (
}
}

export const interceptNavigation =
(instanceInfo: InstanceInfo, subscriptions: Subscription[], setIsBuying: (isBuying: boolean) => void, subscribed: () => void) =>
(request: WebViewNavigation): boolean => {
log.debug('Navigating to', request.url)
try {
// if (isOsStoreUrl(request.url)) {
// void Linking.openURL(request.url)
// return false
// }
interface Offers {
productId: string
offers: SubscriptionOffer[]
}

if (isStartIapUrl(request.url)) {
const url = new URL(request.url)
let productId = url.searchParams.get('productId') ?? 'test_comfort'
const getSubscriptionOffers = (
basePlanId: string,
subscriptions: Subscription[]
): Offers => {
log.debug('Get Subscription offers')

if (productId === 'test-comfort-monthly') {
productId = 'test_comfort'
}
if (Platform.OS !== 'android') {
throw new Error('getSubscriptionOffers should not be called on iOS')
}

if (productId === 'test-super-comfort-monthly') {
productId = 'test_super_comfort'
}
const subscriptionsAndroid = subscriptions as SubscriptionAndroid[]

if (!productId) {
log.error('The IAP url do not contain any product ID')
return false
const offer = subscriptionsAndroid
.flatMap(subscription => {
return subscription.subscriptionOfferDetails.map(
subsriptionOfferDetails => {
return {
sku: subscription.productId,
offerToken: subsriptionOfferDetails.offerToken,
basePlanId: subsriptionOfferDetails.basePlanId
}
}

log.debug('Should start IAP on navigation with productId', productId)
void buySubscription(productId, instanceInfo, subscriptions, setIsBuying, subscribed)
return false
}

return true
} catch (error) {
const errorMessage = getErrorMessage(error)
log.error(
`Error while analysing WebView navigation. Intercept it anyway to prevent unexpected behavior: ${errorMessage}`
)
})
.find(offer => offer.basePlanId === basePlanId)

return false
}
if (!offer) {
throw new Error(`No base plan found for ${basePlanId}`)
}

export const interceptOpenWindow =
() =>
(syntheticEvent: WebViewOpenWindowEvent): void => {
log.debug('Intercept', syntheticEvent.nativeEvent.targetUrl)
try {
const { nativeEvent } = syntheticEvent
// For the moment, cloudery update is not deployed so we can not intercept the redirection on https://iapflagship.
// So I intercept the redirection on Stripe website in the Stripe payment modal.
const destinationUrl = nativeEvent.targetUrl.includes('stripe')
? 'https://iapflagship?productId=test_comfort_monthly'
: nativeEvent.targetUrl

if (isStartIapUrl(destinationUrl)) {
const url = new URL(destinationUrl)
const productId = url.searchParams.get('productId')
log.debug('Should start IAP on open window with productId', productId)
void requestSubscription({ sku: productId })
return
}
} catch (error) {
const errorMessage = getErrorMessage(error)
log.error(`Error while intercepting WebView openWindow: ${errorMessage}`)
}
log.debug('Found subscription offer', offer)
return {
productId: offer.sku,
offers: [offer]
}
}

Check failure on line 203 in src/app/domain/iap/services/clouderyOffer.ts

View workflow job for this annotation

GitHub Actions / Quality Checks

Insert `⏎`
42 changes: 6 additions & 36 deletions src/app/view/IAP/ClouderyOffer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React, { useEffect, useState } from 'react'
import { Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { withIAPContext, useIAP, initConnection } from 'react-native-iap'
import React, { useState } from 'react'
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { withIAPContext } from 'react-native-iap'
import WebView from 'react-native-webview'
import type {
WebViewOpenWindowEvent,
WebViewNavigation
} from 'react-native-webview/lib/WebViewTypes'
import type { WebViewNavigation } from 'react-native-webview/lib/WebViewTypes'

import { useClouderyOffer } from '/app/view/IAP/hooks/useClouderyOffer'
import { BackTo } from '/components/ui/icons/BackTo'
Expand All @@ -20,46 +17,22 @@ const HEADER_PADDING_TOP = statusBarHeight + 8
const HEADER_PADDING_BOTTOM = 8
const HEADER_LINE_HEIGHT = 16
const TOUCHABLE_VERTICAL_PADDING = 8
const IOS_OFFERS = ['test_comfort_monthly', 'test_super_comfort_monthly']
const ANDROID_OFFERS = ['test_comfort', 'test_super_comfort']
const SKUS = Platform.OS === 'ios' ? IOS_OFFERS : ANDROID_OFFERS

const ClouderyOfferWithIAPContext = (): JSX.Element | null => {
const { products, subscriptions, getProducts, getSubscriptions } = useIAP() // Not necessary ?
const {
popupUrl,
instanceInfoLoaded,
interceptNavigation,
interceptOpenWindow,
hidePopup,
isBuying
} = useClouderyOffer()

useEffect(() => {
const initIAP = async (): Promise<void> => {
await initConnection()
const productsResult = await getProducts({ skus: SKUS })
const subscriptionsResult = await getSubscriptions({ skus: SKUS })

console.log('🟠 productsResult', productsResult)
console.log('🟠 subscriptionsResult', subscriptionsResult)
}

if (popupUrl) {
void initIAP()
}
}, [popupUrl, getSubscriptions, getProducts])

console.log('🟠 products', products)
console.log('🟠 subscriptions', JSON.stringify(subscriptions))

return popupUrl && instanceInfoLoaded ? (
<>
<WebViewWithLoadingOverlay
hidePopup={hidePopup}
popupUrl={popupUrl}
interceptNavigation={interceptNavigation}
interceptOpenWindow={interceptOpenWindow}
/>
{isBuying && (
<>
Expand All @@ -77,20 +50,18 @@ const ClouderyOfferWithIAPContext = (): JSX.Element | null => {
) : null
}

export const ClouderyOffer = withIAPContext(ClouderyOfferWithIAPContext) // Not necessary ?
export const ClouderyOffer = withIAPContext(ClouderyOfferWithIAPContext)

interface WebViewWithLoadingOverlayProps {
hidePopup: () => void
popupUrl: string
interceptNavigation: (request: WebViewNavigation) => boolean
interceptOpenWindow: (syntheticEvent: WebViewOpenWindowEvent) => void
}

const WebViewWithLoadingOverlay = ({
hidePopup,
popupUrl,
interceptNavigation,
interceptOpenWindow
interceptNavigation
}: WebViewWithLoadingOverlayProps): JSX.Element => {
const [loading, setLoading] = useState(true)
const { t } = useI18n()
Expand All @@ -112,7 +83,6 @@ const WebViewWithLoadingOverlay = ({
<WebView
source={{ uri: popupUrl }}
onShouldStartLoadWithRequest={interceptNavigation}
onOpenWindow={interceptOpenWindow}
onLoadEnd={(): void => setLoading(false)}
/>
{loading && (
Expand Down
Loading

0 comments on commit 8bfac6a

Please sign in to comment.