-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathAgenticRestEndpoint.php
More file actions
227 lines (188 loc) · 6.9 KB
/
Copy pathAgenticRestEndpoint.php
File metadata and controls
227 lines (188 loc) · 6.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
<?php
/**
* Base class for all agentic commerce REST endpoints.
*
* @package WooCommerce\PayPalCommerce\StoreSync\Endpoint
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\StoreSync\Endpoint;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use JsonException;
use WC_REST_Controller;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\StoreSync\Errors\Http\InternalServerError;
use WooCommerce\PayPalCommerce\StoreSync\Errors\Http\NotFoundError;
use WooCommerce\PayPalCommerce\StoreSync\Errors\AgenticError;
use WooCommerce\PayPalCommerce\StoreSync\Schema\PayPalCart;
use WooCommerce\PayPalCommerce\StoreSync\Auth\AuthServiceProvider;
use WooCommerce\PayPalCommerce\StoreSync\Response\CartResponse;
use WooCommerce\PayPalCommerce\StoreSync\Response\ResponseFactory;
use WooCommerce\PayPalCommerce\StoreSync\Session\AgenticSessionHandler;
use WooCommerce\PayPalCommerce\StoreSync\CartValidation\CartValidationProcessor;
use WooCommerce\PayPalCommerce\StoreSync\Helper\PayPalOrderManager;
use WooCommerce\PayPalCommerce\StoreSync\Helper\AgenticSessionManager;
/**
* Base class for REST controllers in the agentic commerce module.
*/
abstract class AgenticRestEndpoint extends WC_REST_Controller {
/**
* Endpoint namespace.
*/
protected const NAMESPACE = 'wc/v3/agentic';
/**
* JWT scope(s) required for the endpoint.
*/
protected const REQUIRED_SCOPES = array( 'cart' );
private AuthServiceProvider $auth_provider;
private AgenticSessionHandler $session_handler;
private AgenticSessionManager $session_manager;
protected ResponseFactory $response_factory;
protected CartValidationProcessor $validation_processor;
protected LoggerInterface $logger;
protected PayPalOrderManager $order_manager;
public function __construct(
AuthServiceProvider $auth_provider,
AgenticSessionHandler $session_handler,
AgenticSessionManager $session_manager,
ResponseFactory $response_factory,
CartValidationProcessor $validation_processor,
LoggerInterface $logger,
PayPalOrderManager $order_manager
) {
$this->auth_provider = $auth_provider;
$this->session_handler = $session_handler;
$this->session_manager = $session_manager;
$this->response_factory = $response_factory;
$this->validation_processor = $validation_processor;
$this->logger = $logger;
$this->order_manager = $order_manager;
}
/**
* Verify JWT access.
*
* @param WP_REST_Request $request The request object.
* @return bool|WP_Error True if access is granted, otherwise a WP_Error object.
*/
public function check_permission( WP_REST_Request $request ) {
$token = $request->get_header( 'Authorization' );
$auth_service = $this->auth_provider->auth_service();
$context = $auth_service->get_token( $token );
if ( is_wp_error( $context ) ) {
assert( $context instanceof WP_Error );
$this->logger->error( '[REST] Permission denied', $context->get_all_error_data() );
return $context;
}
return $auth_service->verify_claims( $context, static::REQUIRED_SCOPES );
}
protected function with_session( callable $callback ) {
return $this->session_manager->with_session( $callback );
}
/**
* Successful API response, always returns cart details.
*
* @param CartResponse $cart The PayPalCart response object.
* @param int $status_code HTTP status code.
* @return WP_REST_Response The successful response.
*/
protected function cart_details( CartResponse $cart, int $status_code ): WP_REST_Response {
$this->logger->info( "[REST] $status_code Response", $cart->to_array() );
return new WP_REST_Response( $cart->to_array(), $status_code );
}
/**
* Returns an error REST API response.
*
* @param AgenticError $error The error object.
* @return WP_REST_Response The error response.
*/
protected function error( AgenticError $error ): WP_REST_Response {
$error_id = $error->get_debug_id();
$this->logger->error( "[REST] Error - $error_id", $error->to_array() );
return new WP_REST_Response( $error->to_array(), $error->get_status_code() );
}
/**
* Parses and validates JSON request body.
*
* @param WP_REST_Request $request The request object.
* @return array|AgenticError Parsed data or error response.
*/
protected function parse_json_body( WP_REST_Request $request ) {
$body = $request->get_body();
if ( empty( $body ) ) {
return new InternalServerError( 'Request body is required' );
}
try {
return json_decode( $body, true, 512, JSON_THROW_ON_ERROR );
} catch ( JsonException $exception ) {
return new InternalServerError( 'Request body contains invalid JSON. Error: ' . $exception->getMessage() );
}
}
/**
* Parse and validate cart from request body.
*
* @param WP_REST_Request $request The request object.
* @return PayPalCart|AgenticError Valid cart or error.
*/
protected function get_cart_from_request( WP_REST_Request $request ) {
$data = $this->parse_json_body( $request );
if ( $data instanceof AgenticError ) {
return $data;
}
$cart = PayPalCart::from_array( $data );
return $this->validation_processor->validate_cart( $cart );
}
/**
* Load cart data from local storage (ie from session table) with standardized error handling.
*
* @param string $cart_id The cart ID to load.
* @return array|AgenticError Cart session data or error.
*/
protected function get_stored_cart( string $cart_id ) {
$session = $this->session_handler->load_cart_session( $cart_id );
if ( ! $session ) {
return new NotFoundError(
"Cart with ID '{$cart_id}' does not exist or has expired",
array(
array(
'field' => 'cartId',
'issue' => 'NOT_FOUND',
'description' => "Cart with ID '{$cart_id}' does not exist. Verify cart ID or create a new cart.",
),
)
);
}
return $session;
}
protected function create_local_cart( PayPalCart $cart, string $ec_token ): string {
return $this->session_handler->create_cart_session( $cart, $ec_token );
}
protected function store_local_cart( string $cart_id, PayPalCart $cart, ?string $ec_token = null ): bool {
return $this->session_handler->update_cart_session( $cart_id, $cart, $ec_token );
}
protected function flush_local_cart( string $cart_id ): bool {
return $this->session_handler->destroy_cart_session( $cart_id );
}
/**
* Get standard cart ID argument definition for route registration.
*
* @return array Cart ID argument configuration.
*/
protected function get_cart_id_arg(): array {
return array(
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => fn( $param ) => $this->validate_cart_id( $param ),
);
}
/**
* Standard cart ID validation callback.
*
* @param mixed $param The parameter to validate.
* @return bool True if valid cart ID format.
*/
private function validate_cart_id( $param ): bool {
return is_string( $param ) && strlen( $param ) >= 10;
}
}