...
 
Commits (8)
......@@ -31,6 +31,7 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
esproposal.decorators=ignore
module.system=haste
module.system.haste.use_name_reducers=true
......
......@@ -163,6 +163,7 @@ android {
}
dependencies {
implementation project(':tipsi-stripe')
implementation project(':react-native-svg')
implementation project(':react-native-device-info')
implementation project(':react-native-sentry')
......
......@@ -3,6 +3,7 @@ package com.minds.mobile;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.gettipsi.stripe.StripeReactPackage;
import com.horcrux.svg.SvgPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import io.sentry.RNSentryPackage;
......@@ -59,6 +60,7 @@ public class MainApplication extends Application implements ShareApplication, Re
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new StripeReactPackage(),
new SvgPackage(),
new RNDeviceInfo(),
new RNSentryPackage(),
......
rootProject.name = 'Minds'
include ':tipsi-stripe'
project(':tipsi-stripe').projectDir = new File(rootProject.projectDir, '../node_modules/tipsi-stripe/android')
include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
include ':react-native-device-info'
......
......@@ -66,7 +66,7 @@
"balance":"Balance",
"gasEth":"Gas (ETH)",
"walletNameExample":"eg. Mobile Spending",
"receiverAddress":"Recieve Address",
"receiverAddress":"Receive Address",
"shouldBeReceiver":"Should this address be your receiver address for Wire & Boost?",
"downloadPrivate":"Download the private key of this wallet",
"deleteWarning":"WARNING: this is irreversable and all tokens will be lost",
......@@ -349,6 +349,7 @@
"referral_complete": "Referral Complete"
},
"payments":{
"confirmDeleteCard":"Do you want to delete this card?",
"cardExpirationMonth":"Card Expiration Month",
"cardExpirationYear":"Card Expiration Year",
"yourSavedCard":"YOUR SAVED CARDS:",
......@@ -556,7 +557,9 @@
}
},
"wire":{
"amountMonth":"{{amount}}/month",
"selectCredit":"Select a credit card.",
"willNotRecur":"You can send {{currency}} to this user, however it will not recur.",
"amountMonth":"{{amount}} / month",
"noAddress":"This channel has not configured their {{type}} address yet",
"amountMonthDescription":"THIS POST CAN ONLY BE SEEN BY SUPPORTERS WHO WIRE {{amount}}/MONTH TO @{{name}}",
"weHaveReceivedYourTransaction":"We've received your transaction",
......
......@@ -110,6 +110,7 @@
"string_decoder": "^0.10.31",
"stripe-client": "^1.1.3",
"timers-browserify": "^1.0.1",
"tipsi-stripe": "8.0.0-beta.6",
"tty-browserify": "0.0.0",
"url": "~0.10.1",
"util": "~0.10.3",
......
import stripe from 'tipsi-stripe';
import mindsService from './minds.service';
let intialized = false;
export const initStripe = async() => {
if (intialized) return;
intialized = true;
const settings = await mindsService.getSettings();
await stripe.setOptions({
publishableKey: settings.stripe_key,
//androidPayMode: 'test', // Android only
});
}
export default stripe;
export default colors = {
primary : '#4690D6',
secondary : '#FFDD63',
terciary : '#4C9EB1',
tertiary : '#4C9EB1',
danger : '#f53d3d',
light : '#f7f7f7',
medium : '#b0bec5',
......
......@@ -89,8 +89,8 @@ export const CommonStyle = StyleSheet.create({
colorSecondary: {
color: colors.secondary
},
colorTerciary: {
color: colors.terciary
colorTertiary: {
color: colors.tertiary
},
colorPrimary: {
color: colors.primary
......@@ -144,8 +144,8 @@ export const CommonStyle = StyleSheet.create({
backgroundTransparent: {
backgroundColor: 'transparent'
},
backgroundTerciary: {
backgroundColor: colors.terciary
backgroundTertiary: {
backgroundColor: colors.tertiary
},
// borders
borderWhite: {
......@@ -163,8 +163,8 @@ export const CommonStyle = StyleSheet.create({
borderSecondary: {
borderColor: colors.secondary
},
borderTerciary: {
borderColor: colors.terciary
borderTertiary: {
borderColor: colors.tertiary
},
borderPrimary: {
borderColor: colors.primary
......@@ -451,6 +451,14 @@ export const CommonStyle = StyleSheet.create({
textAlign: 'justify'
},
fullWidth: {
width: '100%'
},
halfWidth: {
width: '50%'
},
// Overlay∫
blackOverlay: {
backgroundColor:'black',
......
......@@ -26,7 +26,6 @@ import McIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import colors from '../styles/Colors';
import { CommonStyle as CS } from '../styles/Common';
import CenteredLoading from '../common/components/CenteredLoading';
import RewardsCarousel from '../channel/carousel/RewardsCarousel';
import featuresService from '../common/services/features.service';
......@@ -41,6 +40,7 @@ import BtcPayment from './methods/BtcPayment';
import PaymentMethodIcon from './methods/PaymentMethodIcon';
import Button from '../common/components/Button';
import numberFromat from '../common/helpers/number';
import StripeCardSelector from './methods/StripeCardSelector';
/**
* Wire Fab Screen
......@@ -112,11 +112,7 @@ export default class FabScreen extends Component {
* Modal navigation
*/
static navigationOptions = ({ navigation }) => ({
header: (
<View style={[CS.backgroundLight, CS.rowJustifyEnd]}>
<Icon size={40} name="ios-close" onPress={() => navigation.goBack()} style={[CS.marginRight3x, CS.marginTop3x]}/>
</View>
),
header: null,
transitionConfig: {
isModal: true
}
......@@ -132,33 +128,59 @@ export default class FabScreen extends Component {
this.props.wire.setShowBtc(false);
}
onSelectCard = (card) => {
this.props.wire.setPaymentMethodId(card.id);
}
goBackUSD = () => {
this.props.wire.setShowCardselector(false)
}
getBody() {
const buttonDisabled = this.props.wire.sending || this.props.wire.errors.length > 0;
if (this.props.wire.showBtc) {
return (
<BtcPayment amount={this.props.wire.amount} address={this.props.wire.owner.btc_address} onCancel={this.onCancelBtc}/>
)
}
if (this.props.wire.showCardselector) {
return (
<View style={CS.columnAlignCenter}>
<Text style={[CS.marginTop2x, CS.fontHairline, CS.fontXL, CS.marginBottom2x]}>{i18n.t('wire.selectCredit')}</Text>
<StripeCardSelector onCardSelected={this.onSelectCard}/>
<View style={[CS.rowJustifyCenter, CS.paddingTop3x, CS.marginTop4x]}>
<Button
text={i18n.t('goback').toUpperCase()}
disabled={buttonDisabled || !this.props.wire.paymentMethodId}
onPress={this.goBackUSD}
textStyle={[CS.fontL, CS.padding]}
/>
<Button
text={i18n.t('send').toUpperCase()}
disabled={buttonDisabled || !this.props.wire.paymentMethodId}
onPress={this.confirmSend}
textStyle={[CS.fontL, CS.padding]}
inverted
/>
</View>
</View>
)
}
const owner = this.getOwner();
const txtAmount = this.getTextAmount();
const amount = this.props.wire.amount.toString();
const buttonDisabled = this.props.wire.sending || this.props.wire.errors.length > 0;
const currencySelector = (
<View style={CS.alignCenter}>
<PaymentMethodIcon value={this.props.wire.currency} size={30} style={CS.colorPrimary} onPress={this.selectMethod}/>
<Text style={[CS.fontL, CS.colorPrimary]} onPress={this.selectMethod}>
{this.props.wire.currency.toUpperCase()}
</Text>
</View>
)
return (
<Fragment>
<Text style={[CS.fontM, CS.textCenter]}>{i18n.to('wire.supportMessage', {payments: featuresService.has('wire-multi-currency') ? 'tokens , ETH, BTC or USD' : 'tokens' }, {
<Text style={[CS.fontL, CS.textCenter, CS.marginTop2x]}>{i18n.to('wire.supportMessage', {payments: featuresService.has('wire-multi-currency') ? 'tokens , ETH, BTC or USD' : 'tokens' }, {
name: <Text style={CS.bold}>@{ owner.username }</Text>
})}</Text>
<View style={[styles.carouselContainer, CS.paddingTop2x]}>
<View style={[CS.paddingBottom, CS.paddingTop3x]}>
{this.props.wire.owner.wire_rewards.rewards && <SubscriptionTierCarousel
amount={amount}
rewards={this.props.wire.owner.wire_rewards.rewards}
......@@ -167,22 +189,18 @@ export default class FabScreen extends Component {
/>}
</View>
<View>
<View style={CS.marginTop3x, CS.marginBottom2x}>
{this.props.wire.errors.map(e => <Text style={[CS.colorDanger, CS.fontM, CS.textCenter]}>{e}</Text>)}
</View>
{this.props.wire.currency === 'btc' && <Text style={[CS.fontM, CS.textCenter]}>You can send BTC to this user, however it will not recur.</Text>}
<PaymentMethodSelector
ref={this.paymethodRef}
value={this.props.wire.currency}
onSelect={this.props.wire.setCurrency}
/>
<View style={[CS.rowJustifySpaceEvenly, CS.marginBottom3x, CS.marginTop3x, CS.alignJustifyCenter, CS.alignCenter]}>
<View style={[CS.flexContainer, CS.centered]}>
<PaymentMethodSelector
button={currencySelector}
ref={this.paymethodRef}
value={this.props.wire.currency}
onSelect={this.props.wire.setCurrency}
/>
</View>
<TextInput
ref="input"
......@@ -195,17 +213,20 @@ export default class FabScreen extends Component {
</View>
<View>
<CheckBox
title={i18n.t('wire.repeatMessage')}
checked={this.props.wire.recurring}
onPress={() => this.props.wire.toggleRecurring()}
left
checkedIcon="check-circle-o"
checkedColor={ colors.primary }
uncheckedIcon="circle-o"
uncheckedColor={ colors.greyed }
containerStyle={[CS.backgroundLight]}
/>
{ ['usd','tokens'].includes(this.props.wire.currency) ?
<CheckBox
title={i18n.t('wire.repeatMessage')}
checked={this.props.wire.recurring}
onPress={() => this.props.wire.toggleRecurring()}
left
checkedIcon="check-circle-o"
checkedColor={ colors.primary }
uncheckedIcon="circle-o"
uncheckedColor={ colors.greyed }
containerStyle={[CS.backgroundLight]}
/>:
<Text style={[CS.fontM, CS.textCenter, CS.marginTop2x, CS.marginBottom2x]}>{i18n.t('wire.willNotRecur', {currency: this.props.wire.currency.toUpperCase()})}</Text>
}
</View>
{ this.props.wire.owner.wire_rewards && this.props.wire.owner.wire_rewards.length && <View>
......@@ -228,22 +249,19 @@ export default class FabScreen extends Component {
* Render screen
*/
render() {
if (!this.props.wire.loaded) {
return <CenteredLoading/>
}
// sending?
let icon;
if (this.props.wire.sending) {
icon = <ActivityIndicator size={'large'} color={colors.primary}/>
} else {
icon = <Icon size={64} name="ios-flash" style={CS.colorPrimary} />
icon = <Icon size={64} name="ios-flash" style={[CS.colorPrimary, CS.paddingBottom2x]} />
}
const body = this.getBody();
const body = !this.props.wire.loaded ? <ActivityIndicator size={'large'} color={colors.primary}/> : this.getBody();
return (
<ScrollView contentContainerStyle={[CS.backgroundLight, CS.paddingLeft2x, CS.paddingRight2x, CS.flexContainer, CS.alignCenter]}>
<ScrollView contentContainerStyle={[CS.backgroundLight, CS.paddingLeft2x, CS.paddingRight2x, CS.columnAlignCenter, CS.alignCenter, CS.flexContainer, CS.paddingTop2x]}>
<Icon size={40} name="ios-close" onPress={() => this.props.navigation.goBack()} style={[CS.marginRight3x, CS.marginTop3x, CS.positionAbsoluteTopRight]}/>
{icon}
{body}
</ScrollView>
......@@ -259,10 +277,15 @@ export default class FabScreen extends Component {
return;
}
// we only show the btc component
if (this.props.wire.currency === 'btc') {
return this.send();
}
if (this.props.wire.currency === 'usd' && !this.props.wire.showCardselector) {
return this.props.wire.setShowCardselector(true);
}
Alert.alert(
i18n.t('confirmMessage'),
i18n.t('wire.confirmMessage', {amount: this.props.wire.formatAmount(this.props.wire.amount), name: this.props.wire.owner.username}),
......@@ -320,10 +343,4 @@ export default class FabScreen extends Component {
}
const selectedcolor = '#4690D6';
const color = '#444'
const styles = {
carouselContainer: {
height: 180
}
}
const color = '#444';
......@@ -67,7 +67,7 @@ class WireService {
* Send wire
* @param {object} opts
*/
async send(opts): Promise<any> {
async send(opts: Object): Promise<any> {
const payload = await this.getTransactionPayloads(opts);
if (!payload) {
......@@ -91,13 +91,27 @@ class WireService {
*/
async getTransactionPayloads(opts: Object): any {
let payload
let payload: Object;
if (opts.currency == 'tokens' || opts.currency == 'eth') {
payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: opts.currency === 'tokens', buyable: opts.currency === 'tokens', confirmTokenExchange: opts.amount, currency: opts.currency });
switch (opts.currency) {
case 'tokens':
case 'eth':
payload = await BlockchainWalletService.selectCurrent(
i18n.t('wire.selectWalletMessage'),
{
signable: true,
offchain: opts.currency === 'tokens',
buyable: opts.currency === 'tokens',
confirmTokenExchange: opts.amount,
currency: opts.currency
}
);
break;
case 'usd':
payload = {type: 'usd'};
break;
}
if (!payload || payload.cancelled) {
return;
}
......@@ -147,6 +161,12 @@ class WireService {
receiver: opts.owner.eth_wallet,
txHash: await BlockchainWireService.createEth(opts.owner.eth_wallet, opts.amount)
};
case 'usd':
return {
method: payload.type,
paymentMethodId: opts.paymentMethodId
}
}
throw new Error('Unknown type');
......
......@@ -7,14 +7,6 @@ import {
import wireService from './WireService';
import i18n from '../common/services/i18n.service';
export type PayloadType =
| 'onchain'
| 'offchain'
| 'usd'
| 'eth'
| 'erc20'
| 'btc';
/**
* Wire store
*/
......@@ -25,14 +17,29 @@ class WireStore {
@observable.shallow owner = null;
@observable recurring = false;
@observable showBtc = false;
@observable showCardselector = false;
@observable loaded = false;
@observable errors = [];
@observable paymentMethodId: ?string = null;
guid: string;
@action
setShowBtc = (value: boolean) => {
this.showBtc = value;
}
@action
setShowCardselector = (value: boolean) => {
this.paymentMethodId = null;
this.showCardselector = value;
}
setPaymentMethodId(value: string) {
this.paymentMethodId = value;
}
@action
setCurrency = (value: string) => {
this.currency = value;
......@@ -63,6 +70,7 @@ class WireStore {
@action
setOwner(owner: any) {
this.owner = owner;
this.guid = owner.guid || owner.entity_guid;
}
async loadUserRewards(): Promise<any> {
......@@ -159,7 +167,8 @@ class WireStore {
guid: this.guid,
owner: this.owner,
recurring: this.recurring,
currency: this.currency
currency: this.currency,
paymentMethodId: this.paymentMethodId
});
this.stopSending();
......@@ -178,8 +187,10 @@ class WireStore {
@action
reset() {
this.paymentMethodId = null,
this.amount = 1;
this.showBtc = false;
this.showCardselector = false;
this.currency = 'tokens';
this.sending = false;
this.owner = null;
......
// @flow
import * as React from 'react'
import { Text, StyleSheet } from 'react-native';
import Menu, { MenuItem } from 'react-native-material-menu';
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native';
import featuresService from '../../common/services/features.service';
import testID from '../../common/helpers/testID';
......@@ -72,18 +70,16 @@ export default class PaymentMethodSelector extends React.PureComponent<PropsType
render(): React.Node {
return (
<Menu ref={this.menuRef} button={this.props.button}>
{this.methods.map((method: any, i: number): MenuItem => (
<MenuItem
key={i}
onPress={method.handle}
textStyle={CS.fontXL}
{...testID(`PAYMENT METHOD ${method.label}`)}
>
<Text style={(method.label.toLowerCase() === this.props.value) ? styles.selected : null}><PaymentMethodIcon value={method.label} size={15} /> {method.label}</Text>
</MenuItem>
<View style={[CS.rowJustifySpaceEvenly, CS.fullWidth]}>
{this.methods.map((method: any, i: number): React.Node => (
<TouchableOpacity style={[CS.alignCenter, CS.padding ]} {...testID(`PAYMENT METHOD ${method.label}`)} key={i} onPress={method.handle}>
<PaymentMethodIcon value={method.label} size={30} style={(method.label.toLowerCase() === this.props.value) ? styles.selected : null} />
<Text style={[CS.fontL, (method.label.toLowerCase() === this.props.value) ? styles.selected : null]} >
{method.label.toUpperCase()}
</Text>
</TouchableOpacity>
))}
</Menu>
</View>
);
}
}
......
// @flow
import * as React from 'react';
import { Text, View, StyleSheet, Alert } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
import Icon from 'react-native-vector-icons/FontAwesome';
import i18n from '../../common/services/i18n.service';
const {value: slideWidth, viewportHeight} = viewportPercentage(75);
const {value: itemHorizontalMargin} = viewportPercentage(2);
const itemWidth = slideWidth + itemHorizontalMargin * 2;
const itemHeight = {height: parseInt(itemWidth * 0.55)};
type PropsType = {
paymentmethods: Array<any>,
onCardSelected: Function,
onCardDeleted: Function
};
/**
* Stripe card Carousel
*/
export default class StripeCardCarousel extends React.PureComponent<PropsType> {
carouselRef: ?React.ElementRef<Carousel>;
/**
* Constructor
* @param {PropsType} props
*/
constructor(props: PropsType) {
super(props);
if (props.paymentmethods && props.onCardSelected) {
this.props.onCardSelected(props.paymentmethods[props.paymentmethods.length - 1]);
}
this.carouselRef = React.createRef();
}
/**
* Component did update
* @param {PropsType} prevProps
*/
componentDidUpdate(prevProps: PropsType) {
// if the array of cards changes we snap to the last item
if (prevProps.paymentmethods !== this.props.paymentmethods) {
setTimeout(() => {
if (this.carouselRef && this.carouselRef.current) {
this.carouselRef.current.snapToItem(this.props.paymentmethods.length);
}
}, 100);
}
}
delete(card: any) {
Alert.alert(
i18n.t('confirmMessage'),
i18n.t('payments.confirmDeleteCard'),
[
{ text: i18n.t('cancel'), style: 'cancel' },
{ text: i18n.t('ok'), onPress: (): any => this.props.onCardDeleted(card) },
],
{ cancelable: false }
);
}
/**
* Renders a card
* @param {Object} row
*/
_renderItem = (row: any): React.Node => {
const even = row.index % 2;
return (
<View
key={`card${row.item.id}`}
style={[itemHeight, even ? CS.backgroundPrimary : CS.backgroundTertiary, CS.borderRadius4x, CS.padding2x, CS.shadow]}
>
{this.getCardIcon(row.item.card_brand)}
<Icon name="close" style={[CS.positionAbsoluteTopRight, CS.margin, CS.colorWhite]} size={25} onPress={(): any => this.delete(row.item)}/>
<Text style={[CS.fontM, CS.fontMedium, CS.colorWhite, CS.paddingBottom2x]}>{row.item.card_country}</Text>
<Text style={[CS.fontXL, CS.fontMedium, CS.colorWhite, CS.textCenter, CS.paddingTop3x]}>********** {row.item.card_last4}</Text>
<Text numberOfLines={5} style={[CS.fontL, CS.fontHairline, CS.colorWhite, CS.textRight , CS.paddingTop3x]}>{row.item.card_expires}</Text>
</View>
);
}
/**
* Get credit card icon
* @param {string} card
*/
getCardIcon(card: string): React.Node {
let name;
switch (card) {
case 'visa':
name = 'cc-visa';
break;
case 'amex':
name = 'cc-amex';
break;
case 'mastercard':
name = 'cc-mastercard';
break;
case 'jcb':
name = 'cc-jcb';
break;
case 'discover':
name = 'cc-discover';
break;
case 'diners':
name = 'cc-diners-club';
break;
default:
name = 'credit-card-alt'
}
return <Icon name={name} size={30} color="white"/>
}
/**
* Card Selected
* @param {number} index
*/
onSelected = (index: number) => {
if (this.props.onCardSelected) {
this.props.onCardSelected(this.props.paymentmethods[index]);
}
}
/**
* Render
*/
render(): React.Node {
return (
<Carousel
layout={'stack'}
layoutCardOffset={7}
onSnapToItem={this.onSelected}
firstItem={this.props.paymentmethods.length - 1}
containerCustomStyle={styles.carousle}
enableSnap={true}
ref={this.carouselRef}
data={ this.props.paymentmethods }
renderItem={this._renderItem}
inactiveSlideScale={0.94}
inactiveSlideOpacity={0.7}
sliderWidth={viewportHeight}
itemWidth={itemWidth}
/>
)
}
}
const styles = StyleSheet.create({
carousle: {
flexGrow: 0,
}
})
\ No newline at end of file
// @flow
import * as React from 'react'
import { View, ActivityIndicator, Alert } from 'react-native';
import { CommonStyle as CS } from '../../styles/Common';
import api from '../../common/services/api.service';
import stripe, { initStripe } from '../../common/services/stripe.service';
import Button from '../../common/components/Button';
import i18nService from '../../common/services/i18n.service';
import StripeCardCarousel from './StripeCardCarousel';
type PropsType = {
onCardSelected: ?Function
};
type StateType = {
cards: Array<any>,
loaded: boolean,
inProgress: boolean
};
/**
* Stripe card selector
*/
export default class StripeCardSelector extends React.PureComponent<PropsType, StateType> {
intentKey = '';
intentId = '';
state = {
cards: [],
loaded: false,
inProgress: false
}
/**
* Component did mount
*/
componentDidMount() {
this.loadCards();
}
/**
* On card deleted
* @param {any} card
*/
onCardDeleted = (card: any) => {
const index = this.state.cards.findIndex((r: any): any => r === card);
this.removeCard(index);
}
/**
* Render
*/
render(): React.Node {
return (
<View>
{(!this.state.loaded) && <ActivityIndicator/>}
{(this.state.cards.length > 0) && <StripeCardCarousel
paymentmethods={this.state.cards}
onCardSelected={this.props.onCardSelected}
onCardDeleted={this.onCardDeleted}
/>}
<View style={[CS.rowJustifyCenter, CS.marginTop2x]}>
<Button inverted text="Add Card" onPress={this.addNewCard} textStyle={[CS.fontL, CS.padding]}/>
</View>
</View>
)
}
/**
* Load cards
*/
async loadCards(): Promise<any> {
try {
const result = await api.get('api/v2/payments/stripe/paymentmethods');
if (result && result.paymentmethods) {
return this.setState({cards: result.paymentmethods.reverse(), loaded: true});
}
} catch (err) {
console.log(err);
}
this.setState({loaded: true});
}
/**
* Show error message
* @param {string} message
*/
showError(message: string) {
Alert.alert(message + '\n' + i18nService.t('tryAgain'));
}
/**
* Get setup intent from server
*/
async getSetupIntent(): Promise<boolean> {
try {
const { intent } = (
await api.put('api/v2/payments/stripe/intents/setup')
);
this.intentKey = intent.client_secret;
this.intentId = intent.id;
return true;
} catch(err) {
this.showError(i18nService.t('cantReachServer'));
return false;
}
}
/**
* Remove a credit card from the payment methods
* @param {number} index
*/
async removeCard(index: number): Promise<any> {
if (this.state.inProgress) return;
this.setState({inProgress: true});
try {
await api.delete('api/v2/payments/stripe/paymentmethods/' + this.state.cards[index].id)
const cards = this.state.cards.slice(0);
cards.splice(index, 1);
this.setState({cards, inProgress: false})
} catch (err) {
this.setState({inProgress: false});
}
}
/**
* Add a new credit card using a setup intent
*/
addNewCard = async (): Promise<any> => {
try {
const intent = this.getSetupIntent();
if (!intent) return;
await initStripe();
const paymentMethod = await stripe.paymentRequestWithCardForm({requiredBillingAddressFields: 'full'});
const params = {
paymentMethodId: paymentMethod.id,
clientSecret: this.intentKey
};
const { setupIntent, error } = await stripe.confirmSetupIntent(params);
if (error) {
throw error;
}
// we log the result
console.log('Setup Intent Confirmed', setupIntent);
// finally we save the card
this.saveCard();
} catch (err) {
if (err.message && err.message === 'Cancelled by user') return;
this.showError(err.message || i18nService.t('errorMessage'));
console.log(err);
}
}
/**
* Save card
*/
async saveCard(): Promise<any> {
const { success } = await api.post(
'api/v2/payments/stripe/paymentmethods/apply',
{
intent_id: this.intentId,
}
);
this.intentKey = '';
this.loadCards();
}
}
\ No newline at end of file
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import { Text, View, StyleSheet } from 'react-native';
import Carousel, { Pagination } from 'react-native-snap-carousel';
import featuresService from '../../common/services/features.service';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
import i18n from '../../common/services/i18n.service';
const {value: slideWidth, viewportHeight} = viewportPercentage(75);
const {value: itemHorizontalMargin} = viewportPercentage(2);
......@@ -46,7 +47,7 @@ export default class SubscriptionTierCarousel extends PureComponent {
}];
const methodsMap = [{ method: 'tokens', currency: 'tokens' }];
if (featuresService.has('wire-multi-currency') || true) {
if (featuresService.has('wire-multi-currency')) {
methodsMap.push({ method: 'money', currency: 'usd' });
}
......@@ -85,7 +86,7 @@ export default class SubscriptionTierCarousel extends PureComponent {
return (
<View key={`rewards${row.item.amount}`} style={[CS.rowJustifyCenter, CS.backgroundLightGreyed, CS.borderRadius5x, CS.padding2x, CS.border, CS.borderGreyed]}>
<View style={CS.columnAlignCenter}>
<Text style={[CS.fontXXL, CS.fontMedium, CS.colorDark]}>{amount} {this.getPluralizedCurrency(currency, row.item.amount)} / month</Text>
<Text style={[CS.fontXXL, CS.fontMedium, CS.colorDark]}>{i18n.t('wire.amountMonth',{amount: amount + ' ' + this.getPluralizedCurrency(currency, row.item.amount)})}</Text>
<Text numberOfLines={5} style={[CS.fontL, CS.fontHairline, CS.colorDark]}>{row.item.description}</Text>
</View>
</View>
......@@ -109,20 +110,48 @@ export default class SubscriptionTierCarousel extends PureComponent {
let current = this.rewards.findIndex(r => r.amount == this.props.amount && r.currency == this.props.currency);
return (
<Carousel
onSnapToItem={this.onSelected}
enableSnap={true}
// layout={'tinder'}
layoutCardOffset={`10`}
ref={(c) => { this._carousel = c; }}
data={this.rewards}
firstItem={current}
renderItem={this._renderItem}
inactiveSlideScale={0.94}
inactiveSlideOpacity={0}
sliderWidth={viewportHeight}
itemWidth={itemWidth}
/>
<View style={[]}>
<Pagination
dotsLength={this.rewards.length}
activeDotIndex={current === -1 ? 0 : current}
containerStyle={styles.paginatorContainer}
dotStyle={styles.dot}
inactiveDotOpacity={0.4}
inactiveDotScale={0.6}
/>
<Carousel
onSnapToItem={this.onSelected}
containerCustomStyle={styles.carousel}
enableSnap={true}
// layout={'tinder'}
layoutCardOffset={`10`}
ref={(c) => { this._carousel = c; }}
data={this.rewards}
firstItem={current}
renderItem={this._renderItem}
inactiveSlideScale={0.94}
inactiveSlideOpacity={0}
sliderWidth={viewportHeight}
itemWidth={itemWidth}
/>
</View>
)
}
}
\ No newline at end of file
}
const styles = StyleSheet.create({
carousel: {
flexGrow: 0,
},
paginatorContainer: {
paddingVertical: 10
},
dot: {
width: 10,
height: 10,
borderRadius: 5,
marginHorizontal: 0,
backgroundColor: 'rgba(46, 46, 46, 0.92)'
}
})
\ No newline at end of file
......@@ -9339,6 +9339,11 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tipsi-stripe@8.0.0-beta.6:
version "8.0.0-beta.6"
resolved "https://registry.yarnpkg.com/tipsi-stripe/-/tipsi-stripe-8.0.0-beta.6.tgz#801c9e4c4d8dfab4a57c6f59ec507da478be2973"
integrity sha512-+/XErvJdiQofIO+dMN1AC9mxQiwYKvCekRHCFSCftTHS6Skn3Lx131GDhNqf0tM+IMAaAJhwq1HNluREm1rcpQ==
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
......