Skip to content

Commit f824021

Browse files
committed
implement paypal payment option #77
1 parent 9930d24 commit f824021

25 files changed

+442
-40
lines changed

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ dependencies {
155155
compile "org.apache.logging.log4j:log4j-jcl:$log4jVersion"
156156
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
157157
compile "com.stripe:stripe-java:$stripeVersion"
158+
compile 'com.paypal.sdk:rest-api-sdk:1.8.0'
158159
compile "com.google.maps:google-maps-services:0.1.7"
159160
compile "org.apache.commons:commons-lang3:3.4"
160161
compile "com.squareup.okhttp:okhttp:2.4.0"

src/main/java/alfio/controller/ReservationController.java

+45-8
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@
2121
import alfio.controller.form.UpdateTicketOwnerForm;
2222
import alfio.controller.support.SessionUtil;
2323
import alfio.controller.support.TicketDecorator;
24-
import alfio.manager.EventManager;
25-
import alfio.manager.NotificationManager;
26-
import alfio.manager.StripeManager;
27-
import alfio.manager.TicketReservationManager;
24+
import alfio.manager.*;
2825
import alfio.manager.support.OrderSummary;
2926
import alfio.manager.support.PaymentResult;
3027
import alfio.manager.system.ConfigurationManager;
@@ -40,6 +37,7 @@
4037
import alfio.util.TemplateManager;
4138
import alfio.util.TemplateManager.TemplateOutput;
4239
import alfio.util.ValidationResult;
40+
import com.paypal.base.rest.PayPalRESTException;
4341
import org.apache.commons.lang3.tuple.Pair;
4442
import org.apache.commons.lang3.tuple.Triple;
4543
import org.springframework.beans.factory.annotation.Autowired;
@@ -74,6 +72,7 @@ public class ReservationController {
7472
private final OrganizationRepository organizationRepository;
7573

7674
private final StripeManager stripeManager;
75+
private final PaypalManager paypalManager;
7776
private final TemplateManager templateManager;
7877
private final MessageSource messageSource;
7978
private final ConfigurationManager configurationManager;
@@ -92,7 +91,8 @@ public ReservationController(EventRepository eventRepository,
9291
ConfigurationManager configurationManager,
9392
NotificationManager notificationManager,
9493
TicketHelper ticketHelper,
95-
TicketFieldRepository ticketFieldRepository) {
94+
TicketFieldRepository ticketFieldRepository,
95+
PaypalManager paypalManager) {
9696
this.eventRepository = eventRepository;
9797
this.eventManager = eventManager;
9898
this.ticketReservationManager = ticketReservationManager;
@@ -104,22 +104,44 @@ public ReservationController(EventRepository eventRepository,
104104
this.notificationManager = notificationManager;
105105
this.ticketHelper = ticketHelper;
106106
this.ticketFieldRepository = ticketFieldRepository;
107+
this.paypalManager = paypalManager;
107108
}
108109

109110
@RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/book", method = RequestMethod.GET)
110111
public String showPaymentPage(@PathVariable("eventName") String eventName,
111112
@PathVariable("reservationId") String reservationId,
113+
//paypal related parameters
114+
@RequestParam(value = "paymentId", required = false) String paypalPaymentId,
115+
@RequestParam(value = "PayerID", required = false) String paypalPayerID,
116+
@RequestParam(value = "paypal-success", required = false) Boolean isPaypalSuccess,
117+
@RequestParam(value = "paypal-error", required = false) Boolean isPaypalError,
118+
@RequestParam(value = "fullName", required = false) String fullName,
119+
@RequestParam(value = "email", required = false) String email,
120+
@RequestParam(value = "billingAddress", required = false) String billingAddress,
121+
@RequestParam(value = "hmac", required = false) String hmac,
112122
Model model,
113123
Locale locale) {
114124

115125
return eventRepository.findOptionalByShortName(eventName)
116126
.map(event -> ticketReservationManager.findById(reservationId)
117127
.map(reservation -> {
118128

119-
if(reservation.getStatus() != TicketReservationStatus.PENDING) {
129+
if (reservation.getStatus() != TicketReservationStatus.PENDING) {
120130
return redirectReservation(Optional.of(reservation), eventName, reservationId);
121131
}
122132

133+
if (Boolean.TRUE.equals(isPaypalSuccess) && paypalPayerID != null && paypalPaymentId != null) {
134+
model.addAttribute("paypalPaymentId", paypalPaymentId)
135+
.addAttribute("paypalPayerID", paypalPayerID)
136+
.addAttribute("paypalCheckoutConfirmation", true)
137+
.addAttribute("fullName", fullName)
138+
.addAttribute("email", email)
139+
.addAttribute("billingAddress", billingAddress)
140+
.addAttribute("hmac", hmac);
141+
} else {
142+
model.addAttribute("paypalCheckoutConfirmation", false);
143+
}
144+
123145
OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale);
124146
model.addAttribute("orderSummary", orderSummary);
125147
model.addAttribute("reservationId", reservationId);
@@ -335,13 +357,28 @@ public String handleReservation(@PathVariable("eventName") String eventName,
335357
bindingResult.reject(ErrorsCode.STEP_2_ORDER_EXPIRED);
336358
}
337359
final TicketReservationManager.TotalPrice reservationCost = ticketReservationManager.totalReservationCostWithVAT(reservationId);
338-
paymentForm.validate(bindingResult, reservationCost, event.getAllowedPaymentProxies());
360+
paymentForm.validate(bindingResult, reservationCost, event);
339361
if (bindingResult.hasErrors()) {
340362
SessionUtil.addToFlash(bindingResult, redirectAttributes);
341363
return redirectReservation(ticketReservation, eventName, reservationId);
342364
}
365+
366+
//handle paypal redirect!
367+
if(paymentForm.getPaymentMethod() == PaymentProxy.PAYPAL && !paymentForm.hasPaypalTokens()) {
368+
OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale);
369+
try {
370+
String checkoutUrl = paypalManager.createCheckoutRequest(event, reservationId, orderSummary, paymentForm.getFullName(), paymentForm.getEmail(), paymentForm.getBillingAddress(), locale);
371+
return "redirect:" + checkoutUrl;
372+
} catch (Exception e) {
373+
bindingResult.reject(ErrorsCode.STEP_2_PAYMENT_REQUEST_CREATION);
374+
return redirectReservation(ticketReservation, eventName, reservationId);
375+
}
376+
}
377+
//
378+
379+
343380
boolean directTicketAssignment = Optional.ofNullable(paymentForm.getExpressCheckoutRequested()).map(b -> Boolean.logicalAnd(b, isExpressCheckoutEnabled(event, ticketReservationManager.orderSummaryForReservationId(reservationId, event, locale)))).orElse(false);
344-
final PaymentResult status = ticketReservationManager.confirm(paymentForm.getStripeToken(), event, reservationId, paymentForm.getEmail(),
381+
final PaymentResult status = ticketReservationManager.confirm(paymentForm.getToken(), paymentForm.getPaypalPayerID(), event, reservationId, paymentForm.getEmail(),
345382
paymentForm.getFullName(), locale, paymentForm.getBillingAddress(), reservationCost, SessionUtil.retrieveSpecialPriceSessionId(request),
346383
Optional.ofNullable(paymentForm.getPaymentMethod()), directTicketAssignment);
347384

src/main/java/alfio/controller/form/PaymentForm.java

+26-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
*/
1717
package alfio.controller.form;
1818

19+
import alfio.manager.PaypalManager;
1920
import alfio.manager.TicketReservationManager;
21+
import alfio.model.Event;
2022
import alfio.model.transaction.PaymentProxy;
2123
import alfio.util.ErrorsCode;
2224
import lombok.Data;
@@ -33,9 +35,12 @@
3335
@Data
3436
public class PaymentForm {
3537
private String stripeToken;
38+
private String paypalPaymentId;
39+
private String paypalPayerID;
3640
private String email;
3741
private String fullName;
3842
private String billingAddress;
43+
private String hmac;
3944
private Boolean cancelReservation;
4045
private Boolean termAndConditionsAccepted;
4146
private PaymentProxy paymentMethod;
@@ -48,7 +53,23 @@ private static void rejectIfOverLength(BindingResult bindingResult, String field
4853
}
4954
}
5055

51-
public void validate(BindingResult bindingResult, TicketReservationManager.TotalPrice reservationCost, List<PaymentProxy> allowedPaymentMethods) {
56+
public String getToken() {
57+
if(paymentMethod == PaymentProxy.STRIPE) {
58+
return stripeToken;
59+
} else if(paymentMethod == PaymentProxy.PAYPAL) {
60+
return paypalPaymentId;
61+
} else {
62+
return null;
63+
}
64+
}
65+
66+
public boolean hasPaypalTokens() {
67+
return StringUtils.isNotBlank(paypalPayerID) && StringUtils.isNotBlank(paypalPaymentId);
68+
}
69+
70+
public void validate(BindingResult bindingResult, TicketReservationManager.TotalPrice reservationCost, Event event) {
71+
72+
List<PaymentProxy> allowedPaymentMethods = event.getAllowedPaymentProxies();
5273

5374
Optional<PaymentProxy> paymentProxyOptional = Optional.ofNullable(paymentMethod);
5475
PaymentProxy paymentProxy = paymentProxyOptional.filter(allowedPaymentMethods::contains).orElse(PaymentProxy.STRIPE);
@@ -80,6 +101,10 @@ public void validate(BindingResult bindingResult, TicketReservationManager.Total
80101
if (email != null && !email.contains("@") && !bindingResult.hasFieldErrors("email")) {
81102
bindingResult.rejectValue("email", ErrorsCode.STEP_2_INVALID_EMAIL);
82103
}
104+
105+
if (hasPaypalTokens() && !PaypalManager.isValidHMAC(fullName, email, billingAddress, hmac, event)) {
106+
bindingResult.reject(ErrorsCode.STEP_2_INVALID_HMAC);
107+
}
83108
}
84109

85110
public Boolean shouldCancelReservation() {

src/main/java/alfio/manager/PaymentManager.java

+19
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import alfio.model.Event;
2121
import alfio.model.transaction.PaymentProxy;
2222
import alfio.repository.TransactionRepository;
23+
import alfio.util.ErrorsCode;
24+
import com.paypal.base.rest.PayPalRESTException;
2325
import com.stripe.exception.StripeException;
2426
import com.stripe.model.Charge;
2527
import lombok.extern.log4j.Log4j2;
@@ -34,12 +36,15 @@
3436
public class PaymentManager {
3537

3638
private final StripeManager stripeManager;
39+
private final PaypalManager paypalManager;
3740
private final TransactionRepository transactionRepository;
3841

3942
@Autowired
4043
public PaymentManager(StripeManager stripeManager,
44+
PaypalManager paypalManager,
4145
TransactionRepository transactionRepository) {
4246
this.stripeManager = stripeManager;
47+
this.paypalManager = paypalManager;
4348
this.transactionRepository = transactionRepository;
4449
}
4550

@@ -87,4 +92,18 @@ public PaymentResult processOfflinePayment(String reservationId, int price, Even
8792
return PaymentResult.successful(transactionId);
8893
}
8994

95+
public PaymentResult processPaypalPayment(String reservationId, String token, String payerId, int price, Event event) {
96+
try {
97+
String transactionId = paypalManager.commitPayment(reservationId, token, payerId, event);
98+
transactionRepository.insert(transactionId, reservationId,
99+
ZonedDateTime.now(), price, event.getCurrency(), "Paypal confirmation", PaymentProxy.PAYPAL.name());
100+
return PaymentResult.successful(transactionId);
101+
} catch (Exception e) {
102+
log.warn("errow while processing paypal payment: " + e.getMessage(), e);
103+
if(e instanceof PayPalRESTException) {
104+
return PaymentResult.unsuccessful(ErrorsCode.STEP_2_PAYPAL_UNEXPECTED);
105+
}
106+
throw new IllegalStateException(e);
107+
}
108+
}
90109
}

0 commit comments

Comments
 (0)