PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / trunk
WCPOS – Point of Sale (POS) plugin for WooCommerce vtrunk
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 1.9.13 1.9.12 1.9.11 1.9.10 1.9.9 All 158 releases
woocommerce-pos / includes / Gateways / Cash.php

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

323 lines 10.5 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 <paul@kilbot.com>
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 return array(
58 'tendered' => get_post_meta( $order->get_id(), '_pos_cash_amount_tendered', true ),
59 'change' => get_post_meta( $order->get_id(), '_pos_cash_change', true ),
60 );
61 }
62
63 /**
64 * Display the payment fields on the checkout modal.
65 */
66 public function payment_fields(): void {
67 global $wp;
68
69 $order_id = isset( $wp->query_vars['order-pay'] ) ? $wp->query_vars['order-pay'] : null;
70 $order = $order_id ? wc_get_order( $order_id ) : null;
71
72 if ( $this->description ) {
73 echo '<p>' . wp_kses_post( $this->description ) . '</p>';
74 }
75
76 $currency_pos = get_option( 'woocommerce_currency_pos' );
77
78 if ( 'left' == $currency_pos || 'left_space' == $currency_pos ) {
79 $left_addon = '<span class="input-group-addon">' . get_woocommerce_currency_symbol( get_woocommerce_currency() ) . '</span>';
80 $right_addon = '';
81 } else {
82 $left_addon = '';
83 $right_addon = '<span class="input-group-addon">' . get_woocommerce_currency_symbol( get_woocommerce_currency() ) . '</span>';
84 }
85
86 echo '
87 <div class="form-row" id="pos-cash-tendered_field" style="display: flex; justify-content: space-between;">
88 <div style="flex: 1;">
89 <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>
90 <div class="input-group">
91 ' . wp_kses_post( $left_addon ) . '
92 <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}}">
93 ' . wp_kses_post( $right_addon ) . '
94 </div>
95 </div>
96 <div style="flex: 1;">
97 <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>
98 <div id="pos-cash-change-display"></div>
99 </div>';
100 wp_nonce_field( 'pos_cash_payment_nonce', 'pos_cash_payment_nonce_field' );
101 echo '
102 </div>
103 ';
104
105 if ( $order && $order->get_total() > 0 ) {
106 echo '
107 <script>
108 document.addEventListener("DOMContentLoaded", function() {
109 var tenderedInput = document.getElementById("pos-cash-tendered");
110 var changeDisplay = document.getElementById("pos-cash-change-display");
111
112 tenderedInput.addEventListener("input", function() {
113 var tenderedAmount = parseFloat(tenderedInput.value);
114 var orderTotal = ' . json_encode( wc_format_decimal( $order->get_total() ) ) . '; // Get order total from PHP
115 var change = tenderedAmount - orderTotal;
116
117 if (change > 0) {
118 changeDisplay.innerHTML = change.toFixed(' . json_encode( wc_get_price_decimals() ) . ');
119 } else {
120 changeDisplay.innerHTML = "";
121 }
122 });
123 });
124 </script>
125 ';
126 }
127 }
128
129 /**
130 * Process the payment.
131 *
132 * @param int $order_id Order ID.
133 *
134 * @return string[]
135 */
136 public function process_payment( $order_id ): array {
137 // Check nonce.
138 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' ) ) {
139 wp_die( /* translators: Checkout error shown when cash nonce validation fails. */ esc_html__( 'Nonce verification failed', 'woocommerce-pos' ) );
140 }
141
142 // get order object.
143 $order = new WC_Order( $order_id );
144 $tendered = $order->get_total();
145
146 // get pos_cash data from $_POST.
147 if ( isset( $_POST['pos-cash-tendered'] ) && ! empty( $_POST['pos-cash-tendered'] ) ) {
148 $tendered = wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['pos-cash-tendered'] ) ) );
149 }
150 $result = $this->apply_tendered_payment( $order, $tendered, true );
151 if ( is_wp_error( $result ) ) {
152 wc_add_notice( $result->get_error_message(), 'error' );
153
154 return array(
155 'result' => 'failure',
156 'redirect' => '',
157 );
158 }
159
160 // Return thankyou redirect
161 // $redirect = add_query_arg(array(
162 // 'wcpos' => 1,
163 // ), get_home_url( null, '/wcpos-checkout/order-received/' . $order->get_id() ));.
164
165 return array(
166 'result' => 'success',
167 'redirect' => $this->get_return_url( $order ),
168 );
169 }
170
171 /**
172 * Process a refund.
173 *
174 * Cash refunds are handled manually, so just return true.
175 *
176 * @param int $order_id Order ID.
177 * @param float|null $amount Refund amount.
178 * @param string $reason Refund reason.
179 *
180 * @return bool
181 */
182 public function process_refund( $order_id, $amount = null, $reason = '' ) {
183 return true;
184 }
185
186 /**
187 * Calculate and display change.
188 *
189 * @param int $order_id Order ID.
190 */
191 public function calculate_change( $order_id ): void {
192 $order = wc_get_order( $order_id );
193 if ( ! $order ) {
194 return;
195 }
196
197 $message = '';
198 $tendered = $order->get_meta( '_pos_cash_amount_tendered' );
199 $change = $order->get_meta( '_pos_cash_change' );
200
201 // construct message.
202 if ( $tendered && $change ) {
203 $message = /* translators: Order note label for the cash amount received from the customer at checkout. */ __( 'Amount Tendered', 'woocommerce-pos' ) . ': ';
204 $message .= wc_price( $tendered, array( 'currency' => $order->get_currency() ) ) . '<br>';
205 $message .= /* translators: Money returned to the customer after a cash payment. */ _x( 'Change', 'Money returned from cash sale', 'woocommerce-pos' ) . ': ';
206 $message .= wc_price( $change, array( 'currency' => $order->get_currency() ) );
207 }
208
209 echo wp_kses_post( $message );
210 }
211
212 /**
213 * Provider family identifier.
214 *
215 * @param WP_REST_Request|null $request Request object.
216 */
217 public function get_pos_provider( ?WP_REST_Request $request = null ): string {
218 return 'wcpos';
219 }
220
221 /**
222 * Process a POS checkout action for cash.
223 *
224 * @param array $state Checkout state.
225 * @param string $action Checkout action.
226 * @param array $payment_data Payment data.
227 * @param WC_Order $order Order object.
228 * @param WP_REST_Request|null $request Request object.
229 *
230 * @return array|\WP_Error
231 */
232 public function process_pos_checkout_action( array $state, string $action, array $payment_data, WC_Order $order, ?WP_REST_Request $request = null ) {
233 if ( 'cancel' === $action ) {
234 $state['status'] = 'cancelled';
235 return $state;
236 }
237
238 if ( 'start' !== $action ) {
239 return $state;
240 }
241
242 $tendered = isset( $payment_data['amount_tendered'] ) && '' !== $payment_data['amount_tendered']
243 ? wc_format_decimal( $payment_data['amount_tendered'] )
244 : wc_format_decimal( $order->get_total() );
245
246 $result = $this->apply_tendered_payment( $order, $tendered, false );
247 if ( is_wp_error( $result ) ) {
248 return $result;
249 }
250
251 $state['status'] = 'completed';
252 $state['provider_data'] = array(
253 'amount_tendered' => $order->get_meta( '_pos_cash_amount_tendered' ),
254 'change' => $order->get_meta( '_pos_cash_change' ),
255 );
256
257 return $state;
258 }
259
260 /**
261 * Apply a tendered cash amount to an order.
262 *
263 * @param WC_Order $order Order object.
264 * @param string $tendered Tendered amount.
265 * @param bool $allow_partial Whether partial cash is allowed.
266 *
267 * @return true|\WP_Error
268 */
269 private function apply_tendered_payment( WC_Order $order, string $tendered, bool $allow_partial ) {
270 $tendered = wc_format_decimal( $tendered );
271
272 if ( (float) $tendered < 0 ) {
273 return new \WP_Error(
274 'wcpos_invalid_tendered_amount',
275 /* translators: Checkout validation error for the cash amount received from the customer. */
276 __( 'Tendered amount must be zero or greater.', 'woocommerce-pos' ),
277 array( 'status' => 400 )
278 );
279 }
280
281 $change = $tendered > $order->get_total() ? wc_format_decimal( floatval( $tendered ) - floatval( $order->get_total() ) ) : '0';
282 $order->set_payment_method( $this->id );
283 $order->set_payment_method_title( $this->title );
284 $order->update_meta_data( '_pos_cash_amount_tendered', $tendered );
285 $order->update_meta_data( '_pos_cash_change', $change );
286
287 if ( $tendered >= $order->get_total() ) {
288 Order_Notes::add_cash_note( $order, $tendered, $change );
289 $order->payment_complete();
290 return true;
291 }
292
293 if ( ! $allow_partial ) {
294 return new \WP_Error(
295 'wcpos_partial_cash_not_supported',
296 __( 'Partial cash payments are not supported by the POS API checkout flow.', 'woocommerce-pos' ),
297 array( 'status' => 400 )
298 );
299 }
300
301 $fee = new WC_Order_Item_Fee();
302 $fee->set_props(
303 array(
304 'name' => /* translators: Fee line-item name added when a cash payment is partial. */ __( 'Partial Payment', 'woocommerce-pos' ),
305 'tax_class' => 0,
306 'amount' => '-' . $tendered,
307 'total' => '-' . $tendered,
308 'total_tax' => 0,
309 )
310 );
311 $fee->add_meta_data( 'date_paid_gmt', gmdate( 'Y-m-d\TH:i:s' ), true );
312 $fee->set_order_id( $order->get_id() );
313 $fee->save();
314
315 $order->add_item( $fee );
316 $order->set_total( wc_format_decimal( floatval( $order->get_total() ) - floatval( $tendered ) ) );
317 Order_Notes::add_cash_note( $order, $tendered, $change );
318 $order->update_status( 'wc-pos-partial' );
319
320 return true;
321 }
322 }
323