PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.20
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.20
1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 All 164 releases
← All changes | includes/Services/Receipt_Data_Builder.php +95 -236 1.10.15 → 1.10.20 View file →
@@ -18,10 +18,9 @@
18 18 /**
19 19 * Build a canonical receipt payload.
20 20 *
21 21 * @param WC_Abstract_Order $order Receipt order.
22 - * @param string $mode Reserved for caller compatibility; the receipt mode is
23 - * carried in the request, not the payload.
22 + * @param string $mode Receipt mode ('live', 'fiscal', 'preview'), passed unchanged to the receipt data filter.
24 23 * @param object|null $pos_store POS store object. Falls back to order meta or default.
25 24 *
26 25 * @return array
27 26 */
@@ -116,9 +115,9 @@
116 115 $customer_name = /* translators: Short WCPOS UI label; keep concise. */ __( 'Guest', 'woocommerce-pos' );
117 116 }
118 117
119 118 $tax_ids = ( new Tax_Id_Reader() )->read_for_order( $order );
120 - $tax_ids = self::with_customer_tax_id_labels( $tax_ids, $presentation_hints['locale'] ?? '' );
119 + $tax_ids = Receipt_Sections::label_tax_ids( $tax_ids, 'customer', $presentation_hints['locale'] ?? '' );
121 120
122 121 $customer = array(
123 122 'id' => $customer_id ? $customer_id : null,
124 123 'name' => $customer_name,
@@ -163,9 +162,8 @@
163 162 : 0.0;
164 163 $price_convenience = $this->get_line_price_convenience_fields(
165 164 $item,
166 165 $order,
167 - $display_incl,
168 166 $qty,
169 167 $unit_subtotal_incl,
170 168 $unit_subtotal_excl,
171 169 $line_subtotal_incl,
@@ -179,29 +177,34 @@
179 177 'sku' => $item->get_product() ? $item->get_product()->get_sku() : '',
180 178 'name' => $item->get_name(),
181 179 'qty' => $qty,
182 180 'qty_refunded' => $qty_refunded,
183 - 'unit_subtotal' => $display_incl ? $unit_subtotal_incl : $unit_subtotal_excl,
184 - 'unit_subtotal_incl' => $unit_subtotal_incl,
185 - 'unit_subtotal_excl' => $unit_subtotal_excl,
186 - 'unit_price' => $display_incl ? $unit_price_incl : $unit_price_excl,
187 - 'unit_price_incl' => $unit_price_incl,
188 - 'unit_price_excl' => $unit_price_excl,
189 - 'line_subtotal' => $display_incl ? $line_subtotal_incl : $line_subtotal_excl,
190 - 'line_subtotal_incl' => $line_subtotal_incl,
191 - 'line_subtotal_excl' => $line_subtotal_excl,
192 - 'discounts' => $display_incl ? $discounts_incl : $discounts_excl,
193 - 'discounts_incl' => $discounts_incl,
194 - 'discounts_excl' => $discounts_excl,
195 - 'line_total' => $display_incl ? $line_total_incl : $line_total_excl,
196 - 'line_total_incl' => $line_total_incl,
197 - 'line_total_excl' => $line_total_excl,
181 + 'unit_subtotal' => array(
182 + 'incl' => $unit_subtotal_incl,
183 + 'excl' => $unit_subtotal_excl,
184 + ),
185 + 'unit_price' => array(
186 + 'incl' => $unit_price_incl,
187 + 'excl' => $unit_price_excl,
188 + ),
189 + 'line_subtotal' => array(
190 + 'incl' => $line_subtotal_incl,
191 + 'excl' => $line_subtotal_excl,
192 + ),
193 + 'discounts' => array(
194 + 'incl' => $discounts_incl,
195 + 'excl' => $discounts_excl,
196 + ),
197 + 'line_total' => array(
198 + 'incl' => $line_total_incl,
199 + 'excl' => $line_total_excl,
200 + ),
198 201 'total_refunded' => $total_refunded,
199 202 'taxes' => $this->get_line_taxes( $item ),
200 203 'meta' => $this->get_item_meta_pairs( $item ),
201 204 'attributes' => $this->get_product_attribute_pairs( $item ),
202 205 );
203 - $lines[] = array_merge( $line, $price_convenience );
206 + $lines[] = Receipt_Sections::line( array_merge( $line, $price_convenience ), $display_incl );
204 207 }
205 208
206 209 $shipping = array();
207 210 foreach ( $order->get_items( 'shipping' ) as $shipping_item ) {
@@ -210,16 +213,18 @@
210 213 }
211 214 $ship_total_excl = (float) $shipping_item->get_total();
212 215 $ship_total_tax = (float) $shipping_item->get_total_tax();
213 216 $ship_total_incl = $ship_total_excl + $ship_total_tax;
214 - $shipping[] = array(
215 - 'label' => $shipping_item->get_name(),
216 - 'method_id' => (string) $shipping_item->get_method_id(),
217 - 'total' => $display_incl ? $ship_total_incl : $ship_total_excl,
218 - 'total_incl' => $ship_total_incl,
219 - 'total_excl' => $ship_total_excl,
220 - 'taxes' => $this->get_item_taxes( $shipping_item ),
221 - 'meta' => $this->get_item_meta_pairs( $shipping_item ),
217 + $shipping[] = Receipt_Sections::shipping(
218 + $shipping_item->get_name(),
219 + (string) $shipping_item->get_method_id(),
220 + array(
221 + 'incl' => $ship_total_incl,
222 + 'excl' => $ship_total_excl,
223 + ),
224 + $this->get_item_taxes( $shipping_item ),
225 + $this->get_item_meta_pairs( $shipping_item ),
226 + $display_incl
222 227 );
223 228 }
224 229
225 230 $fees = array();
@@ -226,15 +231,17 @@
226 231 foreach ( $order->get_fees() as $fee ) {
227 232 $fee_total_excl = (float) $fee->get_total();
228 233 $fee_total_tax = (float) $fee->get_total_tax();
229 234 $fee_total_incl = $fee_total_excl + $fee_total_tax;
230 - $fees[] = array(
231 - 'label' => $fee->get_name(),
232 - 'total' => $display_incl ? $fee_total_incl : $fee_total_excl,
233 - 'total_incl' => $fee_total_incl,
234 - 'total_excl' => $fee_total_excl,
235 - 'taxes' => $this->get_item_taxes( $fee ),
236 - 'meta' => $this->get_item_meta_pairs( $fee ),
235 + $fees[] = Receipt_Sections::fee(
236 + $fee->get_name(),
237 + array(
238 + 'incl' => $fee_total_incl,
239 + 'excl' => $fee_total_excl,
240 + ),
241 + $this->get_item_taxes( $fee ),
242 + $this->get_item_meta_pairs( $fee ),
243 + $display_incl
237 244 );
238 245 }
239 246
240 247 $discounts = array();
@@ -245,15 +252,17 @@
245 252 $coupon = $this->get_order_coupon( $coupon_item );
246 253 $coupon_excl = (float) $coupon_item->get_discount();
247 254 $coupon_tax = (float) $coupon_item->get_discount_tax();
248 255 $coupon_incl = $coupon_excl + $coupon_tax;
249 - $discounts[] = array(
250 - 'label' => $this->get_coupon_label( $coupon_item, $coupon ),
251 - 'code' => $coupon_item->get_code(),
252 - 'discount_type' => $coupon ? (string) $coupon->get_discount_type() : '',
253 - 'total' => $display_incl ? $coupon_incl : $coupon_excl,
254 - 'total_incl' => $coupon_incl,
255 - 'total_excl' => $coupon_excl,
256 + $discounts[] = Receipt_Sections::discount(
257 + $this->get_coupon_label( $coupon_item, $coupon ),
258 + $coupon_item->get_code(),
259 + $coupon ? (string) $coupon->get_discount_type() : '',
260 + array(
261 + 'incl' => $coupon_incl,
262 + 'excl' => $coupon_excl,
263 + ),
264 + $display_incl
256 265 );
257 266 }
258 267
259 268 $discount_total_excl = (float) $order->get_discount_total();
@@ -259,116 +268,37 @@
259 268 $discount_total_excl = (float) $order->get_discount_total();
260 269 $discount_total_tax = (float) $order->get_discount_tax();
261 270 $discount_total_incl = $discount_total_excl + $discount_total_tax;
262 271
263 - // Legacy POS lines already include regular-to-selling savings in WooCommerce's
264 - // discount total. Add only current-shape savings to total_saved to avoid overlap.
265 - $sale_savings_totals = array(
266 - 'incl' => 0.0,
267 - 'excl' => 0.0,
268 - );
269 - $additional_savings = array(
270 - 'incl' => 0.0,
271 - 'excl' => 0.0,
272 - );
273 - $savings_complete = array(
274 - 'incl' => true,
275 - 'excl' => true,
276 - );
277 - $price_precision = wc_get_price_decimals();
278 - foreach ( $lines as $line ) {
279 - foreach ( array( 'incl', 'excl' ) as $basis ) {
280 - $key = 'line_savings_' . $basis;
281 - if ( ! isset( $line[ $key ] ) || ! is_numeric( $line[ $key ] ) ) {
282 - $savings_complete[ $basis ] = false;
283 - continue;
284 - }
285 -
286 - $line_savings = (float) $line[ $key ];
287 - $sale_savings_totals[ $basis ] += $line_savings;
288 - if ( empty( $line['savings_in_discounts'] ) ) {
289 - $subtotal_key = 'line_subtotal_' . $basis;
290 - $selling_key = 'line_selling_total_' . $basis;
291 - if (
292 - $line_savings > 0.0
293 - && (
294 - ! isset( $line[ $subtotal_key ], $line[ $selling_key ] )
295 - || round( abs( (float) $line[ $subtotal_key ] - (float) $line[ $selling_key ] ), $price_precision ) > 0.0
296 - )
297 - ) {
298 - $savings_complete[ $basis ] = false;
299 - continue;
300 - }
301 - $additional_savings[ $basis ] += $line_savings;
302 - }
303 - }
304 - }
305 -
306 - $total_saved = array(
307 - 'incl' => $savings_complete['incl'] ? $discount_total_incl + $additional_savings['incl'] : null,
308 - 'excl' => $savings_complete['excl'] ? $discount_total_excl + $additional_savings['excl'] : null,
309 - );
310 - foreach ( array( 'incl', 'excl' ) as $basis ) {
311 - if ( ! $savings_complete[ $basis ] ) {
312 - $sale_savings_totals[ $basis ] = null;
313 - }
314 - }
315 - $display_basis = $display_incl ? 'incl' : 'excl';
316 -
317 - $subtotal_excl = array_sum( array_column( $lines, 'line_subtotal_excl' ) );
318 - $subtotal_incl = array_sum( array_column( $lines, 'line_subtotal_incl' ) );
319 -
320 - // Item count summaries — useful for packing slips and kitchen tickets
321 - // where Mustache can't sum/count an array at render time.
322 - $total_qty = (float) array_sum( array_column( $lines, 'qty' ) );
323 - $line_count = \count( $lines );
324 -
325 272 $tax_total = (float) $order->get_total_tax();
326 - $total = (float) $order->get_total();
327 -
328 - $total_excl = $total - $tax_total;
329 - $refund_total = method_exists( $order, 'get_total_refunded' )
273 + $total = (float) $order->get_total();
274 + $refund_total = method_exists( $order, 'get_total_refunded' )
330 275 ? abs( (float) $order->get_total_refunded() )
331 276 : 0.0;
332 - // Templates render the customer-facing balance after a partial refund.
333 - // Stays at 0 when nothing was refunded so detailed-receipt's section
334 - // guard `{{#totals.net_total}}…{{/totals.net_total}}` collapses.
335 - $net_total = $refund_total > 0 ? max( 0.0, $total - $refund_total ) : 0.0;
336 -
337 - $totals = array(
338 - 'subtotal' => $display_incl ? $subtotal_incl : $subtotal_excl,
339 - 'subtotal_incl' => $subtotal_incl,
340 - 'subtotal_excl' => $subtotal_excl,
341 - 'discount_total' => $display_incl ? $discount_total_incl : $discount_total_excl,
342 - 'discount_total_incl' => $discount_total_incl,
343 - 'discount_total_excl' => $discount_total_excl,
344 - 'sale_savings_total' => $sale_savings_totals[ $display_basis ],
345 - 'sale_savings_total_incl' => $sale_savings_totals['incl'],
346 - 'sale_savings_total_excl' => $sale_savings_totals['excl'],
347 - 'total_saved' => $total_saved[ $display_basis ],
348 - 'total_saved_incl' => $total_saved['incl'],
349 - 'total_saved_excl' => $total_saved['excl'],
350 - 'total_saved_complete' => $savings_complete[ $display_basis ],
351 - 'tax_total' => $tax_total,
352 - 'total' => $display_incl ? $total : $total_excl,
353 - 'total_incl' => $total,
354 - 'total_excl' => $total_excl,
355 - 'paid_total' => $total,
356 - 'change_total' => (float) $order->get_meta( '_pos_cash_change' ),
357 - 'refund_total' => $refund_total,
358 - 'net_total' => $net_total,
359 - 'total_qty' => $total_qty,
360 - 'line_count' => $line_count,
277 + $totals = Receipt_Sections::totals(
278 + $lines,
279 + array(
280 + 'discount_total' => array(
281 + 'incl' => $discount_total_incl,
282 + 'excl' => $discount_total_excl,
283 + ),
284 + 'tax_total' => $tax_total,
285 + 'total' => $total,
286 + 'paid_total' => $total,
287 + 'change_total' => (float) $order->get_meta( '_pos_cash_change' ),
288 + 'refund_total' => $refund_total,
289 + ),
290 + $display_incl
361 291 );
362 292
363 293 $payments = array(
364 - array(
365 - 'method_id' => $order->get_payment_method(),
366 - 'method_title' => $order->get_payment_method_title(),
367 - 'amount' => $total,
368 - 'transaction_id' => (string) $order->get_transaction_id(),
369 - 'tendered' => (float) $order->get_meta( '_pos_cash_amount_tendered' ),
370 - 'change' => (float) $order->get_meta( '_pos_cash_change' ),
294 + Receipt_Sections::payment(
295 + $order->get_payment_method(),
296 + $order->get_payment_method_title(),
297 + $total,
298 + (string) $order->get_transaction_id(),
299 + (float) $order->get_meta( '_pos_cash_amount_tendered' ),
300 + (float) $order->get_meta( '_pos_cash_change' )
371 301 ),
372 302 );
373 303
374 304 $tax_summary = $this->get_tax_summary( $order );
@@ -418,11 +348,15 @@
418 348 * templates. Extensions can add their own keys to any section (for
419 349 * example a flag on a `discounts[]` row) or adjust labels. Keys defined
420 350 * by Receipt_Data_Schema should keep their documented types.
421 351 *
352 + * Sample previews for the template editor and gallery also run through this filter,
353 + * with mode `preview` and an unsaved order whose id is 0; a plugin that needs a
354 + * persisted order should return `$data` unchanged when `$order->get_id()` is 0.
355 + *
422 356 * @param array $data Receipt data (see Receipt_Data_Schema).
423 357 * @param WC_Abstract_Order $order Order the receipt is for.
424 - * @param string $mode Receipt mode: 'live' or 'fiscal'.
358 + * @param string $mode Receipt mode: 'live', 'fiscal' or 'preview', passed unchanged.
425 359 *
426 360 * @since 1.10.8
427 361 *
428 362 * @hook woocommerce_pos_receipt_data
@@ -500,9 +434,8 @@
500 434 * Derive recorded prices and savings without changing WooCommerce discounts.
501 435 *
502 436 * @param \WC_Order_Item_Product $item Order item.
503 437 * @param WC_Abstract_Order $order Receipt order.
504 - * @param bool $display_incl Whether generic values include tax.
505 438 * @param float $qty Item quantity.
506 439 * @param float $unit_subtotal_incl Stored unit subtotal including tax.
507 440 * @param float $unit_subtotal_excl Stored unit subtotal excluding tax.
508 441 * @param float $subtotal_incl Stored line subtotal including tax.
@@ -509,14 +442,13 @@
509 442 * @param float $subtotal_excl Stored line subtotal excluding tax.
510 443 * @param float $total_incl Stored line total including tax.
511 444 * @param float $total_excl Stored line total excluding tax.
512 445 *
513 - * @return array<string,float|bool|null>
446 + * @return array<string,array|bool>
514 447 */
515 448 private function get_line_price_convenience_fields(
516 449 \WC_Order_Item_Product $item,
517 450 WC_Abstract_Order $order,
518 - bool $display_incl,
519 451 float $qty,
520 452 float $unit_subtotal_incl,
521 453 float $unit_subtotal_excl,
522 454 float $subtotal_incl,
@@ -608,32 +540,16 @@
608 540 && $stored_discount >= $recorded_savings;
609 541 break;
610 542 }
611 543
612 - $select = static function ( array $values ) use ( $display_incl ): ?float {
613 - return $display_incl ? $values['incl'] : $values['excl'];
614 - };
615 -
616 544 return array(
617 - 'regular_price' => $select( $regular ),
618 - 'regular_price_incl' => $regular['incl'],
619 - 'regular_price_excl' => $regular['excl'],
620 - 'selling_price' => $select( $selling ),
621 - 'selling_price_incl' => $selling['incl'],
622 - 'selling_price_excl' => $selling['excl'],
623 - 'unit_savings' => $select( $unit_savings ),
624 - 'unit_savings_incl' => $unit_savings['incl'],
625 - 'unit_savings_excl' => $unit_savings['excl'],
626 - 'line_regular_total' => $select( $line_regular ),
627 - 'line_regular_total_incl' => $line_regular['incl'],
628 - 'line_regular_total_excl' => $line_regular['excl'],
629 - 'line_selling_total' => $select( $line_selling ),
630 - 'line_selling_total_incl' => $line_selling['incl'],
631 - 'line_selling_total_excl' => $line_selling['excl'],
632 - 'line_savings' => $select( $line_savings ),
633 - 'line_savings_incl' => $line_savings['incl'],
634 - 'line_savings_excl' => $line_savings['excl'],
635 - 'savings_in_discounts' => $savings_in_discounts,
545 + 'regular_price' => $regular,
546 + 'selling_price' => $selling,
547 + 'unit_savings' => $unit_savings,
548 + 'line_regular_total' => $line_regular,
549 + 'line_selling_total' => $line_selling,
550 + 'line_savings' => $line_savings,
551 + 'savings_in_discounts' => $savings_in_discounts,
636 552 );
637 553 }
638 554
639 555 /**
@@ -699,18 +615,16 @@
699 615 $tax_amount = (float) $tax_item->get_tax_total() + (float) $tax_item->get_shipping_tax_total();
700 616 $rate = (float) $tax_item->get_rate_percent();
701 617 $rate_id = (string) $tax_item->get_rate_id();
702 618 $taxable_excl = $taxable_bases[ $rate_id ] ?? null;
703 - $taxable_incl = null !== $taxable_excl ? $taxable_excl + $tax_amount : null;
704 619
705 - $summary[] = array(
706 - 'code' => $rate_id,
707 - 'rate' => $rate > 0 ? $rate : null,
708 - 'label' => $tax_item->get_label( $order ),
709 - 'compound' => method_exists( $tax_item, 'is_compound' ) ? (bool) $tax_item->is_compound() : false,
710 - 'taxable_amount_excl' => $taxable_excl,
711 - 'tax_amount' => $tax_amount,
712 - 'taxable_amount_incl' => $taxable_incl,
620 + $summary[] = Receipt_Sections::tax_summary_entry(
621 + $rate_id,
622 + $rate,
623 + $tax_item->get_label( $order ),
624 + method_exists( $tax_item, 'is_compound' ) ? (bool) $tax_item->is_compound() : false,
625 + $taxable_excl,
626 + $tax_amount
713 627 );
714 628 }
715 629
716 630 return $summary;
@@ -899,22 +813,9 @@
899 813 return $pairs;
900 814 }
901 815
902 816 if ( $product instanceof \WC_Product_Variation ) {
903 - foreach ( $product->get_variation_attributes() as $attribute_key => $attribute_value ) {
904 - if ( '' === (string) $attribute_value ) {
905 - continue;
906 - }
907 -
908 - $taxonomy = preg_replace( '/^attribute_/', '', (string) $attribute_key );
909 - $value = $product->get_attribute( $taxonomy );
910 - $pairs[] = array(
911 - 'key' => wp_strip_all_tags( wc_attribute_label( $taxonomy, $product ) ),
912 - 'value' => wp_strip_all_tags( '' !== $value ? $value : (string) $attribute_value ),
913 - );
914 - }
915 -
916 - return $pairs;
817 + return Receipt_Sections::variation_attribute_pairs( $product );
917 818 }
918 819
919 820 foreach ( $product->get_attributes() as $attribute ) {
920 821 if ( ! $attribute instanceof \WC_Product_Attribute || ! $attribute->get_visible() ) {
@@ -1079,48 +980,6 @@
1079 980 );
1080 981 }
1081 982
1082 983 return $refunds;
1083 - }
1084 -
1085 -
1086 - /**
1087 - * Ensure customer tax IDs include display labels for logicless templates.
1088 - *
1089 - * @param array<int,array<string,mixed>> $tax_ids Customer tax IDs.
1090 - * @param string $locale Receipt locale.
1091 - * @return array<int,array<string,mixed>>
1092 - */
1093 - private static function with_customer_tax_id_labels( array $tax_ids, string $locale = '' ): array {
1094 - return self::with_tax_id_labels( $tax_ids, 'customer', $locale );
1095 - }
1096 -
1097 - /**
1098 - * Resolve a display label for each tax-ID entry. Precedence: explicit
1099 - * `label` → `<scope>_tax_id_label_<type>` i18n key → scope-specific
1100 - * `_other` fallback.
1101 - *
1102 - * @param array<int,array<string,mixed>> $tax_ids Tax IDs.
1103 - * @param string $scope "store" or "customer".
1104 - * @param string $locale Receipt locale.
1105 - * @return array<int,array<string,mixed>>
1106 - */
1107 - private static function with_tax_id_labels( array $tax_ids, string $scope, string $locale = '' ): array {
1108 - $labels = Receipt_I18n_Labels::get_labels( $locale );
1109 - $prefix = $scope . '_tax_id_label_';
1110 -
1111 - return array_map(
1112 - static function ( array $tax_id ) use ( $labels, $prefix ): array {
1113 - if ( ! empty( $tax_id['label'] ) ) {
1114 - return $tax_id;
1115 - }
1116 -
1117 - $type = isset( $tax_id['type'] ) ? (string) $tax_id['type'] : 'other';
1118 - $key = $prefix . $type;
1119 - $tax_id['label'] = $labels[ $key ] ?? $labels[ $prefix . 'other' ];
1120 -
1121 - return $tax_id;
1122 - },
1123 - $tax_ids
1124 - );
1125 984 }
1126 985 }