PluginProbe
Timetics – Appointment Booking Calendar & Scheduling / 1.0.64
Timetics – Appointment Booking Calendar & Scheduling v1.0.64
1.0.64 1.0.62 1.0.63 1.0.61 1.0.60 1.0.59 1.0.58 1.0.57 1.0.56 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.2 1.0.20 1.0.21 All 65 releases
← All changes | core/bookings/booking.php +475 -63 1.0.21 → 1.0.64 View file →
@@ -6,8 +6,10 @@
6 6 */
7 7
8 8 namespace Timetics\Core\Bookings;
9 9
10 +defined( 'ABSPATH' ) || exit;
11 +
10 12 use Timetics\Core\Appointments\Appointment;
11 13 use Timetics\Core\Customers\Customer;
12 14 use Timetics\Core\Integrations\Google\Service\Calendar;
13 15 use Timetics\Core\Staffs\Staff;
@@ -89,8 +91,9 @@
89 91 'recurrence' => [],
90 92 'parent' => 0,
91 93 'cancel_reason' => '',
92 94 'custom_location_url' => '',
95 + 'security_token' => ''
93 96 ];
94 97
95 98 /**
96 99 * Booking Constructor
@@ -122,10 +125,10 @@
122 125 * @return integer
123 126 */
124 127 public function get_customer_id() {
125 128 return $this->get_prop( 'customer' );
126 - }
127 -
129 + }
130 +
128 131 /**
129 132 * Get attendee id
130 133 *
131 134 * @return integer
@@ -132,10 +135,10 @@
132 135 */
133 136 public function get_attendees() {
134 137 $attendees = $this->get_prop( 'attendees' );
135 138 return $attendees ? $attendees : [];
136 - }
137 -
139 + }
140 +
138 141 /**
139 142 * Get custom url
140 143 *
141 144 * @return string
@@ -160,10 +163,10 @@
160 163 * @return integer
161 164 */
162 165 public function get_speaker_id() {
163 166 return $this->get_prop( 'speaker' );
164 - }
165 -
167 + }
168 +
166 169 /**
167 170 * Get booking type
168 171 *
169 172 * @return string
@@ -178,9 +181,9 @@
178 181 * @return integer
179 182 */
180 183 public function get_event_id() {
181 184 return $this->get_prop( 'event' );
182 - }
185 + }
183 186
184 187 /**
185 188 * Get customer
186 189 *
@@ -241,10 +244,10 @@
241 244 * @return string
242 245 */
243 246 public function get_location_type() {
244 247 return $this->get_prop( 'location_type' );
245 - }
246 -
248 + }
249 +
247 250 /**
248 251 * Get booking time
249 252 *
250 253 * @return string
@@ -354,8 +357,17 @@
354 357 return $this->get_prop( 'payment_method' );
355 358 }
356 359
357 360 /**
361 + * Get payment status
362 + *
363 + * @return string
364 + */
365 + public function get_payment_status() {
366 + return $this->get_prop( 'payment_status' );
367 + }
368 +
369 + /**
358 370 * Get appointment
359 371 *
360 372 * @return Appointment
361 373 */
@@ -526,9 +538,9 @@
526 538 *
527 539 * @return string
528 540 */
529 541 private function generate_randon_id() {
530 - return chr( rand( ord( 'A' ), ord( 'Z' ) ) ) . rand( 575639, 575639 + 400 );
542 + return chr( wp_rand( ord( 'A' ), ord( 'Z' ) ) ) . wp_rand( 575639, 575639 + 400 );
531 543 }
532 544
533 545 // Setter.
534 546
@@ -649,8 +661,190 @@
649 661 return $updated;
650 662 }
651 663
652 664 /**
665 + * Find the Booking_Entry (shared per-slot schedule record) this booking occupies.
666 + *
667 + * @param Appointment|null $meeting Optional pre-built meeting for this booking.
668 + * @param string $start_date Slot date. Defaults to the booking's own.
669 + * @param string $start_time Slot start. Defaults to the booking's own.
670 + * @param string $timezone Timezone of the two above. Defaults to the booking's own.
671 + * @return Booking_Entry|null The first matching entry, or null if none.
672 + */
673 + private function find_slot_entry( $meeting = null, $start_date = '', $start_time = '', $timezone = '' ) {
674 + $meeting = $meeting ?: new Appointment( $this->get_appointment() );
675 + $booking_entry = new Booking_Entry();
676 +
677 + $start_date = $start_date ?: $this->get_start_date();
678 + $start_time = $start_time ?: $this->get_start_time();
679 + $timezone = $timezone ?: $this->get_timezone();
680 +
681 + $date_time = timetics_convert_timezone(
682 + $start_date . ' ' . $start_time,
683 + $timezone,
684 + $meeting->get_timezone()
685 + );
686 +
687 + $entries = $booking_entry->find(
688 + [
689 + 'staff_id' => $this->get_staff_id(),
690 + 'meeting_id' => $this->get_appointment(),
691 + 'date' => $date_time->format( 'Y-m-d' ),
692 + 'start' => $date_time->format( 'h:i a' ),
693 + ]
694 + );
695 +
696 + return $entries ? $booking_entry->first() : null;
697 + }
698 +
699 + /**
700 + * Release the slot held by this booking.
701 + *
702 + * @return void
703 + */
704 + public function release_slot() {
705 + if ( ! $this->is_booking() ) {
706 + return;
707 + }
708 +
709 + // Idempotency guard: never release the same booking's slot twice.
710 + if ( $this->get_prop( 'slot_released' ) ) {
711 + return;
712 + }
713 +
714 + $this->release_slot_at( $this->get_start_date(), $this->get_start_time() );
715 +
716 + update_post_meta( $this->id, $this->meta_prefix . 'slot_released', 1 );
717 + }
718 +
719 + /**
720 + * Take back the slot this booking released.
721 + *
722 + * A declined payment marks the booking failed and releases its slot, so an
723 + * attempt the customer walks away from does not block the time forever. When
724 + * the customer retries on that same booking the slot has to be taken back
725 + * before any money moves, otherwise the booking finalizes while the slot still
726 + * reads as free and the next customer can book straight over it.
727 + *
728 + * @return bool True when the slot is held, false when it is no longer free.
729 + */
730 + public function reserve_slot() {
731 + if ( ! $this->is_booking() ) {
732 + return false;
733 + }
734 +
735 + // Nothing was released, so the slot is still held from booking creation.
736 + if ( ! $this->get_prop( 'slot_released' ) ) {
737 + return true;
738 + }
739 +
740 + $meeting = new Appointment( $this->get_appointment() );
741 + $entry = $this->find_slot_entry( $meeting );
742 + $seats = ! empty( $this->get_seat() ) ? (array) $this->get_seat() : [];
743 + $taking = max( 1, count( $seats ) );
744 +
745 + if ( $entry ) {
746 + $booked = intval( $entry->get_booked() ) + $taking;
747 +
748 + // Someone else took the time while this booking was in the failed state.
749 + if ( $booked > $meeting->get_effective_capacity() ) {
750 + return false;
751 + }
752 +
753 + $existing = ! empty( $entry->get_seats() ) ? (array) $entry->get_seats() : [];
754 +
755 + $entry->update(
756 + [
757 + 'booked' => $booked,
758 + 'seats' => array_values( array_unique( array_merge( $existing, $seats ) ) ),
759 + ]
760 + );
761 + } else {
762 + // A one-to-one release deletes the entry outright, so recreate it. Entries
763 + // are stored in the meeting's timezone, the booking's in the customer's.
764 + $start = timetics_convert_timezone( $this->get_start_date() . ' ' . $this->get_start_time(), $this->get_timezone(), $meeting->get_timezone() );
765 + $end = timetics_convert_timezone( $this->get_start_date() . ' ' . $this->get_end_time(), $this->get_timezone(), $meeting->get_timezone() );
766 +
767 + $booking_entry = new Booking_Entry();
768 +
769 + $booking_entry->create(
770 + [
771 + 'meeting_id' => $meeting->get_id(),
772 + 'staff_id' => $this->get_staff_id(),
773 + 'customer_id' => $this->get_customer(),
774 + 'booking_id' => $this->id,
775 + 'booked' => $taking,
776 + 'date' => $start->format( 'Y-m-d' ),
777 + 'start' => $start->format( 'h:i a' ),
778 + 'end' => $end->format( 'h:i a' ),
779 + 'seats' => $seats,
780 + ]
781 + );
782 + }
783 +
784 + delete_post_meta( $this->id, $this->meta_prefix . 'slot_released' );
785 +
786 + return true;
787 + }
788 +
789 + /**
790 + * Release the entry for one named slot of this booking.
791 + *
792 + * Rescheduling saves the new time first, so the old slot has to be named
793 + * rather than read off the booking. No idempotency guard: the booking lives
794 + * on and may release more slots as it moves.
795 + *
796 + * @param string $start_date Slot date, Y-m-d.
797 + * @param string $start_time Slot start.
798 + * @param string $timezone Timezone of the two above. Defaults to the booking's own.
799 + *
800 + * @return void
801 + */
802 + public function release_slot_at( $start_date, $start_time, $timezone = '' ) {
803 + if ( ! $this->is_booking() ) {
804 + return;
805 + }
806 +
807 + $meeting = new Appointment( $this->get_appointment() );
808 + $entry = $this->find_slot_entry( $meeting, $start_date, $start_time, $timezone );
809 +
810 + if ( ! $entry ) {
811 + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
812 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug-only diagnostics for a missing booking entry.
813 + error_log(
814 + sprintf(
815 + 'Timetics release_slot: no Booking_Entry found for booking #%d ( staff %s, meeting %s, slot %s %s ).',
816 + $this->id,
817 + $this->get_staff_id(),
818 + $this->get_appointment(),
819 + $start_date,
820 + $start_time
821 + )
822 + );
823 + }
824 +
825 + return;
826 + }
827 +
828 + if ( 'one-to-one' === strtolower( $meeting->get_type() ) ) {
829 + $entry->delete();
830 +
831 + return;
832 + }
833 +
834 + $booked = max( 0, intval( $entry->get_booked() ) - 1 );
835 + $booked_seat = ! empty( $this->get_seat() ) ? $this->get_seat() : [];
836 + $existing_seat = ! empty( $entry->get_seats() ) ? $entry->get_seats() : [];
837 +
838 + $entry->update(
839 + [
840 + 'booked' => $booked,
841 + 'seats' => array_values( array_diff( $existing_seat, $booked_seat ) ),
842 + ]
843 + );
844 + }
845 +
846 + /**
653 847 * Delete booking
654 848 *
655 849 * @return bool | WP_Error
656 850 */
@@ -699,46 +893,45 @@
699 893 ];
700 894
701 895 $args = wp_parse_args( $args, $defaults );
702 896
703 - $args = apply_filters( 'timetics/admin/bookings/all', $defaults, $args);
897 + $args = apply_filters( 'timetics/admin/bookings/all', $args, $defaults );
704 898
899 + $meta_query = ! empty( $args['meta_query'] ) && is_array( $args['meta_query'] ) ? $args['meta_query'] : [];
900 +
705 901 if ( ! empty( $args['start_date'] ) ) {
706 - //_tt_booking_start_date
707 - $args['meta_query'] = [
708 - [
709 - 'key' => '_tt_booking_start_date',
710 - 'value' => $args['start_date'],
711 - 'compare' => '>=',
712 - 'type' => 'DATE',
713 - ],
902 + $meta_query[] = [
903 + 'key' => '_tt_booking_start_date',
904 + 'value' => $args['start_date'],
905 + 'compare' => '>=',
906 + 'type' => 'DATE',
714 907 ];
715 908 }
716 909
717 910 if ( ! empty( $args['meeting'] ) ) {
718 - // @codingStandardsIgnoreStart
719 - $args['meta_query'] = [
720 - [
721 - 'key' => '_tt_booking_appointment',
722 - 'value' => $args['meeting'],
723 - 'compare' => '=',
724 - ],
911 + $meta_query[] = [
912 + 'key' => '_tt_booking_appointment',
913 + 'value' => $args['meeting'],
914 + 'compare' => '=',
725 915 ];
726 - // @codingStandardsIgnoreEnd
727 916 }
728 917
729 918 if ( ! empty( $args['staff'] ) ) {
730 - // @codingStandardsIgnoreStart
731 - $args['meta_query'] = [
732 - [
733 - 'key' => '_tt_booking_staff',
734 - 'value' => $args['staff'],
735 - 'compare' => '=',
736 - ],
919 + $meta_query[] = [
920 + 'key' => '_tt_booking_staff',
921 + 'value' => $args['staff'],
922 + 'compare' => '=',
737 923 ];
738 - // @codingStandardsIgnoreEnd
739 924 }
740 -
925 +
926 + if ( ! empty( $meta_query ) ) {
927 + if ( count( $meta_query ) > 1 && empty( $meta_query['relation'] ) ) {
928 + $meta_query['relation'] = 'AND';
929 + }
930 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings
931 + $args['meta_query'] = $meta_query;
932 + }
933 +
741 934 $post = new WP_Query( $args );
742 935
743 936 return [
744 937 'total' => $post->found_posts,
@@ -765,37 +958,25 @@
765 958
766 959 }
767 960
768 961 public function create_appointment_event() {
769 - $data = [
770 - 'summary' => timetics_get_option( 'booking_created_customer_email_title' ),
771 - 'description' => timetics_get_option( 'booking_created_customer_email_body' ),
772 - ];
773 -
962 + // No summary/description passed, so prepare_event() falls back to the
963 + // meeting's own name and description. The booking-created email subject
964 + // and body used to be reused here, which put the notification copy
965 + // ("New meeting scheduled!", greeting, date and time lines) into the
966 + // calendar entry instead of the meeting's details.
774 967 $calendar = new Calendar();
775 - $event_data = $this->prepare_event( $data );
968 + $event_data = $this->prepare_event();
776 969
777 970 if ( ! $event_data ) {
778 971 return;
779 972 }
780 973
781 - $booking_entry = new Booking_Entry();
782 - $meeting = new Appointment( $this->get_appointment() );
974 + $entry = $this->find_slot_entry();
783 975
784 - $date_time = timetics_convert_timezone( $this->get_start_date() .' '. $this->get_start_time(), $this->get_timezone(), $meeting->get_timezone() );
976 + $event = false;
785 977
786 - $entries = $booking_entry->find(
787 - [
788 - 'staff_id' => $this->get_staff_id(),
789 - 'meeting_id' => $this->get_appointment(),
790 - 'date' => $date_time->format('Y-m-d'),
791 - 'start' => $date_time->format('h:i a'),
792 - ]
793 - );
794 -
795 - if ( $entries ) {
796 - $entry = $booking_entry->first();
797 -
978 + if ( $entry ) {
798 979 $event = $entry->get_google_event();
799 980
800 981 if ( ! $event ) {
801 982 $event = $calendar->create_event( $event_data );
@@ -806,8 +987,15 @@
806 987 }
807 988
808 989 if ( $event ) {
809 990 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
991 +
992 + // Also record the bare event id. Google_Calendar_Sync uses this meta
993 + // to recognise events Timetics itself created, so that they are not
994 + // pulled back in as external events and used to block their own slot.
995 + if ( ! empty( $event['id'] ) ) {
996 + $this->set_google_event_id( $event['id'] );
997 + }
810 998 }
811 999 }
812 1000
813 1001 /**
@@ -819,15 +1007,13 @@
819 1007 if ( ! timetics_google_setup() ) {
820 1008 return;
821 1009 }
822 1010
823 - $data = [
824 - 'summary' => timetics_get_option( 'booking_rescheduled_customer_email_title' ),
825 - 'description' => timetics_get_option( 'booking_rescheduled_customer_email_body' ),
826 - ];
827 -
1011 + // Same as create_appointment_event(): the meeting's name and description
1012 + // belong in the calendar entry, not the reschedule email copy. Keeping
1013 + // them consistent also stops a reschedule from rewriting the title.
828 1014 $calendar = new Calendar();
829 - $event_data = $this->prepare_event( $data );
1015 + $event_data = $this->prepare_event();
830 1016 $calendar_event = $this->get_event();
831 1017
832 1018 if ( ! is_array( $calendar_event ) ) {
833 1019 return;
@@ -869,8 +1055,12 @@
869 1055 $event = $calendar->delete_event( $calendar_event['id'], $access_token );
870 1056
871 1057 // update calendar event data.
872 1058 update_post_meta( $this->id, $this->meta_prefix . 'calendar_event', $event );
1059 +
1060 + // The event no longer exists in Google, so drop the id used by
1061 + // Google_Calendar_Sync to skip Timetics-created events.
1062 + $this->set_google_event_id( '' );
873 1063 }
874 1064 }
875 1065
876 1066 /**
@@ -961,6 +1151,228 @@
961 1151 */
962 1152 private function send_notification( $action ) {
963 1153
964 1154 do_action( 'timetics_booking_notification', $this, $action );
1155 + }
1156 +
1157 + /**
1158 + * Get booking ids by appointment id
1159 + *
1160 + * @param array Collection of appointment ids
1161 + *
1162 + * @return array Collection of booking ids
1163 + */
1164 + public static function get_booking_ids_by_appointment( $appointment_ids, $per_page = -1 ) {
1165 + if ( empty( $appointment_ids ) ) {
1166 + return [];
1167 + }
1168 +
1169 + $query = new WP_Query([
1170 + 'post_type' => 'timetics-booking',
1171 + 'post_status' => 'any',
1172 + 'posts_per_page' => $per_page,
1173 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering bookings by appointment
1174 + 'meta_query' => [
1175 + [
1176 + 'key' => '_tt_booking_appointment',
1177 + 'value' => $appointment_ids,
1178 + 'compare' => 'IN',
1179 + ],
1180 + ],
1181 + ]);
1182 +
1183 + return [
1184 + 'total' => $query->found_posts,
1185 + 'items' => $query->posts,
1186 + ];
1187 + }
1188 +
1189 + /**
1190 + * Get booking ids visible to a given user.
1191 + *
1192 + * Union of:
1193 + * - bookings where the user is the assigned staff (`_tt_booking_staff`)
1194 + * - bookings whose appointment is authored by the user
1195 + *
1196 + * @param integer $user_id WP user id.
1197 + *
1198 + * @return int[] Deduped list of booking post ids.
1199 + */
1200 + public static function get_visible_ids_for_user( $user_id ) {
1201 + $user_id = (int) $user_id;
1202 +
1203 + if ( ! $user_id ) {
1204 + return [];
1205 + }
1206 +
1207 + $staff_ids = get_posts( [
1208 + 'post_type' => 'timetics-booking',
1209 + 'post_status' => 'any',
1210 + 'posts_per_page' => -1,
1211 + 'fields' => 'ids',
1212 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to current staff user
1213 + 'meta_query' => [
1214 + [
1215 + 'key' => '_tt_booking_staff',
1216 + 'value' => $user_id,
1217 + ],
1218 + ],
1219 + ] );
1220 +
1221 + $appointment_ids = Appointment::get_appointment_ids_by_author( $user_id );
1222 +
1223 + $appointment_booking_ids = [];
1224 + if ( ! empty( $appointment_ids ) ) {
1225 + $appointment_booking_ids = get_posts( [
1226 + 'post_type' => 'timetics-booking',
1227 + 'post_status' => 'any',
1228 + 'posts_per_page' => -1,
1229 + 'fields' => 'ids',
1230 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- needed to scope bookings to appointments owned by current user
1231 + 'meta_query' => [
1232 + [
1233 + 'key' => '_tt_booking_appointment',
1234 + 'value' => $appointment_ids,
1235 + 'compare' => 'IN',
1236 + ],
1237 + ],
1238 + ] );
1239 + }
1240 +
1241 + return array_values( array_unique( array_map( 'intval', array_merge( $staff_ids, $appointment_booking_ids ) ) ) );
1242 + }
1243 +
1244 + /**
1245 + * Get Google Calendar Event ID for this booking
1246 + *
1247 + * @return string
1248 + */
1249 + public function get_google_event_id() {
1250 + return $this->get_metadata( 'google_event_id' );
1251 + }
1252 +
1253 + /**
1254 + * Set Google Calendar Event ID for this booking
1255 + *
1256 + * @param string $event_id
1257 + * @return bool
1258 + */
1259 + public function set_google_event_id( $event_id ) {
1260 + // Written directly rather than through save_metadata(), which takes an
1261 + // array and only accepts keys declared in $this->data — neither is true
1262 + // here, so it silently discarded every write.
1263 + $meta_key = $this->meta_prefix . 'google_event_id';
1264 +
1265 + if ( ! $event_id ) {
1266 + return delete_post_meta( $this->id, $meta_key );
1267 + }
1268 +
1269 + return update_post_meta( $this->id, $meta_key, $event_id );
1270 + }
1271 +
1272 + public function set_sync_status( $status ) {
1273 + $meta_key = $this->meta_prefix . 'google_calendar_sync_status';
1274 +
1275 + if ( ! $status ) {
1276 + return delete_post_meta( $this->id, $meta_key );
1277 + }
1278 +
1279 + return update_post_meta( $this->id, $meta_key, $status );
1280 + }
1281 +
1282 + public function get_sync_status() {
1283 + return $this->get_metadata( 'google_calendar_sync_status' );
1284 + }
1285 + /**
1286 + * Get all Google Event IDs for all bookings
1287 + *
1288 + * @return array
1289 + */
1290 + public function get_all_google_event_ids() {
1291 + $meta_key = $this->meta_prefix . 'google_event_id';
1292 +
1293 + $posts = get_posts(
1294 + array(
1295 + 'post_type' => 'any',
1296 + 'posts_per_page' => -1,
1297 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering posts by meta key
1298 + 'meta_query' => array(
1299 + array(
1300 + 'key' => $meta_key,
1301 + 'value' => '',
1302 + 'compare' => '!=',
1303 + ),
1304 + ),
1305 + 'fields' => 'ids',
1306 + )
1307 + );
1308 +
1309 + if ( empty( $posts ) ) {
1310 + return array();
1311 + }
1312 +
1313 + $event_ids = array();
1314 + foreach ( $posts as $post_id ) {
1315 + $event_id = get_post_meta( $post_id, $meta_key, true );
1316 + if ( ! empty( $event_id ) ) {
1317 + $event_ids[] = $event_id;
1318 + }
1319 + }
1320 +
1321 + return $event_ids;
1322 + }
1323 +
1324 + /**
1325 + * Get name.
1326 + *
1327 + * @since 1.0.0
1328 + *
1329 + * @return string
1330 + */
1331 + public function get_security_token() {
1332 + return $this->get_prop( 'security_token' );
1333 + }
1334 +
1335 + /**
1336 + * Generate and store a secure reschedule token for a booking
1337 + *
1338 + * @param int $booking_id
1339 + * @return string The generated token
1340 + */
1341 + public function generate_security_token() {
1342 + $token = bin2hex(random_bytes(4)); // 8 hex chars
1343 + return $token;
1344 + }
1345 +
1346 + /**
1347 + * Rotate the booking's security token so the previously-issued one cannot
1348 + * be reused (e.g. after a payment is approved).
1349 + *
1350 + * @return void
1351 + */
1352 + public function rotate_security_token() {
1353 + $meta_key = $this->meta_prefix . 'security_token';
1354 + $new_token = $this->generate_security_token();
1355 + update_post_meta( $this->id, $meta_key, $new_token );
1356 + }
1357 +
1358 + /**
1359 + * Get the Stripe PaymentIntent id bound to this booking, if any.
1360 + *
1361 + * @return string
1362 + */
1363 + public function get_stripe_payment_intent_id() {
1364 + return (string) get_post_meta( $this->id, '_tt_stripe_payment_intent_id', true );
1365 + }
1366 +
1367 + /**
1368 + * Bind a Stripe PaymentIntent id to this booking. Used for replay
1369 + * protection: a second make_payment call with a different intent will be
1370 + * rejected.
1371 + *
1372 + * @param string $intent_id
1373 + * @return void
1374 + */
1375 + public function set_stripe_payment_intent_id( $intent_id ) {
1376 + update_post_meta( $this->id, '_tt_stripe_payment_intent_id', sanitize_text_field( (string) $intent_id ) );
965 1377 }
966 1378 }