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

Feature/ios premium #1584

Merged
merged 33 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
695c7bd
WIP
tomivm Aug 29, 2023
b295694
Merge branch 'master' into feature/ios-premium
tomivm Aug 29, 2023
1728c0d
wip
tomivm Aug 29, 2023
6514a1b
Find offer by product ID on IOS
tomivm Sep 15, 2023
e6f4121
Create function to filter ios IAP transactions
tomivm Sep 15, 2023
8be98e2
Check if apple account already bought a subscription for other user
tomivm Sep 15, 2023
17c3a1d
For IOS show error if subscriber not found and transaction exist
tomivm Sep 17, 2023
0680295
Add error message for IOS
tomivm Sep 17, 2023
3ccfd5c
call manage subscriptions on Cancel click on IOS
tomivm Sep 17, 2023
748cf27
Allow click subscribe button on IOS
tomivm Sep 17, 2023
64f302e
Restore purchase In IOS if local transactions exists
tomivm Sep 17, 2023
4c651db
on purchase validator for IOS use single transaction
tomivm Sep 17, 2023
fde5373
Finish receipt after verify it and return that is expired/not subscribed
tomivm Sep 17, 2023
720aad2
Manage Error during verifying receipt (for IOS)
tomivm Sep 19, 2023
e1a6112
Add translated message to the unverified state
tomivm Sep 22, 2023
107dece
Add unverified message to cboard.json
tomivm Sep 22, 2023
2fc1cc1
Update owned product in case that user change subscription
tomivm Sep 22, 2023
f370e11
Fix Account already bought a subscription on IOS
tomivm Sep 22, 2023
0202e02
check if transaction and product exist on db before getActualProduct
tomivm Sep 22, 2023
662df56
Manage if IOS account already brought a subscription to another user
tomivm Sep 25, 2023
5f93469
Open the APP store web page on cancel a subscription in web or electron
tomivm Sep 26, 2023
4b49a69
update variable name for product Id
tomivm Sep 28, 2023
52c5cf7
Localize prices of subscriptions in IOS
tomivm Sep 29, 2023
34eab07
Fix price in US
tomivm Sep 29, 2023
089cac9
Fix error message
tomivm Sep 29, 2023
651f64d
Remove purchase plugin verbosity
tomivm Sep 29, 2023
7836d06
Merge remote-tracking branch 'cboard-org/master' into feature/ios-pre…
tomivm Sep 29, 2023
7125bca
Remove unnecessary import of isIOS
tomivm Oct 4, 2023
72aa983
Avoid remove of subscription on offline mode or server error
tomivm Oct 4, 2023
289e6e5
Small fix on verify purchase
tomivm Oct 4, 2023
c4cdd5f
Avoid push paypal products on IOS plans
tomivm Oct 4, 2023
5df41b4
Show circular progress before update the subscriber to avoid confusions
tomivm Oct 4, 2023
e19a773
Merge branch 'master' into feature/ios-premium
tomivm Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/Settings/Settings.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,7 +96,7 @@ export class Settings extends PureComponent {
}
];

if (!isIOS() && !isInFreeCountry) {
if (!isInFreeCountry) {
const subscribeSection = {
icon: <MonetizationOnIcon />,
text: messages.subscribe,
Expand Down
13 changes: 10 additions & 3 deletions src/components/Settings/Subscribe/Subscribe.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import './Subscribe.css';

import SubscriptionInfo from './SubscriptionInfo';
import SubscriptionPlans from './SubscriptionPlans';
import { CircularProgress } from '@material-ui/core';

const propTypes = {
/**
Expand All @@ -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 = {
Expand All @@ -46,7 +48,8 @@ const Subscribe = ({
onSubscribeCancel,
onPaypalApprove,
onCancelSubscription,
cancelSubscriptionStatus
cancelSubscriptionStatus,
updatingStatus
}) => {
return (
<div className="Subscribe">
Expand All @@ -56,7 +59,11 @@ const Subscribe = ({
onClose={onClose}
// fullWidth
>
{!subscription.isSubscribed ? (
{updatingStatus ? (
<div className="Subscribe__Loading__Container">
<CircularProgress />
</div>
) : !subscription.isSubscribed ? (
<SubscriptionPlans
subscription={subscription}
onRefreshSubscription={onRefreshSubscription}
Expand Down
1 change: 1 addition & 0 deletions src/components/Settings/Subscribe/Subscribe.constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export const EMPTY_PRODUCT = 'empty_product';
export const ON_TRIAL_PERIOD = 'on_trial_period';
export const GOOGLE_PLAY_STORE_URL =
'https://play.google.com/store/account/subscriptions';
export const APP_STORE_URL = 'https://www.apple.com/app-store/';
101 changes: 75 additions & 26 deletions src/components/Settings/Subscribe/Subscribe.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getUser, isLogged } from '../../App/App.selectors';
import API from '../../../api';
import messages from './Subscribe.messages';

import { isAndroid } from '../../../cordova-util';
import { isAndroid, isIOS } from '../../../cordova-util';
import {
updateSubscriberId,
updateSubscription,
Expand All @@ -34,14 +34,17 @@ export class SubscribeContainer extends PureComponent {
};

state = {
cancelSubscriptionStatus: ''
cancelSubscriptionStatus: '',
updatingStatus: true
};

componentDidMount() {
async componentDidMount() {
const { updateIsSubscribed, updatePlans } = this.props;
const requestOrigin =
'Function: componentDidMount - Component: Subscribe Container';
updateIsSubscribed(requestOrigin);
this.setState({ updatingStatus: true });
await updateIsSubscribed(requestOrigin);
this.setState({ updatingStatus: false });
updatePlans();
}

Expand Down Expand Up @@ -195,7 +198,7 @@ export class SubscribeContainer extends PureComponent {

let localReceipts = '';
let offers, offer;
if (isAndroid()) {
if (isAndroid() || isIOS()) {
const storeProducts = await window.CdvPurchase.store.products;
const prod = storeProducts.find(p => {
return p.id === product.subscriptionId;
Expand All @@ -207,14 +210,22 @@ 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);
return;
}
}

const filterInAppPurchaseIOSTransactions = uniqueReceipt =>
uniqueReceipt.transactions.filter(
transaction => transaction.transactionId !== 'appstore.application'
);

try {
// update the api
const requestOrigin =
Expand All @@ -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 = {
Expand All @@ -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'
}
});
}
Expand Down Expand Up @@ -306,6 +354,7 @@ export class SubscribeContainer extends PureComponent {
onPaypalApprove={this.handlePaypalApprove}
onCancelSubscription={this.handleCancelSubscription}
cancelSubscriptionStatus={this.state.cancelSubscriptionStatus}
updatingStatus={this.state.updatingStatus}
/>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/components/Settings/Subscribe/Subscribe.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
.Subscribe__Loading__Container {
padding: 1em;
height: 92vh;
display: flex;
justify-content: center;
align-items: center;
}
.Subscribe__Alert {
margin: 8px 0;
}
Expand Down
10 changes: 10 additions & 0 deletions src/components/Settings/Subscribe/Subscribe.messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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...'
Expand Down
15 changes: 12 additions & 3 deletions src/components/Settings/Subscribe/SubscriptionInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -131,13 +131,15 @@ const SubscriptionInfo = ({
</div>
<div className="Subscribe__Info__Button__Container">
<Button
variant={isAndroid() ? 'contained' : 'text'}
variant={isAndroid() || isIOS() ? 'contained' : 'text'}
fullWidth={false}
color="primary"
disabled={ownedProduct.platform === 'paypal' && status !== ACTIVE}
onClick={() => {
if (isAndroid() && ownedProduct.platform === 'android-playstore')
window.CdvPurchase.store.manageSubscriptions();
if (isIOS() && ownedProduct.platform === 'ios-appstore')
window.CdvPurchase.store.manageSubscriptions();
if (ownedProduct.platform === 'paypal') setCancelDialog(true);
if (!isAndroid() && ownedProduct.platform === 'android-playstore') {
if (isElectron()) {
Expand All @@ -148,6 +150,13 @@ const SubscriptionInfo = ({
window.open(GOOGLE_PLAY_STORE_URL, '_blank');
}
}
if (!isIOS() && ownedProduct.platform === 'ios-appstore') {
if (isElectron()) {
window.cordova.plugins.DefaultBrowser.open(APP_STORE_URL);
} else {
window.open(APP_STORE_URL, '_blank');
}
}
}}
style={{ marginLeft: '1em' }}
>
Expand Down
14 changes: 9 additions & 5 deletions src/components/Settings/Subscribe/SubscriptionPlans.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
ON_TRIAL_PERIOD
} from './Subscribe.constants';
import { formatDuration, formatTitle } from './Subscribe.helpers';
import { isAndroid, isCordova, isElectron } from '../../../cordova-util';
import { isAndroid, isCordova, isElectron, isIOS } from '../../../cordova-util';
import { CircularProgress } from '@material-ui/core';

import { Link } from 'react-router-dom';
Expand All @@ -26,7 +26,8 @@ import {
EXPIRED,
NOT_SUBSCRIBED,
PROCCESING,
ON_HOLD
ON_HOLD,
UNVERIFIED
} from '../../../providers/SubscriptionProvider/SubscriptionProvider.constants';
import Button from '@material-ui/core/Button';
import List from '@material-ui/core/List';
Expand Down Expand Up @@ -80,7 +81,7 @@ const SubscriptionPlans = ({
} = subscription;

let plans = [];
if (!isAndroid() && products) {
if (!(isAndroid() || isIOS()) && products) {
products.forEach(product => {
if (product.paypalId) plans.push(product);
});
Expand All @@ -95,6 +96,7 @@ const SubscriptionPlans = ({

const subscriptionStatus = (function() {
if (error.showError) return ERROR;
if (status === UNVERIFIED) return UNVERIFIED;
if (isOnTrialPeriod && !isSubscribed && status !== PROCCESING)
return ON_TRIAL_PERIOD;
if (products.length || status !== NOT_SUBSCRIBED)
Expand All @@ -111,6 +113,7 @@ const SubscriptionPlans = ({
error: 'error',
empty_product: 'warning',
on_trial_period: 'info',
unverified: 'warning',

on_hold: 'warning', //TODO
paused: 'info', //TODO
Expand Down Expand Up @@ -144,7 +147,8 @@ const SubscriptionPlans = ({
const getMessage = () => {
function errorMessage() {
if (error && error.code === '0001') {
return messages.googleAccountAlreadyOwns;
if (isAndroid()) return messages.googleAccountAlreadyOwns;
if (isIOS()) return messages.appleAccountAlreadyOwns;
}
return messages.error;
}
Expand Down Expand Up @@ -228,7 +232,7 @@ const SubscriptionPlans = ({
/{formatDuration(product.billingPeriod)}
</Typography>
</Box>
{isAndroid() && (
{(isAndroid() || isIOS()) && (
<Button
variant="contained"
fullWidth={true}
Expand Down
Loading