Skip to content

Commit

Permalink
Implement apple wallet passes (#446)
Browse files Browse the repository at this point in the history
* Fix product test data

* Implement Apple Wallet Passes

* Remove redundant code

* Add cascade to Ticket - Order relation
  • Loading branch information
JoepdeJong committed Oct 11, 2023
1 parent 2f5d2bd commit 6b9a898
Show file tree
Hide file tree
Showing 13 changed files with 398 additions and 22 deletions.
3 changes: 2 additions & 1 deletion config/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,5 @@ management:
health.enabled: true

links:
gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf
gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf
passes: passes
6 changes: 5 additions & 1 deletion config/application.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,8 @@ wisvch.payments:
# CH mollie api key
mollie:
apikey: test
clientUri: http://localhost:8080/events
clientUri: http://localhost:8080/events

links:
gtc: https://ch.tudelft.nl/wp-content/uploads/Deelnemersvoorwaarden_versie_12_06_2023.pdf
passes: https://ch.tudelft.nl/passes
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ch.wisv.events.core.exception.normal;

import ch.wisv.events.core.exception.LogLevelEnum;

/**
* TicketPassFailedException class.
*/
public class TicketPassFailedException extends EventsException {

/**
* TicketPassFailedException.
*/
public TicketPassFailedException() {
super(LogLevelEnum.DEBUG, "Ticket has not been found!");
}

/**
* TicketPassFailedException.
*
* @param message of type String
*/
public TicketPassFailedException(String message) {
super(LogLevelEnum.DEBUG, message);
}
}
8 changes: 8 additions & 0 deletions src/main/java/ch/wisv/events/core/model/event/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,12 @@ public boolean isSoldOut() {
public boolean hasExternalProductUrl() {
return this.externalProductUrl != null && this.externalProductUrl.length() > 0;
}

public String toString() {
return this.title;
}

public int hashCode() {
return this.id;
}
}
10 changes: 10 additions & 0 deletions src/main/java/ch/wisv/events/core/model/product/Product.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.UUID;
import javax.persistence.*;

import ch.wisv.events.core.model.event.Event;
import ch.wisv.events.core.util.VatRate;
import lombok.AccessLevel;
import lombok.Data;
Expand Down Expand Up @@ -95,6 +96,14 @@ public class Product {
*/
public String redirectUrl;

/**
* Field event.
*/

@ManyToOne
@JoinTable(name = "event_products", joinColumns = @JoinColumn(name = "products_id"))
public Event event;

/**
* Field productList.
*/
Expand Down Expand Up @@ -222,4 +231,5 @@ public void increaseReserved(int amount) {
public boolean isSoldOut() {
return this.maxSold != null && this.sold >= this.maxSold;
}

}
2 changes: 1 addition & 1 deletion src/main/java/ch/wisv/events/core/model/ticket/Ticket.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class Ticket {
/**
* Customer which owns the Ticket.
*/
@ManyToOne
@ManyToOne(cascade = {javax.persistence.CascadeType.ALL})
public Order order;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ch.wisv.events.core.exception.normal.TicketNotFoundException;
import ch.wisv.events.core.exception.normal.TicketNotTransferableException;
import ch.wisv.events.core.exception.normal.TicketPassFailedException;
import ch.wisv.events.core.model.customer.Customer;
import ch.wisv.events.core.model.order.Order;
import ch.wisv.events.core.model.product.Product;
Expand Down Expand Up @@ -124,4 +125,6 @@ public interface TicketService {
*/
BufferedImage generateQrCode(Ticket ticket) throws WriterException, IllegalArgumentException;

byte[] getApplePass(Ticket ticket) throws TicketPassFailedException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ch.wisv.events.core.exception.normal.EventNotFoundException;
import ch.wisv.events.core.exception.normal.TicketNotFoundException;
import ch.wisv.events.core.exception.normal.TicketNotTransferableException;
import ch.wisv.events.core.exception.normal.TicketPassFailedException;
import ch.wisv.events.core.model.customer.Customer;
import ch.wisv.events.core.model.event.Event;
import ch.wisv.events.core.model.order.Order;
Expand All @@ -13,37 +14,48 @@
import ch.wisv.events.core.repository.TicketRepository;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.time.format.DateTimeFormatter;
import java.util.*;

import ch.wisv.events.core.service.event.EventService;
import ch.wisv.events.core.service.mail.MailService;
import ch.wisv.events.core.util.QrCode;
import com.google.zxing.WriterException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.validation.constraints.NotNull;

/**
* TicketServiceImpl class.
*/
@Service
public class TicketServiceImpl implements TicketService {
/** TicketRepository. */
/**
* TicketRepository.
*/
private final TicketRepository ticketRepository;

/** EventService. */
/**
* EventService.
*/
private final EventService eventService;

/**
* MailService.
*/
private final MailService mailService;

@Value("${links.passes}")
@NotNull
private String passesLink;

/**
* TicketServiceImpl constructor.
*
* @param ticketRepository of type TicketRepository
* @param mailService of type MailService
* @param mailService of type MailService
*/
public TicketServiceImpl(TicketRepository ticketRepository, EventService eventService, MailService mailService) {
this.ticketRepository = ticketRepository;
Expand All @@ -56,7 +68,6 @@ public TicketServiceImpl(TicketRepository ticketRepository, EventService eventSe
*
* @param product of type Product
* @param uniqueCode of type String
*
* @return Ticket
*/
@Override
Expand All @@ -69,9 +80,7 @@ public Ticket getByUniqueCode(Product product, String uniqueCode) throws TicketN
* Get ticket by key.
*
* @param key of type String
*
* @return Ticket
*
* @throws TicketNotFoundException when ticket is not found
*/
@Override
Expand All @@ -85,7 +94,6 @@ public Ticket getByKey(String key) throws TicketNotFoundException {
*
* @param product of type Product
* @param customer of type Customer
*
* @return List of Tickets
*/
@Override
Expand All @@ -97,7 +105,6 @@ public List<Ticket> getAllByProductAndCustomer(Product product, Customer custome
* Get all Ticket by a Product.
*
* @param product of type Product
*
* @return List of Tickets
*/
@Override
Expand All @@ -109,7 +116,6 @@ public List<Ticket> getAllByProduct(Product product) {
* Get all Ticket by a Customer.
*
* @param customer of type Customer
*
* @return List of Tickets
*/
@Override
Expand All @@ -121,7 +127,6 @@ public List<Ticket> getAllByCustomer(Customer customer) {
* Get all Tickets of an Order.
*
* @param order of type Order
*
* @return List of Tickets
*/
@Override
Expand Down Expand Up @@ -167,7 +172,6 @@ public List<Ticket> getAll() {
* Create a Ticket by an OrderProduct.
*
* @param order of type Order
*
* @return List of Ticket
*/
@Override
Expand Down Expand Up @@ -199,7 +203,6 @@ public List<Ticket> createByOrder(Order order) {
* Generate a Ticket unique String.
*
* @param product of type Product
*
* @return String
*/
private String generateUniqueString(Product product) {
Expand All @@ -215,10 +218,11 @@ private String generateUniqueString(Product product) {

/**
* Generate a QR code from the uniqueCode.
*
* @param ticket of type Ticket
* @throws WriterException when QR code is not generated
* @throws IllegalArgumentException when uniqueCode is not a valid UUID.
* @return BufferedImage
* @throws WriterException when QR code is not generated
* @throws IllegalArgumentException when uniqueCode is not a valid UUID.
*/
public BufferedImage generateQrCode(Ticket ticket) throws IllegalArgumentException, WriterException {
// Assert that the uniqueCode is a UUID (LEGACY CHECK)
Expand All @@ -231,8 +235,9 @@ public BufferedImage generateQrCode(Ticket ticket) throws IllegalArgumentExcepti

/**
* Transfer a Ticket to another Customer.
*
* @param currentCustomer of type Customer
* @param newCustomer of type Customer
* @param newCustomer of type Customer
*/
public void transfer(Ticket ticket, Customer currentCustomer, Customer newCustomer) throws TicketNotTransferableException {
// Get event from ticket product
Expand All @@ -258,4 +263,26 @@ public void transfer(Ticket ticket, Customer currentCustomer, Customer newCustom
mailService.sendTransferConfirmation(ticket, currentCustomer, newCustomer);
}

public byte[] getApplePass(Ticket ticket) throws TicketPassFailedException {
try {
RestTemplate restTemplate = new RestTemplate();


Map<String, String> params = new HashMap<>();
params.put("name", ticket.getProduct().getTitle());
params.put("description", ticket.getProduct().getDescription());
// Format date as yyyy-MM-dd
params.put("date", ticket.getProduct().getEvent().getStart().format(DateTimeFormatter.ISO_LOCAL_DATE));
params.put("time", ticket.getProduct().getEvent().getStart().format(DateTimeFormatter.ISO_LOCAL_TIME));
params.put("location", ticket.getProduct().getEvent().getLocation());
params.put("code", ticket.getUniqueCode());

return restTemplate.getForObject(passesLink +
"?name={name}&description={description}&date={date}&time={time}&location={location}&code={code}"
, byte[].class, params);
} catch (Exception e) {
e.printStackTrace();
throw new TicketPassFailedException(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private Product createProduct(JSONObject jsonObject) {
(String) jsonObject.get("title"),
(String) jsonObject.get("description"),
(Double) jsonObject.get("cost"),
(VatRate) jsonObject.get("vatRate"),
VatRate.valueOf((String) jsonObject.get("vatRate")),
((Long) jsonObject.get("maxSold")).intValue(),
LocalDateTime.now().minusHours(1).truncatedTo(ChronoUnit.MINUTES),
LocalDateTime.now().plusDays(days).truncatedTo(ChronoUnit.MINUTES)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ch.wisv.events.core.exception.normal.CustomerNotFoundException;
import ch.wisv.events.core.exception.normal.TicketNotFoundException;
import ch.wisv.events.core.exception.normal.TicketNotTransferableException;
import ch.wisv.events.core.exception.normal.TicketPassFailedException;
import ch.wisv.events.core.model.customer.Customer;
import ch.wisv.events.core.model.ticket.Ticket;
import ch.wisv.events.core.service.auth.AuthenticationService;
Expand Down Expand Up @@ -158,4 +159,33 @@ public void getQrCode(HttpServletResponse response, @PathVariable String key) th
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}

/**
* Get wallet pass of ticket.
*/
@GetMapping("/{key}/wallet.pkpass")
public void getApplePass(HttpServletResponse response, @PathVariable String key) throws IOException {
Customer customer = authenticationService.getCurrentCustomer();
try {
Ticket ticket = ticketService.getByKey(key);

if (!ticket.owner.equals(customer)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}

byte[] bytes = ticketService.getApplePass(ticket);

response.setContentType("application/vnd.apple.pkpass");
response.setContentLength(bytes.length);
response.getOutputStream().write(bytes);
response.getOutputStream().close();
}
catch (TicketNotFoundException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
catch (TicketPassFailedException | IOException e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
5 changes: 5 additions & 0 deletions src/main/resources/dev/data/products.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"title": "T.U.E.S.Day lecture ticket",
"description": "Ticket for the T.U.E.S.Day lecture.",
"cost": 0.00,
"vatRate": "VAT_HIGH",
"maxSold": 5,
"maxSoldPerCustomer": 1,
"key": "dd729c96-dacf-4eab-a2e8-19d2a1ad917c"
Expand All @@ -11,6 +12,7 @@
"title": "Symposium ticket",
"description": "Ticket for the Symposium.",
"cost": 5.00,
"vatRate": "VAT_HIGH",
"maxSold": 5,
"maxSoldPerCustomer": 5,
"key": "cfc66e3d-cf77-441d-a04a-9e81bd3e1117"
Expand All @@ -19,6 +21,7 @@
"title": "WIFI Rally ticket",
"description": "Ticket for the WIFI Rally.",
"cost": 15.00,
"vatRate": "VAT_HIGH",
"maxSold": 5,
"maxSoldPerCustomer": 1,
"key": "f0d3a2a5-150e-4799-8679-db6778edb2a9"
Expand All @@ -27,6 +30,7 @@
"title": "MatCH ticket",
"description": "Ticket for the MatCH activity.",
"cost": 1.00,
"vatRate": "VAT_HIGH",
"maxSold": 5,
"maxSoldPerCustomer": 5,
"key": "8d7900db-07d6-4922-91da-55e80daec441"
Expand All @@ -35,6 +39,7 @@
"title": "Annuarium",
"description": "",
"cost": 1.00,
"vatRate": "VAT_HIGH",
"maxSold": 250,
"maxSoldPerCustomer": 25,
"key": "7a5b9158-a9e4-40f5-ad7f-586092274e63"
Expand Down
Loading

0 comments on commit 6b9a898

Please sign in to comment.