PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
1.10.22 1.10.21 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 All 166 releases
woocommerce-pos / includes / Gateways / Cash.php

Cash.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.21, at includes/Gateways/Cash.php

325 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Provides a Cash Payment Gateway.
4 *
5 * @author Paul Kilmurray <[email protected]>
6 *
7 * @see https://wcpos.com
8 *
9 * @package WCPOS\WooCommercePOS
10 */
11
12 namespace WCPOS\WooCommercePOS\Gateways;
13
14 use WC_Order;
15 use WC_Order_Item_Fee;
16 use WCPOS\WooCommercePOS\Payments\Abstract_POS_Gateway;
17 use WCPOS\WooCommercePOS\Services\Order_Notes;
18 use WP_REST_Request;
19
20 /**
21 * Cash class.
22 */
23 class Cash extends Abstract_POS_Gateway {
24 /**
25 * Constructor for the gateway.
26 */
27 public function __construct() {
28 $this->id = 'pos_cash';
29 $this->title = /* translators: POS payment gateway label shown during checkout. */ __( 'Cash', 'woocommerce-pos' );
30 $this->description = '';
31 $this->icon = apply_filters( 'woocommerce_pos_cash_icon', '' );
32 $this->has_fields = true;
33 $this->enabled = 'no';
34 $this->supports = array( 'products', 'refunds' );
35
36 // Actions.
37 // @phpstan-ignore-next-line.
38 add_action(
39 'woocommerce_pos_update_options_payment_gateways_' . $this->id,
40 array(
41 $this,
42 'process_admin_options',
43 )
44 );
45 add_action( 'woocommerce_thankyou_pos_cash', array( $this, 'calculate_change' ) );
46 $this->register_pos_gateway_contract_hooks();
47 }
48
49 /**
50 * Get payment details.
51 *
52 * @param WC_Order $order Order object.
53 *
54 * @return array
55 */
56 public static function payment_details( WC_Order $order ) {
57 // Read through the order: process_payment() writes these with update_meta_data(),
58 // and on HPOS that is wc_orders_meta, which get_post_meta() cannot see.
59 return array(
60 'tendered' => $order->get_meta( '_pos_cash_amount_tendered' ),
61 'change' => $order->get_meta( '_pos_cash_change' ),
62 );
63 }
64
65 /**
66 * Display the payment fields on the checkout modal.
67 */
68 public function payment_fields(): void {
69 global $wp;
70
71 $order_id = isset( $wp->query_vars['order-pay'] ) ? $wp->query_vars['order-pay'] : null;
72 $order = $order_id ? wc_get_order( $order_id ) : null;
73
74 if ( $this->description ) {
75 echo '<p>' . wp_kses_post( $this->description ) . '</p>';
76 }
77
78 $currency_pos = get_option( 'woocommerce_currency_pos' );
79
80 if ( 'left' == $currency_pos || 'left_space' == $currency_pos ) {
81 $left_addon = '<span class="input-group-addon">' . get_woocommerce_currency_symbol( get_woocommerce_currency() ) . '</span>';
82 $right_addon = '';
83 } else {
84 $left_addon = '';
85 $right_addon = '<span class="input-group-addon">' . get_woocommerce_currency_symbol( get_woocommerce_currency() ) . '</span>';
86 }
87
88 echo '
89 <div class="form-row" id="pos-cash-tendered_field" style="display: flex; justify-content: space-between;">
90 <div style="flex: 1;">
91 <label for="pos-cash-tendered" style="padding-left:0">' . /* translators: Cash checkout field label for the amount of money received from the customer. */ esc_html__( 'Amount Tendered', 'woocommerce-pos' ) . '</label>
92 <div class="input-group">
93 ' . wp_kses_post( $left_addon ) . '
94 <input type="text" class="form-control" name="pos-cash-tendered" id="pos-cash-tendered" maxlength="20" data-numpad="cash" data-label="' . /* translators: Cash numpad label for the amount of money received from the customer. */ esc_attr__( 'Amount Tendered', 'woocommerce-pos' ) . '" data-placement="bottom" data-value="{{total}}">
95 ' . wp_kses_post( $right_addon ) . '
96 </div>
97 </div>
98 <div style="flex: 1;">
99 <label style="padding-left:0">' . /* translators: Cash checkout label for money returned to the customer when the received amount exceeds the total. */ esc_html__( 'Change', 'woocommerce-pos' ) . '</label>
100 <div id="pos-cash-change-display"></div>
101 </div>';
102 wp_nonce_field( 'pos_cash_payment_nonce', 'pos_cash_payment_nonce_field' );
103 echo '
104 </div>
105 ';
106
107 if ( $order && $order->get_total() > 0 ) {
108 echo '
109 <script>
110 document.addEventListener("DOMContentLoaded", function() {
111 var tenderedInput = document.getElementById("pos-cash-tendered");
112 var changeDisplay = document.getElementById("pos-cash-change-display");
113
114 tenderedInput.addEventListener("input", function() {
115 var tenderedAmount = parseFloat(tenderedInput.value);
116 var orderTotal = ' . json_encode( wc_format_decimal( $order->get_total() ) ) . '; // Get order total from PHP
117 var change = tenderedAmount - orderTotal;
118
119 if (change > 0) {
120 changeDisplay.innerHTML = change.toFixed(' . json_encode( wc_get_price_decimals() ) . ');
121 } else {
122 changeDisplay.innerHTML = "";
123 }
124 });
125 });
126 </script>
127 ';
128 }
129 }
130
131 /**
132 * Process the payment.
133 *
134 * @param int $order_id Order ID.
135 *
136 * @return string[]
137 */
138 public function process_payment( $order_id ): array {
139 // Check nonce.
140 if ( ! isset( $_POST['pos_cash_payment_nonce_field'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['pos_cash_payment_nonce_field'] ) ), 'pos_cash_payment_nonce' ) ) {
141 wp_die( /* translators: Checkout error shown when cash nonce validation fails. */ esc_html__( 'Nonce verification failed', 'woocommerce-pos' ) );
142 }
143
144 // get order object.
145 $order = new WC_Order( $order_id );
146 $tendered = $order->get_total();
147
148 // get pos_cash data from $_POST.
149 if ( isset( $_POST['pos-cash-tendered'] ) && ! empty( $_POST['pos-cash-tendered'] ) ) {
150 $tendered = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['pos-cash-tendered'] ) ) );
151 }
152 $result = $this->apply_tendered_payment( $order, $tendered, true );
153 if ( is_wp_error( $result ) ) {
154 wc_add_notice( $result->get_error_message(), 'error' );
155
156 return array(
157 'result' => 'failure',
158 'redirect' => '',
159 );
160 }
161
162 // Return thankyou redirect
163 // $redirect = add_query_arg(array(
164 // 'wcpos' => 1,
165 // ), get_home_url( null, '/wcpos-checkout/order-received/' . $order->get_id() ));.
166
167 return array(
168 'result' => 'success',
169 'redirect' => $this->get_return_url( $order ),
170 );
171 }
172
173 /**
174 * Process a refund.
175 *
176 * Cash refunds are handled manually, so just return true.
177 *
178 * @param int $order_id Order ID.
179 * @param float|null $amount Refund amount.
180 * @param string $reason Refund reason.
181 *
182 * @return bool
183 */
184 public function process_refund( $order_id, $amount = null, $reason = '' ) {
185 return true;
186 }
187
188 /**
189 * Calculate and display change.
190 *
191 * @param int $order_id Order ID.
192 */
193 public function calculate_change( $order_id ): void {
194 $order = wc_get_order( $order_id );
195 if ( ! $order ) {
196 return;
197 }
198
199 $message = '';
200 $tendered = $order->get_meta( '_pos_cash_amount_tendered' );
201 $change = $order->get_meta( '_pos_cash_change' );
202
203 // construct message.
204 if ( $tendered && $change ) {
205 $message = /* translators: Order note label for the cash amount received from the customer at checkout. */ __( 'Amount Tendered', 'woocommerce-pos' ) . ': ';
206 $message .= wc_price( $tendered, array( 'currency' => $order->get_currency() ) ) . '<br>';
207 $message .= /* translators: Money returned to the customer after a cash payment. */ _x( 'Change', 'Money returned from cash sale', 'woocommerce-pos' ) . ': ';
208 $message .= wc_price( $change, array( 'currency' => $order->get_currency() ) );
209 }
210
211 echo wp_kses_post( $message );
212 }
213
214 /**
215 * Provider family identifier.
216 *
217 * @param WP_REST_Request|null $request Request object.
218 */
219 public function get_pos_provider( ?WP_REST_Request $request = null ): string {
220 return 'wcpos';
221 }
222
223 /**
224 * Process a POS checkout action for cash.
225 *
226 * @param array $state Checkout state.
227 * @param string $action Checkout action.
228 * @param array $payment_data Payment data.
229 * @param WC_Order $order Order object.
230 * @param WP_REST_Request|null $request Request object.
231 *
232 * @return array|\WP_Error
233 */
234 public function process_pos_checkout_action( array $state, string $action, array $payment_data, WC_Order $order, ?WP_REST_Request $request = null ) {
235 if ( 'cancel' === $action ) {
236 $state['status'] = 'cancelled';
237 return $state;
238 }
239
240 if ( 'start' !== $action ) {
241 return $state;
242 }
243
244 $tendered = isset( $payment_data['amount_tendered'] ) && '' !== $payment_data['amount_tendered']
245 ? wc_format_decimal( $payment_data['amount_tendered'] )
246 : wc_format_decimal( $order->get_total() );
247
248 $result = $this->apply_tendered_payment( $order, $tendered, false );
249 if ( is_wp_error( $result ) ) {
250 return $result;
251 }
252
253 $state['status'] = 'completed';
254 $state['provider_data'] = array(
255 'amount_tendered' => $order->get_meta( '_pos_cash_amount_tendered' ),
256 'change' => $order->get_meta( '_pos_cash_change' ),
257 );
258
259 return $state;
260 }
261
262 /**
263 * Apply a tendered cash amount to an order.
264 *
265 * @param WC_Order $order Order object.
266 * @param string $tendered Tendered amount.
267 * @param bool $allow_partial Whether partial cash is allowed.
268 *
269 * @return true|\WP_Error
270 */
271 private function apply_tendered_payment( WC_Order $order, string $tendered, bool $allow_partial ) {
272 $tendered = wc_format_decimal( $tendered );
273
274 if ( (float) $tendered < 0 ) {
275 return new \WP_Error(
276 'wcpos_invalid_tendered_amount',
277 /* translators: Checkout validation error for the cash amount received from the customer. */
278 __( 'Tendered amount must be zero or greater.', 'woocommerce-pos' ),
279 array( 'status' => 400 )
280 );
281 }
282
283 $change = $tendered > $order->get_total() ? wc_format_decimal( floatval( $tendered ) - floatval( $order->get_total() ) ) : '0';
284 $order->set_payment_method( $this->id );
285 $order->set_payment_method_title( $this->title );
286 $order->update_meta_data( '_pos_cash_amount_tendered', $tendered );
287 $order->update_meta_data( '_pos_cash_change', $change );
288
289 if ( $tendered >= $order->get_total() ) {
290 Order_Notes::add_cash_note( $order, $tendered, $change );
291 $order->payment_complete();
292 return true;
293 }
294
295 if ( ! $allow_partial ) {
296 return new \WP_Error(
297 'wcpos_partial_cash_not_supported',
298 __( 'Partial cash payments are not supported by the POS API checkout flow.', 'woocommerce-pos' ),
299 array( 'status' => 400 )
300 );
301 }
302
303 $fee = new WC_Order_Item_Fee();
304 $fee->set_props(
305 array(
306 'name' => /* translators: Fee line-item name added when a cash payment is partial. */ __( 'Partial Payment', 'woocommerce-pos' ),
307 'tax_class' => 0,
308 'amount' => '-' . $tendered,
309 'total' => '-' . $tendered,
310 'total_tax' => 0,
311 )
312 );
313 $fee->add_meta_data( 'date_paid_gmt', gmdate( 'Y-m-d\TH:i:s' ), true );
314 $fee->set_order_id( $order->get_id() );
315 $fee->save();
316
317 $order->add_item( $fee );
318 $order->set_total( wc_format_decimal( floatval( $order->get_total() ) - floatval( $tendered ) ) );
319 Order_Notes::add_cash_note( $order, $tendered, $change );
320 $order->update_status( 'wc-pos-partial' );
321
322 return true;
323 }
324 }
325