PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 5.4.2
WooCommerce Square v5.4.2
5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.2.0 3.3.0 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.7.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 4.0.0 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.9.0 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 5.0.0 5.0.1 5.1.0 5.1.1 5.1.2 5.2.0 5.3.0 5.3.1 5.3.2 5.3.3
woocommerce-square / includes / Internal / Abilities / Domain / GetOrderPaymentStatus.php
woocommerce-square / includes / Internal / Abilities / Domain Last commit date
AbstractSquareAbility.php 1 month ago GetConnectionStatus.php 1 month ago GetLocations.php 1 month ago GetOrderPaymentStatus.php 1 month ago GetPendingJobs.php 1 month ago GetProductSyncState.php 1 month ago GetSyncRecords.php 1 month ago GetSyncStatus.php 1 month ago
GetOrderPaymentStatus.php
233 lines
1 <?php
2 /**
3 * Get order payment status ability definition.
4 *
5 * @package WooCommerce\Square
6 */
7
8 // @phan-file-suppress PhanUndeclaredClassMethod, PhanUndeclaredFunction @phan-suppress-current-line UnusedSuppression -- Abilities API + AbilityDefinition added in WC 10.9; suppression covers older-WC compat runs where this class never loads.
9
10 namespace WooCommerce\Square\Internal\Abilities\Domain;
11
12 defined( 'ABSPATH' ) || exit;
13
14 use Automattic\WooCommerce\Abilities\AbilityDefinition;
15 use WooCommerce\Square\Internal\Abilities\Abilities_Registrar;
16 use WooCommerce\Square\Plugin;
17
18 /**
19 * Registers the woocommerce-square/get-order-payment-status ability.
20 *
21 * Returns Square-specific payment state for a single WooCommerce order:
22 * transaction id, capture state, gift-card split-payment metadata, refund
23 * totals, and the underlying order status/total. Lets agents answer "why
24 * did order X's payment behave this way?" without round-tripping through
25 * the WC admin UI.
26 *
27 * Backing detail: reads post-meta only (no Square API calls, no transient
28 * writes). Both Square gateways are checked — square_credit_card and
29 * square_cash_app_pay. When the order's payment method is something else,
30 * the ability still returns the order's status/total + an
31 * `is_square_payment: false` signal so agents can route accordingly
32 * rather than receiving an error.
33 *
34 * @internal Only loaded when WooCommerce 10.9+ is active.
35 */
36 class GetOrderPaymentStatus extends AbstractSquareAbility implements AbilityDefinition {
37
38 public static function get_name(): string {
39 return 'woocommerce-square/get-order-payment-status';
40 }
41
42 public static function get_registration_args(): array {
43 return array(
44 'label' => __( 'Get Square payment status for an order', 'woocommerce-square' ),
45 'description' => __( 'Return Square-specific payment state for a single WooCommerce order: transaction id, capture state, gift-card split-payment metadata, refund totals, plus the underlying order status and total. Useful for diagnosing per-order payment issues.', 'woocommerce-square' ),
46 'category' => self::CATEGORY_SLUG,
47 'input_schema' => array(
48 'type' => 'object',
49 'properties' => array(
50 'order_id' => array(
51 'type' => 'integer',
52 'minimum' => 1,
53 'description' => __( 'WooCommerce order ID.', 'woocommerce-square' ),
54 ),
55 ),
56 'required' => array( 'order_id' ),
57 'additionalProperties' => false,
58 ),
59 'execute_callback' => array( self::class, 'execute' ),
60 'permission_callback' => array( Abilities_Registrar::class, 'can_manage_woocommerce_square' ),
61 'meta' => array(
62 'annotations' => array(
63 'readonly' => true,
64 'destructive' => false,
65 'idempotent' => true,
66 ),
67 'show_in_rest' => true,
68 'mcp' => array(
69 'public' => true,
70 ),
71 ),
72 );
73 }
74
75 /**
76 * Execute callback.
77 *
78 * @param mixed $input Input args; `order_id` is required.
79 * @return array|\WP_Error
80 */
81 public static function execute( $input = null ) {
82 $input = is_array( $input ) ? $input : array();
83 $order_id = isset( $input['order_id'] ) ? (int) $input['order_id'] : 0;
84
85 if ( $order_id <= 0 ) {
86 return new \WP_Error(
87 'woocommerce_square_invalid_order_id',
88 __( 'A positive order_id is required.', 'woocommerce-square' ),
89 array( 'status' => 400 )
90 );
91 }
92
93 if ( ! function_exists( 'wc_get_order' ) ) {
94 return self::not_initialized_error();
95 }
96
97 $order = wc_get_order( $order_id );
98 if ( ! $order ) {
99 return new \WP_Error(
100 'woocommerce_square_order_not_found',
101 /* translators: %d: requested order ID */
102 sprintf( __( 'Order %d was not found.', 'woocommerce-square' ), $order_id ),
103 array( 'status' => 404 )
104 );
105 }
106
107 $payment_method = (string) $order->get_payment_method();
108
109 // Treat as Square only when the live plugin actually owns this
110 // gateway id AND it is one of the two Square-branded gateways. The
111 // gift-cards gateway is intentionally excluded — its data attaches
112 // to the parent Square charge via gift_card_* meta read below.
113 $plugin = self::get_plugin_or_error();
114 $is_square = false;
115 $prefix = '';
116 if ( ! is_wp_error( $plugin )
117 && $plugin->has_gateway( $payment_method )
118 && in_array( $payment_method, array( Plugin::GATEWAY_ID, Plugin::CASH_APP_PAY_GATEWAY_ID ), true )
119 ) {
120 // Defer to the gateway for the meta-key prefix so the storage
121 // path (Payment_Gateway::update_order_meta()) and this reader
122 // share a single source of truth. If the gateway ever changes
123 // how it derives its prefix, this reader follows automatically.
124 $gateway = $plugin->get_gateway( $payment_method );
125 if ( $gateway && method_exists( $gateway, 'get_order_meta_prefix' ) ) {
126 $prefix = (string) $gateway->get_order_meta_prefix();
127 $is_square = '' !== $prefix;
128 }
129 }
130
131 $result = array(
132 'order_id' => $order_id,
133 'order_status' => (string) $order->get_status(),
134 'order_total' => (float) $order->get_total(),
135 'order_currency' => (string) $order->get_currency(),
136 'payment_method' => $payment_method,
137 'is_square_payment' => $is_square,
138 'refunds' => self::collect_refunds( $order ),
139 );
140
141 if ( ! $is_square ) {
142 return $result;
143 }
144
145 $read = static function ( $key ) use ( $order, $prefix ) {
146 $value = $order->get_meta( $prefix . $key, true );
147 return '' === $value ? null : $value;
148 };
149
150 // Pre-fetch every meta key once. get_meta() does a linear scan of the
151 // order's meta_data on each call — read each here, reference the local
152 // from the result array below.
153 $trans_id = $read( 'trans_id' );
154 $trans_date = $read( 'trans_date' );
155 $square_order_id = $read( 'square_order_id' );
156 $square_location_id = $read( 'square_location_id' );
157 $square_version = $read( 'square_version' );
158 $charge_type = $read( 'charge_type' );
159 $charge_captured = $read( 'charge_captured' );
160 $capture_total = $read( 'capture_total' );
161 $authorization_amt = $read( 'authorization_amount' );
162 $auth_can_capture = $read( 'auth_can_be_captured' );
163 $retry_count = $read( 'retry_count' );
164 $is_tender_gift_card = $read( 'is_tender_type_gift_card' );
165
166 $result['square'] = array(
167 'transaction_id' => null === $trans_id ? null : (string) $trans_id,
168 'transaction_date' => null === $trans_date ? null : (string) $trans_date,
169 'square_order_id' => null === $square_order_id ? null : (string) $square_order_id,
170 'square_location_id' => null === $square_location_id ? null : (string) $square_location_id,
171 'square_version' => null === $square_version ? null : (string) $square_version,
172 'charge_type' => null === $charge_type ? null : (string) $charge_type,
173 'charge_captured' => null === $charge_captured ? null : (string) $charge_captured,
174 'capture_total' => null === $capture_total ? null : (float) $capture_total,
175 'authorization_amount' => null === $authorization_amt ? null : (float) $authorization_amt,
176 'auth_can_be_captured' => null === $auth_can_capture ? null : (string) $auth_can_capture,
177 'retry_count' => null === $retry_count ? null : (int) $retry_count,
178 'has_square_charge' => null !== $trans_id,
179 'is_fully_captured' => 'yes' === $charge_captured,
180 'is_partially_captured' => 'partial' === $charge_captured,
181 );
182
183 // Match the canonical reader in Handlers\Order::is_tender_type_gift_card(),
184 // which compares against the string '1' (WC stores meta-bool `true` as '1').
185 // Previous version compared against 'yes' here, which never matched and
186 // made the gift-card-only branch dead.
187 $gift_card_split = 'PARTIAL' === $charge_type || '1' === (string) $is_tender_gift_card;
188
189 if ( $gift_card_split ) {
190 $gift_trans_id = $read( 'gift_card_trans_id' );
191 $gift_partial = $read( 'gift_card_partial_total' );
192 $gift_refunded = $read( 'gift_card_recorded_refund_total' );
193 $gift_line_item = $read( 'gift_card_line_item_id' );
194
195 $result['square']['gift_card'] = array(
196 'is_split_payment' => true,
197 'transaction_id' => null === $gift_trans_id ? null : (string) $gift_trans_id,
198 'partial_total' => null === $gift_partial ? null : (float) $gift_partial,
199 'refunded_total' => null === $gift_refunded ? 0.0 : (float) $gift_refunded,
200 'line_item_id' => null === $gift_line_item ? null : (string) $gift_line_item,
201 );
202 } else {
203 $result['square']['gift_card'] = array( 'is_split_payment' => false );
204 }
205
206 return $result;
207 }
208
209 /**
210 * Collect a compact summary of WC refunds attached to the order.
211 *
212 * @param \WC_Order $order Order being inspected.
213 * @return array
214 */
215 private static function collect_refunds( \WC_Order $order ): array {
216 $out = array();
217 foreach ( $order->get_refunds() as $refund ) {
218 if ( ! is_object( $refund ) || ! method_exists( $refund, 'get_id' ) ) {
219 continue;
220 }
221 $out[] = array(
222 'id' => (int) $refund->get_id(),
223 'amount' => (float) $refund->get_amount(),
224 'reason' => method_exists( $refund, 'get_reason' ) ? (string) $refund->get_reason() : '',
225 'date' => method_exists( $refund, 'get_date_created' ) && $refund->get_date_created()
226 ? $refund->get_date_created()->date( 'c' )
227 : null,
228 );
229 }
230 return $out;
231 }
232 }
233