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

#259 support more currencies #673

Merged
merged 4 commits into from
Jul 3, 2019
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ dependencies {
exclude group: "org.eclipse.jetty.websocket", module:"javax-websocket-server-impl"
exclude module : 'spring-boot-starter-logging'
}

implementation "org.joda:joda-money:1.0.1"
}

// -- license configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public ResponseEntity<EventModification.AdditionalService> update(@PathVariable(
.map(event -> {
int result = additionalServiceRepository.update(additionalServiceId, additionalService.isFixPrice(),
additionalService.getOrdinal(), additionalService.getAvailableQuantity(), additionalService.getMaxQtyPerOrder(), additionalService.getInception().toZonedDateTime(event.getZoneId()),
additionalService.getExpiration().toZonedDateTime(event.getZoneId()), additionalService.getVat(), additionalService.getVatType(), Optional.ofNullable(additionalService.getPrice()).map(MonetaryUtil::unitToCents).orElse(0));
additionalService.getExpiration().toZonedDateTime(event.getZoneId()), additionalService.getVat(), additionalService.getVatType(), Optional.ofNullable(additionalService.getPrice()).map(p -> MonetaryUtil.unitToCents(p, event.getCurrency())).orElse(0));
Validate.isTrue(result <= 1, "too many records updated");
Stream.concat(additionalService.getTitle().stream(), additionalService.getDescription().stream()).
forEach(t -> {
Expand All @@ -125,7 +125,7 @@ public ResponseEntity<EventModification.AdditionalService> insert(@PathVariable(
return eventRepository.findOptionalById(eventId)
.map(event -> {
AffectedRowCountAndKey<Integer> result = additionalServiceRepository.insert(eventId,
Optional.ofNullable(additionalService.getPrice()).map(MonetaryUtil::unitToCents).orElse(0),
Optional.ofNullable(additionalService.getPrice()).map(p -> MonetaryUtil.unitToCents(p, event.getCurrency())).orElse(0),
additionalService.isFixPrice(),
additionalService.getOrdinal(),
additionalService.getAvailableQuantity(),
Expand Down
11 changes: 6 additions & 5 deletions src/main/java/alfio/controller/api/admin/EventApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static alfio.util.Wrappers.optionally;
import static alfio.util.Validator.*;
import static alfio.util.Wrappers.optionally;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.springframework.web.bind.annotation.RequestMethod.*;
Expand Down Expand Up @@ -315,17 +315,18 @@ private Stream<String[]> exportLines(String eventName, Principal principal, List

return eventManager.findAllConfirmedTicketsForCSV(eventName, username).stream().map(trs -> {
Ticket t = trs.getTicket();
var currencyCode = t.getCurrencyCode();
TicketReservation reservation = trs.getTicketReservation();
List<String> line = new ArrayList<>();
if(fields.contains("ID")) {line.add(t.getUuid());}
if(fields.contains("Creation")) {line.add(t.getCreation().withZoneSameInstant(eventZoneId).toString());}
if(fields.contains("Category")) {line.add(categoriesMap.get(t.getCategoryId()).getName());}
if(fields.contains("Event")) {line.add(eventName);}
if(fields.contains("Status")) {line.add(t.getStatus().toString());}
if(fields.contains("OriginalPrice")) {line.add(MonetaryUtil.centsToUnit(t.getSrcPriceCts()).toString());}
if(fields.contains("PaidPrice")) {line.add(MonetaryUtil.centsToUnit(t.getFinalPriceCts()).toString());}
if(fields.contains("Discount")) {line.add(MonetaryUtil.centsToUnit(t.getDiscountCts()).toString());}
if(fields.contains("VAT")) {line.add(MonetaryUtil.centsToUnit(t.getVatCts()).toString());}
if(fields.contains("OriginalPrice")) {line.add(MonetaryUtil.centsToUnit(t.getSrcPriceCts(), currencyCode).toString());}
if(fields.contains("PaidPrice")) {line.add(MonetaryUtil.centsToUnit(t.getFinalPriceCts(), currencyCode).toString());}
if(fields.contains("Discount")) {line.add(MonetaryUtil.centsToUnit(t.getDiscountCts(), currencyCode).toString());}
if(fields.contains("VAT")) {line.add(MonetaryUtil.centsToUnit(t.getVatCts(), currencyCode).toString());}
if(fields.contains("ReservationID")) {line.add(t.getTicketsReservationId());}
if(fields.contains("Full Name")) {line.add(t.getFullName());}
if(fields.contains("First Name")) {line.add(t.getFirstName());}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public void addPromoCode(@RequestBody PromoCodeDiscountModification promoCode) {
Integer organizationId = promoCode.getOrganizationId();
ZoneId zoneId = zoneIdFromEventId(eventId, promoCode.getUtcOffset());

int discount = promoCode.getDiscountValue();
int discount = promoCode.getDiscountValue(eventRepository.getEventCurrencyCode(eventId));

eventManager.addPromoCode(promoCode.getPromoCode(), eventId, organizationId, promoCode.getStart().toZonedDateTime(zoneId),
promoCode.getEnd().toZonedDateTime(zoneId), discount, promoCode.getDiscountType(), promoCode.getCategories(), promoCode.getMaxUsage(),
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/alfio/controller/api/admin/UtilsApiController.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import alfio.controller.api.support.TicketHelper;
import alfio.manager.EventNameManager;
import alfio.util.MustacheCustomTag;
import alfio.util.Wrappers;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.text.StringEscapeUtils;
import org.joda.money.CurrencyUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
Expand Down Expand Up @@ -96,10 +98,11 @@ public Map<String, Object> getApplicationInfo(Principal principal) {
}

@RequestMapping(value = "/currencies", method = GET)
public List<CurrencyDescriptor> getCurrencies() {
return Currency.getAvailableCurrencies().stream()
.filter(c -> c.getDefaultFractionDigits() == 2 && !CURRENCIES_BLACKLIST.contains(c.getCurrencyCode())) //currencies which don't support cents are filtered out. Support will be implemented in the next version
.map(c -> new CurrencyDescriptor(c.getCurrencyCode(), c.getDisplayName(), c.getSymbol(), c.getDefaultFractionDigits()))
public List<CurrencyDescriptor> getCurrencies(Locale locale) {
return CurrencyUnit.registeredCurrencies().stream()
//we don't support pseudo currencies, as it is very unlikely that payment providers would support them
.filter(c -> !c.isPseudoCurrency() && !CURRENCIES_BLACKLIST.contains(c.getCode()) && Wrappers.optionally(() -> Currency.getInstance(c.getCode())).isPresent())
.map(c -> new CurrencyDescriptor(c.getCode(), c.toCurrency().getDisplayName(locale), c.getSymbol(locale), c.getDecimalPlaces()))
.collect(Collectors.toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
*/
package alfio.controller.api.v2.model;

import alfio.controller.api.support.CurrencyDescriptor;
import alfio.model.Event;
import alfio.model.transaction.PaymentMethod;
import alfio.model.transaction.PaymentProxy;
import alfio.model.user.Organization;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.joda.money.CurrencyUnit;

import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -81,6 +83,14 @@ public String getWebsiteUrl() {
return event.getWebsiteUrl();
}

public CurrencyDescriptor getCurrencyDescriptor() {
if(event.isFreeOfCharge()) {
return null;
}
var currencyUnit = CurrencyUnit.of(event.getCurrency());
return new CurrencyDescriptor(currencyUnit.getCode(), currencyUnit.toCurrency().getDisplayName(), currencyUnit.getSymbol(), currencyUnit.getDecimalPlaces());
}

public List<Language> getContentLanguages() {
return event.getContentLanguages()
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
*/
package alfio.controller.api.v2.user;

import alfio.controller.api.v2.model.*;
import alfio.controller.api.v2.model.AdditionalService;
import alfio.controller.api.v2.model.EventWithAdditionalInfo;
import alfio.controller.api.v2.model.EventWithAdditionalInfo.PaymentProxyWithParameters;
import alfio.controller.api.v2.model.*;
import alfio.controller.api.v2.model.TicketCategory;
import alfio.controller.api.v2.model.EventWithAdditionalInfo.PaymentProxyWithParameters;
import alfio.controller.decorator.SaleableAdditionalService;
import alfio.controller.decorator.SaleableTicketCategory;
import alfio.controller.form.ReservationForm;
Expand Down Expand Up @@ -498,7 +498,7 @@ private Optional<String> createTicketReservation(ReservationForm reservation,
public ResponseEntity<ValidatedResponse<EventCode>> validateCode(@PathVariable("eventName") String eventName,
@RequestParam("code") String code) {

return eventRepository.findOptionalEventAndOrganizationIdByShortName(eventName).map(e -> {
return eventRepository.findOptionalByShortName(eventName).map(e -> {
var res = checkCode(e, code);
if(res.isSuccess()) {

Expand All @@ -507,7 +507,7 @@ public ResponseEntity<ValidatedResponse<EventCode>> validateCode(@PathVariable("
.orElseGet(() -> {
var promoCodeDiscount = res.getValue().getRight().orElseThrow();
var type = promoCodeDiscount.getCodeType() == PromoCodeDiscount.CodeType.ACCESS ? EventCode.EventCodeType.ACCESS : EventCode.EventCodeType.DISCOUNT;
String formattedDiscountAmount = promoCodeDiscount.getDiscountType() == PromoCodeDiscount.DiscountType.FIXED_AMOUNT ? promoCodeDiscount.getFormattedDiscountAmount().toString() : Integer.toString(promoCodeDiscount.getDiscountAmount());
String formattedDiscountAmount = promoCodeDiscount.getDiscountType() == PromoCodeDiscount.DiscountType.FIXED_AMOUNT ? MonetaryUtil.formatCents(promoCodeDiscount.getDiscountAmount(), e.getCurrency()) : Integer.toString(promoCodeDiscount.getDiscountAmount());
return new EventCode(code, type, promoCodeDiscount.getDiscountType(), formattedDiscountAmount);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
import java.util.stream.Collectors;

import static alfio.model.PriceContainer.VatStatus.*;
import static alfio.model.PriceContainer.VatStatus.INCLUDED_EXEMPT;
import static alfio.model.system.ConfigurationKeys.*;
import static alfio.util.MonetaryUtil.unitToCents;

Expand Down Expand Up @@ -458,11 +457,12 @@ private void checkAndApplyVATRules(String eventName, String reservationId, Conta
bindingResult.rejectValue("vatNr", "error.vat");
} else {
var reservation = ticketReservationManager.findById(reservationId).orElseThrow();
var currencyCode = reservation.getCurrencyCode();
PriceContainer.VatStatus vatStatus = determineVatStatus(event.getVatStatus(), vatValidation.isVatExempt());
var updatedPrice = ticketReservationManager.totalReservationCostWithVAT(reservation.withVatStatus(vatStatus));// update VatStatus to the new value for calculating the new price
var calculator = new ReservationPriceCalculator(reservation.withVatStatus(vatStatus), updatedPrice, ticketReservationManager.findTicketsInReservation(reservationId), event);
ticketReservationRepository.updateBillingData(vatStatus, reservation.getSrcPriceCts(),
unitToCents(calculator.getFinalPrice()), unitToCents(calculator.getVAT()), unitToCents(calculator.getAppliedDiscount()),
unitToCents(calculator.getFinalPrice(), currencyCode), unitToCents(calculator.getVAT(), currencyCode), unitToCents(calculator.getAppliedDiscount(), currencyCode),
reservation.getCurrencyCode(), StringUtils.trimToNull(vatValidation.getVatNr()),
country, contactAndTicketsForm.isInvoiceRequested(), reservationId);
vatChecker.logSuccessfulValidation(vatValidation, reservationId, event.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import alfio.model.PriceContainer;
import alfio.model.PromoCodeDiscount;
import alfio.model.TicketCategory;
import alfio.util.MonetaryUtil;
import lombok.experimental.Delegate;

import java.math.BigDecimal;
Expand Down Expand Up @@ -99,11 +100,6 @@ public Optional<PromoCodeDiscount> getDiscount() {
return Optional.ofNullable(promoCodeDiscount);
}

@Override
public String getCurrencyCode() {
return event.getCurrency();
}

@Override
public Optional<BigDecimal> getOptionalVatPercentage() {
return Optional.ofNullable(event.getVat());
Expand All @@ -115,7 +111,7 @@ public VatStatus getVatStatus() {
}

public String getFormattedFinalPrice() {
return getFinalPriceToDisplay(getFinalPrice().add(getAppliedDiscount()), getVAT(), getVatStatus()).toString();
return MonetaryUtil.formatUnit(getFinalPriceToDisplay(getFinalPrice().add(getAppliedDiscount()), getVAT(), getVatStatus()), getCurrencyCode());
}

public int getMaxTicketsAfterConfiguration() {
Expand All @@ -127,7 +123,7 @@ public int getAvailableTickets() {
}

public String getDiscountedPrice() {
return getFinalPriceToDisplay(getFinalPrice(), getVAT(), getVatStatus()).toString();
return MonetaryUtil.formatUnit(getFinalPriceToDisplay(getFinalPrice(), getVAT(), getVatStatus()), getCurrencyCode());
}

public boolean getSupportsDiscount() {
Expand Down
24 changes: 15 additions & 9 deletions src/main/java/alfio/manager/AdminReservationManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
import alfio.model.user.User;
import alfio.repository.*;
import alfio.repository.user.UserRepository;
import alfio.util.*;
import alfio.util.Json;
import alfio.util.LocaleUtil;
import alfio.util.TemplateManager;
import alfio.util.TemplateResource;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ObjectUtils;
Expand Down Expand Up @@ -71,6 +74,7 @@
import static alfio.model.Audit.EventType.*;
import static alfio.model.modification.DateTimeModification.fromZonedDateTime;
import static alfio.util.EventUtil.generateEmptyTickets;
import static alfio.util.MonetaryUtil.unitToCents;
import static alfio.util.Wrappers.optionally;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
Expand Down Expand Up @@ -371,7 +375,7 @@ private Result<Pair<TicketReservation, List<Ticket>>> createReservation(Result<L
String reservationId = UUID.randomUUID().toString();
Date validity = Date.from(arm.getExpiration().toZonedDateTime(event.getZoneId()).toInstant());
ticketReservationRepository.createNewReservation(reservationId, ZonedDateTime.now(event.getZoneId()), validity, null,
arm.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded());
arm.getLanguage(), event.getId(), event.getVat(), event.isVatIncluded(), event.getCurrency());
AdminReservationModification.CustomerData customerData = arm.getCustomerData();
ticketReservationRepository.updateTicketReservation(reservationId, TicketReservationStatus.PENDING.name(), customerData.getEmailAddress(),
customerData.getFullName(), customerData.getFirstName(), customerData.getLastName(), arm.getLanguage(),
Expand All @@ -395,10 +399,11 @@ private Result<List<Ticket>> reserveForTicketsInfo(Event event, AdminReservation
if (reservedForUpdate.size() == 0 || reservedForUpdate.size() != attendees.size()) {
return Result.error(ErrorCode.CategoryError.NOT_ENOUGH_SEATS);
}
ticketRepository.reserveTickets(reservationId, reservedForUpdate, categoryId, arm.getLanguage(), category.getSrcPriceCts());
var currencyCode = category.getCurrencyCode();
ticketRepository.reserveTickets(reservationId, reservedForUpdate, categoryId, arm.getLanguage(), category.getSrcPriceCts(), currencyCode);
Ticket ticket = ticketRepository.findById(reservedForUpdate.get(0), categoryId);
TicketPriceContainer priceContainer = TicketPriceContainer.from(ticket, null, event.getCurrency(), event.getVat(), event.getVatStatus(), null);
ticketRepository.updateTicketPrice(reservedForUpdate, categoryId, event.getId(), category.getSrcPriceCts(), MonetaryUtil.unitToCents(priceContainer.getFinalPrice()), MonetaryUtil.unitToCents(priceContainer.getVAT()), MonetaryUtil.unitToCents(priceContainer.getAppliedDiscount()));
TicketPriceContainer priceContainer = TicketPriceContainer.from(ticket, null, event.getVat(), event.getVatStatus(), null);
ticketRepository.updateTicketPrice(reservedForUpdate, categoryId, event.getId(), category.getSrcPriceCts(), unitToCents(priceContainer.getFinalPrice(), currencyCode), unitToCents(priceContainer.getVAT(), currencyCode), unitToCents(priceContainer.getAppliedDiscount(), currencyCode), currencyCode);
List<SpecialPrice> codes = category.isAccessRestricted() ? bindSpecialPriceTokens(categoryId, attendees) : Collections.emptyList();
assignTickets(event, attendees, categoryId, reservedForUpdate, codes, reservationId, arm.getLanguage(), category.getSrcPriceCts());
List<Ticket> tickets = reservedForUpdate.stream().map(id -> ticketRepository.findById(id, categoryId)).collect(toList());
Expand Down Expand Up @@ -476,7 +481,7 @@ private void assignTickets(Event event,
if(!attendee.getAdditionalInfo().isEmpty()) {
ticketFieldRepository.updateOrInsert(attendee.getAdditionalInfo(), ticketId, event.getId());
}
specialPriceIterator.map(Iterator::next).ifPresent(code -> ticketRepository.reserveTicket(reservationId, ticketId, code.getId(), userLanguage, srcPriceCts));
specialPriceIterator.map(Iterator::next).ifPresent(code -> ticketRepository.reserveTicket(reservationId, ticketId, code.getId(), userLanguage, srcPriceCts, event.getCurrency()));
}
}
}
Expand Down Expand Up @@ -581,11 +586,12 @@ public void removeTickets(String eventName, String reservationId, List<Integer>
} else {
// recalculate totals
var totalPrice = ticketReservationManager.totalReservationCostWithVAT(reservationId);
var currencyCode = totalPrice.getCurrencyCode();
var updatedTickets = ticketRepository.findTicketsInReservation(reservationId);
var calculator = new ReservationPriceCalculator(reservation, totalPrice, updatedTickets, e);
ticketReservationRepository.updateBillingData(calculator.getVatStatus(),
calculator.getSrcPriceCts(), MonetaryUtil.unitToCents(calculator.getFinalPrice()), MonetaryUtil.unitToCents(calculator.getVAT()),
MonetaryUtil.unitToCents(calculator.getAppliedDiscount()), calculator.getCurrencyCode(), reservation.getVatNr(), reservation.getVatCountryCode(),
calculator.getSrcPriceCts(), unitToCents(calculator.getFinalPrice(), currencyCode), unitToCents(calculator.getVAT(), currencyCode),
unitToCents(calculator.getAppliedDiscount(), currencyCode), calculator.getCurrencyCode(), reservation.getVatNr(), reservation.getVatCountryCode(),
reservation.isInvoiceRequested(), reservationId);
}
});
Expand Down Expand Up @@ -676,7 +682,7 @@ public Result<Boolean> refund(String eventName, String reservationId, BigDecimal
TicketReservation reservation = res.getLeft();
return reservation.getPaymentMethod() != null
&& reservation.getPaymentMethod().isSupportRefund()
&& paymentManager.refund(reservation, e, MonetaryUtil.unitToCents(refundAmount), username);
&& paymentManager.refund(reservation, e, unitToCents(refundAmount, reservation.getCurrencyCode()), username);
});
}

Expand Down
Loading