First working stripe charge

Refactored Proof, ProofContainer and MembershipContainer
Minor fixes
This commit is contained in:
nikolark committed 2017-10-03 02:08:29 +02:00
1 parent 2d4d0b2cf4
commit 9ab8061cc3
14 files changed
+297 -236

No files matched your search

-2
View File
@@ -9,7 +9,6 @@ import EventListScreen from "./screens/EventListScreen";
import LoginScreen from "./screens/LoginScreen";
import MembershipScreen from "./screens/MembershipScreen";
import UserRegisterScreen from "./screens/UserRegisterScreen";
import ChargeScreen from "./screens/ChargeScreen";
/* Navigation */
@@ -49,7 +48,6 @@ const DuskenContainer = StackNavigator({
Root: { screen: tabNav },
Login: { screen: LoginScreen },
UserRegister: { screen: UserRegisterScreen },
Charge: { screen: ChargeScreen },
EventDetail: { screen: EventDetailScreen },
});
+26 -106
View File
@@ -1,5 +1,3 @@
import Config from 'react-native-config';
const LOGIN_REQUEST = 'LOGIN_REQUEST';
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILURE = 'LOGIN_FAILURE';
@@ -10,6 +8,9 @@ const USER_DATA_FAILURE = 'USER_DATA_FAILURE';
const REGISTER_USER_REQUEST = 'REGISTER_USER_REQUEST';
const REGISTER_USER_SUCCESS = 'REGISTER_USER_SUCCESS';
const REGISTER_USER_FAILURE = 'REGISTER_USER_FAILURE';
const MEMBERSHIP_CHARGE_REQUEST = 'MEMBERSHIP_CHARGE_REQUEST';
const MEMBERSHIP_CHARGE_SUCCESS = 'MEMBERSHIP_CHARGE_SUCCESS';
const MEMBERSHIP_CHARGE_FAILURE = 'MEMBERSHIP_CHARGE_FAILURE';
function loginRequest() {
return {
@@ -77,6 +78,25 @@ function registerUserFailure(registerError) {
registerError
}
}
function membershipChargeRequest() {
return {
type: MEMBERSHIP_CHARGE_REQUEST
}
}
function membershipChargeSuccess(data) {
return {
type: MEMBERSHIP_CHARGE_SUCCESS,
data
}
}
function membershipChargeFailure(chargeError) {
return {
type: MEMBERSHIP_CHARGE_FAILURE,
chargeError
}
}
export {
LOGIN_REQUEST, loginRequest,
@@ -89,107 +109,7 @@ export {
REGISTER_USER_REQUEST, registerUserRequest,
REGISTER_USER_SUCCESS, registerUserSuccess,
REGISTER_USER_FAILURE, registerUserFailure,
};
export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest());
return fetch(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'username': username,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(loginSuccess(username, data.token))
},
data => {
console.log(data);
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
}
)
}
}
export function requestUserData(token) {
return (dispatch) => {
// We are now fetching user data
dispatch(userDataRequest());
const apiURL = Config.DUSKEN_API_URL;
return fetch(`${apiURL}/api/me/`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Token ' + token
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(userDataSuccess(data))
},
data => {
dispatch(userDataFailure('Det funka ikke :-('))
}
)
}
}
export function requestRegisterUser(firstName, lastName, email, phoneNumber, password) {
return (dispatch) => {
dispatch(registerUserRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/user/register/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
'first_name': firstName,
'last_name': lastName,
'email': email,
'phone_number': phoneNumber,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(registerUserSuccess(data))
},
data => {
dispatch(registerUserFailure(data || {'non_field_errors': ['Kunne ikke registrere bruker, prøv igjen']}))
}
)
}
}
MEMBERSHIP_CHARGE_REQUEST, membershipChargeRequest,
MEMBERSHIP_CHARGE_SUCCESS, membershipChargeSuccess,
MEMBERSHIP_CHARGE_FAILURE, membershipChargeFailure,
};
+146
View File
@@ -0,0 +1,146 @@
import Config from 'react-native-config';
import {
loginFailure, loginRequest, loginSuccess,
registerUserFailure, registerUserRequest, registerUserSuccess,
userDataFailure, userDataRequest, userDataSuccess,
membershipChargeFailure, membershipChargeRequest, membershipChargeSuccess,
} from "./actions";
export function requestLogin(username, password) {
return (dispatch) => {
// We are now logging in
dispatch(loginRequest());
return fetch(`${Config.DUSKEN_API_URL}/auth/obtain-token/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'username': username,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(loginSuccess(username, data.token))
},
data => {
console.log(data);
dispatch(loginFailure(data || {'non_field_errors': ['Kunne ikke logge inn, prøv igjen']}))
}
)
}
}
export function requestUserData(auth_token) {
return (dispatch) => {
// We are now fetching user data
dispatch(userDataRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/me/`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${auth_token}`
}
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(userDataSuccess(data))
},
data => {
dispatch(userDataFailure('Det funka ikke :-('))
}
)
}
}
export function requestRegisterUser(firstName, lastName, email, phoneNumber, password) {
return (dispatch) => {
dispatch(registerUserRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/user/register/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
'first_name': firstName,
'last_name': lastName,
'email': email,
'phone_number': phoneNumber,
'password': password
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(registerUserSuccess(data))
},
data => {
dispatch(registerUserFailure(data || {'non_field_errors': ['Kunne ikke registrere bruker, prøv igjen']}))
}
)
}
}
export function requestMembershipCharge(auth_token, stripe_token, membership_type) {
return (dispatch) => {
dispatch(membershipChargeRequest());
return fetch(`${Config.DUSKEN_API_URL}/api/membership/charge/`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${auth_token}`
},
body: JSON.stringify({
'stripe_token': stripe_token,
'membership_type': membership_type,
'payment_method': 'app',
})
})
.then(response => response.json().then(json => ({ json, response })))
.then(({json, response}) => {
if (response.ok === false) {
return Promise.reject(json)
}
return json
})
.then(
data => {
dispatch(membershipChargeSuccess(data))
},
data => {
dispatch(membershipChargeFailure(data || {'non_field_errors': ['Kunne ikke belaste kort, prøv igjen']}))
}
)
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import Login from "./Login";
import {requestLogin} from "../../actions";
import {requestLogin} from "../../api";
class LoginContainer extends Component {
constructor(state) {
-27
View File
@@ -1,27 +0,0 @@
import stripe from 'tipsi-stripe'
import React, {Component} from 'react';
import Config from 'react-native-config';
import {ActivityIndicator, Text, View} from "react-native";
// import {ScrollView, Text, View, Linking, StyleSheet, Platform} from "react-native";
// import {Button, Text as NBText} from "native-base";
stripe.init({
publishableKey: Config.STRIPE_PUBLISHABLE_KEY,
});
export default class Charge extends Component {
// TODO: You are here
render() {
return <View>
<Text>In charge view</Text>
<ActivityIndicator/>
</View>
}
openStripe() {
stripe.createTokenWithCard().then((data) => {
// returns a token.tokenId which should be saved
})
}
}
+16 -14
View File
@@ -2,8 +2,9 @@ import React, {Component} from 'react';
import {connect} from "react-redux";
import Membership from "./Membership";
import Proof from "./Proof";
import { requestUserData, logout } from "../../actions";
import { logout } from "../../actions";
import {requestUserData} from "../../api";
import ProofContainer from "./ProofContainer";
class MembershipContainer extends Component {
constructor(props) {
@@ -11,9 +12,9 @@ class MembershipContainer extends Component {
this.state = {
isAuthenticated: props.isAuthenticated,
userToken: props.userToken,
isFetchingUserData: props.isFetchingUserData,
user: props.user,
userToken: props.userToken,
lastOrder: props.lastOrder,
}
}
@@ -29,8 +30,12 @@ class MembershipContainer extends Component {
});
}
if(this.state.user !== nextProps.user) {
this.setState({user: nextProps.user})
if(this.state.lastOrder !== nextProps.lastOrder) {
this.setState({lastOrder: nextProps.lastOrder}, () => {
if(nextProps.userToken) {
this.fetchUser();
}
});
}
}
@@ -46,22 +51,18 @@ class MembershipContainer extends Component {
this.props.onRegisterPress();
};
onChargePress = () => {
this.props.onChargePress();
};
onLogoutPress = () => {
this.props.logout();
this.props.onLogoutPress();
};
render() {
if (this.state.user) {
return <Proof
user={this.state.user}
if (this.props.user) {
return <ProofContainer
user={this.props.user}
userToken={this.state.userToken}
fetchUser={this.fetchUser}
onLogoutPress={this.onLogoutPress}
onChargePress={this.onChargePress}
/>;
}
return <Membership onLoginPress={this.onLoginPress} onRegisterPress={this.onRegisterPress} />;
@@ -74,6 +75,7 @@ function stateToProps(state) {
isFetchingUserData: state.isFetchingUserData,
userToken: state.userToken,
user: state.user,
lastOrder: state.lastOrder
};
}
+12 -44
View File
@@ -1,27 +1,18 @@
import React, {Component} from 'react';
import {Image, Linking, StyleSheet, Platform, RefreshControl, TouchableOpacity, View} from "react-native";
import {Image, StyleSheet, TouchableOpacity, View} from "react-native";
import {Button, Body, Card, CardItem, Text, Content} from 'native-base';
import Confetti from 'react-native-confetti';
export default class Proof extends Component {
// FIXME: Move logic to container
CONFETTI_TIMEOUT = 15000;
constructor(props) {
super(props);
this.state = {
user: props.user,
refreshing: false,
stopConfetti: false,
}
};
componentWillReceiveProps(nextProps) {
if(this.state.user !== nextProps.user) {
this.setState({user: nextProps.user})
}
}
componentDidUpdate () {
if (this._confettiView && this.state.stopConfetti) {
this._confettiView.stopConfetti();
@@ -35,26 +26,6 @@ export default class Proof extends Component {
}
}
// fetchUser = () => {
// this.props.fetchUser().then(() => {
// this.setState({refreshing: false});
// });
// };
// _onRefresh() {
// console.log('onRefresh')
// this.setState({refreshing: true});
// this.fetchUser()
// }
// refreshControl() {
// return (
// <RefreshControl
// refreshing={this.state.refreshing}
// onRefresh={this._onRefresh.bind(this)}
// />)
// }
onMembershipPress = () => {
this.setState({showConfetti: true}, () => {
if(this._confettiView) {
@@ -67,12 +38,12 @@ export default class Proof extends Component {
};
membershipValidTo() {
if( !this.state.user.last_membership) {
if( !this.props.user.last_membership) {
return;
}
let validTo = this.state.user.last_membership.end_date;
if (this.state.user.last_membership.membership_type === 'lifelong') {
let validTo = this.props.user.last_membership.end_date;
if (this.props.user.last_membership.membership_type === 'lifelong') {
validTo = 'Livsvarig'
}
@@ -87,8 +58,8 @@ export default class Proof extends Component {
}
membershipStatus() {
if (!this.state.user.is_member) {
if ( !this.state.user.last_membership ) {
if (!this.props.user.is_member) {
if ( !this.props.user.last_membership ) {
return (
<CardItem>
<View style={styles.notMember}>
@@ -101,7 +72,7 @@ export default class Proof extends Component {
}
let statusText = 'Medlem';
if (this.state.user.is_volunteer) {
if (this.props.user.is_volunteer) {
statusText = 'Aktiv';
}
@@ -114,7 +85,7 @@ export default class Proof extends Component {
}
purchaseButton() {
if (this.state.user.is_member) {
if (this.props.user.is_member) {
return;
}
return <CardItem>
@@ -131,14 +102,14 @@ export default class Proof extends Component {
}
confetti() {
if (this.state.user.is_member) {
if (this.props.user.is_member) {
// FIXME: allmost infinite confetti. Are the animations paused when not visible, if not power hog?
return <Confetti ref={(node) => this._confettiView = node} confettiCount={Number.MAX_SAFE_INTEGER} />
}
}
logo() {
if (!this.state.user.is_member) {
if (!this.props.user.is_member) {
return;
}
// FIXME: URL to config
@@ -149,9 +120,6 @@ export default class Proof extends Component {
/>
}
fetchUser = () => {
this.props.fetchUser()
};
render() {
return (
@@ -174,9 +142,9 @@ export default class Proof extends Component {
};
membershipName() {
let name = `${this.state.user.first_name} ${this.state.user.last_name}`;
let name = `${this.props.user.first_name} ${this.props.user.last_name}`;
if(name === ' ') {
name = this.state.user.email;
name = this.props.user.email;
}
return <CardItem>
<Text style={styles.nameText}>{name}</Text>
+69 -2
View File
@@ -1,15 +1,82 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import stripe from 'tipsi-stripe'
import Config from 'react-native-config';
import Proof from "./Proof";
import {requestMembershipCharge} from "../../api";
stripe.init({
publishableKey: Config.STRIPE_PUBLISHABLE_KEY,
});
class ProofContainer extends Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
}
}
componentWillReceiveProps(nextProps) {
if(this.state.user !== nextProps.user) {
this.setState({user: nextProps.user})
}
}
render() {
return <Proof />
return <Proof {...this.state} user={this.props.user} onChargePress={this.openStripe} />
}
openStripe = () => {
const options = {
prefilledInformation: {
email: this.props.user.email,
},
smsAutofillDisabled: true,
};
stripe.paymentRequestWithCardForm(options).then(
(token) => {
// returns a token.tokenId which should be saved
this.props.requestMembershipCharge(
this.props.userToken,
{id: token.tokenId, email: this.props.user.email},
'standard');
},
(error) => {
// TODO: display error
console.log(error);
}
);
};
// fetchUser = () => {
// this.props.fetchUser().then(() => {
// this.setState({refreshing: false});
// });
// };
// _onRefresh() {
// console.log('onRefresh')
// this.setState({refreshing: true});
// this.fetchUser()
// }
// refreshControl() {
// return (
// <RefreshControl
// refreshing={this.state.refreshing}
// onRefresh={this._onRefresh.bind(this)}
// />)
// }
}
export default connect()(ProofContainer);
export default connect(
(state) => ({
userToken: state.userToken
}),
{requestMembershipCharge}
)(ProofContainer);
+1 -1
View File
@@ -163,7 +163,7 @@ export default class UserRegister extends Component {
onPress={this.onRegisterPress}
style={styles.registerButton}
>
<Text>Registrer</Text>
<Text>Registrer meg</Text>
</Button>);
}
}
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {connect} from "react-redux";
import UserRegister from "./UserRegister";
import {requestRegisterUser} from "../../actions";
import {requestRegisterUser} from "../../api";
class UserRegisterContainer extends Component {
+23 -1
View File
@@ -8,7 +8,7 @@ import {
USER_DATA_FAILURE,
REGISTER_USER_REQUEST,
REGISTER_USER_FAILURE,
REGISTER_USER_SUCCESS,
REGISTER_USER_SUCCESS, MEMBERSHIP_CHARGE_REQUEST, MEMBERSHIP_CHARGE_FAILURE, MEMBERSHIP_CHARGE_SUCCESS,
} from './actions';
const initialState = {
@@ -16,11 +16,14 @@ const initialState = {
isLoggingIn: false,
isFetchingUserData: false,
isRegisteringUser: false,
isChargingMembership: false,
userToken: null,
user: null,
loginError: null,
userError: null,
registerError: null,
chargeError: null,
lastOrder: null,
};
export default function duskenApp(state = initialState, action) {
@@ -92,6 +95,25 @@ export default function duskenApp(state = initialState, action) {
userToken: token,
});
/* User data */
case MEMBERSHIP_CHARGE_REQUEST:
return Object.assign({}, state, {
isChargingMembership: true,
chargeError: null
});
case MEMBERSHIP_CHARGE_FAILURE:
return Object.assign({}, state, {
isChargingMembership: false,
chargeError: action.chargeError
});
case MEMBERSHIP_CHARGE_SUCCESS:
return Object.assign({}, state, {
isChargingMembership: false,
lastOrder: action.data,
});
default:
return state
}
-30
View File
@@ -1,30 +0,0 @@
import React, { Component } from 'react';
import Charge from "../modules/membership/Charge";
import {StyleProvider} from "native-base";
import getTheme from '../../native-base-theme/components';
export default class ChargeScreen extends Component {
static navigationOptions = {
title: 'Kjøp medlemskap',
headerStyle: {
backgroundColor: '#f58220',
},
headerTitleStyle: {
color: 'white',
},
headerTintColor: 'white',
};
onCharged = () => {
// This works since ChargeScreen is allways navigated to from MembershipScreen
this.props.navigation.goBack();
};
render() {
return (
<StyleProvider style={getTheme()}>
<Charge onCharged={this.onCharged} />
</StyleProvider>
)
}
}
+1 -6
View File
@@ -31,18 +31,13 @@ export default class MembershipScreen extends Component {
this.props.navigation.navigate('UserRegister');
};
chargeNavigate = () => {
this.props.navigation.navigate('Charge');
};
render() {
return (
<StyleProvider style={getTheme()}>
<MembershipContainer
onLoginPress={this.loginNavigate}
onLogoutPress={this.logoutNavigate}
onRegisterPress={this.registerNavigate}
onChargePress={this.chargeNavigate} />
onRegisterPress={this.registerNavigate} />
</StyleProvider>
)
}
+1 -1
View File
@@ -5,7 +5,7 @@ import getTheme from '../../native-base-theme/components';
export default class UserRegisterScreen extends Component {
static navigationOptions = {
title: 'Registrer',
title: 'Bli medlem',
headerStyle: {
backgroundColor: '#f58220',
},