Skip to content

Commit

Permalink
ios complete purchase (#1891)
Browse files Browse the repository at this point in the history
* @ReactModule annotation (#1886)

Co-authored-by: Andres Aguilar <andres.aguilar@nfl.com>

* 10.0.1

* product purchase lifecycle (#1885)

* ci: replace publication for next branch

* refactor!: create a PurchaseError class (#1866)

* refactor!: remove support for default export (#1864)

* chore: clean up non existing errors (#1868)

* chore: move some logic into internal files (#1871)

* refactor: extract event-emitter to specific file (#1872)

* refactor!: make all methods take an object of params (#1873)

* integrate ios storekit 2 (#1882)

* set min ios version to 15

* consolidated buy methods

* removed checks for older versions of ios

* cleared most errors

* swiftlint

* continue migration, purchases

* return promises to class

* moved utils to ios

* clean up promises and error codes

* serialized Transactions

* removed remaining old methods, added serialization

* default to Xcode 4 spaces

* Split files

* Added more transaction methods

* removed global autofinish

* fix lint on doc

Co-authored-by: Andres Aguilar <andres.aguilar@nfl.com>

* Able to purchase products

* swift lint

* fixed lint issues, removed methods

Co-authored-by: hyochan <dooboolab@gmail.com>
Co-authored-by: Jérémy Barbet <jeremgraph@gmail.com>
Co-authored-by: Andres Aguilar <andres.aguilar@nfl.com>

* Revert "product purchase lifecycle" (#1887)

Revert "product purchase lifecycle (#1885)"

This reverts commit 7f9ff8e.

* Able to complete a consumable purchase

* Fix lint issues

Co-authored-by: Andres Aguilar <andres.aguilar@nfl.com>
Co-authored-by: hyochan <dooboolab@gmail.com>
Co-authored-by: Jérémy Barbet <jeremgraph@gmail.com>
  • Loading branch information
4 people authored Aug 26, 2022
1 parent 06b2675 commit 9989e91
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 93 deletions.
4 changes: 2 additions & 2 deletions IapExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ PODS:
- React-Core
- RNGestureHandler (2.5.0):
- React-Core
- RNIap (10.0.0):
- RNIap (10.0.1):
- React-Core
- RNScreens (3.15.0):
- React-Core
Expand Down Expand Up @@ -575,7 +575,7 @@ SPEC CHECKSUMS:
ReactCommon: e30ec17dfb1d4c4f3419eac254350d6abca6d5a2
RNCMaskedView: cb9670ea9239998340eaab21df13fa12a1f9de15
RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50
RNIap: 6f4925d680b31ff5d252f982aad12c8b66200b31
RNIap: bd08738dfb53b85355d9723457457d54f2dbca30
RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: 7ab6e3ee4ce47d7b789d1cb520163833e515f452
Expand Down
14 changes: 0 additions & 14 deletions IapExample/src/screens/ClassSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ import {
initConnection,
Product,
ProductPurchase,
promotedProductListener,
PurchaseError,
purchaseErrorListener,
purchaseUpdatedListener,
requestPurchase,
requestSubscription,
Expand All @@ -45,8 +43,6 @@ interface State {

export class ClassSetup extends Component<{}, State> {
private purchaseUpdate: EmitterSubscription | null = null;
private purchaseError: EmitterSubscription | null = null;
private promotedProduct: EmitterSubscription | null = null;

constructor(props: {}) {
super(props);
Expand Down Expand Up @@ -92,20 +88,10 @@ export class ClassSetup extends Component<{}, State> {
}
},
);

this.purchaseError = purchaseErrorListener((error: PurchaseError) => {
Alert.alert('purchase error', JSON.stringify(error));
});

this.promotedProduct = promotedProductListener((productId?: string) =>
Alert.alert('Product promoted', productId),
);
}

componentWillUnmount() {
this.purchaseUpdate?.remove();
this.purchaseError?.remove();
this.promotedProduct?.remove();

endConnection();
}
Expand Down
6 changes: 4 additions & 2 deletions IapExample/src/screens/Products.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const Products = () => {
initConnectionError,
finishTransaction,
getProducts,
setCurrentPurchase,
} = useIAP();

const handleGetProducts = async () => {
Expand All @@ -33,7 +34,8 @@ export const Products = () => {

const handleBuyProduct = async (sku: Sku) => {
try {
await requestPurchase({sku});
const transaction = await requestPurchase({sku});
setCurrentPurchase(transaction);
} catch (error) {
if (error instanceof PurchaseError) {
errorLog({message: `[${error.code}]: ${error.message}`, error});
Expand All @@ -46,7 +48,7 @@ export const Products = () => {
useEffect(() => {
const checkCurrentPurchase = async () => {
try {
if (currentPurchase?.transactionReceipt) {
if (currentPurchase?.id) {
await finishTransaction({
purchase: currentPurchase,
isConsumable: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.module.annotations.ReactModule
import java.util.HashSet

@ReactModule(name = RNIapAmazonModule.TAG)
class RNIapAmazonModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
var hasListener = false
Expand Down
3 changes: 2 additions & 1 deletion android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import java.util.ArrayList

@ReactModule(name = RNIapModule.TAG)
class RNIapModule(
private val reactContext: ReactApplicationContext,
private val builder: BillingClient.Builder = BillingClient.newBuilder(reactContext).enablePendingPurchases(),
Expand Down
66 changes: 41 additions & 25 deletions ios/IapSerializationUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,36 @@ import Foundation
import StoreKit

func serialize(_ p: Product) -> [String: Any?] {
return ["displayName": p.displayName,
"description": p.description,
"id": p.id,
"displayPrice": p.displayPrice,
"price": p.price,
"isFamilyShareable": p.isFamilyShareable,
"jsonRepresentation": String( decoding: p.jsonRepresentation, as: UTF8.self),
// "debugDescription": p.debugDescription,
"subscription": serialize(p.subscription),
"type": serialize(p.type)
return [
"debugDescription": serializeDebug( p.debugDescription),
"description": p.description,
"displayName": p.displayName,
"displayPrice": p.displayPrice,
"id": p.id,
"isFamilyShareable": p.isFamilyShareable,
"jsonRepresentation": serializeDebug(p.jsonRepresentation),
"price": p.price,
"subscription": serialize(p.subscription),
"type": serialize(p.type)
]
}

func serializeDebug (_ d: Data) -> String? {
#if DEBUG
return String( decoding: d, as: UTF8.self)
#else
return nil
#endif
}

func serializeDebug (_ s: String) -> String? {
#if DEBUG
return s
#else
return nil
#endif
}

func serialize(_ e: Error?) -> [String: Any?]? {
guard let e = e else {return nil}
return ["localizedDescription": e.localizedDescription]
Expand All @@ -30,51 +47,50 @@ func serialize(_ e: Error?) -> [String: Any?]? {
func serialize(_ si: Product.SubscriptionInfo?) -> [String: Any?]? {
guard let si = si else {return nil}
return [
"subscriptionGroupID": si.subscriptionGroupID,
"promotionalOffers": si.promotionalOffers.map {(offer: Product.SubscriptionOffer) in serialize(offer)},
"introductoryOffer": serialize(si.introductoryOffer),
"promotionalOffers": si.promotionalOffers.map {(offer: Product.SubscriptionOffer) in serialize(offer)},
"subscriptionGroupID": si.subscriptionGroupID,
"subscriptionPeriod": si.subscriptionPeriod
]
}

func serialize(_ so: Product.SubscriptionOffer?) -> [String: Any?]? {
guard let so = so else {return nil}
return [
"id": so.id,
"price": so.price,
"displayPrice": so.displayPrice,
"type": so.type,
"id": so.id,
"paymentMode": so.paymentMode,
"period": so.period,
"periodCount": so.periodCount
"periodCount": so.periodCount,
"price": so.price,
"type": so.type
]
}

// Transaction
func serialize(_ t: Transaction) -> [String: Any?] {
return ["id": t.id,
return ["appAccountToken": t.appAccountToken,
"appBundleID": t.appBundleID,
"offerID": t.offerID,
"subscriptionGroupID": t.subscriptionGroupID,
"appAccountToken": t.appAccountToken,
// "debugDescription": t.debugDescription,
"debugDescription": serializeDebug(t.debugDescription),
"deviceVerification": t.deviceVerification,
"deviceVerificationNonce": t.deviceVerificationNonce,
"expirationDate": t.expirationDate,
"id": t.id,
"isUpgraded": t.isUpgraded,
"jsonRepresentation": t.jsonRepresentation,
"jsonRepresentation": serializeDebug(t.jsonRepresentation),
"offerID": t.offerID,
"offerType": serialize(t.offerType),
"expirationDate": t.expirationDate,
"originalID": t.originalID,
"originalPurchaseDate": t.originalPurchaseDate,
"ownershipType": serialize(t.ownershipType),
"productType": serialize(t.productType),
"productID": t.productID,
"productType": serialize(t.productType),
"purchaseDate": t.purchaseDate,
"purchasedQuantity": t.purchasedQuantity,
"revocationDate": t.revocationDate,
"revocationReason": t.revocationReason,
"purchaseDate": t.purchaseDate,
"signedDate": t.signedDate,
"subscriptionGroupID": t.subscriptionGroupID,
"webOrderLineItemID": t.webOrderLineItemID
]
}
Expand Down
16 changes: 12 additions & 4 deletions ios/RNIapIos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,18 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate {
_ transactionIdentifier: String,
resolve: @escaping RCTPromiseResolveBlock = { _ in },
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
) async {
await transactions[transactionIdentifier]?.finish()
transactions.removeValue(forKey: transactionIdentifier)
resolve(nil)
) {
Task {
if let transaction = transactions[transactionIdentifier] {
debugMessage("Finishing transaction")
await transaction.finish()
debugMessage("Finished transaction")
transactions.removeValue(forKey: transactionIdentifier)
resolve(nil)
} else {
reject(IapErrors.E_DEVELOPER_ERROR.rawValue, "Invalid transaction Id", nil)
}
}
}

@objc public func pendingTransactions (
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-iap",
"version": "10.0.0",
"version": "10.0.1",
"description": "React Native In App Purchase Module.",
"repository": "https://github.com/dooboolab/react-native-iap",
"author": "dooboolab <support@dooboolab.com> (https://github.com/dooboolab)",
Expand Down
30 changes: 3 additions & 27 deletions src/eventEmitter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {EmitterSubscription, NativeEventEmitter} from 'react-native';
import {NativeEventEmitter} from 'react-native';

import {getAndroidModule, getIosModule, getNativeModule} from './iap';
import {isAndroid, isIos} from './internal';
import type {PurchaseError} from './purchaseError';
import {getAndroidModule, getNativeModule} from './iap';
import {isAndroid} from './internal';
import type {Purchase} from './types';

const eventEmitter = new NativeEventEmitter(getNativeModule());
Expand All @@ -25,26 +24,3 @@ export const purchaseUpdatedListener = (

return emitterSubscription;
};

/**
* Add IAP purchase error event
*/
export const purchaseErrorListener = (
listener: (error: PurchaseError) => void,
): EmitterSubscription => eventEmitter.addListener('purchase-error', listener);

/**
* Add IAP promoted subscription event
*
* @platform iOS
*/
export const promotedProductListener = (listener: () => void) => {
if (isIos) {
return new NativeEventEmitter(getIosModule()).addListener(
'iap-promoted-product',
listener,
);
}

return null;
};
10 changes: 6 additions & 4 deletions src/hooks/useIAP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type IAP_STATUS = {
getPurchaseHistory: () => Promise<void>;
getProducts: ({skus}: {skus: string[]}) => Promise<void>;
getSubscriptions: ({skus}: {skus: string[]}) => Promise<void>;
setCurrentPurchase: (currentPurchase: Purchase | undefined) => void;
};

export function useIAP(): IAP_STATUS {
Expand Down Expand Up @@ -97,18 +98,18 @@ export function useIAP(): IAP_STATUS {
} catch (err) {
throw err;
} finally {
if (purchase.productId === currentPurchase?.productId) {
if (purchase.id === currentPurchase?.id) {
setCurrentPurchase(undefined);
}

if (purchase.productId === currentPurchaseError?.productId) {
if (purchase.id === currentPurchaseError?.id) {
setCurrentPurchaseError(undefined);
}
}
},
[
currentPurchase?.productId,
currentPurchaseError?.productId,
currentPurchase?.id,
currentPurchaseError?.id,
setCurrentPurchase,
setCurrentPurchaseError,
],
Expand All @@ -128,5 +129,6 @@ export function useIAP(): IAP_STATUS {
getSubscriptions,
getAvailablePurchases,
getPurchaseHistory,
setCurrentPurchase,
};
}
10 changes: 1 addition & 9 deletions src/hooks/withIAPContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useContext, useEffect, useMemo, useState} from 'react';

import {purchaseErrorListener, purchaseUpdatedListener} from '../eventEmitter';
import {purchaseUpdatedListener} from '../eventEmitter';
import {initConnection} from '../iap';
import type {PurchaseError} from '../purchaseError';
import type {
Expand Down Expand Up @@ -116,16 +116,8 @@ export function withIAPContext<T>(Component: React.ComponentType<T>) {
},
);

const purchaseErrorSubscription = purchaseErrorListener(
(error: PurchaseError) => {
setCurrentPurchase(undefined);
setCurrentPurchaseError(error);
},
);

return () => {
purchaseUpdateSubscription.remove();
purchaseErrorSubscription.remove();
};
}, [connected]);

Expand Down
2 changes: 1 addition & 1 deletion src/iap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export const finishTransaction = ({
return (
Platform.select({
ios: async () => {
return getIosModule().finishTransaction(purchase.transactionId);
return getIosModule().finishTransaction(purchase.id + '');
},
android: async () => {
if (purchase) {
Expand Down
4 changes: 2 additions & 2 deletions src/purchaseError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ export class PurchaseError implements Error {
public responseCode?: number,
public debugMessage?: string,
public code?: ErrorCode,
public productId?: string,
public id?: string,
) {
this.name = '[react-native-iap]: PurchaseError';
this.message = message;
this.responseCode = responseCode;
this.debugMessage = debugMessage;
this.code = code;
this.productId = productId;
this.id = id;
}
}
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface ProductPurchase {
transactionReceipt: string;
purchaseToken?: string;
//iOS
id: String;
quantityIOS?: number;
originalTransactionDateIOS?: string;
originalTransactionIdentifierIOS?: string;
Expand Down

0 comments on commit 9989e91

Please sign in to comment.