From b57eb8c122ffbcc2f061daeaec7d87b47f1808b6 Mon Sep 17 00:00:00 2001 From: amihaiemil Date: Sat, 19 Dec 2020 18:02:57 +0200 Subject: [PATCH 1/4] #280 Stripe BillingInfo front-end form and validation (fe + be) --- .../com/selfxdsd/selfweb/api/WalletsApi.java | 7 +- .../selfweb/api/input/StripeWalletInput.java | 126 ++++ .../api/input/validators/NoSpecialChars.java | 58 ++ .../validators/NoSpecialCharsValidator.java | 61 ++ .../api/input/validators/ValidCountry.java | 52 ++ .../validators/ValidCountryValidator.java | 62 ++ src/main/resources/public/js/wallets.js | 355 ++++++++++- src/main/resources/templates/head.html | 9 + src/main/resources/templates/project.html | 585 ++++++++++-------- 9 files changed, 1041 insertions(+), 274 deletions(-) create mode 100644 src/main/java/com/selfxdsd/selfweb/api/input/StripeWalletInput.java create mode 100644 src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialChars.java create mode 100644 src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialCharsValidator.java create mode 100644 src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountry.java create mode 100644 src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountryValidator.java diff --git a/src/main/java/com/selfxdsd/selfweb/api/WalletsApi.java b/src/main/java/com/selfxdsd/selfweb/api/WalletsApi.java index 868e5c2f..87e326b2 100644 --- a/src/main/java/com/selfxdsd/selfweb/api/WalletsApi.java +++ b/src/main/java/com/selfxdsd/selfweb/api/WalletsApi.java @@ -24,6 +24,7 @@ import com.selfxdsd.api.*; import com.selfxdsd.api.exceptions.WalletAlreadyExistsException; +import com.selfxdsd.selfweb.api.input.StripeWalletInput; import com.selfxdsd.selfweb.api.output.JsonWallet; import com.selfxdsd.selfweb.api.output.JsonWallets; import org.slf4j.Logger; @@ -34,6 +35,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; import javax.validation.constraints.Positive; import java.math.BigDecimal; import java.math.RoundingMode; @@ -99,6 +101,7 @@ public ResponseEntity wallets( * Create a Stripe Wallet for one of the authenticated user's projects. * @param owner Owner of the project (login of user or org name). * @param name Repo name. + * @param billingInfo Billing information. * @return Stripe Wallet as JSON string. */ @PostMapping( @@ -107,8 +110,8 @@ public ResponseEntity wallets( ) public ResponseEntity createStripeWallet( @PathVariable final String owner, - @PathVariable final String name - ) { + @PathVariable final String name, + @Valid final StripeWalletInput billingInfo) { ResponseEntity response; final Project found = this.user.projects().getProjectById( owner + "/" + name, user.provider().name() diff --git a/src/main/java/com/selfxdsd/selfweb/api/input/StripeWalletInput.java b/src/main/java/com/selfxdsd/selfweb/api/input/StripeWalletInput.java new file mode 100644 index 00000000..0efd5d72 --- /dev/null +++ b/src/main/java/com/selfxdsd/selfweb/api/input/StripeWalletInput.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2020, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.selfweb.api.input; + +import com.selfxdsd.selfweb.api.input.validators.NoSpecialChars; +import com.selfxdsd.selfweb.api.input.validators.ValidCountry; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; + +/** + * Input for a new Stripe wallet (project billing info). + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 0.0.1 + * @checkstyle HiddenField (200 lines) + * @checkstyle JavadocVariable (200 lines) + * @checkstyle JavadocMethod (200 lines) + */ +public final class StripeWalletInput { + + @NotBlank(message = "Legal name is mandatory!") + @NoSpecialChars + private String legalName; + + @NotBlank(message = "Country is mandatory!") + @NoSpecialChars + @ValidCountry + private String country; + + @NotBlank(message = "Address is mandatory!") + @NoSpecialChars + private String address; + + @NotBlank(message = "City is mandatory!") + @NoSpecialChars + private String city; + + @NotBlank(message = "Zipcode is mandatory!") + @NoSpecialChars + private String zipcode; + + @NotBlank(message = "E-Mail is mandatory!") + @Email(message = "Please provide a valid e-mail address.") + @NoSpecialChars + private String email; + + @NoSpecialChars + private String other; + + public String getLegalName() { + return this.legalName; + } + + public void setLegalName(final String legalName) { + this.legalName = legalName; + } + + public String getCountry() { + return this.country; + } + + public void setCountry(final String country) { + this.country = country; + } + + public String getAddress() { + return this.address; + } + + public void setAddress(final String address) { + this.address = address; + } + + public String getCity() { + return this.city; + } + + public void setCity(final String city) { + this.city = city; + } + + public String getZipcode() { + return this.zipcode; + } + + public void setZipcode(final String zipcode) { + this.zipcode = zipcode; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(final String email) { + this.email = email; + } + + public String getOther() { + return this.other; + } + + public void setOther(final String other) { + this.other = other; + } +} diff --git a/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialChars.java b/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialChars.java new file mode 100644 index 00000000..263d8d6c --- /dev/null +++ b/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialChars.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2020, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.selfweb.api.input.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; + +/** + * Annotation for String inputs. Forbids the mentioned special chars. + * @author Mihai Andronache (amihaiemil@gmail.com + * @version $Id$ + * @since 0.0.1 + * @checkstyle JavadocMethod (200 lines) + */ +@Documented +@Target({ FIELD }) +@Constraint(validatedBy = NoSpecialCharsValidator.class) +@Retention(RetentionPolicy.RUNTIME) +public @interface NoSpecialChars { + + String message() default "No special chars allowed."; + + /** + * The forbidden chars. + * By default, the forbidden chars are: "[](){};<>/\`!%^*$? + * @return String containing the forbidden chars. + */ + String forbidden() default "\"[](){};<>/\\`!%^*$?"; + + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialCharsValidator.java b/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialCharsValidator.java new file mode 100644 index 00000000..7b9bd041 --- /dev/null +++ b/src/main/java/com/selfxdsd/selfweb/api/input/validators/NoSpecialCharsValidator.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2020, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.selfweb.api.input.validators; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * Validator for the annotation {@link NoSpecialChars}. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 0.0.1 + */ +public final class NoSpecialCharsValidator + implements ConstraintValidator { + + /** + * The forbidden chars. + */ + private String forbidden; + + @Override + public void initialize(final NoSpecialChars constraintAnnotation) { + this.forbidden = constraintAnnotation.forbidden(); + } + + @Override + public boolean isValid( + final String value, + final ConstraintValidatorContext context + ) { + boolean isValid = true; + for (int i = 0; i < this.forbidden.length(); i++) { + if(value.contains(String.valueOf(this.forbidden.charAt(i)))) { + isValid = false; + break; + } + } + return isValid; + } +} diff --git a/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountry.java b/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountry.java new file mode 100644 index 00000000..8666ab30 --- /dev/null +++ b/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountry.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2020, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.selfweb.api.input.validators; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; + +/** + * Annotation for String inputs. Validates the country code. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 0.0.1 + * @checkstyle JavadocMethod (200 lines) + */ +@Documented +@Target({ FIELD }) +@Constraint(validatedBy = ValidCountryValidator.class) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidCountry { + + String message() default "Invalid country."; + + Class[] groups() default {}; + Class[] payload() default {}; + +} diff --git a/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountryValidator.java b/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountryValidator.java new file mode 100644 index 00000000..9ae41dd8 --- /dev/null +++ b/src/main/java/com/selfxdsd/selfweb/api/input/validators/ValidCountryValidator.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2020, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.selfweb.api.input.validators; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; +import java.util.Locale; + +/** + * Validator for {@link ValidCountry}. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 0.0.1 + * @checkstyle ReturnCount (200 lines) + */ +public final class ValidCountryValidator + implements ConstraintValidator { + + /** + * Forbidden countries (Stripe regulations): Syria, Iran, N. Korea, Cuba. + */ + private List forbiddenCountries = List.of( + "SY", "IR", "KP", "CU" + ); + + @Override + public boolean isValid( + final String value, + final ConstraintValidatorContext context + ) { + if(this.forbiddenCountries.contains(value)) { + return false; + } + for(final String country : Locale.getISOCountries()) { + if(value.equalsIgnoreCase(country)) { + return true; + } + } + return false; + } +} diff --git a/src/main/resources/public/js/wallets.js b/src/main/resources/public/js/wallets.js index 50a09201..29b03302 100644 --- a/src/main/resources/public/js/wallets.js +++ b/src/main/resources/public/js/wallets.js @@ -372,4 +372,357 @@ function walletAsPieChart(wallet) { } else { $("#walletCardTitle").html("Stripe Wallet"); } -} \ No newline at end of file +} + +$(document).ready( + function() { + + $("#stripeCustomerForm").submit( + function(e) { + e.preventDefault(); + + var valid = true; + $.each($("#stripeCustomerForm .required"), function(index, element) { + if($(element).val().trim() == '') { + $(element).addClass("is-invalid"); + valid = false; + } else { + $(element).removeClass("is-invalid"); + } + }); + $.each($("#stripeCustomerForm input"), function(index, element) { + if(hasSpecialChars($(element).val())) { + $("." + $(element).attr("name") + "-error").html( + "The only special characters allowed are ,.-_&@#'" + ); + $(element).addClass("is-invalid"); + valid = false; + } + }); + $.each($("#stripeCustomerForm textarea"), function(index, element) { + if(hasSpecialChars($(element).val())) { + $("." + $(element).attr("name") + "-error").html( + "The only special characters allowed are ,.-_&@#'" + ); + $(element).addClass("is-invalid"); + valid = false; + } else { + $(element).removeClass("is-invalid"); + } + }); + + if(valid) { + $("#stripeCustomerButton").addClass("disabled"); + $("#loadingStripeCustomerForm").show(); + var owner = $("#owner").text(); + var name = $("#name").text(); + var form = $(this); + $.ajax( + { + type: "POST", + url: "/api/projects/" + owner + "/" + name + "/wallets/stripe", + data: form.serialize(), + success: function (wallet) { + $("#noRealWallet").hide(); + + $("#stripeCash").html(formatEuro(wallet.cash)); + $("#stripeDebt").html(formatEuro(wallet.debt)); + $("#stripeAvailable").html(formatEuro(wallet.available)); + cashLimitColor($("#stripeCash"), updatedWallet); + if (wallet.active) { + $("#stripeWalletBadge").addClass("badge-success") + $("#stripeWalletBadge").html("active") + $("#activateStripeWallet").hide(); + } else { + $("#activateStripeWallet").show(); + } + + $("#stripeCustomerButton").removeClass("disabled"); + $("#loadingStripeCustomerForm").hide(); + + $("#realWalletOverview").show(); + + installUpdateCashLimitPopover( + $("#stripeUpdateCashLimitAction"), + $("#stripeCash"), + wallet.type, + (updatedWallet) => { + $("#stripeCash").html(formatEuro(updatedWallet.cash)); + $("#stripeDebt").html(formatEuro(updatedWallet.debt)); + $("#stripeAvailable").html(formatEuro(updatedWallet.available)); + cashLimitColor($("#stripeCash"), updatedWallet); + } + ); + }, + error: function (jqXHR, error, errorThrown) { + $("#stripeCustomerButton").removeClass("disabled"); + $("#loadingStripeCustomerForm").hide(); + if (jqXHR.status && jqXHR.status == 400) { + console.error("Bad Request: " + jqXHR.responseText); + $("#setupCustomerError").show(); + } else { + console.log("Server error status: " + jqXHR.status); + console.log("Server error: " + jqXHR.responseText); + alert( + "Something went wrong (" + jqXHR.status + ")." + + "Please, try again later." + ); + } + } + } + ); + } + } + ); + + $("#addStripePaymentMethodForm").submit( + function(e) { + e.preventDefault(); + $("#addStripePaymentMethodButton").addClass("disabled"); + $("#loadingStripePaymentForm").show(); + var owner =$("#owner").text(); + var name =$("#name").text(); + var form = $(this); + $.ajax( + { + type: "POST", + url: "/api/projects/" + owner + "/" + name + + "/wallets/stripe/paymentMethods/setup", + data: form.serialize(), + success: function (setupIntent) { + $("#addStripePaymentMethodButton").removeClass("disabled"); + $("#loadingStripePaymentForm").hide(); + var stripe = Stripe('pk_test_51HFJACFQ8qkNTW7CcB9UoOCrCPBcipFuU0UnsuuaOT4zCxB3217kBgWCIN0cRnJ7ETJazRxMILYHyV0fUBqd5Kca00ClMmHcO0'); + + var elements = stripe.elements(); + var cardElement = elements.create('card'); + cardElement.mount('#payment-method-element'); + $("#addStripePaymentMethodButton").hide(); + $("#payment-method-element-card").show(); + + + $("#addNewCardButton").on( + "click", + function(event) { + event.preventDefault(); + $("#addNewCardButton").addClass("disabled"); + $("#cancelNewCardButton").addClass("disabled"); + $("#loadingAddNewCard").show(); + + stripe.confirmCardSetup( + setupIntent.clientSecret, + { + payment_method: { + card: cardElement, + billing_details: {}, + }, + } + ).then(function(result) { + console.log("STRIPE RESULT: " + JSON.stringify(result)); + if(result.error) { + var message; + if(result.error.message) { + message = result.error.message; + } else { + message = "Something went wrong. Please refresh the page and try again."; + } + $("#addNewCardErrorMessage").html(message); + $("#addNewCardError").show(); + } else { + $("#addNewCardErrorMessage").html(""); + $("#addNewCardError").hide(); + + console.log('Send paymentmethodid to server...'); + var paymentMethodInfo = { + paymentMethodId: result.setupIntent.payment_method + } + $.ajax( + { + type: "POST", + contentType: "application/json", + url: "/api/projects/" + owner + "/" + name + + "/wallets/stripe/paymentMethods", + data: JSON.stringify(paymentMethodInfo), + success: function (paymentMethod) { + var active; + if(paymentMethod.self.active) { + activePaymentMethodFound = true; + active = ""; + } else { + active = ""; + } var issuer = paymentMethod.stripe.card.brand; + issuer = issuer.substr(0,1).toUpperCase() + issuer.substr(1); + $('#realPaymentMethodsTable > tbody').append( + "" + + "" + + issuer + + "" + + "" + + "****** " + paymentMethod.stripe.card.last4 + + "" + + "" + + paymentMethod.stripe.card.exp_month + "/" + paymentMethod.stripe.card.exp_year + + "" + + "" + + active + + "" + + "" + ) + $('.pmToggle').bootstrapToggle({ + on: 'Active', + off: 'Inactive', + width: '45%' + }); + $($('input.pmToggle')[$('input.pmToggle').length - 1]).on( + 'change', + function() { + if($(this).prop('checked')) { + $('input.pmToggle').not(this).prop('checked', false); + let parent = $('input.pmToggle').not(this).parent(); + parent.removeClass('btn-primary'); + parent.addClass('btn-default'); + parent.addClass('off'); + activatePaymentMethod(owner, name, paymentMethod); + $("#activateStripeWalletButton").removeClass("disabled"); + } else { //we don't allow manual inactivation of a PaymentMethod + $(this).bootstrapToggle('on'); + } + } + ); + $("#noRealPaymentMethods").hide(); + $("#realPaymentMethods").show(); + }, + error: function(jqXHR, error, errorThrown) { + $("#addStripePaymentMethodButton").removeClass("disabled"); + $("#loadingStripePaymentForm").hide(); + if(jqXHR.status && jqXHR.status == 400){ + console.error("Bad Request: " + jqXHR.responseText); + $("#stripePaymentMethodFormError").show(); + } else { + console.log("Server error status: " + jqXHR.status); + console.log("Server error: " + jqXHR.responseText); + alert( + "Something went wrong (" + jqXHR.status + ")." + + "Please, try again later." + ); + } + } + } + ); + + + $("#cancelNewCardButton").trigger("click"); + } + $("#addNewCardButton").removeClass("disabled"); + $("#cancelNewCardButton").removeClass("disabled"); + $("#loadingAddNewCard").hide(); + }); + } + ) + + }, + error: function(jqXHR, error, errorThrown) { + $("#addStripePaymentMethodButton").removeClass("disabled"); + $("#loadingStripePaymentForm").hide(); + if(jqXHR.status && jqXHR.status == 400){ + console.error("Bad Request: " + jqXHR.responseText); + $("#stripePaymentMethodFormError").show(); + } else { + console.log("Server error status: " + jqXHR.status); + console.log("Server error: " + jqXHR.responseText); + alert( + "Something went wrong (" + jqXHR.status + ")." + + "Please, try again later." + ); + } + } + } + ); + } + ); + + $("#cancelNewCardButton").on( + "click", + function(event) { + event.preventDefault(); + $("#payment-method-element").html(''); + $("#addNewCardErrorMessage").html(""); + $("#addNewCardError").hide(); + $("#addStripePaymentMethodButton").show(); + $("#payment-method-element-card").hide(); + } + ) + $("#activateFakeWalletButton").on( + "click", + function(event) { + event.preventDefault(); + activateWallet( + $("#owner").text(), + $("#name").text(), + "fake" + ) + } + ) + $("#activateStripeWalletButton").on( + "click", + function(event) { + if(!$(this).hasClass("disabled")) { + event.preventDefault(); + activateWallet( + $("#owner").text(), + $("#name").text(), + "stripe" + ) + } + } + ) + + // $("#addContractForm").submit( + // function(e) { + // e.preventDefault(); + // var valid = true; + // $.each($("#addContractForm .required"), function(index, element) { + // if($(element).val() == '') { + // $(element).addClass("is-invalid"); + // valid = valid && false; + // } else { + // $(element).removeClass("is-invalid"); + // valid = valid && true; + // } + // }); + // if(valid) { + // var formData = $(this).serialize(); + // //check if username exists before submit + // usersService.exists( + // $("#username").val(), + // "github", + // function(){ + // $("#addContractLoading").show(); + // clearFormErrors(); + // disableForm(true); + // } + // ).then( + // function(){ + // return contractsService.add(project, formData) + // } + // ).then( + // function(contract){ + // $("#addContractForm input").val(''); + // $('#addContractForm option:first').prop('selected',true); + // //we check the current page (0 based) displayed in table. + // //if is last page, we're adding the contract to table. + // //since it's the latest contract created. + // loadContracts(); + // } + // ).catch(handleError) + // .finally( + // function(){ + // disableForm(false); + // $("#addContractLoading").hide(); + // }); + // return false; + // } + // } + // ); + } +) \ No newline at end of file diff --git a/src/main/resources/templates/head.html b/src/main/resources/templates/head.html index f4fd28e6..10feed6b 100644 --- a/src/main/resources/templates/head.html +++ b/src/main/resources/templates/head.html @@ -85,6 +85,15 @@ function formatEuro(amount) { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount); } + function hasSpecialChars(text) { + var chars="\"[](){};<>/\\`!%^*$?" + for (var i = 0; i < chars.length; i++) { + if(text.includes(chars.charAt(i))) { + return true; + }; + } + return false; + } diff --git a/src/main/resources/templates/project.html b/src/main/resources/templates/project.html index 189ef032..c0c604be 100644 --- a/src/main/resources/templates/project.html +++ b/src/main/resources/templates/project.html @@ -560,10 +560,320 @@