@@ -48,9 +58,7 @@ class="ticket_form_label"
class="ticket_field"
size="7"
value=""
- data-validation-is-greater-than="0"
- data-validation-is-less-than="#ticket_price"
- data-validation-error=""
+
/>
diff --git a/src/admin-views/editor/fieldset/price.php b/src/admin-views/editor/fieldset/price.php
index 435aae34ad..a09f2efd54 100644
--- a/src/admin-views/editor/fieldset/price.php
+++ b/src/admin-views/editor/fieldset/price.php
@@ -4,13 +4,7 @@
$post_id = get_the_ID();
}
-$validation_attrs = [
- 'data-validation-error="' . esc_attr( sprintf(
- // Translators: %s: singular version of the Ticket label.
- _x( '%s price must be greater than zero.', 'ticket price validation error', 'event-tickets' ),
- tribe_get_ticket_label_singular( 'ticket_price_validation_error' )
- ) ) . '"'
-];
+$validation_attrs = [];
$ticket = null;
$is_paypal_ticket = false;
@@ -23,11 +17,29 @@
$is_paypal_ticket = $provider instanceof Tribe__Tickets__Commerce__PayPal__Main || $provider instanceof \TEC\Tickets\Commerce\Module;
+// Determine whether or not free tickets are allowed.
+$is_free_ticket_allowed = true;
+
+if ( $provider instanceof Tribe__Tickets__Commerce__PayPal__Main ) {
+ $is_free_ticket_allowed = false;
+}
+
+if ( $provider instanceof \TEC\Tickets\Commerce\Module ) {
+ $is_free_ticket_allowed = tec_tickets_commerce_is_free_ticket_allowed();
+}
+
$description_string = sprintf( _x( 'Leave blank for free %s', 'price description', 'event-tickets' ), tribe_get_ticket_label_singular( 'price_description' ) );
$description_string = esc_html( apply_filters( 'tribe_tickets_price_description', $description_string, $ticket_id ) );
-$price_description = $is_paypal_ticket ? '' : $description_string;
+$price_description = ! $is_free_ticket_allowed ? '' : $description_string;
-if ( $is_paypal_ticket ) {
+if ( ! $is_free_ticket_allowed ) {
+ $validation_attrs[] = 'data-validation-error="' . esc_attr(
+ sprintf(
+ // Translators: %s: singular version of the Ticket label.
+ _x( '%s price must be greater than zero.', 'ticket price validation error', 'event-tickets' ),
+ tribe_get_ticket_label_singular( 'ticket_price_validation_error' )
+ )
+ ) . '"';
$validation_attrs[] = 'data-required';
$validation_attrs[] = 'data-validation-is-greater-than="0"';
}
diff --git a/src/functions/attendees/provider.php b/src/functions/attendees/provider.php
index c0ea858e78..886932a4fa 100644
--- a/src/functions/attendees/provider.php
+++ b/src/functions/attendees/provider.php
@@ -9,7 +9,7 @@
* In order the function will check the `TEC_TICKETS_ATTENDEES_PAGE` constant,
* the `TEC_TICKETS_ATTENDEES_PAGE` environment variable,
*
- * @since TBD
+ * @since 5.10.0
*
* @return bool Whether "Attendees" page is enabled or not.
*/
@@ -26,7 +26,7 @@ function tec_tickets_attendees_page_is_enabled(): bool {
/**
* Allows filtering of the Attendees page provider.
*
- * @since TBD
+ * @since 5.10.0
*
* @param boolean $enabled Determining if the "Attendees" page is enabled.
*/
diff --git a/src/functions/commerce/tickets.php b/src/functions/commerce/tickets.php
index db5dbb576e..9e31b476c9 100644
--- a/src/functions/commerce/tickets.php
+++ b/src/functions/commerce/tickets.php
@@ -6,6 +6,7 @@
*/
use TEC\Tickets\Commerce\Models\Ticket_Model;
+use TEC\Tickets\Commerce\Settings;
/**
* Fetches and returns a decorated post object representing an ticket.
@@ -126,3 +127,14 @@ function tec_tc_get_ticket( $ticket = null, $output = OBJECT, $filter = 'raw', $
return $post;
}
+
+/**
+ * Checks if the free ticket is allowed.
+ *
+ * @since 5.10.0
+ *
+ * @return bool
+ */
+function tec_tickets_commerce_is_free_ticket_allowed() {
+ return Settings::is_free_ticket_allowed();
+}
diff --git a/src/modules/data/blocks/ticket/constants.js b/src/modules/data/blocks/ticket/constants.js
index 542a4cfdc7..03be05d95b 100644
--- a/src/modules/data/blocks/ticket/constants.js
+++ b/src/modules/data/blocks/ticket/constants.js
@@ -52,3 +52,6 @@ export const PRICE_POSITIONS = [ PREFIX, SUFFIX ];
// eslint-disable-next-line no-undef
export const TICKET_LABELS = window?.tribe_editor_config?.tickets?.ticketLabels;
export const SALE_PRICE_LABELS = window?.tribe_editor_config?.tickets?.salePrice;
+
+// eslint-disable-next-line max-len
+export const IS_FREE_TC_TICKET_ALLOWED = window?.tribe_editor_config?.tickets?.commerce?.isFreeTicketAllowed;
diff --git a/src/modules/data/blocks/ticket/selectors.js b/src/modules/data/blocks/ticket/selectors.js
index 5ab1b6a45f..8b9546a16c 100644
--- a/src/modules/data/blocks/ticket/selectors.js
+++ b/src/modules/data/blocks/ticket/selectors.js
@@ -17,6 +17,7 @@ const {
INDEPENDENT,
SHARED,
TICKET_TYPES,
+ IS_FREE_TC_TICKET_ALLOWED,
} = constants;
const { tickets: ticketsConfig, post: postConfig } = globals;
@@ -716,8 +717,16 @@ export const isTempSharedCapacityValid = createSelector(
export const isZeroPriceValid = createSelector(
[ getTicketTempPrice, getTicketsProvider ],
( price, provider ) => {
- return 0 < parseInt( price, 10 ) ||
- ! [ constants.TC_CLASS, constants.TICKETS_COMMERCE_MODULE_CLASS ].includes( provider );
+ if ( 0 < parseInt( price, 10 ) ) {
+ return true;
+ }
+ if ( constants.TC_CLASS === provider ) {
+ return false;
+ }
+ if ( constants.TICKETS_COMMERCE_MODULE_CLASS === provider ) {
+ return IS_FREE_TC_TICKET_ALLOWED;
+ }
+ return true;
},
);
diff --git a/src/resources/js/admin/tickets-attendees.js b/src/resources/js/admin/tickets-attendees.js
index 231f253216..6ea52065ce 100644
--- a/src/resources/js/admin/tickets-attendees.js
+++ b/src/resources/js/admin/tickets-attendees.js
@@ -1,7 +1,7 @@
/**
* Makes sure we have all the required levels on the Tribe Object
*
- * @since TBD
+ * @since 5.10.0
* @type {Object}
*/
tribe.tickets = tribe.tickets || {};
@@ -11,7 +11,7 @@ tribe.dialogs.events = tribe.dialogs.events || {};
/**
* Configures ET Attendees Object in the Global Tribe variable
*
- * @since TBD
+ * @since 5.10.0
* @type {Object}
*/
tribe.tickets.attendees = {};
@@ -19,7 +19,7 @@ tribe.tickets.attendees = {};
/**
* Initializes in a Strict env the code that manages the plugin Attendees library.
*
- * @since TBD
+ * @since 5.10.0
* @param {Object} $ jQuery
* @param {Object} obj tribe.tickets.attendees
* @return {void}
@@ -30,7 +30,7 @@ tribe.tickets.attendees = {};
/*
* Manual Attendees Selectors.
*
- * @since TBD
+ * @since 5.10.0
*/
obj.selectors = {
modalWrapper: '.tribe-modal__wrapper--attendee-details',
@@ -42,7 +42,7 @@ tribe.tickets.attendees = {};
/**
* Handler for when the modal is being "closed".
*
- * @since TBD
+ * @since 5.10.0
* @param {Object} event The close event.
* @param {Object} dialogEl The dialog element.
* @return {void}
@@ -57,7 +57,7 @@ tribe.tickets.attendees = {};
/**
* Bind handler for when the modal is being "closed".
*
- * @since TBD
+ * @since 5.10.0
* @return {void}
*/
obj.bindModalClose = function() {
@@ -70,7 +70,7 @@ tribe.tickets.attendees = {};
/**
* Unbinds events for the modal content container.
*
- * @since TBD
+ * @since 5.10.0
* @param {jQuery} $container jQuery object of the container.
*/
obj.unbindModalEvents = function( $container ) {
@@ -81,7 +81,7 @@ tribe.tickets.attendees = {};
/**
* Handler for when the modal is opened.
*
- * @since TBD
+ * @since 5.10.0
* @param {Object} event The show event.
* @param {Object} dialogEl The dialog element.
* @param {Object} trigger The event.
@@ -130,7 +130,7 @@ tribe.tickets.attendees = {};
/**
* Get context to send on the request.
*
- * @since TBD
+ * @since 5.10.0
* @return {Object}
*/
obj.getContext = function() {
@@ -142,7 +142,7 @@ tribe.tickets.attendees = {};
/**
* Bind handler for when the modal is being "opened".
*
- * @since TBD
+ * @since 5.10.0
* @return {void}
*/
obj.bindModalOpen = function() {
@@ -155,7 +155,7 @@ tribe.tickets.attendees = {};
/**
* Handles the initialization of the scripts when Document is ready.
*
- * @since TBD
+ * @since 5.10.0
* @return {void}
*/
obj.ready = function() {
diff --git a/src/resources/js/commerce/gateway/free/checkout.js b/src/resources/js/commerce/gateway/free/checkout.js
new file mode 100644
index 0000000000..ce0e96af4f
--- /dev/null
+++ b/src/resources/js/commerce/gateway/free/checkout.js
@@ -0,0 +1,248 @@
+/* global tribe, jQuery, tecTicketsCommerceGatewayFreeCheckout */
+
+/**
+ * Path to this script in the global tribe Object.
+ *
+ * @since 5.10.0
+ *
+ * @type {Object}
+ */
+tribe.tickets.commerce.gateway.free = tribe.tickets.commerce.gateway.free || {};
+
+/**
+ * This script Object for public usage of the methods.
+ *
+ * @since 5.10.0
+ *
+ * @type {Object}
+ */
+tribe.tickets.commerce.gateway.free.checkout = {};
+
+( ( $, obj, ky ) => {
+ 'use strict';
+
+ /**
+ * Pull the variables from the PHP backend.
+ *
+ * @since 5.10.0
+ *
+ * @type {Object}
+ */
+ obj.checkout = tecTicketsCommerceGatewayFreeCheckout;
+
+ /**
+ * Checkout Selectors.
+ *
+ * @since 5.10.0
+ *
+ * @type {Object}
+ */
+ obj.selectors = {
+ submitButton: '#tec-tc-gateway-free-checkout-button',
+ hiddenElement: '.tribe-common-a11y-hidden'
+ };
+
+ /**
+ * Loader container.
+ *
+ * @since 5.10.0
+ *
+ * @type {Object|null}
+ */
+ obj.checkoutContainer = null;
+
+ /**
+ * Preventing errors to be thrown when using Ky
+ *
+ * @since 5.10.0
+ *
+ * @param {Object} error
+ *
+ * @return {*}
+ */
+ obj.onBeforeRetry = async ( error ) => {
+ console.log( error );
+
+ return ky.stop;
+ };
+
+ /**
+ * Preventing errors to be thrown when using Ky
+ *
+ * @since 5.10.0
+ *
+ * @param {Object} error
+ *
+ * @return {*}
+ */
+ obj.onBeforeError = async ( error ) => {
+ console.log( error );
+
+ return ky.stop;
+ };
+
+ /**
+ * Get the request arguments to setup the calls.
+ *
+ * @since 5.10.0
+ *
+ * @param data
+ * @param headers
+ *
+ * @return {{headers: {"X-WP-Nonce"}, throwHttpErrors: boolean, json, hooks: {beforeError: (function(*): *)[]}}}
+ */
+ obj.getRequestArgs = ( data, headers ) => {
+ if ( 'undefined' === typeof headers ) {
+ headers = {
+ 'X-WP-Nonce': obj.checkout.nonce
+ };
+ }
+
+ const args = {
+ headers: headers,
+ hooks: {
+ beforeRetry: [
+ obj.onBeforeRetry
+ ],
+ beforeError: [
+ obj.onBeforeError
+ ]
+ },
+ timeout: 30000,
+ throwHttpErrors: false
+ };
+
+ if ( data ) {
+ args.json = data;
+ }
+
+ return args;
+ };
+
+ /**
+ * Hides the notice for the checkout container.
+ *
+ * @since 5.10.0
+ *
+ * @param {jQuery} $container Parent container of notice element.
+ */
+ obj.hideNotice = ( $container ) => {
+ if ( ! $container.length ) {
+ $container = $( tribe.tickets.commerce.selectors.checkoutContainer );
+ }
+
+ const notice = tribe.tickets.commerce.notice;
+ const $item = $container.find( notice.selectors.item );
+ notice.hide( $item );
+ };
+
+ /**
+ * Shows the notice for the checkout container.
+ *
+ * @since 5.10.0
+ *
+ * @param {jQuery} $container Parent container of notice element.
+ * @param {string} title Notice Title.
+ * @param {string} content Notice message content.
+ */
+ obj.showNotice = ( $container, title, content ) => {
+ if ( ! $container || ! $container.length ) {
+ $container = $( tribe.tickets.commerce.selectors.checkoutContainer );
+ }
+ const notice = tribe.tickets.commerce.notice;
+ const $item = $container.find( notice.selectors.item );
+ notice.populate( $item, title, content );
+ notice.show( $item );
+ };
+
+ /**
+ * Toggle the submit button enabled/disabled
+ *
+ * @param enable
+ */
+ obj.submitButton = ( enable ) => {
+ $( obj.selectors.submitButton ).prop( 'disabled', ! enable );
+ };
+
+ /**
+ * Starts the process to submit a payment.
+ *
+ * @since 5.10.0
+ *
+ * @param {Event} event The Click event from the payment.
+ */
+ obj.handlePayment = async ( event ) => {
+ event.preventDefault();
+
+ obj.checkoutContainer = $( event.target ).closest( tribe.tickets.commerce.selectors.checkoutContainer );
+
+ obj.hideNotice( obj.checkoutContainer );
+
+ tribe.tickets.loader.show( obj.checkoutContainer );
+
+ let order = await obj.handleCreateOrder();
+ obj.submitButton( false );
+
+ if ( order.success ) {
+ window.location.replace( order.redirect_url );
+ } else {
+ tribe.tickets.loader.hide( obj.checkoutContainer );
+ obj.showNotice( {}, order.message, '' );
+ }
+
+ obj.submitButton( true );
+ };
+
+ /**
+ * Create an order and start the payment process.
+ *
+ * @since 5.10.0
+ *
+ * @return {Promise<*>}
+ */
+ obj.handleCreateOrder = async () => {
+ const args = obj.getRequestArgs( {
+ purchaser: obj.getPurchaserData(),
+ } );
+ let response;
+
+ try {
+ response = await tribe.ky.post( obj.checkout.orderEndpoint, args ).json();
+ } catch( error ) {
+ response = error;
+ }
+
+ tribe.tickets.debug.log( 'free', 'createOrder', response );
+
+ return response;
+ };
+
+ /**
+ * Get purchaser form data.
+ *
+ * @since 5.10.0
+ *
+ * @return {Object}
+ */
+ obj.getPurchaserData = () => tribe.tickets.commerce.getPurchaserData( $( tribe.tickets.commerce.selectors.purchaserFormContainer ) );
+
+ /**
+ * Bind script loader to trigger script dependent methods.
+ *
+ * @since 5.10.0
+ */
+ obj.bindEvents = () => {
+ $( obj.selectors.submitButton ).on( 'click', obj.handlePayment );
+ };
+
+ /**
+ * When the page is ready.
+ *
+ * @since 5.10.0
+ */
+ obj.ready = () => {
+ obj.bindEvents();
+ };
+
+ $( obj.ready );
+} )( jQuery, tribe.tickets.commerce.gateway.free, tribe.ky );
diff --git a/src/resources/postcss/tickets-admin/attendees/_modal.pcss b/src/resources/postcss/tickets-admin/attendees/_modal.pcss
index ecf6ab1b13..de0c7a61a8 100644
--- a/src/resources/postcss/tickets-admin/attendees/_modal.pcss
+++ b/src/resources/postcss/tickets-admin/attendees/_modal.pcss
@@ -1,7 +1,7 @@
/**
* Event Tickets Admin Attendees - Modal
*
- * @since TBD
+ * @since 5.10.0
*/
.event-tickets {
diff --git a/src/resources/postcss/tickets-commerce/gateway/free.pcss b/src/resources/postcss/tickets-commerce/gateway/free.pcss
new file mode 100644
index 0000000000..065b29edc2
--- /dev/null
+++ b/src/resources/postcss/tickets-commerce/gateway/free.pcss
@@ -0,0 +1,8 @@
+/**
+ * Event Tickets - Tickets Commerce Free Gateway Stylesheet.
+ *
+ * @since 5.10.0
+ */
+
+@import '../../tickets-common.pcss';
+@import 'free/_all.pcss';
diff --git a/src/resources/postcss/tickets-commerce/gateway/free/_all.pcss b/src/resources/postcss/tickets-commerce/gateway/free/_all.pcss
new file mode 100644
index 0000000000..ac56a1208c
--- /dev/null
+++ b/src/resources/postcss/tickets-commerce/gateway/free/_all.pcss
@@ -0,0 +1,10 @@
+/**
+ * Event Tickets - Tickets Commerce Free Gateway Stylesheet.
+ *
+ * @since 5.10.0
+ */
+.event-tickets {
+ .tribe-tickets__commerce-checkout-section-header {
+ display: none;
+ }
+}
diff --git a/src/resources/postcss/tickets/_block.pcss b/src/resources/postcss/tickets/_block.pcss
index 3a604ef214..8a826a33c4 100644
--- a/src/resources/postcss/tickets/_block.pcss
+++ b/src/resources/postcss/tickets/_block.pcss
@@ -125,6 +125,7 @@
border-radius: var(--tec-spacer-2);
color: var(--tec-color-icon-focus);
display: inline-block;
+ font-family: var(--tec-font-family-sans-serif);
font-size: var(--tec-font-size-0);
font-weight: var(--tec-font-weight-bold);
margin-bottom: 5px;
diff --git a/src/views/emails/template-parts/body/order/order-gateway-data.php b/src/views/emails/template-parts/body/order/order-gateway-data.php
index 7552dfdd23..75603e2110 100644
--- a/src/views/emails/template-parts/body/order/order-gateway-data.php
+++ b/src/views/emails/template-parts/body/order/order-gateway-data.php
@@ -12,6 +12,7 @@
* @version 5.6.0
*
* @since 5.6.0
+ * @since 5.10.0 Don't show if gateway order number is same as regular order number.
*
* @var Tribe__Template $this Current template object.
* @var \TEC\Tickets\Emails\Email_Abstract $email The email object.
@@ -28,6 +29,11 @@
return;
}
+// No need to show gateway ID if it's the same as the order ID.
+if ( is_numeric( $order->gateway_order_id ) && intval( $order->ID ) === intval( $order->gateway_order_id ) ) {
+ return;
+}
+
$gateway = tribe( Manager::class )->get_gateway_by_key( $order->gateway );
$link_or_id = $order->gateway_order_id;
if ( $gateway ) {
diff --git a/src/views/emails/template-parts/body/order/order-total.php b/src/views/emails/template-parts/body/order/order-total.php
index 3c188e7d08..36356e9863 100644
--- a/src/views/emails/template-parts/body/order/order-total.php
+++ b/src/views/emails/template-parts/body/order/order-total.php
@@ -12,6 +12,7 @@
* @version 5.5.11
*
* @since 5.5.11
+ * @since 5.10.0 Allow for zero value total.
*
* @var Tribe__Template $this Current template object.
* @var \TEC\Tickets\Emails\Email_Abstract $email The email object.
@@ -23,7 +24,7 @@
* @var \WP_Post $order The order object.
*/
-if ( empty( $order ) || empty( $order->total ) ) {
+if ( empty( $order ) || empty( $order->total_value ) ) {
return;
}
diff --git a/src/views/emails/template-parts/body/order/payment-info.php b/src/views/emails/template-parts/body/order/payment-info.php
index ceb706ff25..7107dbf7e1 100644
--- a/src/views/emails/template-parts/body/order/payment-info.php
+++ b/src/views/emails/template-parts/body/order/payment-info.php
@@ -13,6 +13,7 @@
*
* @since 5.5.11
* @since 5.6.4 Capitalize payment gateway name.
+ * @since 5.10.0 Don't show if gateway name is blank.
*
* @var Tribe__Template $this Current template object.
* @var \TEC\Tickets\Emails\Email_Abstract $email The email object.
@@ -30,6 +31,10 @@
$gateway_name = tribe( TEC\Tickets\Commerce\Order::class )->get_gateway_label( $order );
+if ( empty( $gateway_name ) ) {
+ return;
+}
+
$payment_info = empty( $order->status_slug ) || 'completed' !== strtolower( $order->status_slug ) ?
sprintf(
// Translators: %s - Payment provider's name.
diff --git a/src/views/v2/commerce/gateway/free/button.php b/src/views/v2/commerce/gateway/free/button.php
new file mode 100644
index 0000000000..10cc573baf
--- /dev/null
+++ b/src/views/v2/commerce/gateway/free/button.php
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/src/views/v2/commerce/gateway/free/container.php b/src/views/v2/commerce/gateway/free/container.php
new file mode 100644
index 0000000000..7927a52bab
--- /dev/null
+++ b/src/views/v2/commerce/gateway/free/container.php
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/src/views/v2/commerce/order/details/payment-method.php b/src/views/v2/commerce/order/details/payment-method.php
index 2082a4f1f6..9ad82273e8 100644
--- a/src/views/v2/commerce/order/details/payment-method.php
+++ b/src/views/v2/commerce/order/details/payment-method.php
@@ -7,13 +7,14 @@
*
* See more documentation about our views templating system.
*
- * @link https://evnt.is/1amp Help article for RSVP & Ticket template files.
+ * @link https://evnt.is/1amp Help article for RSVP & Ticket template files.
*
- * @since 5.1.10
+ * @since 5.1.10
*
* @since 5.2.0 Added Payment method label.
+ * @since 5.10.0 Check if payment method is empty before rendering.
*
- * @version 5.1.10
+ * @version 5.10.0
*
* @var \Tribe__Template $this [Global] Template object.
* @var Module $provider [Global] The tickets provider instance.
@@ -24,7 +25,9 @@
* @var string $payment_method [Global] The payment method label.
*/
-if ( empty( $order->gateway ) ) {
+use TEC\Tickets\Commerce\Module;
+
+if ( empty( $payment_method ) ) {
return;
}
diff --git a/src/views/v2/commerce/ticket/regular-price.php b/src/views/v2/commerce/ticket/regular-price.php
index 51d7840db1..0519107690 100644
--- a/src/views/v2/commerce/ticket/regular-price.php
+++ b/src/views/v2/commerce/ticket/regular-price.php
@@ -23,4 +23,4 @@
return;
}
-echo esc_html( $price->get_currency() );
+echo esc_html( $price->get_currency_display() );
diff --git a/src/views/v2/commerce/ticket/sale-price.php b/src/views/v2/commerce/ticket/sale-price.php
index ea7166baa4..ebad701ddb 100644
--- a/src/views/v2/commerce/ticket/sale-price.php
+++ b/src/views/v2/commerce/ticket/sale-price.php
@@ -21,11 +21,12 @@
if ( empty( $on_sale ) ) {
return;
}
+
?>
- get_currency() ); ?>
+ get_currency_display() ); ?>
diff --git a/tests/ft_integration/Tribe/Tickets/__snapshots__/MetaboxTest__test_get_panels__post with RSVP__0.snapshot.html b/tests/ft_integration/Tribe/Tickets/__snapshots__/MetaboxTest__test_get_panels__post with RSVP__0.snapshot.html
index 2e408be6fa..b5a7a73075 100644
--- a/tests/ft_integration/Tribe/Tickets/__snapshots__/MetaboxTest__test_get_panels__post with RSVP__0.snapshot.html
+++ b/tests/ft_integration/Tribe/Tickets/__snapshots__/MetaboxTest__test_get_panels__post with RSVP__0.snapshot.html
@@ -431,8 +431,10 @@
Ticket Settings
class="ticket_field ticket_form_right"
size="7"
value=""
- data-validation-error="Ticket price must be greater than zero." data-required data-validation-is-greater-than="0" />
-