diff --git a/src/components/Settings/Settings.component.js b/src/components/Settings/Settings.component.js
index de13fce71..bbfa9ce4c 100644
--- a/src/components/Settings/Settings.component.js
+++ b/src/components/Settings/Settings.component.js
@@ -25,7 +25,7 @@ import FullScreenDialog from '../UI/FullScreenDialog';
import Paper from '@material-ui/core/Paper';
import UserIcon from '../UI/UserIcon';
import SettingsTour from './SettingsTour.component';
-import { isCordova, isAndroid, isIOS } from '../../cordova-util';
+import { isCordova, isAndroid } from '../../cordova-util';
import './Settings.css';
import { CircularProgress } from '@material-ui/core';
@@ -96,7 +96,7 @@ export class Settings extends PureComponent {
}
];
- if (!isIOS() && !isInFreeCountry) {
+ if (!isInFreeCountry) {
const subscribeSection = {
icon: ,
text: messages.subscribe,
diff --git a/src/components/Settings/Subscribe/Subscribe.component.js b/src/components/Settings/Subscribe/Subscribe.component.js
index 1188b7721..0675b22f0 100644
--- a/src/components/Settings/Subscribe/Subscribe.component.js
+++ b/src/components/Settings/Subscribe/Subscribe.component.js
@@ -9,6 +9,7 @@ import './Subscribe.css';
import SubscriptionInfo from './SubscriptionInfo';
import SubscriptionPlans from './SubscriptionPlans';
+import { CircularProgress } from '@material-ui/core';
const propTypes = {
/**
@@ -29,7 +30,8 @@ const propTypes = {
onRefreshSubscription: PropTypes.func,
onSubscribeCancel: PropTypes.func.isRequired,
onCancelSubscription: PropTypes.func.isRequired,
- cancelSubscriptionStatus: PropTypes.string.isRequired
+ cancelSubscriptionStatus: PropTypes.string.isRequired,
+ updatingStatus: PropTypes.bool.isRequired
};
const defaultProps = {
@@ -46,7 +48,8 @@ const Subscribe = ({
onSubscribeCancel,
onPaypalApprove,
onCancelSubscription,
- cancelSubscriptionStatus
+ cancelSubscriptionStatus,
+ updatingStatus
}) => {
return (
@@ -56,7 +59,11 @@ const Subscribe = ({
onClose={onClose}
// fullWidth
>
- {!subscription.isSubscribed ? (
+ {updatingStatus ? (
+
+
+
+ ) : !subscription.isSubscribed ? (
{
return p.id === product.subscriptionId;
@@ -207,7 +210,10 @@ export class SubscribeContainer extends PureComponent {
try {
await window.CdvPurchase.store.update();
offers = prod.offers;
- offer = offers.find(offer => offer.tags[0] === product.tag);
+ const findCallback = isAndroid()
+ ? offer => offer.tags[0] === product.tag
+ : offer => offer.productId === product.subscriptionId;
+ offer = offers.find(findCallback);
} catch (err) {
console.error('Cannot subscribe product. Error: ', err.message);
this.handleError(err);
@@ -215,6 +221,11 @@ export class SubscribeContainer extends PureComponent {
}
}
+ const filterInAppPurchaseIOSTransactions = uniqueReceipt =>
+ uniqueReceipt.transactions.filter(
+ transaction => transaction.transactionId !== 'appstore.application'
+ );
+
try {
// update the api
const requestOrigin =
@@ -225,40 +236,77 @@ export class SubscribeContainer extends PureComponent {
// check if current subscriber already bought in this device
if (localReceipts.length) {
const lastReceipt = localReceipts.slice(-1)[0];
- if (
- lastReceipt &&
- lastReceipt?.transactions[0]?.nativePurchase?.orderId !==
- subscriber.transaction?.transactionId
- ) {
- this.handleError({
- code: '0001',
- message: intl.formatMessage(messages.googleAccountAlreadyOwns)
- });
- return;
+ if (isAndroid()) {
+ if (
+ lastReceipt &&
+ lastReceipt?.transactions[0]?.nativePurchase?.orderId !==
+ subscriber.transaction?.transactionId
+ ) {
+ this.handleError({
+ code: '0001',
+ message: intl.formatMessage(messages.googleAccountAlreadyOwns)
+ });
+ return;
+ }
+ }
+ if (isIOS()) {
+ //IOS have a unique receipt here => 'lastReceipt'
+ const inAppPurchaseTransactions = filterInAppPurchaseIOSTransactions(
+ localReceipts[0]
+ );
+
+ const lastTransaction = inAppPurchaseTransactions.slice(-1)[0];
+ if (
+ inAppPurchaseTransactions.length > 0 &&
+ lastTransaction?.transactionId !==
+ subscriber.transaction?.transactionId
+ ) {
+ this.handleError({
+ code: '0001',
+ message: intl.formatMessage(messages.appleAccountAlreadyOwns)
+ });
+ return;
+ }
}
}
+
await API.updateSubscriber(apiProduct);
// proceed with the purchase
- if (isAndroid()) {
+ if (isAndroid() || isIOS()) {
const order = await window.CdvPurchase.store.order(offer);
if (order && order.isError) throw order;
updateSubscription({
ownedProduct: {
...product,
- platform: 'android-playstore'
+ platform: isAndroid() ? 'android-playstore' : 'app-store'
}
});
}
} catch (err) {
if (err.response?.data.error === 'subscriber not found') {
// check if current subscriber already bought in this device
- if (localReceipts.length) {
- this.handleError({
- code: '0001',
- message: intl.formatMessage(messages.googleAccountAlreadyOwns)
- });
- return;
+ if (isAndroid()) {
+ if (localReceipts.length) {
+ this.handleError({
+ code: '0001',
+ message: intl.formatMessage(messages.googleAccountAlreadyOwns)
+ });
+ return;
+ }
+ }
+ if (isIOS()) {
+ const localInAppPurchaseTransactions = filterInAppPurchaseIOSTransactions(
+ localReceipts[0]
+ );
+
+ if (localInAppPurchaseTransactions.length) {
+ this.handleError({
+ code: '0001',
+ message: intl.formatMessage(messages.appleAccountAlreadyOwns)
+ });
+ return;
+ }
}
try {
const newSubscriber = {
@@ -269,13 +317,13 @@ export class SubscribeContainer extends PureComponent {
};
const res = await API.createSubscriber(newSubscriber);
updateSubscriberId(res._id);
- if (isAndroid()) {
+ if (isAndroid() || isIOS()) {
const order = await window.CdvPurchase.store.order(offer);
if (order && order.isError) throw order;
updateSubscription({
ownedProduct: {
...product,
- platform: 'android-playstore'
+ platform: isAndroid() ? 'android-playstore' : 'app-store'
}
});
}
@@ -306,6 +354,7 @@ export class SubscribeContainer extends PureComponent {
onPaypalApprove={this.handlePaypalApprove}
onCancelSubscription={this.handleCancelSubscription}
cancelSubscriptionStatus={this.state.cancelSubscriptionStatus}
+ updatingStatus={this.state.updatingStatus}
/>
);
}
diff --git a/src/components/Settings/Subscribe/Subscribe.css b/src/components/Settings/Subscribe/Subscribe.css
index 687df3158..97b778ad0 100644
--- a/src/components/Settings/Subscribe/Subscribe.css
+++ b/src/components/Settings/Subscribe/Subscribe.css
@@ -1,3 +1,10 @@
+.Subscribe__Loading__Container {
+ padding: 1em;
+ height: 92vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
.Subscribe__Alert {
margin: 8px 0;
}
diff --git a/src/components/Settings/Subscribe/Subscribe.messages.js b/src/components/Settings/Subscribe/Subscribe.messages.js
index 37d4e4c6d..08fa05288 100644
--- a/src/components/Settings/Subscribe/Subscribe.messages.js
+++ b/src/components/Settings/Subscribe/Subscribe.messages.js
@@ -89,6 +89,11 @@ export default defineMessages({
id: 'cboard.components.Settings.Subscribe.not_subscribed',
defaultMessage: 'You are not subscribed. '
},
+ unverified: {
+ id: 'cboard.components.Settings.Subscribe.unverified',
+ defaultMessage:
+ 'An error occurred during validation of your purchase. Please refresh'
+ },
refresh: {
id: 'cboard.components.Settings.Subscribe.refresh',
defaultMessage: 'Refresh'
@@ -148,6 +153,11 @@ export default defineMessages({
defaultMessage:
'It looks like your Google account has already purchased a product. Try restarting the app.'
},
+ appleAccountAlreadyOwns: {
+ id: 'cboard.components.Settings.Subscribe.appleAccountAlreadyOwns',
+ defaultMessage:
+ 'It looks like your Apple account has already purchased a product. Try restarting the app.'
+ },
fallback: {
id: 'cboard.components.Settings.Subscribe.fallback',
defaultMessage: 'Wait please...'
diff --git a/src/components/Settings/Subscribe/SubscriptionInfo.js b/src/components/Settings/Subscribe/SubscriptionInfo.js
index 40589f125..f90e62fdd 100644
--- a/src/components/Settings/Subscribe/SubscriptionInfo.js
+++ b/src/components/Settings/Subscribe/SubscriptionInfo.js
@@ -29,8 +29,8 @@ import {
import RefreshIcon from '@material-ui/icons/Refresh';
import IconButton from '../../UI/IconButton';
-import { isAndroid, isElectron } from '../../../cordova-util';
-import { GOOGLE_PLAY_STORE_URL } from './Subscribe.constants';
+import { isAndroid, isElectron, isIOS } from '../../../cordova-util';
+import { APP_STORE_URL, GOOGLE_PLAY_STORE_URL } from './Subscribe.constants';
const propTypes = {
ownedProduct: PropTypes.object.isRequired,
@@ -131,13 +131,15 @@ const SubscriptionInfo = ({