mailer()->get_emails(); return isset( $emails['WC_Email_Customer_Note'] ) && $emails['WC_Email_Customer_Note']->is_enabled(); } public static function online_payment_ready(): bool { $ready = false; foreach ( WC()->payment_gateways()->payment_gateways() as $gateway ) { if ( $gateway->enabled === 'yes' && ! in_array( $gateway->id, array( 'bacs', 'cheque', 'cod' ), true ) && $gateway->supports( 'products' ) ) { $ready = true; break; } } return (bool) apply_filters( 'storesuit_online_payment_ready', $ready ); } private static function compose( int $order_id, array $data ): array { Store::require_cap( 'storesuit_orders' ); $order = Orders::get( $order_id ); $to = $order->get_billing_email(); if ( ! is_email( $to ) ) { throw new \InvalidArgumentException( I18n::t( 'Add a valid customer email address to the order first.' ) ); } if ( ! self::email_ready() ) { throw new \RuntimeException( I18n::t( 'Enable the WooCommerce customer note email before sending from StoreRuler.' ) ); } $kind = Store::text( $data, 'kind' ); $shipment_id = ''; $name = sanitize_text_field( $order->get_formatted_billing_full_name() ); $number = $order->get_order_number(); if ( $kind === 'payment_link' ) { if ( ! self::online_payment_ready() ) { throw new \RuntimeException( I18n::t( 'Enable an online payment method before sending this payment link.' ) ); } $summary = Orders::summary( $order ); if ( ! $summary['payment_url'] || ! Orders::usable_payment_link( $order ) ) { throw new \InvalidArgumentException( I18n::t( 'This order has no active payment link to send.' ) ); } $amount = html_entity_decode( wp_strip_all_tags( wc_price( $summary['outstanding'], array( 'currency'=>$summary['currency'] ) ) ), ENT_QUOTES, 'UTF-8' ); $body = I18n::format( 'Hello %s, for order #%s you can pay %s on our secure payment page:', $name, $number, $amount ) . "\n" . $summary['payment_url']; } elseif ( $kind === 'tracking' ) { $shipment_id = Store::text( $data, 'shipment_id' ); if ( $shipment_id === '' || strlen( $shipment_id ) > 80 ) { throw new \InvalidArgumentException( I18n::t( 'Choose a shipment with a tracking number.' ) ); } $shipment = null; foreach ( (array) $order->get_meta( '_storesuit_shipments' ) as $row ) { if ( is_array( $row ) && (string) ( $row['id'] ?? '' ) === $shipment_id ) { $shipment = $row; break; } } if ( ! $shipment || empty( $shipment['tracking'] ) || ( $shipment['mode'] ?? '' ) === 'handover' ) { throw new \InvalidArgumentException( I18n::t( 'Choose a shipment with a tracking number.' ) ); } $body = I18n::format( 'Hello %s, your order #%s was shipped with %s.', $name, $number, sanitize_text_field( (string) ( $shipment['carrier_label'] ?? I18n::t( 'Carrier' ) ) ) ) . "\n" . I18n::format( 'Tracking number: %s', sanitize_text_field( (string) $shipment['tracking'] ) ); if ( ! empty( $shipment['tracking_url'] ) ) { $body .= "\n" . I18n::format( 'Carrier tracking page: %s', esc_url_raw( $shipment['tracking_url'] ) ); } } else { throw new \InvalidArgumentException( I18n::t( 'Choose a supported customer message.' ) ); } return array( 'kind'=>$kind, 'shipment_id'=>$shipment_id, 'to'=>$to, 'body'=>$body ); } private static function signature( int $order_id, array $message, int $expires ): string { return hash_hmac( 'sha256', wp_json_encode( array( $order_id, get_current_user_id(), $message, $expires ) ), wp_salt( 'auth' ) ); } public static function preview( int $order_id, array $data ): array { $message = self::compose( $order_id, $data ); $expires = time() + 600; return $message + array( 'expires'=>$expires, 'token'=>self::signature( $order_id, $message, $expires ), 'delivery_note'=>I18n::t( 'WooCommerce will email this customer-visible order note. Delivery to the inbox is not verified here.' ) ); } public static function history( \WC_Order $order ): array { $rows = $order->get_meta( '_storesuit_message_history' ); return is_array( $rows ) ? array_values( array_slice( $rows, -20 ) ) : array(); } public static function send( int $order_id, array $data ): array { if ( empty( $data['confirmed'] ) ) { throw new \InvalidArgumentException( I18n::t( 'Review the recipient and message before sending.' ) ); } $message = self::compose( $order_id, $data ); $expires = absint( $data['expires'] ?? 0 ); $token = Store::text( $data, 'token' ); if ( $expires < time() || $expires > time() + 600 || ! hash_equals( self::signature( $order_id, $message, $expires ), $token ) ) { throw new \RuntimeException( I18n::t( 'The order or message changed. Review the message again.' ) ); } $request_key = Store::text( $data, 'request_key' ); if ( ! preg_match( '/^[a-zA-Z0-9_-]{16,80}$/', $request_key ) ) { throw new \InvalidArgumentException( I18n::t( 'The action key is missing. Refresh the page.' ) ); } $option = 'storesuit_message_request_' . hash( 'sha256', $order_id . '|' . $message['kind'] . '|' . $message['shipment_id'] . '|' . $request_key ); return Store::lock( $option, static function () use ( $order_id, $option, $message ) { $saved = get_option( $option, false ); if ( is_array( $saved ) ) { if ( ( $saved['status'] ?? '' ) === 'submitted' ) { return $saved; } throw new \RuntimeException( I18n::t( 'This message request needs review before another attempt.' ) ); } $pending = array( 'status'=>'pending', 'kind'=>$message['kind'], 'at'=>current_time( 'mysql' ) ); if ( ! add_option( $option, $pending, '', false ) ) { throw new \RuntimeException( I18n::t( 'This message request needs review before another attempt.' ) ); } $order = Orders::get( $order_id ); $note_id = $order->add_order_note( $message['body'], true ); if ( ! $note_id ) { update_option( $option, array( 'status'=>'attention', 'kind'=>$message['kind'], 'at'=>$pending['at'] ), false ); throw new \RuntimeException( I18n::t( 'The email request could not be added. Check the order before trying again.' ) ); } $result = array( 'status'=>'submitted', 'kind'=>$message['kind'], 'at'=>$pending['at'] ); update_option( $option, $result, false ); $rows = self::history( $order ); $rows[] = $result; $order->update_meta_data( '_storesuit_message_history', array_slice( $rows, -20 ) ); $order->save(); Store::audit( 'order', $order_id, 'customer_message_submitted', array( 'kind'=>$message['kind'] ) ); return $result; } ); } }