CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/billing/add-payment-method.tsx
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Button, ButtonToolbar, Row, Col, Well } from "../antd-bootstrap";
7
import { Component, Rendered, redux } from "../app-framework";
8
import { ErrorDisplay, Loading } from "../components";
9
import { HelpEmailLink } from "../customize";
10
import { powered_by_stripe } from "./util";
11
import { loadStripe, StripeCard } from "./stripe";
12
13
interface Props {
14
on_close?: Function; // optionally called when this should be closed
15
hide_cancel_button?: boolean;
16
}
17
18
const CARD_STYLE = {
19
margin: "15px",
20
border: "1px solid grey",
21
padding: "30px",
22
background: "white",
23
borderRadius: "5px",
24
};
25
26
interface State {
27
submitting: boolean;
28
error: string;
29
loading: boolean;
30
}
31
32
export class AddPaymentMethod extends Component<Props, State> {
33
private mounted: boolean = false;
34
private card?: StripeCard;
35
36
constructor(props, state) {
37
super(props, state);
38
this.state = {
39
submitting: false,
40
error: "",
41
loading: true,
42
};
43
}
44
45
public async componentDidMount(): Promise<void> {
46
this.mounted = true;
47
const stripe = await loadStripe();
48
if (!this.mounted) return;
49
this.setState({ loading: false });
50
const elements = stripe.elements();
51
this.card = elements.create("card");
52
if (this.card == null) throw Error("bug -- card cannot be null");
53
this.card.mount("#card-element");
54
}
55
56
public componentWillUnmount(): void {
57
this.mounted = false;
58
}
59
60
private async submit_payment_method(): Promise<void> {
61
this.setState({ error: "", submitting: true });
62
const actions = redux.getActions("billing");
63
const store = redux.getStore("billing");
64
if (store.get("customer") == null) {
65
actions.setState({ continue_first_purchase: true });
66
}
67
const stripe = await loadStripe();
68
let result: {
69
error?: { message: string };
70
token?: { id: string };
71
} = {};
72
try {
73
result = await stripe.createToken(this.card);
74
if (!this.mounted) return;
75
if (result.error != null) {
76
this.setState({ error: result.error.message });
77
return;
78
} else if (result.token != null) {
79
await actions.submit_payment_method(result.token.id);
80
if (!this.mounted) return;
81
}
82
} catch (err) {
83
if (this.mounted) {
84
result.error = { message: err.toString() }; // used in finally
85
this.setState({ error: err.toString() });
86
}
87
} finally {
88
if (this.mounted) {
89
this.setState({ submitting: false });
90
if (this.props.on_close != null && result.error == null)
91
this.props.on_close();
92
}
93
}
94
}
95
96
private render_cancel_button(): Rendered {
97
if (this.props.hide_cancel_button) return;
98
return (
99
<Button
100
onClick={() =>
101
this.props.on_close != null ? this.props.on_close() : undefined
102
}
103
>
104
Cancel
105
</Button>
106
);
107
}
108
109
private render_add_button(): Rendered {
110
return (
111
<Button
112
onClick={() => this.submit_payment_method()}
113
bsStyle="primary"
114
disabled={this.state.submitting}
115
>
116
{this.state.submitting ? <Loading /> : "Add Credit Card"}
117
</Button>
118
);
119
}
120
121
private render_payment_method_buttons(): Rendered {
122
return (
123
<div>
124
<Row>
125
<Col sm={4}>{powered_by_stripe()}</Col>
126
<Col sm={8}>
127
<ButtonToolbar className="pull-right" style={{ marginTop: "10px" }}>
128
{this.render_add_button()}
129
{this.render_cancel_button()}
130
</ButtonToolbar>
131
</Col>
132
</Row>
133
<div style={{ color: "#666", marginTop: "15px" }}>
134
(Wire transfers for non-recurring purchases above $100 are possible.
135
Please email <HelpEmailLink />
136
.)
137
</div>
138
</div>
139
);
140
}
141
142
private render_error(): Rendered {
143
if (this.state.error) {
144
return (
145
<ErrorDisplay
146
error={this.state.error}
147
onClose={() => this.setState({ error: "" })}
148
/>
149
);
150
}
151
}
152
153
private render_card(): Rendered {
154
return (
155
<div style={CARD_STYLE}>
156
{this.state.loading ? <Loading theme="medium" /> : undefined}
157
<div id="card-element">
158
{/* a Stripe Element will be inserted here. */}
159
</div>
160
</div>
161
);
162
}
163
164
public render(): Rendered {
165
return (
166
<Well style={{ boxShadow: "5px 5px 5px lightgray", zIndex: 2 }}>
167
{this.render_card()}
168
{this.render_error()}
169
{this.render_payment_method_buttons()}
170
</Well>
171
);
172
}
173
}
174
175