Skip to content

Commit

Permalink
feat: adds restore purchases capability
Browse files Browse the repository at this point in the history
  • Loading branch information
cb-haripriyan committed May 12, 2023
1 parent ff45fd7 commit 697a1fa
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
package com.chargebee.android.reactnative

import com.chargebee.android.Chargebee
import com.chargebee.android.reactnative.ChargebeeReactNativeSpec
import com.chargebee.android.ErrorDetail
import com.chargebee.android.billingservice.CBCallback
import com.chargebee.android.billingservice.CBPurchase
import com.chargebee.android.billingservice.GPErrorCode
import com.chargebee.android.exceptions.CBException
import com.chargebee.android.exceptions.CBProductIDResult
import com.chargebee.android.exceptions.ChargebeeResult
import com.chargebee.android.models.*
import com.chargebee.android.models.CBProduct
import com.chargebee.android.models.CBRestoreSubscription
import com.chargebee.android.models.CBSubscription
import com.chargebee.android.network.ReceiptDetail
import com.chargebee.android.reactnative.models.*
import com.chargebee.android.reactnative.models.CBReactNativeError
import com.chargebee.android.reactnative.models.PurchaseResult
import com.chargebee.android.reactnative.models.errorCode
import com.chargebee.android.reactnative.models.messageUserInfo
import com.chargebee.android.reactnative.models.toMap
import com.chargebee.android.reactnative.utils.convertArrayToWritableArray
import com.chargebee.android.reactnative.utils.convertAuthenticationDetailToDictionary
import com.chargebee.android.reactnative.utils.convertListToWritableArray
import com.chargebee.android.reactnative.utils.convertPurchaseResultToDictionary
import com.chargebee.android.reactnative.utils.convertQueryParamsToArray
import com.chargebee.android.reactnative.utils.convertReadableArray
import com.chargebee.android.reactnative.utils.convertReadableMapToCustomer
import com.chargebee.android.reactnative.utils.convertRestoredSubscriptionsToDictionary
import com.chargebee.android.reactnative.utils.convertSubscriptionsToDictionary
import com.facebook.react.bridge.*
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap

class ChargebeeReactNativeModule internal constructor(context: ReactApplicationContext) :
ChargebeeReactNativeSpec(context) {
Expand Down Expand Up @@ -159,6 +169,23 @@ class ChargebeeReactNativeModule internal constructor(context: ReactApplicationC
}
}

@ReactMethod
override fun restorePurchases(includeInActiveProducts: Boolean, promise: Promise) {
val activity = currentActivity
activity?.let {
CBPurchase.restorePurchases(it, includeInActiveProducts, object :
CBCallback.RestorePurchaseCallback {
override fun onSuccess(result: List<CBRestoreSubscription>) {
promise.resolve(convertRestoredSubscriptionsToDictionary(result))
}
override fun onError(error: CBException) {
val messageUserInfo = error.messageUserInfo()
promise.reject("${CBReactNativeError.RESTORE_FAILED.code}", messageUserInfo.getString("message"), error, messageUserInfo)
}
})
}
}

companion object {
const val NAME = "ChargebeeReactNative"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ enum class CBReactNativeError(val code: Int) {
REQUEST_FAILED(2012),
PRODUCT_PURCHASED_ALREADY(2013),

// Restore Error
NO_RECEIPT(2014),
REFRESH_RECEIPT_FAILED(2015),
RESTORE_FAILED(2016),

// General Errors
SYSTEM_ERROR(3000);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.chargebee.android.reactnative.utils

import com.android.billingclient.api.SkuDetails
import com.chargebee.android.models.CBProduct
import com.chargebee.android.models.CBRestoreSubscription
import com.chargebee.android.reactnative.models.PurchaseResult
import com.chargebee.android.models.SubscriptionDetailsWrapper
import com.chargebee.android.network.CBAuthResponse
Expand Down Expand Up @@ -101,3 +102,19 @@ internal fun convertAuthenticationDetailToDictionary(authResponseData: Any): Wri
}
return writableMap
}

internal fun convertRestoredSubscriptionsToDictionary(restoredSubscriptions: List<CBRestoreSubscription>): WritableArray {
val writableArray: WritableArray = WritableNativeArray()
for (item in restoredSubscriptions) {
writableArray.pushMap(convertRestoredSubscriptionToDictionary(item))
}
return writableArray
}

internal fun convertRestoredSubscriptionToDictionary(restoredSubscription: CBRestoreSubscription): WritableMap {
val writableMap: WritableMap = WritableNativeMap()
writableMap.putString("subscriptionId", restoredSubscription.subscriptionId)
writableMap.putString("planId", restoredSubscription.planId)
writableMap.putString("storeStatus", restoredSubscription.storeStatus)
return writableMap
}
2 changes: 2 additions & 0 deletions android/src/oldarch/ChargebeeReactNativeSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ abstract class ChargebeeReactNativeSpec internal constructor(context: ReactAppli
abstract fun purchaseProduct(productId: String, customer: ReadableMap, promise: Promise)
abstract fun retrieveSubscriptions(queryParams: ReadableMap, promise: Promise)

abstract fun restorePurchases(includeInActiveProducts: Boolean, promise: Promise)

}
10 changes: 5 additions & 5 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PODS:
- boost (1.76.0)
- Chargebee (1.0.18)
- ChargebeeReactNative (2.0.0-beta.2):
- Chargebee (= 1.0.18)
- Chargebee (1.0.21)
- ChargebeeReactNative (2.0.0):
- Chargebee (= 1.0.21)
- React-Core
- CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
Expand Down Expand Up @@ -567,8 +567,8 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
Chargebee: 58a74fe76996f9c21d94c2f6ee0d4938a47aaac2
ChargebeeReactNative: fc463f848d76b82b2fab304288789886500a3cf3
Chargebee: 57f839c20e1516e03234d48b392b960b32824864
ChargebeeReactNative: 584309b223d45ae7ad1e32089c9f018189343910
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: 61839cba7a48c570b7ac3e1cd8a4d0948382202f
Expand Down
71 changes: 71 additions & 0 deletions example/src/components/RestoreSubscriptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
import { Text, Card, Button, Modal } from '@ui-kitten/components';
import Chargebee from '@chargebee/react-native-chargebee';

export const RestoreSubscriptions = () => {
const [visible, setVisible] = React.useState(false);
const [restoreDisabled, setRestoreDisabled] = React.useState(false);
const [restoreData, setRestoreData] = React.useState('');

const restorePurchases = async () => {
console.log("Restoring purchases")
setRestoreDisabled(true)
try {
const restoredSubscriptions = await Chargebee.restorePurchases(true)
console.log("Restored purchases", restoredSubscriptions)
console.log("Restored Subscription Id", restoredSubscriptions[0].subscriptionId)
console.log("Restored Plan Id", restoredSubscriptions[0].planId)
console.log("Restored Store Status", restoredSubscriptions[0].storeStatus)
setRestoreData(JSON.stringify(restoredSubscriptions))
setVisible(true)
} catch (error) {
console.log('Restore Purchases failed', error);
console.log(
'=========================',
Platform.OS,
'========================='
);
const errorModel = {
code: error.code,
message: error.message,
userInfo: {
message: error.userInfo?.message,
apiErrorCode: error.userInfo?.apiErrorCode,
httpStatusCode: error.userInfo?.httpStatusCode,
},
};
console.error(errorModel);
console.log('=========================');
setRestoreData(JSON.stringify(errorModel))
setVisible(true)
}
}
function dismissModal() {
setRestoreDisabled(false)
setVisible(false)
}
return (
<>
<Button style={styles.restore} appearance='outline' disabled={restoreDisabled} onPress={() => restorePurchases()}>Restore Purchases</Button>
<Modal visible={visible}>
<Card disabled={true}>
<Text>
Restored data:
{restoreData}
</Text>
<Button onPress={() => dismissModal()}>
DISMISS
</Button>
</Card>
</Modal>
</>
);
};

const styles = StyleSheet.create({
restore: {
justifyContent: 'center',
}
});

11 changes: 9 additions & 2 deletions example/src/screens/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import Chargebee, {
Subscription,
SubscriptionsRequest,
} from '@chargebee/react-native-chargebee';
import { Text } from '@ui-kitten/components';
import { Layout, Text } from '@ui-kitten/components';
import React, { useEffect, useState } from 'react';
import { Platform } from 'react-native';
import { StyleSheet } from 'react-native';
import LoginForm from '../components/LoginForm';
import { RestoreSubscriptions } from '../components/RestoreSubscriptions';
import type { LoginFunction } from '../types/CBee';

const LoginScreen = ({ navigation }) => {
Expand Down Expand Up @@ -68,13 +69,16 @@ const LoginScreen = ({ navigation }) => {

return (
<>
<Layout style={styles.container}>
<Text style={styles.text} category="h1">
Welcome to CBeeHive
</Text>
<Text style={styles.text} category="s1">
Login to to start learning
</Text>
<LoginForm login={login} />
</Layout>
<RestoreSubscriptions />
</>
);
};
Expand All @@ -84,10 +88,13 @@ export default LoginScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
justifyContent: 'flex-start',
alignItems: 'center',
},
text: {
textAlign: 'center',
},
restore: {
justifyContent: 'center',
}
});
5 changes: 2 additions & 3 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1036,9 +1036,8 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"

"@chargebee/react-native-chargebee@link:..":
version "0.0.0"
uid ""
"@chargebee/react-native-chargebee@../.":
version "2.0.0"

"@egjs/hammerjs@^2.0.17":
version "2.0.17"
Expand Down
34 changes: 33 additions & 1 deletion ios/CBError+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ extension CBError {

var asNSError: NSError {
switch self {
case .invalidRequest(let response), .operationFailed(let response), .paymentFailed(let response):
case .invalidRequest(let response), .operationFailed(let response),
.paymentFailed(let response), .serverError(let response):
var userInfo: [String: Any] = [:]
userInfo["message"] = response.message
userInfo["type"] = response.type
userInfo["apiErrorCode"] = response.apiErrorCode
userInfo["param"] = response.param
userInfo["httpStatusCode"] = response.httpStatusCode
return NSError.init(domain: "ChargebeeError", code: httpStatusCode, userInfo: userInfo)

}

}
Expand All @@ -33,3 +35,33 @@ extension CBPurchaseError {
return userInfo
}
}

extension RestoreError {

var reactNativeError: CBReactNativeError {
switch self {
case .invalidReceiptData, .invalidReceiptURL:
return .invalidReceipt
case .noReceipt:
return .noReceipt
case .refreshReceiptFailed:
return .refreshReceiptFailed
case .restoreFailed:
return .restoreFailed
case .noProductsToRestore:
return .noProductToRestore
case .serviceError:
return .systemError
}
}

var asNSError: NSError {
return NSError(domain: "RestoreError", code: self.reactNativeError.rawValue, userInfo: self.userInfo)
}

var userInfo: [String: Any] {
var userInfo: [String: Any] = [:]
userInfo["message"] = self.errorDescription
return userInfo
}
}
19 changes: 19 additions & 0 deletions ios/CBInAppSubscription+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// CBInAppSubscription+Extensions.swift
// ChargebeeReactNative
//
// Created by cb-haripriyan on 11/05/23.
//

import Foundation
import Chargebee

extension InAppSubscription {
var asDictionary: [String:Any] {
var inAppSubscription: [String: Any] = [:]
inAppSubscription["subscriptionId"] = self.subscriptionID
inAppSubscription["planId"] = self.planID
inAppSubscription["storeStatus"] = self.storeStatus.rawValue
return inAppSubscription
}
}
16 changes: 15 additions & 1 deletion ios/ChargebeeHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,25 @@ public class ChargebeeHelper: NSObject {
}
}
}

@objc public func restorePurchases(includeInActiveProducts: Bool, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {

CBPurchase.shared.restorePurchases(includeInActiveProducts: includeInActiveProducts) { result in
switch result {
case .success(let subscriptions):
let inAppSubscriptions = subscriptions.map { $0.asDictionary }
resolver(inAppSubscriptions)
case .failure(let error):
let restoreError = error.asNSError
rejecter("\(restoreError.code)", error.errorDescription, restoreError)
}
}
}

}

fileprivate func reject(withPurchaseError error: CBPurchaseError, using rejecter: RCTPromiseRejectBlock) {
let purchaseError = NSError.init(domain: "StoreError",
let purchaseError = NSError(domain: "StoreError",
code: CBReactNativeError.errorCode(purchaseError: error).rawValue,
userInfo: error.userInfo)
rejecter("\(purchaseError.code)", error.localizedDescription, purchaseError)
Expand Down
9 changes: 9 additions & 0 deletions ios/ChargebeeReactNative.mm
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ @implementation ChargebeeReactNative
[helper retrieveSubscriptionsWithQueryParams:queryParams resolver:resolve rejecter:reject];
}

RCT_REMAP_METHOD(restorePurchases,
restorePurchasesWithIncludeInActiveProducts:(BOOL *)includeInActiveProducts
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
{
ChargebeeHelper* helper = [ChargebeeHelper shared];
[helper restorePurchasesWithIncludeInActiveProducts:includeInActiveProducts resolver:resolve rejecter:reject];
}

// Don't compile this code when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
Expand Down
File renamed without changes.
Loading

0 comments on commit 697a1fa

Please sign in to comment.