Skip to content

Commit

Permalink
#77 paypal initial work
Browse files Browse the repository at this point in the history
  • Loading branch information
syjer committed Jul 7, 2016
1 parent 9930d24 commit 419aa97
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 19 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
43 changes: 36 additions & 7 deletions src/main/java/alfio/controller/ReservationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -104,22 +104,36 @@ 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,
Model model,
Locale locale) {

return eventRepository.findOptionalByShortName(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);
model.addAttribute("paypalPayerID", paypalPayerID);
model.addAttribute("paypalCheckoutConfirmation", true);
} else {
model.addAttribute("paypalCheckoutConfirmation", false);
}

OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale);
model.addAttribute("orderSummary", orderSummary);
model.addAttribute("reservationId", reservationId);
Expand Down Expand Up @@ -340,8 +354,23 @@ public String handleReservation(@PathVariable("eventName") String eventName,
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, locale);
return "redirect:"+checkoutUrl;
} catch (PayPalRESTException e) {
//FIXME
throw new IllegalStateException(e);
}
}
//


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);

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/alfio/controller/form/PaymentForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
@Data
public class PaymentForm {
private String stripeToken;
private String paypalPaymentId;
private String paypalPayerID;
private String email;
private String fullName;
private String billingAddress;
Expand All @@ -48,6 +50,20 @@ private static void rejectIfOverLength(BindingResult bindingResult, String field
}
}

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, List<PaymentProxy> allowedPaymentMethods) {

Optional<PaymentProxy> paymentProxyOptional = Optional.ofNullable(paymentMethod);
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/alfio/manager/PaymentManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,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;
}

Expand Down Expand Up @@ -87,4 +90,15 @@ 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(token, payerId, event);
transactionRepository.insert(transactionId, reservationId,
ZonedDateTime.now(), price, event.getCurrency(), "FIXME DESCRIPTION", PaymentProxy.PAYPAL.name());
return PaymentResult.successful(transactionId);
} catch (Exception e) {
return PaymentResult.unsuccessful("FIXME PAYPAL ERROR HANDLING");//FIXME
}
}
}
103 changes: 103 additions & 0 deletions src/main/java/alfio/manager/PaypalManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.system.Configuration;
import alfio.model.system.ConfigurationKeys;
import com.paypal.api.payments.*;
import com.paypal.base.rest.APIContext;

import com.paypal.base.rest.PayPalRESTException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
@Log4j2
public class PaypalManager {

private final ConfigurationManager configurationManager;

@Autowired
public PaypalManager(ConfigurationManager configurationManager) {
this.configurationManager = configurationManager;
}

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 List<Transaction> buildPaymentDetails(Event event, OrderSummary orderSummary) {


Amount amount = new Amount();
amount.setCurrency(event.getCurrency());
amount.setTotal(orderSummary.getTotalPrice());

Transaction transaction = new Transaction();
transaction.setDescription("creating a payment");
transaction.setAmount(amount);

List<Item> items = orderSummary.getSummary().stream().map(summaryRow -> new Item(summaryRow.getName(), Integer.toString(summaryRow.getAmount()), summaryRow.getType() == SummaryRow.SummaryType.PROMOTION_CODE ? summaryRow.getSubTotal() : summaryRow.getPrice(), event.getCurrency())).collect(Collectors.toList());

if(!event.isVatIncluded()) {
items.add(new Item("VAT", "1", orderSummary.getTotalVAT(), event.getCurrency()));
}

transaction.setItemList(new ItemList().setItems(items));



List<Transaction> transactions = new ArrayList<>();
transactions.add(transaction);
return transactions;
}

public String createCheckoutRequest(Event event, String reservationId, OrderSummary orderSummary, Locale locale) throws PayPalRESTException {

List<Transaction> transactions = buildPaymentDetails(event, orderSummary);
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();
redirectUrls.setCancelUrl("http://localhost:8080/event/" + eventName + "/reservation/" + reservationId + "/book?paypal-cancel=true");
redirectUrls.setReturnUrl("http://localhost:8080/event/" + eventName + "/reservation/" + reservationId + "/book?paypal-success=true");
payment.setRedirectUrls(redirectUrls);

Payment createdPayment = payment.create(getApiContext(event));

//extract url for approval
return createdPayment.getLinks().stream().filter(l -> "approval_url".equals(l.getRel())).findFirst().map(Links::getHref).orElseThrow(IllegalStateException::new);

}

public String commitPayment(String token, String payerId, Event event) throws Exception {

Payment payment = new Payment();
payment.setId(token);
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId(payerId);
Payment result = payment.execute(getApiContext(event), paymentExecute);

//return result.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
//FIXME
return result.getId();
}

}
9 changes: 6 additions & 3 deletions src/main/java/alfio/manager/TicketReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ Optional<SpecialPrice> fixToken(Optional<SpecialPrice> 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<String> specialPriceSessionId, Optional<PaymentProxy> method, boolean directTicketAssignment) {
PaymentProxy paymentProxy = evaluatePaymentProxy(method, reservationCost);
Expand All @@ -310,6 +310,9 @@ public PaymentResult confirm(String gatewayToken, Event event, String reservatio
return paymentResult;
}
break;
case PAYPAL:
paymentResult = paymentManager.processPaypalPayment(reservationId, gatewayToken, payerId, reservationCost.getPriceWithVAT(), event);
break;
case OFFLINE:
transitionToOfflinePayment(event, reservationId, email, fullName, billingAddress);
paymentResult = PaymentResult.successful(NOT_YET_PAID_TRANSACTION_ID);
Expand Down Expand Up @@ -671,8 +674,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));
});
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/alfio/model/system/ConfigurationKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/alfio/model/transaction/PaymentProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/main/resources/alfio/db/HSQLDB/V99__TEST_DATA.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
INSERT INTO organization(name, description, email) VALUES ('demo', 'demo organization', '[email protected]');

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),
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/alfio/i18n/public.properties
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ 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.description=Pay with 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
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/alfio/i18n/public_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ 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.description=DE-Pay with 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
Expand Down
Loading

0 comments on commit 419aa97

Please sign in to comment.