11
22 const PARSE_APP_ID = "YOUR_PARSE_ID";
33 const PARSE_JS_KEY = "YOUR_PARSE_JS_KEY";
44 Parse.initialize(PARSE_APP_ID, PARSE_JS_KEY);
55 Parse.serverURL = "https://parseapi.back4app.com/";
66
77 const STRIPE_PUBLISHABLE_KEY = "YOUR_STRIPE_PUBLISHABLE_KEY";
88 const stripe = Stripe(STRIPE_PUBLISHABLE_KEY);
99
1010
1111 let currentUser = null;
1212 const setCompleteCurrentUser = async () => {
1313
1414
1515 currentUser = await new Parse.Query(Parse.User)
1616 .equalTo("objectId", Parse.User.current().id)
1717 .first();
1818
1919 retrieveCurrentUserPaymentMethods();
2020 retrieveCurrentUserPayments();
2121 };
2222
2323 const retrieveCurrentUserPaymentMethods = async () => {
2424
2525 const PMQuery = new Parse.Query("PaymentMethod");
2626 PMQuery.equalTo("user", Parse.User.current());
2727 paymentMethods = await PMQuery.find();
2828 renderPaymentMethodOptions(paymentMethods);
2929 };
3030
3131 const retrieveCurrentUserPayments = async () => {
3232
3333 const paymentsQuery = new Parse.Query("Payment");
3434 paymentsQuery.equalTo("user", Parse.User.current());
3535 payments = await paymentsQuery.find();
3636 renderPayments(payments);
3737 };
3838
3939 const renderPaymentMethodOptions = async (paymentMethods) => {
4040 for (let paymentMethod of paymentMethods) {
4141 const optionId = `card-${paymentMethod.get("stripeId")}`;
4242 let optionElement = document.getElementById(optionId);
4343
4444
4545 if (!optionElement) {
4646 optionElement = document.createElement("option");
4747 optionElement.id = optionId;
4848 document
4949 .querySelector("select[name=payment-method]")
5050 .appendChild(optionElement);
5151 }
5252
5353 optionElement.value = paymentMethod.get("stripeId");
5454 optionElement.text = `${paymentMethod.get("card").brand} •••• ${
5555 paymentMethod.get("card").last4
5656 } | Expires ${paymentMethod.get("card").exp_month}/${
5757 paymentMethod.get("card").exp_year
5858 }`;
5959 }
6060 };
6161
6262 const renderPayments = (payments) => {
6363 for (let payment of payments) {
6464 let liElement = document.getElementById(`payment-${payment.id}`);
6565 if (!liElement) {
6666 liElement = document.createElement("li");
6767 liElement.id = `payment-${payment.id}`;
6868 }
6969
7070 const paymentData = payment.get("data");
7171 let content = "";
7272 if (
7373 paymentData.status === "new" ||
7474 paymentData.status === "requires_confirmation"
7575 ) {
7676 content = `Creating Payment for ${formatAmount(
7777 paymentData.amount,
7878 paymentData.currency
7979 )}`;
8080 } else if (paymentData.status === "succeeded") {
8181 const card = paymentData.charges.data[0].payment_method_details.card;
8282 content = `Payment for ${formatAmount(
8383 paymentData.amount,
8484 paymentData.currency
8585 )} on ${card.brand} card •••• ${card.last4} ${paymentData.status}!`;
8686 } else {
8787 content = `Payment for ${formatAmount(
8888 paymentData.amount,
8989 paymentData.currency
9090 )} ${paymentData.status}`;
9191 }
9292 liElement.innerText = content;
9393 document.querySelector("#payments-list").appendChild(liElement);
9494 }
9595 };
9696
9797
9898 if (Parse.User.current() !== null) {
9999 setCompleteCurrentUser();
100100
101101 document.getElementById("auth").style.display = "none";
102102 document.getElementById("content").style.display = "block";
103103 }
104104
105105
106106 document
107107 .querySelector("#signup-toggle")
108108 .addEventListener("click", async (_event) => {
109109 document.getElementById("auth-signin-form").style.display = "none";
110110 document.getElementById("auth-signup-form").style.display = "block";
111111 clearAuthFormFields();
112112 });
113113 document
114114 .querySelector("#signin-toggle")
115115 .addEventListener("click", async (_event) => {
116116 document.getElementById("auth-signup-form").style.display = "none";
117117 document.getElementById("auth-signin-form").style.display = "block";
118118 clearAuthFormFields();
119119 });
120120
121121
122122 const clearAuthFormFields = () => {
123123 document
124124 .querySelector("#auth")
125125 .querySelectorAll("input")
126126 .forEach((input) => (input.value = ""));
127127 };
128128
129129
130130 document
131131 .querySelector("#auth-signin-form")
132132 .addEventListener("submit", async (event) => {
133133 event.preventDefault();
134134 toggleAllButtonsEnabled(false);
135135 const form = new FormData(event.target);
136136 const email = form.get("email");
137137 const password = form.get("password");
138138
139139
140140 try {
141141 let user = await Parse.User.logIn(email, password);
142142 if (user !== null) {
143143 currentUser = user;
144144
145145 document.getElementById("auth").style.display = "none";
146146 document.getElementById("content").style.display = "block";
147147 clearAuthFormFields();
148148 }
149149 } catch (error) {
150150 alert(error);
151151 }
152152 toggleAllButtonsEnabled(true);
153153 });
154154
155155 document
156156 .querySelector("#auth-signup-form")
157157 .addEventListener("submit", async (event) => {
158158 event.preventDefault();
159159 toggleAllButtonsEnabled(false);
160160 const form = new FormData(event.target);
161161 const email = form.get("email");
162162 const password = form.get("password");
163163
164164
165165 try {
166166 let user = await Parse.User.signUp(email, password, { email: email });
167167
168168 user = await Parse.Cloud.run("createStripeCustomer", { userId: user.id });
169169 if (user !== null) {
170170 currentUser = user;
171171
172172 document.getElementById("auth").style.display = "none";
173173 document.getElementById("content").style.display = "block";
174174 clearAuthFormFields();
175175 }
176176 } catch (error) {
177177 alert(error);
178178 }
179179 toggleAllButtonsEnabled(true);
180180 });
181181
182182
183183 document.querySelector("#signout").addEventListener("click", async (_event) => {
184184 await Parse.User.logOut();
185185 currentUser = null;
186186
187187 document.getElementById("auth").style.display = "block";
188188 document.getElementById("content").style.display = "none";
189189 });
190190
191191
192192 const elements = stripe.elements();
193193 const cardElement = elements.create("card");
194194 cardElement.mount("#card-element");
195195
196196
197197 document
198198 .querySelector("#payment-method-form")
199199 .addEventListener("submit", async (event) => {
200200 event.preventDefault();
201201 toggleAllButtonsEnabled(false);
202202 if (!event.target.reportValidity()) {
203203 return;
204204 }
205205 const form = new FormData(event.target);
206206 const cardholderName = form.get("name");
207207
208208 const result = await stripe.confirmCardSetup(
209209 currentUser.get("setupSecret"),
210210 {
211211 payment_method: {
212212 card: cardElement,
213213 billing_details: {
214214 name: cardholderName,
215215 },
216216 },
217217 }
218218 );
219219
220220 if (result.error) {
221221 alert(result.error.message);
222222 toggleAllButtonsEnabled(true);
223223 return null;
224224 }
225225
226226 let setupIntent = result.setupIntent;
227227
228228
229229 let cloudCodeResult = await Parse.Cloud.run("addNewPaymentMethod", {
230230 userId: currentUser.id,
231231 paymentMethodId: setupIntent.payment_method,
232232 });
233233
234234 toggleAllButtonsEnabled(true);
235235 alert("Success on creating a new payment method!");
236236
237237
238238 retrieveCurrentUserPaymentMethods();
239239 });
240240
241241
242242 document
243243 .querySelector("#payment-form")
244244 .addEventListener("submit", async (event) => {
245245 event.preventDefault();
246246 toggleAllButtonsEnabled(false);
247247 const form = new FormData(event.target);
248248 const amount = Number(form.get("amount"));
249249 const currency = form.get("currency");
250250
251251 const paymentMethod = form.get("payment-method");
252252 const paymentData = {
253253 payment_method: paymentMethod,
254254 currency,
255255 amount: formatAmountForStripe(amount, currency),
256256 status: "new",
257257 };
258258
259259 let cloudCodeResult = await Parse.Cloud.run("createNewPayment", {
260260 userId: currentUser.id,
261261 paymentData: paymentData,
262262 });
263263
264264 toggleAllButtonsEnabled(true);
265265 alert("Success on creating a new payment!");
266266
267267 retrieveCurrentUserPayments();
268268 });
269269
270270
271271 const toggleAllButtonsEnabled = (enabledValue) => {
272272 document
273273 .querySelectorAll("button")
274274 .forEach((button) => (button.disabled = !enabledValue));
275275 };
276276
277277 const formatAmount = (amount, currency) => {
278278 amount = zeroDecimalCurrency(amount, currency)
279279 ? amount
280280 : (amount / 100).toFixed(2);
281281 return new Intl.NumberFormat("en-US", {
282282 style: "currency",
283283 currency,
284284 }).format(amount);
285285 };
286286
287287
288288 const formatAmountForStripe = (amount, currency) => {
289289 return zeroDecimalCurrency(amount, currency)
290290 ? amount
291291 : Math.round(amount * 100);
292292 };
293293
294294
295295
296296 const zeroDecimalCurrency = (amount, currency) => {
297297 let numberFormat = new Intl.NumberFormat(["en-US"], {
298298 style: "currency",
299299 currency: currency,
300300 currencyDisplay: "symbol",
301301 });
302302 const parts = numberFormat.formatToParts(amount);
303303 let zeroDecimalCurrency = true;
304304 for (let part of parts) {
305305 if (part.type === "decimal") {
306306 zeroDecimalCurrency = false;
307307 }
308308 }
309309 return zeroDecimalCurrency;
310310 };