Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement apple wallet passes #446

Merged
merged 4 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading