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 21 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
42 changes: 21 additions & 21 deletions src/components/App/__tests__/App.reducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
FINISH_FIRST_VISIT,
UPDATE_CONNECTIVITY,
UPDATE_DISPLAY_SETTINGS,
UPDATE_NAVIGATION_SETTINGS,
UPDATE_NAVIGATION_SETTINGS
} from '../App.constants';
import appReducer from '../App.reducer';

Expand All @@ -24,9 +24,9 @@ describe('reducer', () => {
communicatorTour: {
isCommBoardsEnabled: true,
isPublicBoardsEnabled: true,
isAllMyBoardsEnabled: true,
isAllMyBoardsEnabled: true
},
isAnalyticsTourEnabled: true,
isAnalyticsTourEnabled: true
},
displaySettings: {
uiSize: DISPLAY_SIZE_STANDARD,
Expand All @@ -35,7 +35,7 @@ describe('reducer', () => {
hideOutputActive: false,
increaseOutputButtons: false,
labelPosition: 'Below',
darkThemeActive: false,
darkThemeActive: false
},
navigationSettings: {
active: false,
Expand All @@ -46,12 +46,12 @@ describe('reducer', () => {
shareShowActive: false,
quickUnlockActive: false,
removeOutputActive: false,
vocalizeFolders: false,
vocalizeFolders: false
},
symbolsSettings: {
arasaacActive: false,
arasaacActive: false
},
userData: {},
userData: {}
};
uData = { name: 'martin bedouret', email: '[email protected]' };
mockApp = {
Expand All @@ -62,7 +62,7 @@ describe('reducer', () => {
labelPosition: 'Below',
fontFamily: DEFAULT_FONT_FAMILY,
fontSize: 'Standard',
darkThemeActive: false,
darkThemeActive: false
},
isConnected: true,
isFirstVisit: false,
Expand All @@ -75,9 +75,9 @@ describe('reducer', () => {
shareShowActive: false,
quickUnlockActive: false,
removeOutputActive: false,
vocalizeFolders: false,
vocalizeFolders: false
},
userData: uData,
userData: uData
};
});
it('should return the initial state', () => {
Expand All @@ -86,57 +86,57 @@ describe('reducer', () => {
it('should handle login ', () => {
const login = {
type: LOGIN_SUCCESS,
payload: uData,
payload: uData
};
expect(appReducer(initialState, login)).toEqual({
...initialState,
userData: uData,
isFirstVisit: false,
isFirstVisit: false
});
});
it('should handle logout', () => {
const logout = {
type: LOGOUT,
type: LOGOUT
};
expect(appReducer(initialState, logout)).toEqual(initialState);
});
it('should handle updateDisplaySettings', () => {
const updateDisplaySettings = {
type: UPDATE_DISPLAY_SETTINGS,
payload: mockApp.displaySettings,
payload: mockApp.displaySettings
};
expect(appReducer(initialState, updateDisplaySettings)).toEqual({
...initialState,
displaySettings: mockApp.displaySettings,
displaySettings: mockApp.displaySettings
});
});
it('should handle updateNavigationSettings', () => {
const updateNavigationSettings = {
type: UPDATE_NAVIGATION_SETTINGS,
payload: mockApp.navigationSettings,
payload: mockApp.navigationSettings
};
expect(appReducer(initialState, updateNavigationSettings)).toEqual({
...initialState,
navigationSettings: mockApp.navigationSettings,
navigationSettings: mockApp.navigationSettings
});
});
it('should handle finishFirstVisit ', () => {
const finishFirstVisit = {
type: FINISH_FIRST_VISIT,
type: FINISH_FIRST_VISIT
};
expect(appReducer(initialState, finishFirstVisit)).toEqual({
...initialState,
isFirstVisit: false,
isFirstVisit: false
});
});
it('should handle updateConnectivity', () => {
const updateConnectivity = {
type: UPDATE_CONNECTIVITY,
payload: false,
payload: false
};
expect(appReducer(initialState, updateConnectivity)).toEqual({
...initialState,
isConnected: false,
isConnected: false
});
});
});
2 changes: 1 addition & 1 deletion src/components/Board/Symbol/Symbol.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ it('renders with correct image source path', () => {

it('renders with label', () => {
const wrapper = shallow(
<Symbol label="dummy label" type="p" labelpos="Below" />,
<Symbol label="dummy label" type="p" labelpos="Below" />
);
expect(wrapper.find('.Symbol__label')).toHaveLength(1);
});
2 changes: 1 addition & 1 deletion src/components/Settings/Settings.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class Settings extends PureComponent {
}
];

if (!isIOS() && !isInFreeCountry) {
if (!isInFreeCountry) {
const subscribeSection = {
icon: <MonetizationOnIcon />,
text: messages.subscribe,
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/';
91 changes: 68 additions & 23 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 Down Expand Up @@ -195,7 +195,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 +207,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 +233,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 +314,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
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 ocurs during validation of your purchase. Please refresh'
tomivm marked this conversation as resolved.
Show resolved Hide resolved
},
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
Loading