PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
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 1.9.14 All 163 releases
woocommerce-pos / templates / receipt.php

receipt.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at templates/receipt.php

387 lines 17.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sales Receipt Template.
4 *
5 * This template can be overridden by copying it to yourtheme/woocommerce-pos/receipt.php.
6 * HOWEVER, this is not recommended — don't be surprised if your POS breaks.
7 *
8 * Available variables (always set before this file is included):
9 * $receipt_data — Array with all receipt information (store, meta, lines, totals, etc.)
10 * $order — WC_Abstract_Order object for this receipt
11 *
12 * See the WCPOS documentation for the full $receipt_data structure.
13 *
14 * @package WCPOS\WooCommercePOS
15 */
16
17 // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals -- Template files use short variable names by convention.
18
19 if ( ! \defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 $currency_args = array( 'currency' => $receipt_data['order']['currency'] ?? $receipt_data['meta']['currency'] ?? get_woocommerce_currency() );
24
25 $i18n = $receipt_data['i18n'] ?? array();
26 ?>
27 <!DOCTYPE html>
28 <html <?php language_attributes(); ?>>
29 <head>
30 <meta charset="<?php bloginfo( 'charset' ); ?>">
31 <meta name="viewport" content="width=device-width, initial-scale=1">
32 <style>
33 html, body { margin: 0; padding: 0; background: #fff; }
34 body {
35 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
36 font-size: 13px;
37 line-height: 1.45;
38 color: #1f2937;
39 }
40 .receipt {
41 box-sizing: border-box;
42 padding: 32px 36px;
43 }
44
45 /* Header */
46 /*
47 * Layout-critical flex/gap properties live inline on the markup below:
48 * the PDF renderer's Pdf_Layout_Preprocessor rewrites inline flex/grid
49 * into Dompdf-safe tables (Dompdf has no flexbox engine), and inline
50 * styles are the only declarations it can see.
51 */
52 .receipt-header {
53 padding-bottom: 18px;
54 border-bottom: 2px solid #111827;
55 }
56 .receipt-header .logo img { width: 88px; height: auto; display: block; }
57 .receipt-header .store-name { font-size: 22px; font-weight: 700; letter-spacing: -0.01em; }
58 .receipt-header .store-info { color: #6b7280; font-size: 12px; margin-top: 2px; }
59 .receipt-header .meta { text-align: right; }
60 .status-pill {
61 padding: 4px 10px;
62 background: #f3f4f6;
63 color: #1f2937;
64 border-radius: 999px;
65 font-weight: 700;
66 font-size: 10px;
67 letter-spacing: 0.10em;
68 text-transform: uppercase;
69 margin-bottom: 8px;
70 }
71 .status-pill .dot { width: 6px; height: 6px; border-radius: 50%; background: #6b7280; }
72 .meta-label { font-size: 11px; letter-spacing: 0.16em; color: #6b7280; text-transform: uppercase; }
73 .meta-number { font-size: 22px; font-weight: 700; margin-top: 2px; }
74 .meta-line { color: #6b7280; font-size: 12px; }
75 .meta-line.spaced { margin-top: 6px; }
76
77 /* Items */
78 .items { width: 100%; border-collapse: collapse; margin-top: 22px; font-size: 13px; }
79 .items th {
80 text-align: left;
81 font-weight: 600;
82 color: #6b7280;
83 padding: 8px 6px;
84 border-bottom: 1px solid #e5e7eb;
85 font-size: 11px;
86 text-transform: uppercase;
87 letter-spacing: 0.04em;
88 }
89 .items th.num, .items td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
90 .items td { padding: 10px 6px; border-bottom: 1px solid #f3f4f6; vertical-align: top; }
91 .items .name { font-weight: 600; }
92 .items .total-final { font-weight: 600; }
93 .items .total-strike { font-size: 11px; color: #9ca3af; }
94 .items tr.discount-row { color: #15803d; }
95
96 /* Totals */
97 .totals { margin-top: 20px; max-width: 280px; margin-left: auto; font-size: 13px; }
98 .totals-row { padding: 2px 0; }
99 .totals-row .label { color: #6b7280; }
100 .totals-row .amount { font-variant-numeric: tabular-nums; }
101 .totals-divider { border-top: 2px solid #111827; margin: 8px 0; }
102 .totals-row.grand { padding: 4px 0; font-size: 18px; font-weight: 700; }
103 .totals-row.grand .label { color: #1f2937; }
104 .totals-row.refund { font-size: 12px; color: #b91c1c; }
105 .totals-row.net { padding: 4px 0; font-size: 13px; font-weight: 600; border-top: 1px solid #e5e7eb; }
106
107 /* Cards */
108 .card { margin-top: 20px; padding: 14px 16px; border-radius: 8px; font-size: 12px; line-height: 1.5; }
109 .card-label { font-size: 10px; letter-spacing: 0.12em; color: #6b7280; text-transform: uppercase; margin-bottom: 8px; font-weight: 700; }
110 .card.refunds { background: #fff1f2; border: 1px solid #fecdd3; }
111 .card.refunds .row { padding: 4px 0; }
112 .card.refunds .row .amount { font-variant-numeric: tabular-nums; font-weight: 700; }
113 .card.refunds .reason { color: #6b7280; font-size: 11px; }
114 .card.note { background: #fffbeb; border: 1px solid #fde68a; }
115 .card.note .body { color: #92400e; white-space: pre-wrap; }
116
117 /* Payments */
118 .payments { margin-top: 24px; padding-top: 18px; border-top: 1px solid #e5e7eb; }
119 .payments-label { font-size: 10px; letter-spacing: 0.12em; color: #6b7280; text-transform: uppercase; margin-bottom: 8px; font-weight: 700; }
120 .payment-row { padding: 2px 0; }
121 .payment-row .amount { font-variant-numeric: tabular-nums; }
122 .payment-sub { font-size: 11px; color: #6b7280; padding-left: 4px; }
123 .payment-sub .amount { font-variant-numeric: tabular-nums; }
124
125 /* Footer */
126 .footer { margin-top: 24px; padding-top: 14px; border-top: 1px solid #e5e7eb; text-align: center; color: #6b7280; font-size: 12px; font-style: italic; }
127 .footer-imprint { margin-top: 10px; text-align: center; font-size: 11px; color: #6b7280; }
128
129 del { color: #9ca3af; }
130
131 @media print {
132 body { background: #fff; }
133 }
134 </style>
135 <?php do_action( 'woocommerce_pos_receipt_head' ); ?>
136 </head>
137 <body>
138
139 <div class="receipt">
140
141 <!-- Header -->
142 <header class="receipt-header" style="display: flex; align-items: flex-start; gap: 22px;">
143 <?php if ( ! empty( $receipt_data['store']['logo'] ) ) : ?>
144 <div class="logo" style="flex: 0 0 auto;">
145 <img src="<?php echo esc_url( $receipt_data['store']['logo'] ); ?>" alt="<?php echo esc_attr( $receipt_data['store']['name'] ?? '' ); ?>">
146 </div>
147 <?php endif; ?>
148
149 <div class="store" style="flex: 1 1 auto; min-width: 0;">
150 <div class="store-name"><?php echo esc_html( $receipt_data['store']['name'] ?? get_bloginfo( 'name' ) ); ?></div>
151 <?php if ( ! empty( $receipt_data['store']['address_lines'] ) ) : ?>
152 <?php foreach ( $receipt_data['store']['address_lines'] as $address_line ) : ?>
153 <div class="store-info"><?php echo esc_html( $address_line ); ?></div>
154 <?php endforeach; ?>
155 <?php endif; ?>
156 <?php if ( ! empty( $receipt_data['store']['phone'] ) ) : ?>
157 <div class="store-info"><?php echo esc_html( $receipt_data['store']['phone'] ); ?></div>
158 <?php endif; ?>
159 <?php if ( ! empty( $receipt_data['store']['tax_ids'] ) ) : ?>
160 <?php foreach ( $receipt_data['store']['tax_ids'] as $tax_id ) : ?>
161 <?php
162 $tax_label = $tax_id['label'] ?? '';
163 $tax_value = $tax_id['value'] ?? '';
164 $tax_text = '' !== $tax_label ? $tax_label . ' ' . $tax_value : $tax_value;
165 ?>
166 <div class="store-info"><?php echo esc_html( $tax_text ); ?></div>
167 <?php endforeach; ?>
168 <?php endif; ?>
169 </div>
170
171 <div class="meta" style="flex: 0 0 auto;">
172 <?php
173 $status_label = $receipt_data['order']['status_label'] ?? '';
174 $wc_status = $receipt_data['order']['wc_status'] ?? '';
175 $status_text = '' !== $status_label ? $status_label : $wc_status;
176 ?>
177 <?php if ( '' !== $status_text ) : ?>
178 <div class="status-pill" style="display: inline-flex; align-items: center; gap: 6px;"><span class="dot"></span><?php echo esc_html( $status_text ); ?></div>
179 <?php endif; ?>
180
181 <?php
182 $doc_label = $receipt_data['fiscal']['document_label'] ?? '';
183 if ( '' === $doc_label ) {
184 $doc_label = $i18n['receipt'] ?? __( 'Receipt', 'woocommerce-pos' );
185 }
186 ?>
187 <div class="meta-label"><?php echo esc_html( $doc_label ); ?></div>
188
189 <?php $order_number = $receipt_data['order']['number'] ?? $receipt_data['meta']['order_number'] ?? ''; ?>
190 <div class="meta-number">#<?php echo esc_html( ltrim( (string) $order_number, '#' ) ); ?></div>
191
192 <?php $created_at = $receipt_data['order']['created']['datetime'] ?? $receipt_data['meta']['created_at_local'] ?? $receipt_data['meta']['created_at_gmt'] ?? ''; ?>
193 <?php if ( '' !== $created_at ) : ?>
194 <div class="meta-line spaced"><?php echo esc_html( $created_at ); ?></div>
195 <?php endif; ?>
196
197 <?php if ( ! empty( $receipt_data['cashier']['name'] ) ) : ?>
198 <div class="meta-line"><?php echo esc_html( $i18n['cashier'] ?? __( 'Cashier', 'woocommerce-pos' ) ); ?>: <?php echo esc_html( $receipt_data['cashier']['name'] ); ?></div>
199 <?php endif; ?>
200
201 <?php if ( ! empty( $receipt_data['customer']['name'] ) ) : ?>
202 <div class="meta-line"><?php echo esc_html( $i18n['customer'] ?? __( 'Customer', 'woocommerce-pos' ) ); ?>: <?php echo esc_html( $receipt_data['customer']['name'] ); ?></div>
203 <?php endif; ?>
204
205 <?php if ( ! empty( $receipt_data['customer']['tax_ids'] ) ) : ?>
206 <?php foreach ( $receipt_data['customer']['tax_ids'] as $tax_id ) : ?>
207 <?php
208 $tax_label = $tax_id['label'] ?? '';
209 $tax_value = $tax_id['value'] ?? '';
210 $tax_text = '' !== $tax_label ? $tax_label . ' ' . $tax_value : $tax_value;
211 ?>
212 <div class="meta-line"><?php echo esc_html( $tax_text ); ?></div>
213 <?php endforeach; ?>
214 <?php endif; ?>
215 </div>
216 </header>
217
218 <!-- Items -->
219 <table class="items">
220 <thead>
221 <tr>
222 <th><?php echo esc_html( $i18n['item_short'] ?? __( 'Item', 'woocommerce-pos' ) ); ?></th>
223 <th class="num"><?php echo esc_html( $i18n['qty_short'] ?? __( 'Qty', 'woocommerce-pos' ) ); ?></th>
224 <th class="num"><?php echo esc_html( $i18n['total'] ?? __( 'Total', 'woocommerce-pos' ) ); ?></th>
225 </tr>
226 </thead>
227 <tbody>
228 <?php foreach ( $receipt_data['lines'] as $line ) : ?>
229 <?php $has_discount = ( $line['discounts'] ?? 0 ) > 0; ?>
230 <tr>
231 <td>
232 <div class="name"><?php echo esc_html( $line['name'] ?? '' ); ?></div>
233 </td>
234 <td class="num"><?php echo esc_html( $line['qty'] ?? 0 ); ?></td>
235 <td class="num">
236 <?php if ( $has_discount ) : ?>
237 <div class="total-strike"><del><?php echo wp_kses_post( wc_price( $line['line_subtotal'] ?? 0, $currency_args ) ); ?></del></div>
238 <?php endif; ?>
239 <div class="total-final"><?php echo wp_kses_post( wc_price( $line['line_total'] ?? 0, $currency_args ) ); ?></div>
240 </td>
241 </tr>
242 <?php endforeach; ?>
243
244 <?php foreach ( $receipt_data['fees'] as $fee ) : ?>
245 <tr>
246 <td><div class="name"><?php echo esc_html( $fee['label'] ?? __( 'Fee', 'woocommerce' ) ); ?></div></td>
247 <td class="num">1</td>
248 <td class="num"><div class="total-final"><?php echo wp_kses_post( wc_price( $fee['total'] ?? 0, $currency_args ) ); ?></div></td>
249 </tr>
250 <?php endforeach; ?>
251
252 <?php foreach ( $receipt_data['shipping'] as $ship ) : ?>
253 <tr>
254 <td><div class="name"><?php echo esc_html( $ship['label'] ?? __( 'Shipping', 'woocommerce' ) ); ?></div></td>
255 <td class="num">1</td>
256 <td class="num"><div class="total-final"><?php echo wp_kses_post( wc_price( $ship['total'] ?? 0, $currency_args ) ); ?></div></td>
257 </tr>
258 <?php endforeach; ?>
259
260 <?php foreach ( $receipt_data['discounts'] as $discount ) : ?>
261 <tr class="discount-row">
262 <td>
263 <div class="name">
264 <?php echo esc_html( $i18n['discount'] ?? __( 'Discount', 'woocommerce' ) ); ?>
265 <?php
266 if ( ! empty( $discount['label'] ) ) :
267 ?>
268 &middot; <?php echo esc_html( $discount['label'] ); ?><?php endif; ?>
269 <?php
270 if ( ! empty( $discount['code'] ) ) :
271 ?>
272 (<?php echo esc_html( $discount['code'] ); ?>)<?php endif; ?>
273 </div>
274 </td>
275 <td class="num">&mdash;</td>
276 <td class="num"><div class="total-final">-<?php echo wp_kses_post( wc_price( $discount['total'] ?? 0, $currency_args ) ); ?></div></td>
277 </tr>
278 <?php endforeach; ?>
279 </tbody>
280 </table>
281
282 <!-- Totals -->
283 <div class="totals">
284 <div class="totals-row" style="display: flex; justify-content: space-between;">
285 <span class="label"><?php echo esc_html( $i18n['subtotal'] ?? __( 'Subtotal', 'woocommerce' ) ); ?></span>
286 <span class="amount"><?php echo wp_kses_post( wc_price( $receipt_data['totals']['subtotal'] ?? 0, $currency_args ) ); ?></span>
287 </div>
288
289 <?php $tax_total = $receipt_data['totals']['tax_total'] ?? 0; ?>
290 <?php if ( ! empty( $tax_total ) ) : ?>
291 <div class="totals-row" style="display: flex; justify-content: space-between;">
292 <span class="label"><?php echo esc_html( $i18n['total_tax'] ?? __( 'Total Tax', 'woocommerce' ) ); ?></span>
293 <span class="amount"><?php echo wp_kses_post( wc_price( $tax_total, $currency_args ) ); ?></span>
294 </div>
295 <?php endif; ?>
296
297 <div class="totals-divider"></div>
298
299 <div class="totals-row grand" style="display: flex; justify-content: space-between;">
300 <span class="label"><?php echo esc_html( $i18n['total'] ?? __( 'Total', 'woocommerce' ) ); ?></span>
301 <span class="amount"><?php echo wp_kses_post( wc_price( $receipt_data['totals']['total'] ?? 0, $currency_args ) ); ?></span>
302 </div>
303
304 <?php $refund_total = $receipt_data['totals']['refund_total'] ?? 0; ?>
305 <?php if ( ! empty( $refund_total ) ) : ?>
306 <div class="totals-row refund" style="display: flex; justify-content: space-between;">
307 <span><?php echo esc_html( $i18n['refunded'] ?? __( 'Refunded', 'woocommerce' ) ); ?></span>
308 <span class="amount">-<?php echo wp_kses_post( wc_price( $refund_total, $currency_args ) ); ?></span>
309 </div>
310 <div class="totals-row net" style="display: flex; justify-content: space-between;">
311 <span><?php echo esc_html( $i18n['net_total'] ?? __( 'Net Total', 'woocommerce-pos' ) ); ?></span>
312 <span class="amount"><?php echo wp_kses_post( wc_price( $receipt_data['totals']['net_total'] ?? 0, $currency_args ) ); ?></span>
313 </div>
314 <?php endif; ?>
315 </div>
316
317 <!-- Refunds -->
318 <?php if ( ! empty( $receipt_data['refunds'] ) ) : ?>
319 <div class="card refunds">
320 <div class="card-label"><?php echo esc_html( $i18n['returned_items'] ?? __( 'Returned Items', 'woocommerce-pos' ) ); ?></div>
321 <?php foreach ( $receipt_data['refunds'] as $refund ) : ?>
322 <div class="row" style="display: flex; justify-content: space-between; gap: 12px;">
323 <div>
324 <strong>#<?php echo esc_html( (string) ( $refund['id'] ?? '' ) ); ?></strong>
325 <?php if ( ! empty( $refund['date']['datetime'] ) ) : ?>
326 <span class="reason"> &middot; <?php echo esc_html( $refund['date']['datetime'] ); ?></span>
327 <?php endif; ?>
328 <?php if ( ! empty( $refund['reason'] ) ) : ?>
329 <div class="reason"><?php echo esc_html( $refund['reason'] ); ?></div>
330 <?php endif; ?>
331 </div>
332 <div class="amount"><?php echo wp_kses_post( wc_price( $refund['amount'] ?? 0, $currency_args ) ); ?></div>
333 </div>
334 <?php endforeach; ?>
335 </div>
336 <?php endif; ?>
337
338 <!-- Customer note -->
339 <?php $customer_note = ! empty( $receipt_data['order']['customer_note'] ) ? $receipt_data['order']['customer_note'] : ( $receipt_data['meta']['customer_note'] ?? '' ); ?>
340 <?php if ( '' !== $customer_note ) : ?>
341 <div class="card note">
342 <div class="card-label"><?php echo esc_html( $i18n['customer_note'] ?? __( 'Customer Note', 'woocommerce' ) ); ?></div>
343 <div class="body"><?php echo wp_kses_post( nl2br( $customer_note ) ); ?></div>
344 </div>
345 <?php endif; ?>
346
347 <!-- Payments -->
348 <?php if ( ! empty( $receipt_data['payments'] ) ) : ?>
349 <div class="payments">
350 <div class="payments-label"><?php echo esc_html( $i18n['paid'] ?? /* translators: Receipt payment-section heading for amounts already paid by the customer. */ __( 'Paid', 'woocommerce-pos' ) ); ?></div>
351 <?php foreach ( $receipt_data['payments'] as $payment ) : ?>
352 <div class="payment-row" style="display: flex; justify-content: space-between;">
353 <span><strong><?php echo esc_html( $payment['method_title'] ?? '' ); ?></strong></span>
354 <span class="amount"><?php echo wp_kses_post( wc_price( $payment['amount'] ?? 0, $currency_args ) ); ?></span>
355 </div>
356 <?php if ( ! empty( $payment['tendered'] ) && (float) $payment['tendered'] > 0 ) : ?>
357 <div class="payment-sub" style="display: flex; justify-content: space-between;">
358 <span><?php echo esc_html( $i18n['tendered'] ?? /* translators: Standalone receipt/payment label meaning money received from the customer at checkout. Use the target-language equivalent of "Received" as a concise label; do not use wording that means "Amount paid/deposited", and do not use procurement tender/bid language. */ __( 'Tendered', 'woocommerce-pos' ) ); ?></span>
359 <span class="amount"><?php echo wp_kses_post( wc_price( $payment['tendered'], $currency_args ) ); ?></span>
360 </div>
361 <div class="payment-sub" style="display: flex; justify-content: space-between;">
362 <span><?php echo esc_html( $i18n['change'] ?? /* translators: Receipt/payment label for cash returned to the customer when the received amount is greater than the total; not "change" meaning modify. */ __( 'Change', 'woocommerce-pos' ) ); ?></span>
363 <span class="amount"><?php echo wp_kses_post( wc_price( $payment['change'] ?? 0, $currency_args ) ); ?></span>
364 </div>
365 <?php endif; ?>
366 <?php endforeach; ?>
367 </div>
368 <?php endif; ?>
369
370 <!-- Footer -->
371 <?php
372 // Pro feature: store.personal_notes and store.footer_imprint are available when using the WCPOS Pro Stores add-on.
373 $personal_notes = $receipt_data['store']['personal_notes'] ?? '';
374 $thank_you = $i18n['thank_you_purchase'] ?? __( 'Thank you for your purchase!', 'woocommerce-pos' );
375 $footer_text = '' !== $personal_notes ? $personal_notes : $thank_you;
376 ?>
377 <div class="footer"><?php echo wp_kses_post( nl2br( $footer_text ) ); ?></div>
378
379 <?php if ( ! empty( $receipt_data['store']['footer_imprint'] ) ) : ?>
380 <div class="footer-imprint"><?php echo wp_kses_post( nl2br( $receipt_data['store']['footer_imprint'] ) ); ?></div>
381 <?php endif; ?>
382
383 </div>
384
385 </body>
386 </html>
387