/** * 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 <http://www.gnu.org/licenses/>. */ package alfio.manager; import alfio.manager.support.PaymentResult; import alfio.manager.system.ConfigurationManager; import alfio.model.*; import alfio.model.system.Configuration; import alfio.model.system.ConfigurationKeys; import alfio.model.transaction.PaymentProxy; import alfio.model.transaction.Transaction; 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.Data; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.ZonedDateTime; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @Component @Log4j2 public class PaymentManager { private final StripeManager stripeManager; private final PaypalManager paypalManager; private final TransactionRepository transactionRepository; private final ConfigurationManager configurationManager; @Autowired public PaymentManager(StripeManager stripeManager, PaypalManager paypalManager, TransactionRepository transactionRepository, ConfigurationManager configurationManager) { this.stripeManager = stripeManager; this.paypalManager = paypalManager; this.transactionRepository = transactionRepository; this.configurationManager = configurationManager; } /** * This method processes the pending payment using the configured payment gateway (at the time of writing, only STRIPE) * and returns a PaymentResult. * In order to preserve the consistency of the payment, when a non-gateway Exception is thrown, it rethrows an IllegalStateException * * @param reservationId * @param gatewayToken * @param price * @param event * @param email * @param customerName * @param billingAddress * @return PaymentResult * @throws java.lang.IllegalStateException if there is an error after charging the credit card */ public PaymentResult processStripePayment(String reservationId, String gatewayToken, int price, Event event, String email, CustomerName customerName, String billingAddress) { try { final Charge charge = stripeManager.chargeCreditCard(gatewayToken, price, event, reservationId, email, customerName.getFullName(), billingAddress); log.info("transaction {} paid: {}", reservationId, charge.getPaid()); transactionRepository.insert(charge.getId(), null, reservationId, ZonedDateTime.now(), price, event.getCurrency(), charge.getDescription(), PaymentProxy.STRIPE.name()); return PaymentResult.successful(charge.getId()); } catch (Exception e) { if(e instanceof StripeException) { return PaymentResult.unsuccessful(stripeManager.handleException((StripeException)e)); } throw new IllegalStateException(e); } } public PaymentResult processPaypalPayment(String reservationId, String token, String payerId, int price, Event event) { try { Pair<String, String> captureAndPaymentId = paypalManager.commitPayment(reservationId, token, payerId, event); String captureId = captureAndPaymentId.getLeft(); String paymentId = captureAndPaymentId.getRight(); transactionRepository.insert(captureId, paymentId, reservationId, ZonedDateTime.now(), price, event.getCurrency(), "Paypal confirmation", PaymentProxy.PAYPAL.name()); return PaymentResult.successful(captureId); } 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); } } public List<PaymentMethod> getPaymentMethods(int organizationId) { return PaymentProxy.availableProxies() .stream() .map(p -> { PaymentMethod.PaymentMethodStatus status = ConfigurationKeys.byCategory(p.getSettingCategories()).stream() .allMatch(c -> c.isBackedByDefault() || configurationManager.getStringConfigValue(Configuration.from(organizationId, c)).filter(StringUtils::isNotEmpty).isPresent()) ? PaymentMethod.PaymentMethodStatus.ACTIVE : PaymentMethod.PaymentMethodStatus.ERROR; return new PaymentMethod(p, status); }) .collect(Collectors.toList()); } public String getStripePublicKey(Event event) { return stripeManager.getPublicKey(event); } public String createPaypalCheckoutRequest(Event event, String reservationId, OrderSummary orderSummary, CustomerName customerName, String email, String billingAddress, Locale locale, boolean postponeAssignment) throws Exception { return paypalManager.createCheckoutRequest(event, reservationId, orderSummary, customerName, email, billingAddress, locale, postponeAssignment); } public boolean refund(TicketReservation reservation, Event event, Optional<Integer> amount) { Transaction transaction = transactionRepository.loadByReservationId(reservation.getId()); switch(reservation.getPaymentMethod()) { case PAYPAL: return paypalManager.refund(transaction, event, amount); case STRIPE: return stripeManager.refund(transaction, event, amount); default: throw new IllegalStateException("Cannot refund "); } } public TransactionAndPaymentInfo getInfo(TicketReservation reservation, Event event) { Optional<Transaction> maybeTransaction = transactionRepository.loadOptionalByReservationId(reservation.getId()); return maybeTransaction.map(transaction -> { switch(reservation.getPaymentMethod()) { case PAYPAL: return new TransactionAndPaymentInfo(reservation.getPaymentMethod(), transaction, paypalManager.getInfo(transaction, event).orElse(null)); case STRIPE: return new TransactionAndPaymentInfo(reservation.getPaymentMethod(), transaction, stripeManager.getInfo(transaction, event).orElse(null)); default: return new TransactionAndPaymentInfo(reservation.getPaymentMethod(), transaction, new PaymentInformations(reservation.getPaidAmount(), null)); } }).orElse(new TransactionAndPaymentInfo(reservation.getPaymentMethod(),null, new PaymentInformations(reservation.getPaidAmount(), null))); } @Data public static final class PaymentMethod { public enum PaymentMethodStatus { ACTIVE, ERROR } private final PaymentProxy paymentProxy; private final PaymentMethodStatus status; public boolean isActive() { return status == PaymentMethodStatus.ACTIVE; } } }