get_payment_method(); } /** * On a new checkout order: queue the COD confirmation when it applies. * Returns true when it did (caller then skips "Order Placed"). * * @param \WC_Order $order Order. * * @return bool */ public static function handle_new_order( $order ): bool { try { if ( ! self::enabled() || ! self::is_cod_order( $order ) ) { return false; } $order->update_meta_data( self::META_STATUS, 'pending' ); $order->save_meta_data(); SixRsh_Order_Hooks::dispatch( $order, self::EVENT ); return true; } catch ( \Throwable $e ) { error_log( '[6rsh] COD::handle_new_order exception: ' . $e->getMessage() ); return false; } } /** * Hold back early status messages while the customer hasn't answered. * * @param int $order_id Order ID. * @param string $status Status without wc- prefix. * * @return bool True = do not send this status notification. */ public static function suppress_status( int $order_id, string $status ): bool { if ( ! in_array( $status, array( 'pending', 'processing', 'on-hold' ), true ) ) { return false; } $order = wc_get_order( $order_id ); if ( ! $order || ! self::is_cod_order( $order ) ) { return false; } return 'pending' === $order->get_meta( self::META_STATUS, true ); } /** * The COD message could not be sent: clear the hold so the store's normal * messages still reach the customer. * * @param \WC_Order $order Order. * * @return void */ public static function release_hold( $order ): void { if ( $order instanceof \WC_Order && 'pending' === $order->get_meta( self::META_STATUS, true ) ) { $order->delete_meta_data( self::META_STATUS ); $order->save_meta_data(); } } /** * Apply the customer's answer (from the signed cod.answered callback). * * @param array $data Decoded callback body. * * @return array{success:bool,reason?:string} */ public static function apply_answer( array $data ): array { $order_id = absint( $data['order_id'] ?? 0 ); $answer = (string) ( $data['status'] ?? '' ); if ( ! $order_id || ! in_array( $answer, array( 'confirmed', 'cancelled' ), true ) ) { return array( 'success' => false, 'reason' => 'bad_payload' ); } $order = wc_get_order( $order_id ); if ( ! $order ) { return array( 'success' => false, 'reason' => 'order_not_found' ); } // Idempotent: the same answer twice changes nothing. if ( $order->get_meta( self::META_STATUS, true ) === $answer ) { return array( 'success' => true, 'applied' => true ); } $order->update_meta_data( self::META_STATUS, $answer ); if ( 'confirmed' === $answer ) { $note = __( '[6rsh] The customer CONFIRMED this order on WhatsApp ✅', '6rsh-messaging-notifications' ); $target = self::confirmed_status(); if ( $target && $target !== $order->get_status() && in_array( $order->get_status(), self::CONFIRM_FROM, true ) ) { $order->update_status( $target, $note ); // also saves } else { $order->add_order_note( $note ); $order->save(); } return array( 'success' => true, 'applied' => true, 'order_status' => $order->get_status() ); } $note = __( '[6rsh] The customer CANCELLED this order on WhatsApp.', '6rsh-messaging-notifications' ); // Only COD orders are cancelled automatically: a paid order needs a refund first. if ( self::is_cod_order( $order ) && in_array( $order->get_status(), array( 'pending', 'processing', 'on-hold' ), true ) ) { $order->update_status( 'cancelled', $note ); // also saves + restores stock } else { // Already shipped/completed, or paid online — never undo that automatically. $order->add_order_note( $note . ' ' . __( 'Order status left unchanged — please review it.', '6rsh-messaging-notifications' ) ); $order->save(); } return array( 'success' => true, 'applied' => true ); } /** * Register the store callback once if it never was (throttled to 6h). * * @return void */ public static function maybe_register_callback(): void { if ( get_option( SixRsh_Entitlements::OPTION_CALLBACK_REGISTERED ) || ! get_option( 'sixrsh_api_key' ) ) { return; } if ( get_transient( 'sixrsh_cb_register_try' ) ) { return; } set_transient( 'sixrsh_cb_register_try', 1, 6 * HOUR_IN_SECONDS ); SixRsh_Entitlements::register_callback(); } }