diff --git a/frontend/projects/public/src/app/app-routing.module.ts b/frontend/projects/public/src/app/app-routing.module.ts index e86837e1c3..2941af6989 100644 --- a/frontend/projects/public/src/app/app-routing.module.ts +++ b/frontend/projects/public/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import {BookingComponent} from './reservation/booking/booking.component'; import {OverviewComponent} from './reservation/overview/overview.component'; import {SuccessComponent} from './reservation/success/success.component'; import {OfflinePaymentComponent} from './reservation/offline-payment/offline-payment.component'; +import {CustomOfflinePaymentComponent} from './reservation/custom-offline-payment/custom-offline-payment.component'; import {ViewTicketComponent} from './view-ticket/view-ticket.component'; import {ReservationGuard} from './reservation/reservation.guard'; import {ProcessingPaymentComponent} from './reservation/processing-payment/processing-payment.component'; @@ -61,6 +62,7 @@ const routes: Routes = [ { path: 'book', component: BookingComponent, canActivate: subscriptionReservationsGuard }, { path: 'overview', component: OverviewComponent, canActivate: subscriptionReservationsGuard }, { path: 'waiting-payment', component: OfflinePaymentComponent, canActivate: subscriptionReservationsGuard }, + { path: 'waiting-custom-payment', component: CustomOfflinePaymentComponent, canActivate: subscriptionReservationsGuard }, { path: 'deferred-payment', component: DeferredOfflinePaymentComponent, canActivate: subscriptionReservationsGuard }, { path: 'processing-payment', component: ProcessingPaymentComponent, canActivate: subscriptionReservationsGuard }, { path: 'success', component: SuccessSubscriptionComponent, canActivate: subscriptionReservationsGuard }, @@ -74,6 +76,7 @@ const routes: Routes = [ { path: 'overview', component: OverviewComponent, canActivate: eventReservationsGuard }, { path: 'waitingPayment', redirectTo: 'waiting-payment'}, { path: 'waiting-payment', component: OfflinePaymentComponent, canActivate: eventReservationsGuard }, + { path: 'waiting-custom-payment', component: CustomOfflinePaymentComponent, canActivate: eventReservationsGuard }, { path: 'deferred-payment', component: DeferredOfflinePaymentComponent, canActivate: eventReservationsGuard }, { path: 'processing-payment', component: ProcessingPaymentComponent, canActivate: eventReservationsGuard }, { path: 'success', component: SuccessComponent, canActivate: eventReservationsGuard }, diff --git a/frontend/projects/public/src/app/app.module.ts b/frontend/projects/public/src/app/app.module.ts index 18039a96ef..095549f38e 100644 --- a/frontend/projects/public/src/app/app.module.ts +++ b/frontend/projects/public/src/app/app.module.ts @@ -81,11 +81,13 @@ import {TicketFormComponent} from './reservation/ticket-form/ticket-form.compone import {CountdownComponent} from './countdown/countdown.component'; import {BannerCheckComponent} from './banner-check/banner-check.component'; import {OfflinePaymentComponent} from './reservation/offline-payment/offline-payment.component'; +import {CustomOfflinePaymentComponent} from './reservation/custom-offline-payment/custom-offline-payment.component'; import {OfflinePaymentProxyComponent} from './payment/offline-payment-proxy/offline-payment-proxy.component'; import {OnsitePaymentProxyComponent} from './payment/onsite-payment-proxy/onsite-payment-proxy.component'; import {PaypalPaymentProxyComponent} from './payment/paypal-payment-proxy/paypal-payment-proxy.component'; import {StripePaymentProxyComponent} from './payment/stripe-payment-proxy/stripe-payment-proxy.component'; import {SaferpayPaymentProxyComponent} from './payment/saferpay-payment-proxy/saferpay-payment-proxy.component'; +import {CustomOfflinePaymentProxyComponent} from 'projects/public/src/app/payment/custom-offline-payment-proxy/custom-offline-payment-proxy.component'; import {ProcessingPaymentComponent} from './reservation/processing-payment/processing-payment.component'; import {SummaryTableComponent} from './reservation/summary-table/summary-table.component'; import {InvoiceFormComponent} from './reservation/invoice-form/invoice-form.component'; @@ -167,11 +169,13 @@ export function InitUserService(userService: UserService): () => Promise +
+
+
+ diff --git a/frontend/projects/public/src/app/payment/custom-offline-payment-proxy/custom-offline-payment-proxy.component.ts b/frontend/projects/public/src/app/payment/custom-offline-payment-proxy/custom-offline-payment-proxy.component.ts new file mode 100644 index 0000000000..0bcaebe0a4 --- /dev/null +++ b/frontend/projects/public/src/app/payment/custom-offline-payment-proxy/custom-offline-payment-proxy.component.ts @@ -0,0 +1,47 @@ +import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core'; + +import {PaymentProvider} from '../payment-provider'; +import {UntypedFormGroup} from '@angular/forms'; +import { CustomOfflinePaymentProvider } from './custom-offline-payment-provider'; +import {PaymentMethod, PaymentProxy} from '../../model/event'; + +@Component({ + selector: 'app-custom-offline-payment-proxy', + templateUrl: './custom-offline-payment-proxy.component.html' +}) +export class CustomOfflinePaymentProxyComponent implements OnChanges { + + @Input() + method?: PaymentMethod; + + @Input() + proxy?: PaymentProxy; + + @Input() + parameters?: {[key: string]: any}; + + @Input() + overviewForm?: UntypedFormGroup; + + @Output() + paymentProvider: EventEmitter = new EventEmitter(); + + private compatibleMethods: PaymentMethod[] | string[] = ['ETRANSFER']; + + constructor() { } + + ngOnChanges(changes: SimpleChanges): void { + if (this.matchProxyAndMethod && changes['method']) { + this.paymentProvider.emit(new CustomOfflinePaymentProvider()); + } + } + + public get matchProxyAndMethod(): boolean { + if (!this.method) { + return false; + } + + return (this.compatibleMethods.includes(this.method)) && this.proxy === 'CUSTOM_OFFLINE'; + } + +} diff --git a/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.html b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.html new file mode 100644 index 0000000000..13aae17b2b --- /dev/null +++ b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.html @@ -0,0 +1,60 @@ + + +
+ +
+

{{' '}}

+

+
+ + + + + + +

+

CUSTOM OFFLINE PAYMENTS PAGE

+ + +
  • +

    + +

    +
  • +
  • +
  • +
    + +
      + +
    +
    + +
      + +
    1. +
    2. +
    +
    +
    + + +
    +

    +
    + +
    {{'reservation-page-complete.order-information' | translate: {'0': reservationId, '1': reservationInfo.firstName + ' ' + reservationInfo.lastName} }}
    +
    +
    +
    + +
    +

    +
    +
    +
    + +
    diff --git a/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.scss b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.scss new file mode 100644 index 0000000000..bdfb00ce8c --- /dev/null +++ b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.scss @@ -0,0 +1,4 @@ +.bank-account-owner-info { + padding-left: 1em; + margin-bottom: 0; +} \ No newline at end of file diff --git a/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.ts b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.ts new file mode 100644 index 0000000000..164bf66e66 --- /dev/null +++ b/frontend/projects/public/src/app/reservation/custom-offline-payment/custom-offline-payment.component.ts @@ -0,0 +1,79 @@ +import {Component, OnInit} from '@angular/core'; +import {ReservationService} from '../../shared/reservation.service'; +import {ReservationInfo} from '../../model/reservation-info'; +import {ActivatedRoute} from '@angular/router'; +import {zip} from 'rxjs'; +import {TranslateService} from '@ngx-translate/core'; +import {I18nService} from '../../shared/i18n.service'; +import {AnalyticsService} from '../../shared/analytics.service'; +import {PurchaseContext} from '../../model/purchase-context'; +import {PurchaseContextService, PurchaseContextType} from '../../shared/purchase-context.service'; +import {pollReservationStatus} from '../../shared/util'; + +@Component({ + selector: 'app-custom-offline-payment', + templateUrl: './custom-offline-payment.component.html', + styleUrls: ['./custom-offline-payment.component.scss'] +}) +export class CustomOfflinePaymentComponent implements OnInit { + + reservationInfo?: ReservationInfo; + purchaseContextType?: PurchaseContextType; + publicIdentifier?: string; + reservationId?: string; + paymentReason?: string; + + purchaseContext?: PurchaseContext; + + reservationFinalized?: boolean; + + constructor( + private route: ActivatedRoute, + private reservationService: ReservationService, + public translate: TranslateService, + private i18nService: I18nService, + private analytics: AnalyticsService, + private purchaseContextService: PurchaseContextService) { } + + public ngOnInit(): void { + zip(this.route.data, this.route.params).subscribe(([data, params]) => { + this.purchaseContextType = data['type']; + this.publicIdentifier = params[data['publicIdentifierParameter']]; + this.reservationId = params['reservationId']; + zip( + this.purchaseContextService.getContext(this.purchaseContextType, this.publicIdentifier), + this.reservationService.getReservationInfo(this.reservationId) + ).subscribe(([ev, reservationInfo]) => { + console.log("reservationInfo:", reservationInfo); + console.log("purchaseContext:", ev); + this.purchaseContext = ev; + this.reservationInfo = reservationInfo; + + this.paymentReason = `${this.reservationInfo.shortId}`; + + this.i18nService.setPageTitle('reservation-page-waiting.header.title', ev); + this.analytics.pageView(ev.analyticsConfiguration); + if (this.reservationInfo.status === 'OFFLINE_FINALIZING') { + this.reservationFinalized = false; + pollReservationStatus(this.reservationId, this.reservationService, res => { + if (res.status === 'DEFERRED_OFFLINE_PAYMENT') { + // redirect to deferred payment. Reload the page + location.reload(); + } + this.reservationInfo = res; + this.reservationFinalized = true; + }); + } else { + this.reservationFinalized = true; + } + }); + }); + } + + get invoiceAvailable(): boolean { + return this.reservationFinalized + && this.purchaseContext.invoicingConfiguration.userCanDownloadReceiptOrInvoice + && this.reservationInfo.invoiceNumber !== null; + } + +} diff --git a/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html index cbc15cc2fe..720e88b26a 100644 --- a/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html +++ b/frontend/projects/public/src/app/reservation/payment-method-selector/payment-method-selector.component.html @@ -65,6 +65,11 @@

    [method]="selectedPaymentMethod" (paymentProvider)="registerCurrentPaymentProvider($event)"> + + diff --git a/frontend/projects/public/src/app/reservation/reservation.guard.ts b/frontend/projects/public/src/app/reservation/reservation.guard.ts index 65bcc94d49..fae1a1c7ba 100644 --- a/frontend/projects/public/src/app/reservation/reservation.guard.ts +++ b/frontend/projects/public/src/app/reservation/reservation.guard.ts @@ -8,6 +8,7 @@ import {SuccessComponent} from './success/success.component'; import {OverviewComponent} from './overview/overview.component'; import {BookingComponent} from './booking/booking.component'; import {OfflinePaymentComponent} from './offline-payment/offline-payment.component'; +import {CustomOfflinePaymentComponent} from './custom-offline-payment/custom-offline-payment.component'; import {ProcessingPaymentComponent} from './processing-payment/processing-payment.component'; import {NotFoundComponent} from './not-found/not-found.component'; import {ErrorComponent} from './error/error.component'; @@ -48,6 +49,8 @@ function getRouteFromComponent(component: any, type: PurchaseContextType, public return [type, publicIdentifier, 'reservation', reservationId, 'success']; } else if (component === OfflinePaymentComponent) { return [type, publicIdentifier, 'reservation', reservationId, 'waiting-payment']; + } else if (component === CustomOfflinePaymentComponent) { + return [type, publicIdentifier, 'reservation', reservationId, 'waiting-custom-payment']; } else if (component === DeferredOfflinePaymentComponent) { return [type, publicIdentifier, 'reservation', reservationId, 'deferred-payment']; } else if (component === ProcessingPaymentComponent) { @@ -70,6 +73,7 @@ function getCorrespondingController(type: PurchaseContextType, status: Reservati case 'OFFLINE_PAYMENT': case 'OFFLINE_FINALIZING': return OfflinePaymentComponent; + case 'CUSTOM_OFFLINE_PAYMENT': return CustomOfflinePaymentComponent; case 'DEFERRED_OFFLINE_PAYMENT': return DeferredOfflinePaymentComponent; case 'EXTERNAL_PROCESSING_PAYMENT': case 'WAITING_EXTERNAL_CONFIRMATION': return ProcessingPaymentComponent; diff --git a/src/main/java/alfio/controller/IndexController.java b/src/main/java/alfio/controller/IndexController.java index 23f740d10c..d8955eb0b2 100644 --- a/src/main/java/alfio/controller/IndexController.java +++ b/src/main/java/alfio/controller/IndexController.java @@ -133,6 +133,7 @@ public ResponseEntity replyToK8s() { "/event/{eventShortName}/reservation/{reservationId}/overview", "/event/{eventShortName}/reservation/{reservationId}/waitingPayment", "/event/{eventShortName}/reservation/{reservationId}/waiting-payment", + "/event/{eventShortName}/reservation/{reservationId}/waiting-custom-payment", "/event/{eventShortName}/reservation/{reservationId}/deferred-payment", "/event/{eventShortName}/reservation/{reservationId}/processing-payment", "/event/{eventShortName}/reservation/{reservationId}/success", @@ -150,6 +151,7 @@ public ResponseEntity replyToK8s() { "/subscription/{subscriptionId}/reservation/{reservationId}/overview", "/subscription/{subscriptionId}/reservation/{reservationId}/waitingPayment", "/subscription/{subscriptionId}/reservation/{reservationId}/waiting-payment", + "/subscription/{subscriptionId}/reservation/{reservationId}/waiting-custom-payment", "/subscription/{subscriptionId}/reservation/{reservationId}/deferred-payment", "/subscription/{subscriptionId}/reservation/{reservationId}/processing-payment", "/subscription/{subscriptionId}/reservation/{reservationId}/success", diff --git a/src/main/java/alfio/manager/ReservationFinalizer.java b/src/main/java/alfio/manager/ReservationFinalizer.java index 72e8163ff9..aa86eba058 100644 --- a/src/main/java/alfio/manager/ReservationFinalizer.java +++ b/src/main/java/alfio/manager/ReservationFinalizer.java @@ -248,7 +248,7 @@ private void completeReservation(FinalizeReservation finalizeReservation) { ticketReservationRepository.setMetadata(reservationId, metadata.withFinalized(true)); Locale locale = LocaleUtil.forLanguageTag(reservation.getUserLanguage()); List tickets = null; - if(paymentProxy != PaymentProxy.OFFLINE) { + if(paymentProxy != PaymentProxy.OFFLINE && paymentProxy != PaymentProxy.CUSTOM_OFFLINE) { ticketReservationRepository.updateReservationStatus(reservationId, COMPLETE.name()); tickets = acquireItems(paymentProxy, reservationId, spec.getEmail(), spec.getCustomerName(), spec.getLocale().getLanguage(), spec.getBillingAddress(), spec.getCustomerReference(), spec.getPurchaseContext(), finalizeReservation.isSendTickets()); extensionManager.handleReservationConfirmation(reservation, ticketReservationRepository.getBillingDetailsForReservation(reservationId), spec.getPurchaseContext()); @@ -435,7 +435,8 @@ public void confirmOfflinePayment(Event event, if (!metadata.isReadyForConfirmation()) { throw new IncompatibleStateException("Reservation is not ready to be confirmed"); } - Validate.isTrue(ticketReservation.getPaymentMethod() == PaymentProxy.OFFLINE, "invalid payment method"); + Validate.isTrue(ticketReservation.getPaymentMethod() == PaymentProxy.OFFLINE + || ticketReservation.getPaymentMethod() == PaymentProxy.CUSTOM_OFFLINE, "invalid payment method"); Validate.isTrue(ticketReservation.isPendingOfflinePayment(), "invalid status"); diff --git a/src/main/java/alfio/manager/payment/CustomOfflinePaymentManager.java b/src/main/java/alfio/manager/payment/CustomOfflinePaymentManager.java new file mode 100644 index 0000000000..7b16ecc61c --- /dev/null +++ b/src/main/java/alfio/manager/payment/CustomOfflinePaymentManager.java @@ -0,0 +1,133 @@ +package alfio.manager.payment; + +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.Map; +import java.util.OptionalInt; +import java.util.Set; +import java.util.UUID; + +import alfio.manager.support.PaymentResult; +import alfio.model.transaction.PaymentContext; +import alfio.model.transaction.PaymentMethod; +import alfio.model.transaction.PaymentProvider; +import alfio.model.transaction.PaymentProxy; +import alfio.model.transaction.Transaction; +import alfio.model.transaction.TransactionRequest; +import alfio.repository.TicketReservationRepository; +import alfio.repository.TransactionRepository; +import alfio.repository.system.ConfigurationRepository; +import alfio.util.ClockProvider; + +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import static alfio.manager.TicketReservationManager.NOT_YET_PAID_TRANSACTION_ID; +import static alfio.model.TicketReservation.TicketReservationStatus.CUSTOM_OFFLINE_PAYMENT; + +@Component +public class CustomOfflinePaymentManager implements PaymentProvider { + private final ClockProvider clockProvider; + private final ConfigurationRepository configurationRepository; + private final TicketReservationRepository ticketReservationRepository; + private final TransactionRepository transactionRepository; + private static final Logger log = LoggerFactory.getLogger(CustomOfflinePaymentManager.class); + + public CustomOfflinePaymentManager( + ClockProvider clockProvider, + ConfigurationRepository configurationRepository, + TicketReservationRepository ticketReservationRepository, + TransactionRepository transactionRepository + ) { + this.clockProvider = clockProvider; + this.configurationRepository = configurationRepository; + this.ticketReservationRepository = ticketReservationRepository; + this.transactionRepository = transactionRepository; + } + + @Override + public Set getSupportedPaymentMethods(PaymentContext paymentContext, + TransactionRequest transactionRequest) { + OptionalInt orgId = paymentContext.getConfigurationLevel().getOrganizationId(); + + if(orgId.isPresent()) { + configurationRepository.findByKeyAtOrganizationLevel(orgId.getAsInt(), "CUSTOM_OFFLINE_PAYMENTS"); + } + + return Set.of(PaymentMethod.ETRANSFER); + } + + @Override + public PaymentProxy getPaymentProxy() { + return PaymentProxy.CUSTOM_OFFLINE; + } + + @Override + public boolean accept(PaymentMethod paymentMethod, PaymentContext context, TransactionRequest transactionRequest) { + // TODO Auto-generated method stub + //throw new UnsupportedOperationException("Unimplemented method 'accept'"); + return true; + } + + @Override + public boolean accept(Transaction transaction) { + // TODO Auto-generated method stub + //throw new UnsupportedOperationException("Unimplemented method 'accept'"); + return true; + } + + @Override + public PaymentMethod getPaymentMethodForTransaction(Transaction transaction) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getPaymentMethodForTransaction'"); + } + + @Override + public boolean isActive(PaymentContext paymentContext) { + //throw new UnsupportedOperationException("Unimplemented method 'isActive'"); + return true; + } + + @Override + public PaymentResult doPayment(PaymentSpecification spec) { + // TODO: Add Real Payment Processing + + // GET EVENT END TIME AS DEADLINE + ZonedDateTime deadline = spec.getPurchaseContext().event().get().getEnd(); + + // POST PONE + int updatedReservation = ticketReservationRepository.postponePayment( + spec.getReservationId(), + CUSTOM_OFFLINE_PAYMENT, + Date.from(deadline.toInstant()), + spec.getEmail(), + spec.getCustomerName().getFullName(), + spec.getCustomerName().getFirstName(), + spec.getCustomerName().getLastName(), + spec.getBillingAddress(), + spec.getCustomerReference() + ); + Validate.isTrue(updatedReservation == 1, "expected exactly one updated reservation, got " + updatedReservation); + + // OVERRIDE EXISTING TRANSACTION + PaymentManagerUtils.invalidateExistingTransactions(spec.getReservationId(), transactionRepository); + transactionRepository.insert( + UUID.randomUUID().toString(), + null, + spec.getReservationId(), + ZonedDateTime.now(clockProvider.getClock()), + spec.getPriceWithVAT(), + spec.getCurrencyCode(), + "", + PaymentProxy.OFFLINE.name(), + 0L, + 0L, + Transaction.Status.PENDING, + Map.of() + ); + + // RETURN RESULT + return PaymentResult.successful(NOT_YET_PAID_TRANSACTION_ID); + } +} diff --git a/src/main/java/alfio/model/TicketReservation.java b/src/main/java/alfio/model/TicketReservation.java index 28dc1b08c1..4eff53868b 100644 --- a/src/main/java/alfio/model/TicketReservation.java +++ b/src/main/java/alfio/model/TicketReservation.java @@ -37,6 +37,10 @@ public enum TicketReservationStatus { IN_PAYMENT, EXTERNAL_PROCESSING_PAYMENT, WAITING_EXTERNAL_CONFIRMATION, + /** + * Used for organization defined offline payment methods + */ + CUSTOM_OFFLINE_PAYMENT, OFFLINE_PAYMENT, DEFERRED_OFFLINE_PAYMENT, /** @@ -218,6 +222,7 @@ public Optional getOptionalVatPercentage() { public boolean isPendingOfflinePayment() { return status == TicketReservationStatus.OFFLINE_PAYMENT + || status == TicketReservationStatus.CUSTOM_OFFLINE_PAYMENT || status == TicketReservationStatus.DEFERRED_OFFLINE_PAYMENT; } diff --git a/src/main/java/alfio/model/transaction/PaymentMethod.java b/src/main/java/alfio/model/transaction/PaymentMethod.java index 1c28ba150c..2ee40af8d1 100644 --- a/src/main/java/alfio/model/transaction/PaymentMethod.java +++ b/src/main/java/alfio/model/transaction/PaymentMethod.java @@ -34,7 +34,8 @@ public enum PaymentMethod { PRZELEWY_24, ALIPAY, POSTFINANCE, - TWINT; + TWINT, + ETRANSFER; public static PaymentMethod safeParse(String asString) { return Arrays.stream(PaymentMethod.values()).filter(v -> v.name().equals(asString)).findFirst().orElse(null); diff --git a/src/main/java/alfio/model/transaction/PaymentProxy.java b/src/main/java/alfio/model/transaction/PaymentProxy.java index a786dad576..5606d8c05b 100644 --- a/src/main/java/alfio/model/transaction/PaymentProxy.java +++ b/src/main/java/alfio/model/transaction/PaymentProxy.java @@ -34,7 +34,8 @@ public enum PaymentProxy { ADMIN("manual", false, false, Collections.emptySet(), false, Collections.emptySet(), PaymentMethod.NONE), PAYPAL("paypal", false, true, EnumSet.of(ConfigurationKeys.SettingCategory.PAYMENT_PAYPAL), true, Collections.emptySet(), PaymentMethod.PAYPAL), MOLLIE("mollie", false, true, EnumSet.of(ConfigurationKeys.SettingCategory.PAYMENT_MOLLIE), true, Collections.emptySet(), PaymentMethod.IDEAL), - SAFERPAY("saferpay", false, true, EnumSet.of(ConfigurationKeys.SettingCategory.PAYMENT_SAFERPAY), false, Collections.emptySet(), PaymentMethod.CREDIT_CARD); + SAFERPAY("saferpay", false, true, EnumSet.of(ConfigurationKeys.SettingCategory.PAYMENT_SAFERPAY), false, Collections.emptySet(), PaymentMethod.CREDIT_CARD), + CUSTOM_OFFLINE("Custom Offline", false, true, EnumSet.of(ConfigurationKeys.SettingCategory.PAYMENT_OFFLINE), false, Collections.emptySet(), PaymentMethod.ETRANSFER); private final String description; private final boolean deskPayment; diff --git a/src/main/java/alfio/repository/TicketReservationRepository.java b/src/main/java/alfio/repository/TicketReservationRepository.java index 7f32c0f23e..c98d020781 100644 --- a/src/main/java/alfio/repository/TicketReservationRepository.java +++ b/src/main/java/alfio/repository/TicketReservationRepository.java @@ -92,7 +92,7 @@ int postponePayment(@Bind("reservationId") String reservationId, @Bind("status") @Query("update tickets_reservation set full_name = :fullName where id = :reservationId") int updateAssignee(@Bind("reservationId") String reservationId, @Bind("fullName") String fullName); - @Query("select count(id) from tickets_reservation where status in('OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT') and event_id_fk = :eventId") + @Query("select count(id) from tickets_reservation where status in('OFFLINE_PAYMENT', 'CUSTOM_OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT') and event_id_fk = :eventId") Integer findAllReservationsWaitingForPaymentCountInEventId(@Bind("eventId") int eventId); @Query("select * from tickets_reservation where status = 'OFFLINE_PAYMENT' and date_trunc('day', validity) <= :expiration and offline_payment_reminder_sent = false for update skip locked") diff --git a/src/main/java/alfio/repository/TicketSearchRepository.java b/src/main/java/alfio/repository/TicketSearchRepository.java index 45bdcd39d2..cd965511c1 100644 --- a/src/main/java/alfio/repository/TicketSearchRepository.java +++ b/src/main/java/alfio/repository/TicketSearchRepository.java @@ -134,7 +134,7 @@ List findReservationsForSubscription(@Bind("subscriptionDescr @Query("select distinct on(tr_id) "+RESERVATION_SEARCH_FIELD+", "+TRANSACTION_FIELDS+"," +PROMO_CODE_FIELDS+" from reservation_and_ticket_and_tx where tr_id in (:reservationIds) and tr_status = 'OFFLINE_PAYMENT' and bt_reservation_id is not null") List findOfflineReservationsWithTransaction(@Bind("reservationIds") List reservationIds); - @Query("select * from (select distinct on(tr_id) "+RESERVATION_SEARCH_FIELD+", "+TRANSACTION_FIELDS+"," +PROMO_CODE_FIELDS+" from reservation_and_ticket_and_tx where tr_event_id = :eventId and tr_id is not null and tr_status in('OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT')) d order by tr_registration_ts desc") + @Query("select * from (select distinct on(tr_id) "+RESERVATION_SEARCH_FIELD+", "+TRANSACTION_FIELDS+"," +PROMO_CODE_FIELDS+" from reservation_and_ticket_and_tx where tr_event_id = :eventId and tr_id is not null and tr_status in('OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT', 'CUSTOM_OFFLINE_PAYMENT')) d order by tr_registration_ts desc") List findOfflineReservationsWithOptionalTransaction(@Bind("eventId") int eventId); @Query("select distinct on(tr_id) "+RESERVATION_SEARCH_FIELD+", "+TRANSACTION_FIELDS+"," +PROMO_CODE_FIELDS+" from reservation_and_ticket_and_tx where tr_id in (:reservationIds)") diff --git a/src/main/resources/alfio/i18n/public.properties b/src/main/resources/alfio/i18n/public.properties index 1226622875..53a515b4fd 100644 --- a/src/main/resources/alfio/i18n/public.properties +++ b/src/main/resources/alfio/i18n/public.properties @@ -483,6 +483,8 @@ reservation-page.payment-method.ing-home-pay=ING Home''Pay reservation-page.payment-method.belfius=Belfius Pay Button reservation-page.payment-method.przelewy-24=Przelewy24 reservation-page.payment.mollie.description=You will be redirected to Mollie''s secure checkout to complete the purchase. +reservation-page.payment.custom-offline.description=Instructions will be provided on how to complete payment. +reservation-page.payment-method.etransfer=Interac e-Transfer reservation-page.payment.saferpay.description=You will be redirected to Saferpay''s secure checkout to complete the purchase. reservation-page.payment.select-method=Please select a payment method to continue reservation.payment-processing.delay.message=The confirmation is taking longer than expected diff --git a/src/main/webapp/alfio-admin-v1/js/admin/feature/reservations-list/reservations-list.js b/src/main/webapp/alfio-admin-v1/js/admin/feature/reservations-list/reservations-list.js index 6f0fd38ff3..e99e88737e 100644 --- a/src/main/webapp/alfio-admin-v1/js/admin/feature/reservations-list/reservations-list.js +++ b/src/main/webapp/alfio-admin-v1/js/admin/feature/reservations-list/reservations-list.js @@ -79,7 +79,7 @@ } if(loadPartially.paymentPending) { - PurchaseContextService.findAllReservations(ctrl.purchaseContextType, ctrl.purchaseContext.publicIdentifier, ctrl.currentPagePendingPayment - 1, ctrl.toSearch, ['IN_PAYMENT', 'EXTERNAL_PROCESSING_PAYMENT', 'WAITING_EXTERNAL_CONFIRMATION', 'OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT']).then(function (res) { + PurchaseContextService.findAllReservations(ctrl.purchaseContextType, ctrl.purchaseContext.publicIdentifier, ctrl.currentPagePendingPayment - 1, ctrl.toSearch, ['IN_PAYMENT', 'EXTERNAL_PROCESSING_PAYMENT', 'WAITING_EXTERNAL_CONFIRMATION', 'OFFLINE_PAYMENT', 'CUSTOM_OFFLINE_PAYMENT', 'DEFERRED_OFFLINE_PAYMENT']).then(function (res) { ctrl.paymentPendingReservations = res.data.left; ctrl.paymentPendingFoundReservations = res.data.right; });