PluginProbe
MultiSafepay plugin for WooCommerce / trunk
MultiSafepay plugin for WooCommerce vtrunk
6.11.1 6.12.0 6.13.0 6.2.0 6.2.1 6.3.0 6.3.1 6.4.0 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.7.3 6.8.0 6.8.1 6.8.2 6.8.3 6.9.0 All 84 releases
multisafepay / src / Services / BlocksPaymentDataService.php

BlocksPaymentDataService.php in MultiSafepay plugin for WooCommerce trunk, at src/Services/BlocksPaymentDataService.php

198 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php declare(strict_types=1);
2
3 namespace MultiSafepay\WooCommerce\Services;
4
5 use Automattic\WooCommerce\StoreApi\Payments\PaymentContext;
6 use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
7 use Exception;
8 use MultiSafepay\WooCommerce\Utils\Hpos;
9 use MultiSafepay\WooCommerce\Utils\Logger;
10 use WC_Order;
11
12 /**
13 * Maps Store API payment data to order meta for compatibility with legacy handlers.
14 */
15 class BlocksPaymentDataService {
16
17 public const META_KEY = '_multisafepay_blocks_payment_data';
18
19 /**
20 * Request-lifecycle Blocks payloads are cleared after checkout, but they are still
21 * written to order meta first. Keep a defensive cap so oversized client-controlled
22 * values do not hit the database and fail explicitly instead of truncating opaque payloads.
23 */
24 private const MAX_STORED_PAYMENT_DATA_VALUE_LENGTH = 20000;
25
26 /**
27 * @var Logger
28 */
29 private $logger;
30
31 /**
32 * Keys ending with these suffixes are only needed during the request lifecycle.
33 */
34 public const REQUEST_LIFECYCLE_KEY_PATTERN = '/(?:^|_)(payment_token|browser|payment_component_payload|payment_component_tokenize)$/';
35
36 /**
37 * @param Logger|null $logger
38 */
39 public function __construct( ?Logger $logger = null ) {
40 $this->logger = $logger ?? new Logger();
41 }
42
43 /**
44 * Get stored Blocks payment data from order meta.
45 *
46 * @param WC_Order $order
47 * @return array
48 */
49 public function get_blocks_payment_data( WC_Order $order ): array {
50 $data = $order->get_meta( self::META_KEY );
51 return is_array( $data ) ? $data : array();
52 }
53
54 /**
55 * Get a scalar value from Blocks payment meta.
56 *
57 * @param WC_Order $order
58 * @param string $key
59 * @return string
60 */
61 public function get_blocks_payment_data_value( WC_Order $order, string $key ): string {
62 if ( empty( $key ) ) {
63 return '';
64 }
65 $data = $this->get_blocks_payment_data( $order );
66 if ( ! isset( $data[ $key ] ) ) {
67 return '';
68 }
69
70 $raw_value = $data[ $key ];
71 if ( ! is_scalar( $raw_value ) ) {
72 return '';
73 }
74
75 $value = (string) $raw_value;
76
77 // Wallet payloads are JSON strings (Google Pay token and browser info).
78 // sanitize_text_field() can alter JSON (e.g. stripping characters), which may break
79 // server-side processing. These values are never rendered back to the customer,
80 // so we preserve them as-is.
81 if ( preg_match( '/(?:^|_)payment_token$/', $key ) || 'payment_token' === $key ) {
82 return $value;
83 }
84 if ( preg_match( '/(?:^|_)browser$/', $key ) || 'browser' === $key ) {
85 return $value;
86 }
87
88 return sanitize_text_field( $value );
89 }
90
91 /**
92 * Save MultiSafepay Blocks payment component data to order meta.
93 *
94 * @param PaymentContext $context Holds context for the payment.
95 * @param PaymentResult $result Result of the payment.
96 * @return void
97 * @throws Exception When submitted payment data exceeds the defensive storage limit.
98 */
99 public function save_blocks_payment_data_to_order_meta( PaymentContext $context, PaymentResult $result ): void {
100 unset( $result );
101
102 $payment_method = (string) ( $context->__get( 'payment_method' ) ?? '' );
103 if ( empty( $payment_method ) ) {
104 return;
105 }
106
107 if ( strpos( $payment_method, 'multisafepay_' ) !== 0 ) {
108 return;
109 }
110
111 $payment_data = $context->__get( 'payment_data' );
112 $payment_data = is_array( $payment_data ) ? $payment_data : array();
113
114 $order = $context->__get( 'order' );
115 if ( ! $order instanceof WC_Order ) {
116 return;
117 }
118
119 // Limit stored keys to the expected MultiSafepay Blocks payload.
120 // This avoids unintentionally persisting unrelated request data.
121 //
122 // We also allow the legacy keys (payment_token, browser) so the backend can
123 // behave exactly like classic checkout when Blocks submits wallet-direct data.
124 $allowed_key_pattern = '/^(?:multisafepay_[a-z0-9_]+_(payment_component_payload|payment_component_tokenize|payment_token|browser)|payment_token|browser)$/';
125
126 $filtered_payment_data = array();
127
128 foreach ( $payment_data as $key => $value ) {
129 if ( ! is_string( $key ) || ! preg_match( $allowed_key_pattern, $key ) ) {
130 continue;
131 }
132
133 if ( ! is_scalar( $value ) ) {
134 continue;
135 }
136
137 $value = (string) $value;
138
139 if ( strlen( $value ) > self::MAX_STORED_PAYMENT_DATA_VALUE_LENGTH ) {
140 $this->logger->log_warning(
141 'Rejected oversized Blocks payment data for order ID ' . $order->get_id() .
142 ', key ' . $key .
143 ', received length ' . strlen( $value ) .
144 ', max allowed length ' . self::MAX_STORED_PAYMENT_DATA_VALUE_LENGTH
145 );
146
147 throw new Exception( __( 'We could not process your payment details. Please try again.', 'multisafepay' ) );
148 }
149
150 $filtered_payment_data[ $key ] = $value;
151 }
152
153 if ( empty( $filtered_payment_data ) ) {
154 return;
155 }
156
157 Hpos::update_meta( $order, self::META_KEY, $filtered_payment_data );
158 }
159
160 /**
161 * Remove keys matching a given pattern from stored Blocks payment data.
162 *
163 * @param WC_Order $order
164 * @param string $key_pattern
165 * @return void
166 */
167 public function clear_blocks_payment_data_from_order_meta_by_pattern( WC_Order $order, string $key_pattern ): void {
168 $blocks_payment_data = $this->get_blocks_payment_data( $order );
169
170 if ( empty( $blocks_payment_data ) ) {
171 return;
172 }
173
174 $did_remove_matching_data = false;
175
176 foreach ( array_keys( $blocks_payment_data ) as $key ) {
177 if ( ! is_string( $key ) || ! preg_match( $key_pattern, $key ) ) {
178 continue;
179 }
180
181 unset( $blocks_payment_data[ $key ] );
182 $did_remove_matching_data = true;
183 }
184
185 if ( ! $did_remove_matching_data ) {
186 return;
187 }
188
189 if ( empty( $blocks_payment_data ) ) {
190 Hpos::delete_meta( $order, self::META_KEY );
191 }
192
193 if ( ! empty( $blocks_payment_data ) ) {
194 Hpos::update_meta( $order, self::META_KEY, $blocks_payment_data );
195 }
196 }
197 }
198