Skip to content

Commit

Permalink
Handle errors better
Browse files Browse the repository at this point in the history
  • Loading branch information
LudvigHz committed Apr 21, 2020
1 parent 9dfebcc commit e31cc9e
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 29 deletions.
3 changes: 2 additions & 1 deletion app/models.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export type EventRegistration = {
feedback: string,
sharedMemberships?: number,
consent: EventRegistrationPhotoConsent,
clientSecret?: string
clientSecret?: string,
paymentError?: string
};

type EventPoolBase = {
Expand Down
16 changes: 14 additions & 2 deletions app/reducers/registrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export default createEntityReducer({
case Event.REQUEST_REGISTER.SUCCESS:
case Event.ADMIN_REGISTER.SUCCESS:
case Event.SOCKET_REGISTRATION.SUCCESS:
case Event.PAYMENT_QUEUE.SUCCESS:
case Event.SOCKET_PAYMENT.FAILURE: {
const registration = normalize(action.payload, registrationSchema)
.entities.registrations[action.payload.id];
Expand All @@ -40,7 +39,8 @@ export default createEntityReducer({
}
newState.byId[registration.id] = {
...omit(newState.byId[registration.id], 'unregistrationDate'),
...registration
...registration,
paymentError: (action.meta && action.meta.paymentError) || null
};
newState.items = union(newState.items, [registration.id]);
break;
Expand Down Expand Up @@ -69,6 +69,18 @@ export default createEntityReducer({
};
break;
}
case Event.PAYMENT_QUEUE.FAILURE: {
const registration = normalize(action.payload, registrationSchema)
.entities.registrations[action.payload.id];
if (!registration) {
return;
}
newState.byId[registration.id] = {
...omit(newState.byId[registration.id], 'unregistrationDate'),
paymentError: action.payload.response.jsonData.detail
};
break;
}
case Event.SOCKET_INITIATE_PAYMENT.SUCCESS: {
const registration = normalize(action.payload, registrationSchema)
.entities.registrations[action.payload.id];
Expand Down
1 change: 1 addition & 0 deletions app/routes/events/components/JoinEventForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const PaymentForm = ({
<b>{(event.price / 100).toFixed(2).replace('.', ',')} kr</b>
</div>
<PaymentRequestForm
paymentError={registration.paymentError}
createPaymentIntent={createPaymentIntent}
event={event}
currentUser={currentUser}
Expand Down
2 changes: 1 addition & 1 deletion app/routes/events/components/Stripe.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import 'app/styles/variables.css';
@import '~app/styles/variables.css';

.StripeLabel {
color: #6b7c93;
Expand Down
78 changes: 53 additions & 25 deletions app/routes/events/components/StripeElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,8 @@ type Props = {
currentUser: User,
createPaymentIntent: () => Promise<*>,
paymentStatus: EventRegistrationPaymentStatus,
clientSecret?: string
};

/*
* Taken from https://stripe.com/docs/api/errors
*/
type StripeError = {
type: string,
charge: string,
code: string,
decline_code: string,
message: string,
param: string,
doc_url: string,
payment_intent: Object,
setup_intent: Object,
source: Object
clientSecret?: string,
paymentError?: string
};

type FormProps = Props & {
Expand All @@ -50,23 +35,23 @@ type FormProps = Props & {

type CardFormProps = FormProps & {
ledgend: string,
setError: StripeError => void,
setError: string => void,
setSuccess: () => void,
setLoading: boolean => void,
stripe: Stripe,
paymentStarted: boolean
};

type PaymentRequestFormProps = FormProps & {
setError: StripeError => void,
setError: string => void,
setSuccess: () => void,
setLoading: boolean => void,
setPaymentRequest: boolean => void,
stripe: Stripe
};

type FormState = {
error?: StripeError | null,
error?: string | null,
success?: boolean,
loading: boolean,
paymentRequest: boolean
Expand All @@ -83,6 +68,16 @@ type PaymentRequestFormState = {
complete?: string => void
};

// See https://stripe.com/docs/js/appendix/payment_response#payment_response_object-complete
// for the statuses
type CompleteStatus =
| 'success'
| 'fail'
| 'invalid_payer_name'
| 'invalid_payer_phone'
| 'invalid_payer_email'
| 'invalid_shipping_address';

const StripeElementStyle = {
style: {
base: {
Expand Down Expand Up @@ -119,7 +114,7 @@ class CardForm extends React.Component<CardFormProps, CardFormState> {
const { stripe } = this.props;
const { error } = await stripe.handleCardPayment(clientSecret);
if (error) {
this.props.setError(error);
this.props.setError(error.message);
} else {
this.props.setSuccess();
}
Expand Down Expand Up @@ -210,9 +205,28 @@ class PaymentRequestForm extends React.Component<
}

componentDidUpdate(prevProps) {
if (!prevProps.clientSecret && this.props.clientSecret) {
this.completePayment(this.props.clientSecret);
const { clientSecret, paymentError } = this.props;

if (!prevProps.clientSecret && clientSecret) {
this.completePayment(clientSecret);
}
if (paymentError) {
this.completePaymentManual('fail');
this.props.setError(paymentError);
}
}

componentWillUnmount() {
/*
* If the component unmounts, the registration will have updated,
* and the user will not be able to pay.
* This can be because the payment updated in the backend,
* or the user has unregistered. In the rare case that the payment has started
* processing, we cancel just so the user does not have to wait until the
* payment request times out.
*
*/
this.completePaymentManual('fail');
}

completePayment = async clientSecret => {
Expand All @@ -238,13 +252,27 @@ class PaymentRequestForm extends React.Component<

const { error } = await stripe.handleCardPayment(clientSecret);
if (error) {
this.props.setError(error);
this.props.setError(error.message);
} else {
this.props.setSuccess();
}
this.props.setLoading(false);
};

completePaymentManual = async (status: CompleteStatus) => {
const { complete } = this.state;

if (!complete) {
return;
}

complete(status);

if (status === 'success') {
this.props.setSuccess();
}
};

render() {
return (
<div style={{ flex: 1 }}>
Expand Down Expand Up @@ -299,7 +327,7 @@ class PaymentForm extends React.Component<FormProps, FormState> {
) : (
<>
{loading && <LoadingIndicator loading />}
{error && <div className={stripeStyles.error}>{error.message}</div>}
{error && <div className={stripeStyles.error}>{error}</div>}
<div style={{ display: loading ? 'none' : 'block' }}>
<Elements locale="no">
<InjectedPaymentRequestForm
Expand Down

0 comments on commit e31cc9e

Please sign in to comment.