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. Commercial Alternative to JupyterHub.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/admin/users/create-payment.tsx
Views: 823
1
import {
2
Alert,
3
Button,
4
Card,
5
Flex,
6
Input,
7
InputNumber,
8
Space,
9
Spin,
10
} from "antd";
11
import { createPaymentIntent } from "@cocalc/frontend/purchases/api";
12
import { Icon } from "@cocalc/frontend/components/icon";
13
import { useRef, useState } from "react";
14
import { currency } from "@cocalc/util/misc";
15
import ShowError from "@cocalc/frontend/components/error";
16
import type { LineItem } from "@cocalc/util/stripe/types";
17
import { LineItemsTable } from "@cocalc/frontend/purchases/line-items";
18
19
const DEFAULT_PAYMENT = 10;
20
21
interface Props {
22
account_id: string;
23
onClose?: () => void;
24
}
25
26
export default function CreatePayment({ account_id, onClose }: Props) {
27
const [paymentDescription, setPaymentDescription] = useState<string>(
28
"Manually entered payment initiated by CoCalc staff",
29
);
30
const [amount, setAmount] = useState<number | null>(DEFAULT_PAYMENT);
31
const [total, setTotal] = useState<number>(0);
32
const [description, setDescription] = useState<string>(
33
"Add credit to account",
34
);
35
const purposeRef = useRef<string>(`admin-${Date.now()}`);
36
const [loading, setLoading] = useState<boolean>(false);
37
const [error, setError] = useState<string>("");
38
const [done, setDone] = useState<boolean>(false);
39
const [lineItems, setLineItems] = useState<LineItem[]>([]);
40
41
const doIt = async () => {
42
if (typeof amount != "number") {
43
throw Error("amount must be a number");
44
}
45
try {
46
setError("");
47
setLoading(true);
48
await createPaymentIntent({
49
user_account_id: account_id,
50
lineItems,
51
description: paymentDescription,
52
purpose: purposeRef.current,
53
});
54
setDone(true);
55
} catch (err) {
56
console.warn("Creating payment failed", err);
57
setError(`${err}`);
58
} finally {
59
setLoading(false);
60
}
61
};
62
63
return (
64
<Card title={"Create Payment"}>
65
<Input
66
disabled={done || loading}
67
style={{ flex: 1, maxWidth: "700px", marginBottom: "15px" }}
68
placeholder="Payment Description..."
69
value={paymentDescription}
70
onChange={(e) => setPaymentDescription(e.target.value)}
71
/>
72
<LineItemsTable lineItems={lineItems} />
73
<Flex gap="middle" style={{ margin: "15px 0" }}>
74
<Input
75
disabled={done || loading}
76
style={{ flex: 1, maxWidth: "400px" }}
77
placeholder="Description..."
78
value={description}
79
onChange={(e) => setDescription(e.target.value)}
80
/>
81
<InputNumber
82
disabled={done || loading}
83
min={0}
84
max={10000}
85
addonBefore="$"
86
placeholder="Amount..."
87
style={{ maxWidth: "100px" }}
88
value={amount}
89
onChange={(value) =>
90
setAmount(typeof value == "string" ? null : value)
91
}
92
/>
93
<Button
94
disabled={!amount || !description || loading || done || !!error}
95
onClick={() => {
96
if (!amount) {
97
return;
98
}
99
setLineItems(lineItems.concat([{ amount, description }]));
100
setTotal(total + amount);
101
setAmount(DEFAULT_PAYMENT);
102
setDescription("");
103
}}
104
>
105
Add Line Item
106
</Button>
107
</Flex>
108
<Space style={{ marginBottom: "15px" }}>
109
{onClose != null && (
110
<Button onClick={onClose}>{done ? "Close" : "Cancel"}</Button>
111
)}{" "}
112
<Button
113
disabled={
114
!!error ||
115
done ||
116
loading ||
117
!paymentDescription ||
118
lineItems.length == 0 ||
119
total == 0
120
}
121
type="primary"
122
onClick={doIt}
123
>
124
{done ? (
125
<>Created Payment</>
126
) : (
127
<>
128
Create Payment{" "}
129
{loading && <Spin style={{ marginLeft: "15px" }} />}
130
</>
131
)}
132
</Button>
133
</Space>
134
<br />
135
<ShowError error={error} setError={setError} />
136
<br />
137
{done && (
138
<div>
139
<Alert
140
showIcon
141
style={{ margin: "15px auto 0 auto", maxWidth: "700px" }}
142
type="success"
143
message="Payment Successfully Created"
144
/>
145
</div>
146
)}
147
<div>
148
<Alert
149
style={{ margin: "15px auto", maxWidth: "700px" }}
150
type="info"
151
description={
152
<>
153
User will be charged {currency(total)} (+ tax). When the payment
154
is completed, a credit will be added to the user's account. If
155
they have an automatic payment method on file (e.g. a credit
156
card), then this may be instant. Click the "Payments" button above
157
to see the status of any payments.
158
</>
159
}
160
/>
161
</div>
162
</Card>
163
);
164
}
165
166
export function CreatePaymentButton(props: Props) {
167
const [show, setShow] = useState<boolean>(false);
168
return (
169
<div>
170
<Button onClick={() => setShow(!show)} type={show ? "dashed" : undefined}>
171
<Icon name="credit-card" /> Create Payment
172
</Button>
173
{show && (
174
<div style={{ marginTop: "8px" }}>
175
<CreatePayment {...props} onClose={() => setShow(false)} />
176
</div>
177
)}
178
</div>
179
);
180
}
181
182