Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type '(purchase: ProductPurchase)' is missing the following properties from type 'ProductPurchase': [...] #514

Closed
jvandenaardweg opened this issue Jun 10, 2019 · 4 comments
Labels
🐛 bug Something isn't working ❄️ types Typing issues

Comments

@jvandenaardweg
Copy link
Contributor

jvandenaardweg commented Jun 10, 2019

Version of react-native-iap

3.0.0-rc.1

Version of react-native

0.59.9

Platforms you faced the error (IOS or Android or both?)

iOS

Expected behavior

No type error

Actual behavior

Argument of type '(purchase: ProductPurchase) => Promise<void>' is not assignable to parameter of type 'ProductPurchase'.
  Type '(purchase: ProductPurchase) => Promise<void>' is missing the following properties from type 'ProductPurchase': productId, transactionId, transactionDate, transactionReceipt

Tested environment (Emulator? Real Device?)

Real Device

Steps to reproduce the behavior

import * as RNIap from 'react-native-iap';

export class Sample extends React.PureComponent {
  purchaseUpdateSubscription: any = null;
  
  componentDidMount() {
    this.purchaseUpdateSubscription = RNIap.purchaseUpdatedListener(async (purchase: RNIap.ProductPurchase) => { })
  }
}

Screenshot 2019-06-10 at 10 47 39

I don't quite understand this error, because the type's TS is complaining about seem to be there:

// RNIap.ProductPurchase
export interface ProductPurchase {
  productId: string;
  transactionId: string;
  transactionDate: number;
  transactionReceipt: string;
  signatureAndroid?: string;
  dataAndroid?: string;
  purchaseToken?: string;
}

And the purchaseUpdatedListener definition uses the same ProductPurchase interface.

I'm lost 🤔

@jvandenaardweg jvandenaardweg changed the title Type '(purchase: ProductPurchase)' is missing the following properties from type 'ProductPurchase': productId, transactionId, transactionDate, transactionReceipt Type '(purchase: ProductPurchase)' is missing the following properties from type 'ProductPurchase': [...] Jun 10, 2019
@hyochan hyochan added 🐛 bug Something isn't working ❄️ types Typing issues labels Jun 10, 2019
@hyochan
Copy link
Member

hyochan commented Jun 10, 2019

What happens if you state like below?

purchaseUpdateSubscription = purchaseUpdatedListener((purchase: ProductPurchase | SubscriptionPurchase) => {
  console.log('purchaseUpdatedListener', purchase);
  this.setState({ receipt: purchase.transactionReceipt }, () => this.goNext());
});

@jvandenaardweg
Copy link
Contributor Author

The same...

Argument of type '(purchase: ProductPurchase | SubscriptionPurchase) => Promise<void>' is not assignable to parameter of type 'ProductPurchase'.
  Type '(purchase: ProductPurchase | SubscriptionPurchase) => Promise<void>' is missing the following properties from type 'ProductPurchase': productId, transactionId, transactionDate, transactionReceipt

@hyochan
Copy link
Member

hyochan commented Jun 10, 2019

@jvandenaardweg Heya! Sorry for the types. I've had a minor mistake and this is fixed in `react-native-iap@3.0.0-rc.2'.

Also below is smaple of App.tsx for typescript user.

import React, { Component } from 'react';
import {
  View,
  Text,
  Alert,
  Platform,
  ScrollView,
  StyleSheet,
  Button,
} from 'react-native';
import RNIap, {
  Product,
  ProductPurchase,
  Subscription,
  SubscriptionPurchase,
  purchaseUpdatedListener,
} from 'react-native-iap';

// App Bundle > com.dooboolab.test

const itemSkus = Platform.select({
  ios: [
    'com.cooni.point1000', 'com.cooni.point5000', // dooboolab
  ],
  android: [
    'android.test.purchased', 'android.test.canceled', 'android.test.refunded', 'android.test.item_unavailable',
    // 'point_1000', '5000_point', // dooboolab
  ],
});

const itemSubs = Platform.select({
  ios: [
    'com.cooni.point1000', 'com.cooni.point5000', // dooboolab
  ],
  android: [
    'test.sub1', // subscription
  ],
});

let purchaseUpdateSubscription: any;

interface IProps { };
interface IState {
  productList: Product<string>[] | Subscription<string>[] | any;
  receipt: string;
  availableItemsMessage: string;
};

class Page extends Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);

    this.state = {
      productList: [],
      receipt: '',
      availableItemsMessage: '',
    };
  }

  async componentDidMount() {
    try {
      const result = await RNIap.initConnection();
      await RNIap.consumeAllItemsAndroid();
      console.log('result', result);
    } catch (err) {
      console.warn(err.code, err.message);
    }

    purchaseUpdateSubscription = purchaseUpdatedListener((purchase: ProductPurchase) => {
      console.log('purchaseUpdatedListener', purchase);
      this.setState({ receipt: purchase.transactionReceipt }, () => this.goNext());
    });
  }

  componentWillMount() {
    if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
      purchaseUpdateSubscription = null;
    }
  }

  goNext = () => {
    Alert.alert('Receipt', this.state.receipt);
  }

  getItems = async() => {
    try {
      const products: Product<string>[] = await RNIap.getProducts(itemSkus);
      // const products = await RNIap.getSubscriptions(itemSkus);
      console.log('Products', products);
      this.setState({ productList: products });
    } catch (err) {
      console.warn(err.code, err.message);
    }
  }

  getSubscriptions = async() => {
    try {
      const products: Subscription<string>[] = await RNIap.getSubscriptions(itemSubs);
      console.log('Products', products);
      this.setState({ productList: products });
    } catch (err) {
      console.warn(err.code, err.message);
    }
  }

  getAvailablePurchases = async() => {
    try {
      console.info('Get available purchases (non-consumable or unconsumed consumable)');
      const purchases = await RNIap.getAvailablePurchases();
      console.info('Available purchases :: ', purchases);
      if (purchases && purchases.length > 0) {
        this.setState({
          availableItemsMessage: `Got ${purchases.length} items.`,
          receipt: purchases[0].transactionReceipt,
        });
      }
    } catch (err) {
      console.warn(err.code, err.message);
      Alert.alert(err.message);
    }
  }

  // Version 3 apis
  requestPurchase = async(sku: string) => {
    try {
      RNIap.requestPurchase(sku);
    } catch (err) {
      console.warn(err.code, err.message);
    }
  }

  requestSubscription = async(sku: string) => {
    try {
      RNIap.requestSubscription(sku);
    } catch (err) {
      Alert.alert(err.message);
    }
  }

  // Deprecated apis
  buyItem = async(sku: string) => {
    console.info('buyItem', sku);
    // const purchase = await RNIap.buyProduct(sku);
    // const products = await RNIap.buySubscription(sku);
    // const purchase = await RNIap.buyProductWithoutFinishTransaction(sku);
    try {
      const purchase: ProductPurchase = await RNIap.buyProduct(sku);
      // console.log('purchase', purchase);
      // await RNIap.consumePurchaseAndroid(purchase.purchaseToken);
      this.setState({ receipt: purchase.transactionReceipt }, () => this.goNext());
    } catch (err) {
      console.warn(err.code, err.message);
      const subscription = RNIap.addAdditionalSuccessPurchaseListenerIOS(async(purchase: ProductPurchase) => {
        this.setState({ receipt: purchase.transactionReceipt }, () => this.goNext());
        subscription.remove();
      });
    }
  }

  buySubscribeItem = async(sku: string) => {
    try {
      console.log('buySubscribeItem: ' + sku);
      const purchase = await RNIap.buySubscription(sku);
      console.info(purchase);
      this.setState({ receipt: purchase.transactionReceipt }, () => this.goNext());
    } catch (err) {
      console.warn(err.code, err.message);
      Alert.alert(err.message);
    }
  }

  render() {
    const { productList, receipt, availableItemsMessage } = this.state;
    const receipt100 = receipt.substring(0, 100);

    return (
      <View style={ styles.container }>
        <View style={ styles.header }>
          <Text style={ styles.headerTxt} >react-native-iap V3</Text>
        </View>
        <View style={ styles.content }>
          <ScrollView
            style={{ alignSelf: 'stretch' }}
          >
            <View style={{ height: 50 }} />
            <Button
              onPress={this.getAvailablePurchases}
              color='blue'
              title='Get available purchases'
            />

            <Text style={{ margin: 5, fontSize: 15, alignSelf: 'center' }} >{availableItemsMessage}</Text>

            <Text style={{ margin: 5, fontSize: 9, alignSelf: 'center' }} >{receipt100}</Text>

            <Button
              onPress={() => this.getItems()}
              color='green'
              title={`Get Products ({productList.length})`}
            />
            {
              productList.map((product: Product<string> | Subscription<string>, i: number) => {
                return (
                  <View key={i} style={{
                    flexDirection: 'column',
                  }}>
                    <Text style={{
                      marginTop: 20,
                      fontSize: 12,
                      color: 'black',
                      minHeight: 100,
                      alignSelf: 'center',
                      paddingHorizontal: 20,
                    }} >{JSON.stringify(product)}</Text>
                    <Button
                      onPress={() => this.requestPurchase(product.productId)}
                      // onPress={() => this.requestSubscription(product.productId)}
                      // onPress={() => this.buyItem(product.productId)}
                      // onPress={() => this.buySubscribeItem(product.productId)}
                      color='green'
                      title='Request purchase for above product'
                    />
                  </View>
                );
              })
            }
          </ScrollView>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: Platform.select({
      ios: 0,
      android: 24,
    }),
    paddingTop: Platform.select({
      ios: 0,
      android: 24,
    }),
    backgroundColor: 'white',
  },
  header: {
    flex: 20,
    flexDirection: 'row',
    alignSelf: 'stretch',
    justifyContent: 'center',
    alignItems: 'center',
  },
  headerTxt: {
    fontSize: 26,
    color: 'green',
  },
  content: {
    flex: 80,
    flexDirection: 'column',
    justifyContent: 'center',
    alignSelf: 'stretch',
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
  btn: {
    height: 48,
    width: 240,
    alignSelf: 'center',
    backgroundColor: '#00c40f',
    borderRadius: 0,
    borderWidth: 0,
  },
  txt: {
    fontSize: 16,
    color: 'white',
  },
});

export default Page;

@jvandenaardweg
Copy link
Contributor Author

Thanks @hyochan for the quick fix, it works! :-)

@hyochan hyochan mentioned this issue Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working ❄️ types Typing issues
Projects
None yet
Development

No branches or pull requests

2 participants