From f8240216de7b7fdcf080e9408bb3dd2bb5894b6e Mon Sep 17 00:00:00 2001 From: Sylvain Jermini Date: Thu, 7 Jul 2016 19:57:20 +0200 Subject: [PATCH] implement paypal payment option #77 --- build.gradle | 1 + .../controller/ReservationController.java | 53 ++++- .../alfio/controller/form/PaymentForm.java | 27 ++- .../java/alfio/manager/PaymentManager.java | 19 ++ .../java/alfio/manager/PaypalManager.java | 223 ++++++++++++++++++ .../manager/TicketReservationManager.java | 15 +- .../manager/system/ConfigurationManager.java | 14 +- .../alfio/model/system/ConfigurationKeys.java | 8 +- .../alfio/model/transaction/PaymentProxy.java | 3 +- .../TicketReservationRepository.java | 3 + src/main/java/alfio/util/ErrorsCode.java | 4 + .../alfio/db/HSQLDB/V99__TEST_DATA.sql | 7 +- .../resources/alfio/i18n/public.properties | 7 + .../resources/alfio/i18n/public_de.properties | 7 + .../resources/alfio/i18n/public_it.properties | 11 +- .../resources/alfio/i18n/public_nl.properties | 7 + .../WEB-INF/templates/event/payment/paypal.ms | 1 + .../templates/event/reservation-page.ms | 45 +++- .../admin/partials/event/detail.html | 2 + .../js/admin/ng-app/admin-application.js | 3 +- .../ReservationFlowIntegrationTest.java | 2 +- .../alfio/manager/PaymentManagerTest.java | 6 +- ...cketReservationManagerIntegrationTest.java | 4 +- .../manager/TicketReservationManagerTest.java | 8 +- .../system/DataMigratorIntegrationTest.java | 2 +- 25 files changed, 442 insertions(+), 40 deletions(-) create mode 100644 src/main/java/alfio/manager/PaypalManager.java create mode 100644 src/main/webapp/WEB-INF/templates/event/payment/paypal.ms diff --git a/build.gradle b/build.gradle index 9291735315..5db61dbce5 100644 --- a/build.gradle +++ b/build.gradle @@ -155,6 +155,7 @@ dependencies { compile "org.apache.logging.log4j:log4j-jcl:$log4jVersion" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion" compile "com.stripe:stripe-java:$stripeVersion" + compile 'com.paypal.sdk:rest-api-sdk:1.8.0' compile "com.google.maps:google-maps-services:0.1.7" compile "org.apache.commons:commons-lang3:3.4" compile "com.squareup.okhttp:okhttp:2.4.0" diff --git a/src/main/java/alfio/controller/ReservationController.java b/src/main/java/alfio/controller/ReservationController.java index 68c5ca3a8e..74713299ac 100644 --- a/src/main/java/alfio/controller/ReservationController.java +++ b/src/main/java/alfio/controller/ReservationController.java @@ -21,10 +21,7 @@ import alfio.controller.form.UpdateTicketOwnerForm; import alfio.controller.support.SessionUtil; import alfio.controller.support.TicketDecorator; -import alfio.manager.EventManager; -import alfio.manager.NotificationManager; -import alfio.manager.StripeManager; -import alfio.manager.TicketReservationManager; +import alfio.manager.*; import alfio.manager.support.OrderSummary; import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; @@ -40,6 +37,7 @@ import alfio.util.TemplateManager; import alfio.util.TemplateManager.TemplateOutput; import alfio.util.ValidationResult; +import com.paypal.base.rest.PayPalRESTException; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; import org.springframework.beans.factory.annotation.Autowired; @@ -74,6 +72,7 @@ public class ReservationController { private final OrganizationRepository organizationRepository; private final StripeManager stripeManager; + private final PaypalManager paypalManager; private final TemplateManager templateManager; private final MessageSource messageSource; private final ConfigurationManager configurationManager; @@ -92,7 +91,8 @@ public ReservationController(EventRepository eventRepository, ConfigurationManager configurationManager, NotificationManager notificationManager, TicketHelper ticketHelper, - TicketFieldRepository ticketFieldRepository) { + TicketFieldRepository ticketFieldRepository, + PaypalManager paypalManager) { this.eventRepository = eventRepository; this.eventManager = eventManager; this.ticketReservationManager = ticketReservationManager; @@ -104,11 +104,21 @@ public ReservationController(EventRepository eventRepository, this.notificationManager = notificationManager; this.ticketHelper = ticketHelper; this.ticketFieldRepository = ticketFieldRepository; + this.paypalManager = paypalManager; } @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/book", method = RequestMethod.GET) public String showPaymentPage(@PathVariable("eventName") String eventName, @PathVariable("reservationId") String reservationId, + //paypal related parameters + @RequestParam(value = "paymentId", required = false) String paypalPaymentId, + @RequestParam(value = "PayerID", required = false) String paypalPayerID, + @RequestParam(value = "paypal-success", required = false) Boolean isPaypalSuccess, + @RequestParam(value = "paypal-error", required = false) Boolean isPaypalError, + @RequestParam(value = "fullName", required = false) String fullName, + @RequestParam(value = "email", required = false) String email, + @RequestParam(value = "billingAddress", required = false) String billingAddress, + @RequestParam(value = "hmac", required = false) String hmac, Model model, Locale locale) { @@ -116,10 +126,22 @@ public String showPaymentPage(@PathVariable("eventName") String eventName, .map(event -> ticketReservationManager.findById(reservationId) .map(reservation -> { - if(reservation.getStatus() != TicketReservationStatus.PENDING) { + if (reservation.getStatus() != TicketReservationStatus.PENDING) { return redirectReservation(Optional.of(reservation), eventName, reservationId); } + if (Boolean.TRUE.equals(isPaypalSuccess) && paypalPayerID != null && paypalPaymentId != null) { + model.addAttribute("paypalPaymentId", paypalPaymentId) + .addAttribute("paypalPayerID", paypalPayerID) + .addAttribute("paypalCheckoutConfirmation", true) + .addAttribute("fullName", fullName) + .addAttribute("email", email) + .addAttribute("billingAddress", billingAddress) + .addAttribute("hmac", hmac); + } else { + model.addAttribute("paypalCheckoutConfirmation", false); + } + OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale); model.addAttribute("orderSummary", orderSummary); model.addAttribute("reservationId", reservationId); @@ -335,13 +357,28 @@ public String handleReservation(@PathVariable("eventName") String eventName, bindingResult.reject(ErrorsCode.STEP_2_ORDER_EXPIRED); } final TicketReservationManager.TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId); - paymentForm.validate(bindingResult, reservationCost, event.getAllowedPaymentProxies()); + paymentForm.validate(bindingResult, reservationCost, event); if (bindingResult.hasErrors()) { SessionUtil.addToFlash(bindingResult, redirectAttributes); return redirectReservation(ticketReservation, eventName, reservationId); } + + //handle paypal redirect! + if(paymentForm.getPaymentMethod() == PaymentProxy.PAYPAL && !paymentForm.hasPaypalTokens()) { + OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale); + try { + String checkoutUrl = paypalManager.createCheckoutRequest(event, reservationId, orderSummary, paymentForm.getFullName(), paymentForm.getEmail(), paymentForm.getBillingAddress(), locale); + return "redirect:" + checkoutUrl; + } catch (Exception e) { + bindingResult.reject(ErrorsCode.STEP_2_PAYMENT_REQUEST_CREATION); + return redirectReservation(ticketReservation, eventName, reservationId); + } + } + // + + boolean directTicketAssignment = Optional.ofNullable(paymentForm.getExpressCheckoutRequested()).map(b -> Boolean.logicalAnd(b, isExpressCheckoutEnabled(event, ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale)))).orElse(false); - final PaymentResult status = ticketReservationManager.confirm(paymentForm.getStripeToken(), event, reservationId, paymentForm.getEmail(), + final PaymentResult status = ticketReservationManager.confirm(paymentForm.getToken(), paymentForm.getPaypalPayerID(), event, reservationId, paymentForm.getEmail(), paymentForm.getFullName(), locale, paymentForm.getBillingAddress(), reservationCost, SessionUtil.retrieveSpecialPriceSessionId(request), Optional.ofNullable(paymentForm.getPaymentMethod()), directTicketAssignment); diff --git a/src/main/java/alfio/controller/form/PaymentForm.java b/src/main/java/alfio/controller/form/PaymentForm.java index 06398554f7..b69e2dc968 100644 --- a/src/main/java/alfio/controller/form/PaymentForm.java +++ b/src/main/java/alfio/controller/form/PaymentForm.java @@ -16,7 +16,9 @@ */ package alfio.controller.form; +import alfio.manager.PaypalManager; import alfio.manager.TicketReservationManager; +import alfio.model.Event; import alfio.model.transaction.PaymentProxy; import alfio.util.ErrorsCode; import lombok.Data; @@ -33,9 +35,12 @@ @Data public class PaymentForm { private String stripeToken; + private String paypalPaymentId; + private String paypalPayerID; private String email; private String fullName; private String billingAddress; + private String hmac; private Boolean cancelReservation; private Boolean termAndConditionsAccepted; private PaymentProxy paymentMethod; @@ -48,7 +53,23 @@ private static void rejectIfOverLength(BindingResult bindingResult, String field } } - public void validate(BindingResult bindingResult, TicketReservationManager.TotalPrice reservationCost, List allowedPaymentMethods) { + public String getToken() { + if(paymentMethod == PaymentProxy.STRIPE) { + return stripeToken; + } else if(paymentMethod == PaymentProxy.PAYPAL) { + return paypalPaymentId; + } else { + return null; + } + } + + public boolean hasPaypalTokens() { + return StringUtils.isNotBlank(paypalPayerID) && StringUtils.isNotBlank(paypalPaymentId); + } + + public void validate(BindingResult bindingResult, TicketReservationManager.TotalPrice reservationCost, Event event) { + + List allowedPaymentMethods = event.getAllowedPaymentProxies(); Optional paymentProxyOptional = Optional.ofNullable(paymentMethod); PaymentProxy paymentProxy = paymentProxyOptional.filter(allowedPaymentMethods::contains).orElse(PaymentProxy.STRIPE); @@ -80,6 +101,10 @@ public void validate(BindingResult bindingResult, TicketReservationManager.Total if (email != null && !email.contains("@") && !bindingResult.hasFieldErrors("email")) { bindingResult.rejectValue("email", ErrorsCode.STEP_2_INVALID_EMAIL); } + + if (hasPaypalTokens() && !PaypalManager.isValidHMAC(fullName, email, billingAddress, hmac, event)) { + bindingResult.reject(ErrorsCode.STEP_2_INVALID_HMAC); + } } public Boolean shouldCancelReservation() { diff --git a/src/main/java/alfio/manager/PaymentManager.java b/src/main/java/alfio/manager/PaymentManager.java index d3602dcaa3..8fbcc7b28a 100644 --- a/src/main/java/alfio/manager/PaymentManager.java +++ b/src/main/java/alfio/manager/PaymentManager.java @@ -20,6 +20,8 @@ import alfio.model.Event; import alfio.model.transaction.PaymentProxy; import alfio.repository.TransactionRepository; +import alfio.util.ErrorsCode; +import com.paypal.base.rest.PayPalRESTException; import com.stripe.exception.StripeException; import com.stripe.model.Charge; import lombok.extern.log4j.Log4j2; @@ -34,12 +36,15 @@ public class PaymentManager { private final StripeManager stripeManager; + private final PaypalManager paypalManager; private final TransactionRepository transactionRepository; @Autowired public PaymentManager(StripeManager stripeManager, + PaypalManager paypalManager, TransactionRepository transactionRepository) { this.stripeManager = stripeManager; + this.paypalManager = paypalManager; this.transactionRepository = transactionRepository; } @@ -87,4 +92,18 @@ public PaymentResult processOfflinePayment(String reservationId, int price, Even return PaymentResult.successful(transactionId); } + public PaymentResult processPaypalPayment(String reservationId, String token, String payerId, int price, Event event) { + try { + String transactionId = paypalManager.commitPayment(reservationId, token, payerId, event); + transactionRepository.insert(transactionId, reservationId, + ZonedDateTime.now(), price, event.getCurrency(), "Paypal confirmation", PaymentProxy.PAYPAL.name()); + return PaymentResult.successful(transactionId); + } catch (Exception e) { + log.warn("errow while processing paypal payment: " + e.getMessage(), e); + if(e instanceof PayPalRESTException) { + return PaymentResult.unsuccessful(ErrorsCode.STEP_2_PAYPAL_UNEXPECTED); + } + throw new IllegalStateException(e); + } + } } diff --git a/src/main/java/alfio/manager/PaypalManager.java b/src/main/java/alfio/manager/PaypalManager.java new file mode 100644 index 0000000000..c4d5ef7792 --- /dev/null +++ b/src/main/java/alfio/manager/PaypalManager.java @@ -0,0 +1,223 @@ +/** + * This file is part of alf.io. + * + * alf.io is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * alf.io is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with alf.io. If not, see . + */ +package alfio.manager; + +import alfio.manager.support.OrderSummary; +import alfio.manager.support.SummaryRow; +import alfio.manager.system.ConfigurationManager; +import alfio.model.Event; +import alfio.model.TicketReservation; +import alfio.model.system.Configuration; +import alfio.model.system.ConfigurationKeys; +import alfio.repository.TicketReservationRepository; +import com.paypal.api.payments.*; +import com.paypal.base.rest.APIContext; + +import com.paypal.base.rest.PayPalRESTException; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.codec.digest.HmacUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +@Component +@Log4j2 +public class PaypalManager { + + private final ConfigurationManager configurationManager; + private final MessageSource messageSource; + private final ConcurrentHashMap cachedWebProfiles = new ConcurrentHashMap<>(); + private final TicketReservationRepository ticketReservationRepository; + + @Autowired + public PaypalManager(ConfigurationManager configurationManager, TicketReservationRepository ticketReservationRepository, MessageSource messageSource) { + this.configurationManager = configurationManager; + this.messageSource = messageSource; + this.ticketReservationRepository = ticketReservationRepository; + } + + private APIContext getApiContext(Event event) { + int orgId = event.getOrganizationId(); + boolean isLive = configurationManager.getBooleanConfigValue(Configuration.from(orgId, ConfigurationKeys.PAYPAL_LIVE_MODE), false); + String clientId = configurationManager.getRequiredValue(Configuration.from(orgId, ConfigurationKeys.PAYPAL_CLIENT_ID)); + String clientSecret = configurationManager.getRequiredValue(Configuration.from(orgId, ConfigurationKeys.PAYPAL_CLIENT_SECRET)); + return new APIContext(clientId, clientSecret, isLive ? "live" : "sandbox"); + } + + private static String toWebProfileName(Event event, Locale locale) { + return "ALFIO-" + event.getId() + "-" + event.getShortName() + "-" + locale.toString(); + } + + private Optional getWebProfile(Event event, Locale locale) { + try { + String webProfileName = toWebProfileName(event, locale); + return WebProfile.getList(getApiContext(event)).stream().filter(webProfile -> webProfileName.equals(webProfile.getName())).findFirst(); + } catch(PayPalRESTException e) { + return Optional.empty(); + } + } + + private Optional getOrCreateWebProfile(Event event, Locale locale) { + String webProfileName = toWebProfileName(event, locale); + + if(!cachedWebProfiles.containsKey(webProfileName)) { + getWebProfile(event, locale).ifPresent(p -> { + cachedWebProfiles.put(webProfileName, p.getId()); + }); + } + + if(!cachedWebProfiles.containsKey(webProfileName)) { + WebProfile webProfile = new WebProfile(webProfileName); + webProfile.setInputFields(new InputFields().setNoShipping(1).setAddressOverride(0).setAllowNote(false)); + // meh + // webProfile.setPresentation(new Presentation().setLocaleCode(locale.toString())); + // + try { + cachedWebProfiles.put(webProfileName, webProfile.create(getApiContext(event)).getId()); + } catch(PayPalRESTException e) { + log.warn("error while creating web experience", e); + //do absolutely nothing, worst case: the web experience will not be optimal + } + // + } + return Optional.ofNullable(cachedWebProfiles.get(webProfileName)); + } + + private List buildPaymentDetails(Event event, OrderSummary orderSummary, String reservationId, Locale locale) { + Amount amount = new Amount() + .setCurrency(event.getCurrency()) + .setTotal(orderSummary.getTotalPrice()); + + + Transaction transaction = new Transaction(); + String description = messageSource.getMessage("reservation-email-subject", new Object[] {configurationManager.getShortReservationID(event, reservationId), event.getDisplayName()}, locale); + transaction.setDescription(description).setAmount(amount); + + List items = orderSummary.getSummary().stream() + .map(summaryRow -> fromSummaryRow(summaryRow, event)) + .collect(Collectors.toList()); + + if(!event.isVatIncluded()) { + String vatMsg = messageSource.getMessage("reservation-page.vat", new Object[] {event.getVat()}, locale); + items.add(new Item(vatMsg, "1", orderSummary.getTotalVAT(), event.getCurrency())); + } + + transaction.setItemList(new ItemList().setItems(items)); + + List transactions = new ArrayList<>(); + transactions.add(transaction); + return transactions; + } + + private static Item fromSummaryRow(SummaryRow summaryRow, Event event) { + String quantity = Integer.toString(summaryRow.getAmount()); + String price = summaryRow.getType() == SummaryRow.SummaryType.PROMOTION_CODE ? summaryRow.getSubTotal() : summaryRow.getPrice(); + return new Item(summaryRow.getName(), quantity, price, event.getCurrency()); + } + + public String createCheckoutRequest(Event event, String reservationId, OrderSummary orderSummary, String fullName, String email, String billingAddress, Locale locale) throws Exception { + + Optional experienceProfileId = getOrCreateWebProfile(event, locale); + + List transactions = buildPaymentDetails(event, orderSummary, reservationId, locale); + String eventName = event.getShortName(); + + Payer payer = new Payer(); + payer.setPaymentMethod("paypal"); + + Payment payment = new Payment(); + payment.setIntent("sale"); + payment.setPayer(payer); + payment.setTransactions(transactions); + RedirectUrls redirectUrls = new RedirectUrls(); + + String baseUrl = StringUtils.removeEnd(configurationManager.getRequiredValue(Configuration.from(event.getOrganizationId(), event.getId(), ConfigurationKeys.BASE_URL)), "/"); + String bookUrl = baseUrl+"/event/" + eventName + "/reservation/" + reservationId + "/book"; + + UriComponentsBuilder bookUrlBuilder = UriComponentsBuilder.fromUriString(bookUrl) + .queryParam("fullName", fullName) + .queryParam("email", email) + .queryParam("billingAddress", billingAddress) + .queryParam("hmac", computeHMAC(fullName, email, billingAddress, event)); + String finalUrl = bookUrlBuilder.toUriString(); + + redirectUrls.setCancelUrl(finalUrl + "&paypal-cancel=true"); + redirectUrls.setReturnUrl(finalUrl + "&paypal-success=true"); + payment.setRedirectUrls(redirectUrls); + + experienceProfileId.ifPresent(payment::setExperienceProfileId); + + Payment createdPayment = payment.create(getApiContext(event)); + + + TicketReservation reservation = ticketReservationRepository.findReservationById(reservationId); + //add 15 minutes of validity in case the paypal flow is slow + ticketReservationRepository.updateValidity(reservationId, DateUtils.addMinutes(reservation.getValidity(), 15)); + + if(!"created".equals(createdPayment.getState())) { + throw new Exception(createdPayment.getFailureReason()); + } + + //extract url for approval + return createdPayment.getLinks().stream().filter(l -> "approval_url".equals(l.getRel())).findFirst().map(Links::getHref).orElseThrow(IllegalStateException::new); + + } + + private static String computeHMAC(String fullName, String email, String billingAddress, Event event) { + return HmacUtils.hmacSha256Hex(event.getPrivateKey(), StringUtils.trimToEmpty(fullName) + StringUtils.trimToEmpty(email) + StringUtils.trimToEmpty(billingAddress)); + } + + public static boolean isValidHMAC(String fullName, String email, String billingAddress, String hmac, Event event) { + String computedHmac = computeHMAC(fullName, email, billingAddress, event); + return MessageDigest.isEqual(hmac.getBytes(StandardCharsets.UTF_8), computedHmac.getBytes(StandardCharsets.UTF_8)); + } + + public String commitPayment(String reservationId, String token, String payerId, Event event) throws PayPalRESTException { + + Payment payment = new Payment().setId(token); + PaymentExecution paymentExecute = new PaymentExecution(); + paymentExecute.setPayerId(payerId); + Payment result = payment.execute(getApiContext(event), paymentExecute); + + // state can only be "created", "approved" or "failed". + // if we are at this stage, the only possible options are approved or failed, thus it's safe to re transition the reservation to a pending status: no payment has been made! + if(!"approved".equals(result.getState())) { + log.warn("error in state for reservationId {}, expected 'approved' state, but got '{}', failure reason is {}", reservationId, result.getState(), result.getFailureReason()); + throw new PayPalRESTException(result.getFailureReason()); + } + + // navigate the object graph (ideally taking the first Sale object) result.getTransactions().get(0).getRelatedResources().get(0).getSale().getId() + return result.getTransactions().stream() + .map(Transaction::getRelatedResources) + .flatMap(List::stream) + .map(RelatedResources::getSale) + .filter(Objects::nonNull) + .map(Sale::getId) + .filter(Objects::nonNull) + .findFirst().orElseThrow(IllegalStateException::new); + } +} diff --git a/src/main/java/alfio/manager/TicketReservationManager.java b/src/main/java/alfio/manager/TicketReservationManager.java index fa304a3790..918d54c25b 100644 --- a/src/main/java/alfio/manager/TicketReservationManager.java +++ b/src/main/java/alfio/manager/TicketReservationManager.java @@ -291,7 +291,7 @@ Optional fixToken(Optional token, int ticketCategory return specialPrice; } - public PaymentResult confirm(String gatewayToken, Event event, String reservationId, + public PaymentResult confirm(String gatewayToken, String payerId, Event event, String reservationId, String email, String fullName, Locale userLanguage, String billingAddress, TotalPrice reservationCost, Optional specialPriceSessionId, Optional method, boolean directTicketAssignment) { PaymentProxy paymentProxy = evaluatePaymentProxy(method, reservationCost); @@ -310,6 +310,13 @@ public PaymentResult confirm(String gatewayToken, Event event, String reservatio return paymentResult; } break; + case PAYPAL: + paymentResult = paymentManager.processPaypalPayment(reservationId, gatewayToken, payerId, reservationCost.getPriceWithVAT(), event); + if(!paymentResult.isSuccessful()) { + reTransitionToPending(reservationId); + return paymentResult; + } + break; case OFFLINE: transitionToOfflinePayment(event, reservationId, email, fullName, billingAddress); paymentResult = PaymentResult.successful(NOT_YET_PAID_TRANSACTION_ID); @@ -671,8 +678,8 @@ public OrderSummary orderSummaryForReservationId(String reservationId, Event eve if(reservationCost.getDiscount() != 0) { promoCodeDiscount.ifPresent((promo) -> { String formattedSingleAmount = "-" + (promo.getDiscountType() == DiscountType.FIXED_AMOUNT ? formatCents(promo.getDiscountAmount()) : (promo.getDiscountAmount()+"%")); - summary.add(new SummaryRow(promo.getPromoCode(), - formattedSingleAmount, + summary.add(new SummaryRow(promo.getPromoCode(), + formattedSingleAmount, reservationCost.discountAppliedCount, formatCents(reservationCost.discount), reservationCost.discount, SummaryRow.SummaryType.PROMOTION_CODE)); }); @@ -1023,7 +1030,7 @@ public TicketReservation findByPartialID(String reservationId) { } public String getShortReservationID(Event event, String reservationId) { - return StringUtils.substring(reservationId, 0, configurationManager.getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), PARTIAL_RESERVATION_ID_LENGTH), 8)).toUpperCase(); + return configurationManager.getShortReservationID(event, reservationId); } public int countAvailableTickets(Event event, TicketCategory category) { diff --git a/src/main/java/alfio/manager/system/ConfigurationManager.java b/src/main/java/alfio/manager/system/ConfigurationManager.java index 1c66981b62..9756aa8d11 100644 --- a/src/main/java/alfio/manager/system/ConfigurationManager.java +++ b/src/main/java/alfio/manager/system/ConfigurationManager.java @@ -42,6 +42,7 @@ import java.util.stream.Collector; import java.util.stream.Collectors; +import static alfio.model.system.ConfigurationKeys.PARTIAL_RESERVATION_ID_LENGTH; import static alfio.model.system.ConfigurationPathLevel.EVENT; import static alfio.model.system.ConfigurationPathLevel.ORGANIZATION; import static alfio.model.system.ConfigurationPathLevel.SYSTEM; @@ -155,7 +156,7 @@ public void saveAllOrganizationConfiguration(int organizationId, List { Optional value = evaluateValue(c.getKey(), c.getValue()); Optional existing = configurationRepository.findByKeyAtOrganizationLevel(organizationId, c.getKey()); - if(!value.isPresent()) { + if (!value.isPresent()) { configurationRepository.deleteOrganizationLevelByKey(c.getKey(), organizationId); } else if (existing.isPresent()) { configurationRepository.updateOrganizationLevel(organizationId, c.getKey(), value.get()); @@ -294,11 +295,12 @@ static Map> union(Configu .flatMap(l -> ConfigurationKeys.byPathLevel(l).stream().map(mapEmptyKeys(l))) .sorted((c1, c2) -> new CompareToBuilder().append(c2.getConfigurationPathLevel(), c1.getConfigurationPathLevel()).append(c1.getConfigurationKey(), c2.getConfigurationKey()).toComparison()) .collect(LinkedList::new, (List list, Configuration conf) -> { - int existing = (int)list.stream().filter(c -> c.getConfigurationKey() == conf.getConfigurationKey()).count(); - if(existing == 0) { + int existing = (int) list.stream().filter(c -> c.getConfigurationKey() == conf.getConfigurationKey()).count(); + if (existing == 0) { list.add(conf); } - }, (l1, l2) -> {}); + }, (l1, l2) -> { + }); return configurations.stream().collect(groupByCategory()); } @@ -384,4 +386,8 @@ private static Map> colle .sorted() .collect(groupByCategory()); } + + public String getShortReservationID(Event event, String reservationId) { + return StringUtils.substring(reservationId, 0, getIntConfigValue(Configuration.from(event.getOrganizationId(), event.getId(), PARTIAL_RESERVATION_ID_LENGTH), 8)).toUpperCase(); + } } diff --git a/src/main/java/alfio/model/system/ConfigurationKeys.java b/src/main/java/alfio/model/system/ConfigurationKeys.java index db2b440da9..6dec22b7ed 100644 --- a/src/main/java/alfio/model/system/ConfigurationKeys.java +++ b/src/main/java/alfio/model/system/ConfigurationKeys.java @@ -89,7 +89,13 @@ public enum ConfigurationKeys { WAITING_QUEUE_RESERVATION_TIMEOUT("The maximum time, in hours, before the \"waiting queue\" reservation would expire (default: 4)", false, SettingCategory.GENERAL, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), // - MAIL_ATTEMPTS_COUNT("The number of attempts when trying to sending an email (default: 10)", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)); + MAIL_ATTEMPTS_COUNT("The number of attempts when trying to sending an email (default: 10)", false, SettingCategory.MAIL, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION, EVENT)), + + // + PAYPAL_CLIENT_ID("Paypal REST API client ID", false, SettingCategory.PAYMENT, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION)), + PAYPAL_CLIENT_SECRET("Paypal REST API client secret", false, SettingCategory.PAYMENT, ComponentType.TEXT, false, EnumSet.of(SYSTEM, ORGANIZATION)), + PAYPAL_LIVE_MODE("Enable live mode for Paypal", false, SettingCategory.PAYMENT, ComponentType.BOOLEAN, false, EnumSet.of(SYSTEM, ORGANIZATION)); + // @Getter diff --git a/src/main/java/alfio/model/transaction/PaymentProxy.java b/src/main/java/alfio/model/transaction/PaymentProxy.java index ed87af1516..a9dc9ff711 100644 --- a/src/main/java/alfio/model/transaction/PaymentProxy.java +++ b/src/main/java/alfio/model/transaction/PaymentProxy.java @@ -27,7 +27,8 @@ public enum PaymentProxy { STRIPE("stripe.com", false, true), ON_SITE("on-site payment", true, true), OFFLINE("offline payment", false, true), - NONE("no payment required", false, false); + NONE("no payment required", false, false), + PAYPAL("paypal", false, true); private final String description; private final boolean deskPayment; diff --git a/src/main/java/alfio/repository/TicketReservationRepository.java b/src/main/java/alfio/repository/TicketReservationRepository.java index eba1a74c45..01262c8a27 100644 --- a/src/main/java/alfio/repository/TicketReservationRepository.java +++ b/src/main/java/alfio/repository/TicketReservationRepository.java @@ -59,6 +59,9 @@ int postponePayment(@Bind("reservationId") String reservationId, @Bind("validity @Query("update tickets_reservation set latest_reminder_ts = :latestReminderTimestamp where id = :reservationId") int updateLatestReminderTimestamp(@Bind("reservationId") String reservationId, @Bind("latestReminderTimestamp") ZonedDateTime latestReminderTimestamp); + @Query("update tickets_reservation set validity = :validity where id = :reservationId") + int updateValidity(@Bind("reservationId") String reservationId, @Bind("validity") Date validity); + @Query("select id from tickets_reservation where id = :reservationId for update") String lockReservationForUpdate(@Bind("reservationId") String reservationId); diff --git a/src/main/java/alfio/util/ErrorsCode.java b/src/main/java/alfio/util/ErrorsCode.java index f4cbd11bb3..8bfe9ab998 100644 --- a/src/main/java/alfio/util/ErrorsCode.java +++ b/src/main/java/alfio/util/ErrorsCode.java @@ -42,4 +42,8 @@ public interface ErrorsCode { String STEP_2_EMPTY_FULLNAME = "error.STEP_2_EMPTY_FULLNAME"; String STEP_2_MAX_LENGTH_FULLNAME = "error.STEP_2_MAX_LENGTH_FULLNAME"; String STEP_2_MAX_LENGTH_BILLING_ADDRESS = "error.STEP_2_MAX_LENGTH_BILLING_ADDRESS"; + + String STEP_2_INVALID_HMAC = "error.STEP_2_INVALID_HMAC"; + String STEP_2_PAYMENT_REQUEST_CREATION = "error.STEP_2_PAYMENT_REQUEST_CREATION"; + String STEP_2_PAYPAL_UNEXPECTED = "error.STEP_2_PAYPAL_unexpected"; } diff --git a/src/main/resources/alfio/db/HSQLDB/V99__TEST_DATA.sql b/src/main/resources/alfio/db/HSQLDB/V99__TEST_DATA.sql index 2010e6601d..de6b61c86f 100644 --- a/src/main/resources/alfio/db/HSQLDB/V99__TEST_DATA.sql +++ b/src/main/resources/alfio/db/HSQLDB/V99__TEST_DATA.sql @@ -18,7 +18,7 @@ INSERT INTO organization(name, description, email) VALUES ('demo', 'demo organization', 'info@pippobaudo.com'); insert into event(short_name, website_url, website_t_c_url, location, latitude, longitude, start_ts, end_ts, regular_price_cts, currency, available_seats, vat_included, vat, allowed_payment_proxies, private_key, org_id, time_zone, image_url) -values('eventname', 'http://localhost:8080', 'http://localhost:8080', 'demo location', '0', '0', '2016-10-10 04:00:00' , '2016-10-11 03:59:00' , 1000, 'CHF', 20, 'true', 8, 'STRIPE,ON_SITE,OFFLINE', 'alfio-uberall', 0, 'America/New_York', 'http://localhost:8080/resources/images/sample-logo.png'); +values('eventname', 'http://localhost:8080', 'http://localhost:8080', 'demo location', '0', '0', '2016-10-10 04:00:00' , '2016-10-11 03:59:00' , 1000, 'CHF', 20, 'true', 8, 'STRIPE,ON_SITE,OFFLINE,PAYPAL', 'alfio-uberall', 0, 'America/New_York', 'http://localhost:8080/resources/images/sample-logo.png'); insert into ticket_category(inception, expiration, name, max_tickets, price_cts, access_restricted, tc_status, event_id, bounded) values ('2014-01-10 00:00:00', '2016-10-10 00:00:00', 'Normal', 2, 0, false, 'ACTIVE', 0, true), @@ -77,7 +77,10 @@ insert into configuration (c_key, c_value, description) values ('STRIPE_SECRET_KEY', 'sk_test_cayJOFUUYF9cWOoMXemJd61Z', 'Stripe''s secret key'), ('STRIPE_PUBLIC_KEY', 'pk_test_gY3X0UiTgKCeStUG67i2kEFq', 'Stripe''s public key'), ('BASE_URL', 'http://localhost:8080/', 'Base application url'), - ('SUPPORTED_LANGUAGES', '7', 'supported languages'); + ('SUPPORTED_LANGUAGES', '7', 'supported languages'), + ('PAYPAL_CLIENT_ID', 'AQkquBDf1zctJOWGKWUEtKXm6qVhueUEMvXO_-MCI4DQQ4-LWvkDLIN2fGsd', 'Paypal REST API client ID'), + ('PAYPAL_CLIENT_SECRET','EL1tVxAjhT7cJimnz5-Nsx9k2reTKSVfErNQF-CmrwJgxRtylkGTKlU4RvrX', 'Paypal REST API client secret'), + ('PAYPAL_LIVE_MODE', 'false', 'Enable live mode for Paypal'); -- create fields configuration diff --git a/src/main/resources/alfio/i18n/public.properties b/src/main/resources/alfio/i18n/public.properties index 0dade95219..ff1d1bd66b 100644 --- a/src/main/resources/alfio/i18n/public.properties +++ b/src/main/resources/alfio/i18n/public.properties @@ -57,6 +57,10 @@ reservation-page.billing-address=Billing address reservation-page.payment=Payment reservation-page.credit-card=Credit Card reservation-page.credit-card.description=Fast and secure. You''ll be charged for the cost of your tickets without any additional fees. The confirmation is immediate. +reservation-page.paypal=Paypal +reservation-page.paypal.confirm-button=Confirm paypal payment +reservation-page.paypal.confirm=Confirm to continue. +reservation-page.paypal.description=Pay with your paypal account. You will be redirected to paypal.com. reservation-page.on-site=On-site cash payment reservation-page.on-site.description=You''ll receive a ticket but in order to access the event you will have to pay at the entrance desk. reservation-page.offline=Bank Transfer @@ -170,6 +174,9 @@ error.STEP_2_EMPTY_FULLNAME=Fullname is mandatory error.STEP_2_MAX_LENGTH_FULLNAME=Fullname is too long: maximum allowed is 255 characters error.STEP_2_MAX_LENGTH_BILLING_ADDRESS=Billing address is too long: maximum allowed is 450 characters error.STEP_2_ORDER_HAS_EXPIRED=Order has expired. Click the ''Cancel'' button. +error.STEP_2_INVALID_HMAC=Error in returned data: they may have been modified. You have not been charged Please try again. +error.STEP_2_PAYMENT_REQUEST_CREATION=An unexpected error has occurred. Please try again. +error.STEP_2_PAYPAL_unexpected=An unexpected error has occurred. Please contact the event''s organizers in order to get assistance. error.WRONG_CSRF_TOKEN=Your session has expired. error.email=The e-mail is not valid. error.fullname=The Full Name is not valid. diff --git a/src/main/resources/alfio/i18n/public_de.properties b/src/main/resources/alfio/i18n/public_de.properties index 6ed3947439..55242ca0f2 100644 --- a/src/main/resources/alfio/i18n/public_de.properties +++ b/src/main/resources/alfio/i18n/public_de.properties @@ -56,6 +56,10 @@ reservation-page.billing-address=Rechnungsadresse reservation-page.payment=Zahlungsart reservation-page.credit-card=Kreditkarte reservation-page.credit-card.description=Schnell und sicher. Es werden f\u00FCr die gesamten Kosten keine zus\u00E4tzlichen Geb\u00FChren erhoben. Die Teilnahmebest\u00E4tigung erfolgt unmittelbar nach Zahlungseingang. +reservation-page.paypal=DE-Paypal +reservation-page.paypal.confirm-button=DE-Confirm paypal payment +reservation-page.paypal.confirm=DE-Confirm to continue. +reservation-page.paypal.description=DE-Pay with your paypal account. You will be redirected to paypal.com. reservation-page.on-site=Bezahlung vor Ort reservation-page.on-site.description=Reserviere dir dein Ticket und bezahle es sp\u00E4ter vor Ort reservation-page.offline=Bank\u00FCberweisung @@ -163,6 +167,8 @@ error.STEP_2_EMPTY_FULLNAME=Vor- und Nachname sind obligatorisch error.STEP_2_MAX_LENGTH_FULLNAME=Vor- und Nachname sind zu lange\: es sind maximal 255 Zeichen zul\u00E4ssig error.STEP_2_MAX_LENGTH_BILLING_ADDRESS=Rechnungsadresse ist zu lange\: es sind maximal 450 Zeichen zul\u00E4ssig error.STEP_2_ORDER_HAS_EXPIRED=Bestellung ist abgelaufen. Klicke auf den Abbrechen-Button. +error.STEP_2_INVALID_HMAC=DE-Error in returned data: they may have been modified. You have not been charged Please try again. +error.STEP_2_PAYMENT_REQUEST_CREATION=Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut. error.WRONG_CSRF_TOKEN=Die Sitzung ist abgelaufen. error.email=E-MailAdresse ist ung\u00FCltig. error.fullname=Vor- und Nachname sind ung\u00FCltig. @@ -280,3 +286,4 @@ reservation-page-waiting.questions=Beim Fragen stehen wir gerne contattarci\! datetime.pattern=dd/MM/yyyy HH\:mm reservation-page.express-checkout=Questo biglietto \u00E8 per me. Per favore assegnatemelo direttamente. -reservation-page-complete.thanks-for-your-support=Grazie per il tuo supporto! \ No newline at end of file +reservation-page-complete.thanks-for-your-support=Grazie per il tuo supporto! +error.STEP_2_PAYPAL_unexpected=Errore inatteso. Ti preghiamo di contattare gli organizzatori dell''evento per verificare lo stato della transazione. \ No newline at end of file diff --git a/src/main/resources/alfio/i18n/public_nl.properties b/src/main/resources/alfio/i18n/public_nl.properties index 76db7340b5..8d9048690f 100644 --- a/src/main/resources/alfio/i18n/public_nl.properties +++ b/src/main/resources/alfio/i18n/public_nl.properties @@ -55,6 +55,10 @@ reservation-page.billing-address=Factuuradres reservation-page.payment=Betaling reservation-page.credit-card=Creditkaart reservation-page.credit-card.description=Snel en veilig. Je betaald geen extra kosten en de bevestiging is onmiddellijk. +reservation-page.paypal=NL-Paypal +reservation-page.paypal.confirm-button=NL-Confirm paypal payment +reservation-page.paypal.confirm=NL-Confirm to continue. +reservation-page.paypal.description=NL-Pay with your paypal account. You will be redirected to paypal.com. reservation-page.on-site=Ter plaatse cash betalen reservation-page.on-site.description=U krijgt een ticket, maar om toegang te krijgen moet u bij de ingang betalen. reservation-page.offline=Bank Overschrijving @@ -164,6 +168,8 @@ error.STEP_2_EMPTY_FULLNAME=Volledige naam is ongeldit error.STEP_2_MAX_LENGTH_FULLNAME=Volledige naam is te lang: maximaal 255 tekens error.STEP_2_MAX_LENGTH_BILLING_ADDRESS=Factuuradres is te lang: maximaal 255 tekens error.STEP_2_ORDER_HAS_EXPIRED=Bestelling is verlopen. Klik op de ''Annuleer'' knop. +error.STEP_2_INVALID_HMAC=NL-Error in returned data: they may have been modified. You have not been charged Please try again. +error.STEP_2_PAYMENT_REQUEST_CREATION=Een onverwachte fout heeft zich voorgedaan. Probeer het alstublieft opnieuw. error.WRONG_CSRF_TOKEN=Uw sessie is verlopen. error.email=Email is niet geldig. error.fullname=Volledige naam is niet geldig. @@ -281,3 +287,4 @@ alfio.credits=Met trots gegenereerd door Alf.io, het open source ticket reserver datetime.pattern=dd/MM/yyyy HH\:mm reservation-page.express-checkout=Dit ticket is voor mezelf. Wijs het direct toe aan mij. reservation-page-complete.thanks-for-your-support=Thank you very much for your support! +error.STEP_2_PAYPAL_unexpected=Een onverwachte fout heeft zich voorgedaan. Neem alstublieft contact op met de organisatie van het event voor ondersteuning. diff --git a/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms b/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms new file mode 100644 index 0000000000..e082a3cc6c --- /dev/null +++ b/src/main/webapp/WEB-INF/templates/event/payment/paypal.ms @@ -0,0 +1 @@ +
{{#i18n}}reservation-page.paypal.description{{/i18n}}
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/templates/event/reservation-page.ms b/src/main/webapp/WEB-INF/templates/event/reservation-page.ms index e4351c27ef..258e0bef13 100644 --- a/src/main/webapp/WEB-INF/templates/event/reservation-page.ms +++ b/src/main/webapp/WEB-INF/templates/event/reservation-page.ms @@ -87,6 +87,21 @@
+ {{#paypalCheckoutConfirmation}} + + + + + + + + + + {{#i18n}}reservation-page.paypal.confirm{{/i18n}} + {{/paypalCheckoutConfirmation}} + {{^paypalCheckoutConfirmation}} + +

{{#i18n}}reservation-page.your-details{{/i18n}}

@@ -106,6 +121,9 @@ {{#field-has-error}}[billingAddress]{{#i18n}}{{#field-error}}billingAddress{{/field-error}}{{/i18n}}{{/field-has-error}}
+ + +

{{#i18n}}reservation-page.payment{{/i18n}}

{{#event.multiplePaymentMethods}} @@ -113,8 +131,8 @@ {{#event.allowedPaymentProxies}} @@ -124,8 +142,8 @@ {{^event.multiplePaymentMethods}} {{#event.firstPaymentMethod}}

- {{#is-payment-method}}[STRIPE,{{.}}] - {{#i18n}}reservation-page.credit-card{{/i18n}}{{/is-payment-method}} + {{#is-payment-method}}[STRIPE,{{.}}] {{#i18n}}reservation-page.credit-card{{/i18n}}{{/is-payment-method}} + {{#is-payment-method}}[PAYPAL,{{.}}] {{#i18n}}reservation-page.paypal{{/i18n}}{{/is-payment-method}} {{#is-payment-method}}[ON_SITE,{{.}}] {{#i18n}}reservation-page.on-site{{/i18n}}{{/is-payment-method}} {{#is-payment-method}}[OFFLINE,{{.}}] {{#i18n}}reservation-page.offline{{/i18n}}{{/is-payment-method}}

@@ -135,6 +153,7 @@ {{#event.allowedPaymentProxies}}
{{#is-payment-method}}[STRIPE,{{.}}]{{> /event/payment/stripe }}{{/is-payment-method}} + {{#is-payment-method}}[PAYPAL,{{.}}]{{> /event/payment/paypal }}{{/is-payment-method}} {{#is-payment-method}}[ON_SITE,{{.}}]{{> /event/payment/on-site }}{{/is-payment-method}} {{#is-payment-method}}[OFFLINE,{{.}}]{{> /event/payment/offline }}{{/is-payment-method}}
@@ -157,8 +176,13 @@ + + {{/paypalCheckoutConfirmation}} +
+ + {{#orderSummary.free}}
@@ -167,10 +191,21 @@ {{/orderSummary.free}} {{^orderSummary.free}}
-
+
{{/orderSummary.free}} + + + + diff --git a/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html b/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html index b712144847..7e4278f193 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/event/detail.html @@ -162,6 +162,7 @@

{{::ticketCategory.name}} {{::ticket.ticketReservation.email}} {{::ticket.ticketReservation.paymentMethod}} {{::ticket.transaction.id}} + {{::ticket.transaction.transactionId}} {{::ticket.transactionTimestamp | formatDate:'DD.MM.YYYY HH:mm:ss'}} diff --git a/src/main/webapp/resources/js/admin/ng-app/admin-application.js b/src/main/webapp/resources/js/admin/ng-app/admin-application.js index b755b192c4..151eb27377 100644 --- a/src/main/webapp/resources/js/admin/ng-app/admin-application.js +++ b/src/main/webapp/resources/js/admin/ng-app/admin-application.js @@ -6,7 +6,8 @@ var PAYMENT_PROXY_DESCRIPTIONS = { 'STRIPE': 'Credit card payments', 'ON_SITE': 'On site (cash) payment', - 'OFFLINE': 'Offline payment (bank transfer, invoice, etc.)' + 'OFFLINE': 'Offline payment (bank transfer, invoice, etc.)', + 'PAYPAL' : 'Paypal' }; // diff --git a/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java b/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java index 8e0c405365..67eb677b96 100644 --- a/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java +++ b/src/test/java/alfio/controller/ReservationFlowIntegrationTest.java @@ -208,7 +208,7 @@ public void reservationFlowTest() throws Exception{ // check that the payment page is shown - String reservationPage = reservationController.showPaymentPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH); + String reservationPage = reservationController.showPaymentPage(eventName, reservationIdentifier, null, null, null, null, null, null, null, null, new BindingAwareModelMap(), Locale.ENGLISH); Assert.assertEquals("/event/reservation-page", reservationPage); // diff --git a/src/test/java/alfio/manager/PaymentManagerTest.java b/src/test/java/alfio/manager/PaymentManagerTest.java index 4b0361d7a5..e3cea6833d 100644 --- a/src/test/java/alfio/manager/PaymentManagerTest.java +++ b/src/test/java/alfio/manager/PaymentManagerTest.java @@ -55,14 +55,14 @@ public class PaymentManagerTest {{ describe("success flow", it -> { it.should("return a successful payment result", expect -> { - expect.that(new PaymentManager(successStripe, transactionRepository).processPayment("", "", 100, event, "", "", "")) + expect.that(new PaymentManager(successStripe, null, transactionRepository).processPayment("", "", 100, event, "", "", "")) .is(PaymentResult.successful(paymentId)); }); }); describe("stripe error", it -> { it.should("return an unsuccessful payment result", expect -> { - expect.that(new PaymentManager(failureStripe, transactionRepository).processPayment("", "", 100, event, "", "", "")) + expect.that(new PaymentManager(failureStripe, null, transactionRepository).processPayment("", "", 100, event, "", "", "")) .is(PaymentResult.unsuccessful(error)); }); }); @@ -70,7 +70,7 @@ public class PaymentManagerTest {{ describe("internal error", it -> { it.should("throw IllegalStateException in case of internal error", expect -> { expect.exception(IllegalStateException.class, () -> { - new PaymentManager(successStripe, failureTR).processPayment("", "", 100, event, "", "", ""); + new PaymentManager(successStripe, null, failureTR).processPayment("", "", 100, event, "", "", ""); }); }); }); diff --git a/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java b/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java index 3709c79b30..49b592eba7 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerIntegrationTest.java @@ -149,7 +149,7 @@ public void testTicketSelection() { assertEquals(0, ticketReservationManager.getPendingPayments(event).size()); - PaymentResult confirm = ticketReservationManager.confirm(null, event.getEvent(), reservationId, "email@example.com", "full name", Locale.ENGLISH, "billing address", + PaymentResult confirm = ticketReservationManager.confirm(null, null, event.getEvent(), reservationId, "email@example.com", "full name", Locale.ENGLISH, "billing address", totalPrice, Optional.empty(), Optional.of(PaymentProxy.OFFLINE), false); @@ -172,7 +172,7 @@ public void testTicketSelection() { TicketReservationWithOptionalCodeModification modForDelete = new TicketReservationWithOptionalCodeModification(trForDelete, Optional.empty()); String reservationId2 = ticketReservationManager.createTicketReservation(event.getId(), Collections.singletonList(modForDelete), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false); - ticketReservationManager.confirm(null, event.getEvent(), reservationId2, "email@example.com", "full name", Locale.ENGLISH, "billing address", + ticketReservationManager.confirm(null, null, event.getEvent(), reservationId2, "email@example.com", "full name", Locale.ENGLISH, "billing address", totalPrice, Optional.empty(), Optional.of(PaymentProxy.OFFLINE), false); assertTrue(ticketReservationManager.findById(reservationId2).isPresent()); diff --git a/src/test/java/alfio/manager/TicketReservationManagerTest.java b/src/test/java/alfio/manager/TicketReservationManagerTest.java index 4d81e48dee..51728218d7 100644 --- a/src/test/java/alfio/manager/TicketReservationManagerTest.java +++ b/src/test/java/alfio/manager/TicketReservationManagerTest.java @@ -571,7 +571,7 @@ public class TicketReservationManagerTest {{ when(ticketRepository.updateTicketsStatusWithReservationId(eq(reservationId), eq(TicketStatus.ACQUIRED.toString()))).thenReturn(1); when(ticketReservationRepository.updateTicketReservation(eq(reservationId), eq(IN_PAYMENT.toString()), anyString(), anyString(), anyString(), anyString(), isNull(ZonedDateTime.class), eq(PaymentProxy.STRIPE.toString()))).thenReturn(1); when(paymentManager.processPayment(eq(reservationId), eq(gatewayToken), anyInt(), eq(event), anyString(), anyString(), anyString())).thenReturn(PaymentResult.successful(transactionId)); - PaymentResult result = ticketReservationManager.confirm(gatewayToken, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.STRIPE), false); + PaymentResult result = ticketReservationManager.confirm(gatewayToken, null, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.STRIPE), false); expect.that(result.isSuccessful()).is(true); expect.that(result.getGatewayTransactionId()).is(Optional.of(transactionId)); verify(ticketReservationRepository).updateTicketReservation(eq(reservationId), eq(TicketReservationStatus.IN_PAYMENT.toString()), anyString(), anyString(), anyString(), anyString(), any(), eq(PaymentProxy.STRIPE.toString())); @@ -586,7 +586,7 @@ public class TicketReservationManagerTest {{ when(ticketReservationRepository.updateTicketReservation(eq(reservationId), eq(IN_PAYMENT.toString()), anyString(), anyString(), anyString(), anyString(), isNull(ZonedDateTime.class), eq(PaymentProxy.STRIPE.toString()))).thenReturn(1); when(ticketReservationRepository.updateTicketStatus(eq(reservationId), eq(TicketReservationStatus.PENDING.toString()))).thenReturn(1); when(paymentManager.processPayment(eq(reservationId), eq(gatewayToken), anyInt(), eq(event), anyString(), anyString(), anyString())).thenReturn(PaymentResult.unsuccessful("error-code")); - PaymentResult result = ticketReservationManager.confirm(gatewayToken, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.STRIPE), false); + PaymentResult result = ticketReservationManager.confirm(gatewayToken, null, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.STRIPE), false); expect.that(result.isSuccessful()).is(false); expect.that(result.getGatewayTransactionId()).is(Optional.empty()); expect.that(result.getErrorCode()).is(Optional.of("error-code")); @@ -599,7 +599,7 @@ public class TicketReservationManagerTest {{ when(ticketRepository.updateTicketsStatusWithReservationId(eq(reservationId), eq(TicketStatus.TO_BE_PAID.toString()))).thenReturn(1); when(ticketReservationRepository.updateTicketReservation(eq(reservationId), eq(COMPLETE.toString()), anyString(), anyString(), anyString(), anyString(), any(ZonedDateTime.class), eq(PaymentProxy.ON_SITE.toString()))).thenReturn(1); when(paymentManager.processPayment(eq(reservationId), eq(gatewayToken), anyInt(), eq(event), anyString(), anyString(), anyString())).thenReturn(PaymentResult.unsuccessful("error-code")); - PaymentResult result = ticketReservationManager.confirm(gatewayToken, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.ON_SITE), false); + PaymentResult result = ticketReservationManager.confirm(gatewayToken, null, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.ON_SITE), false); expect.that(result.isSuccessful()).is(true); expect.that(result.getGatewayTransactionId()).is(Optional.of(TicketReservationManager.NOT_YET_PAID_TRANSACTION_ID)); verify(ticketReservationRepository).updateTicketReservation(eq(reservationId), eq(TicketReservationStatus.COMPLETE.toString()), anyString(), anyString(), anyString(), anyString(), any(), eq(PaymentProxy.ON_SITE.toString())); @@ -611,7 +611,7 @@ public class TicketReservationManagerTest {{ it.should("handle the OFFLINE payment method", expect -> { when(ticketReservationRepository.postponePayment(eq(reservationId), any(Date.class), anyString(), anyString(), anyString())).thenReturn(1); - PaymentResult result = ticketReservationManager.confirm(gatewayToken, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.OFFLINE), false); + PaymentResult result = ticketReservationManager.confirm(gatewayToken, null, event, reservationId, "", "", Locale.ENGLISH, "", new TicketReservationManager.TotalPrice(100, 0, 0, 0), Optional.empty(), Optional.of(PaymentProxy.OFFLINE), false); expect.that(result.isSuccessful()).is(true); expect.that(result.getGatewayTransactionId()).is(Optional.of(TicketReservationManager.NOT_YET_PAID_TRANSACTION_ID)); verify(waitingQueueManager, never()).fireReservationConfirmed(eq(reservationId)); diff --git a/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java b/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java index d2dc6b38f9..80a10621f5 100644 --- a/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java +++ b/src/test/java/alfio/manager/system/DataMigratorIntegrationTest.java @@ -292,7 +292,7 @@ public void testUpdateGender() { TicketReservationWithOptionalCodeModification r = new TicketReservationWithOptionalCodeModification(trm, Optional.empty()); Date expiration = DateUtils.addDays(new Date(), 1); String reservationId = ticketReservationManager.createTicketReservation(event.getId(), Collections.singletonList(r), Collections.emptyList(), expiration, Optional.empty(), Optional.empty(), Locale.ENGLISH, false); - ticketReservationManager.confirm("TOKEN", event, reservationId, "email@email.ch", "full name", Locale.ENGLISH, null, new TicketReservationManager.TotalPrice(1000, 10, 0, 0), Optional.empty(), Optional.of(PaymentProxy.ON_SITE), false); + ticketReservationManager.confirm("TOKEN", null, event, reservationId, "email@email.ch", "full name", Locale.ENGLISH, null, new TicketReservationManager.TotalPrice(1000, 10, 0, 0), Optional.empty(), Optional.of(PaymentProxy.ON_SITE), false); List tickets = ticketRepository.findTicketsInReservation(reservationId); UpdateTicketOwnerForm first = new UpdateTicketOwnerForm(); first.setEmail("email@email.ch");