| | |
| | | namespace JVBase\rest\routes; |
| | | |
| | | |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\rest\Rest; |
| | | use Exception; |
| | | use JVBase\rest\Route; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use Exception; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | |
| | | class IntegrationsSquareRoutes extends RestRouteManager |
| | | class IntegrationsSquareRoutes extends Rest |
| | | { |
| | | public function registerRoutes():void |
| | | { |
| | | register_rest_route('jvb/v1/square', '/process-payment', [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'handlePaymentProcessing'], |
| | | 'permission_callback' => '__return_true' // Adjust based on your auth |
| | | ]); |
| | | Route::for('square/process-payment') |
| | | ->post([$this, 'handlePaymentProcessing']) |
| | | ->auth('public') |
| | | ->rateLimit(2); |
| | | |
| | | register_rest_route('jvb/v1/square', '/saved-cards', [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getSavedCards'], |
| | | 'permission_callback' => 'is_user_logged_in' |
| | | ]); |
| | | Route::for('square/saved-cards') |
| | | ->post([$this, 'getSavedCards']) |
| | | ->auth('user') |
| | | ->rateLimit(5); |
| | | |
| | | Route::for('square/order-history') |
| | | ->get([$this, 'getOrderHistory']) |
| | | ->auth('user') |
| | | ->rateLimit(5); |
| | | |
| | | register_rest_route('jvb/v1/square', '/order-history', [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getOrderHistory'], |
| | | 'permission_callback' => 'is_user_logged_in' |
| | | ]); |
| | | |
| | | |
| | | register_rest_route('jvb/v1/square', '/order-status/(?P<order_id>[a-zA-Z0-9_-]+)', [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getOrderStatus'], |
| | | 'permission_callback' => '__return_true' // Allow guests with order ID |
| | | ]); |
| | | Route::for(Route::pattern('square/order-status/{order_id}')) |
| | | ->get([$this, 'getOrderStatus']) |
| | | ->auth('public') |
| | | ->rateLimit(20); |
| | | } |
| | | |
| | | public function handlePaymentProcessing($request): array |
| | | //TODO: Are we processing this through our server at all? Or is it in the javascript going straight to square? |
| | | public function handlePaymentProcessing($request):WP_REST_Response |
| | | { |
| | | $data = $request->get_json_params(); |
| | | |
| | |
| | | // This ensures retries use SAME key |
| | | $cart_id = $data['cart_id'] ?? ''; |
| | | if (!$cart_id) { |
| | | return ['success' => false, 'message' => 'Missing cart ID']; |
| | | return $this->validationError(['message'=>'Missing cart ID']); |
| | | } |
| | | |
| | | // Check if we already processed this cart |
| | | $existing_order = get_transient(BASE . 'cart_order_' . $cart_id); |
| | | if ($existing_order) { |
| | | // Return cached result - prevents double charge |
| | | return $existing_order; |
| | | return $this->success($existing_order); |
| | | } |
| | | |
| | | // Generate idempotency key tied to this specific cart |
| | | $idempotency_key = 'cart_' . $cart_id . '_' . time(); |
| | | |
| | | // Store key to prevent reprocessing |
| | | //TODO: Should we just use our Cache.php? |
| | | set_transient(BASE . 'cart_idempotency_' . $cart_id, $idempotency_key, HOUR_IN_SECONDS); |
| | | |
| | | // Validate required fields |
| | | $required = ['source_id', 'amount', 'items', 'customer']; |
| | | foreach ($required as $field) { |
| | | if (empty($data[$field])) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => "Missing required field: {$field}" |
| | | ]; |
| | | return $this->validationError(['message' => "Missing required field: {$field}"]); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | set_transient(BASE . 'cart_order_' . $cart_id, $result, HOUR_IN_SECONDS); |
| | | |
| | | return $result; |
| | | return $this->success($result); |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('Payment processing failed', [ |
| | | 'error' => $e->getMessage(), |
| | | 'idempotency_key' => $data['idempotency_key'] |
| | | ]); |
| | | |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => $e->getMessage() |
| | | ]; |
| | | return $this->error($e->getMessage()); |
| | | } |
| | | } |
| | | |
| | | public function getSavedCards($request): array |
| | | public function getSavedCards(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $user_id = get_current_user_id(); |
| | | if (!$user_id) { |
| | | return ['success' => false, 'message' => 'Not logged in']; |
| | | $data = $request->get_params(); |
| | | $user_id = absint($data['user']??0); |
| | | if ($user_id === 0) { |
| | | return $this->validationError(['message' => 'Not logged in']); |
| | | } |
| | | |
| | | $square = JVB()->connect('square'); |
| | |
| | | $square_customer_id = get_user_meta($user_id, BASE . '_square_customer_id', true); |
| | | |
| | | if (!$square_customer_id) { |
| | | return ['success' => true, 'cards' => []]; |
| | | return $this->success(['cards' => []]); |
| | | } |
| | | |
| | | // Fetch cards from Square (2025-compliant - separate endpoint) |
| | | $cards_response = $square->getRequest('cards?customer_id=' . $square_customer_id); |
| | | |
| | | if (is_wp_error($cards_response)) { |
| | | return ['success' => false, 'message' => 'Failed to fetch cards']; |
| | | return $this->error('Failed to fetch cards'); |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'cards' => $cards_response['cards'] ?? [] |
| | | ]; |
| | | return $this->success(['cards' => $cards_response['cards']??[]]); |
| | | } |
| | | |
| | | public function getOrderHistory($request): array |
| | | public function getOrderHistory(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $user_id = get_current_user_id(); |
| | | if (!$user_id) { |
| | | return ['success' => false, 'message' => 'Not logged in']; |
| | | $data = $request->get_params(); |
| | | $user_id = absint($data['user']??0); |
| | | if ($user_id === 0) { |
| | | return $this->validationError(['message' => 'Not logged in']); |
| | | } |
| | | |
| | | // Get orders from custom post type |
| | |
| | | |
| | | $order_data = []; |
| | | foreach ($orders as $order) { |
| | | $meta = new \JVBase\meta\MetaManager($order->ID, 'post'); |
| | | $order_data[] = [ |
| | | $meta = Meta::forPost($order->ID); |
| | | $fields = $meta->getAll(['square_order_id', 'status', 'amount', 'items', 'created_at', 'pickup_time']); |
| | | $order_data[] = array_merge([ |
| | | 'wp_order_id' => $order->ID, |
| | | 'square_order_id' => $meta->getValue('square_order_id'), |
| | | 'status' => $meta->getValue('status'), |
| | | 'amount' => $meta->getValue('amount'), |
| | | 'items' => $meta->getValue('items'), |
| | | 'created_at' => $meta->getValue('created_at'), |
| | | 'pickup_time' => $meta->getValue('pickup_time') |
| | | ]; |
| | | ], $fields); |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'orders' => $order_data |
| | | ]; |
| | | return $this->success(['orders' => $order_data]); |
| | | } |
| | | |
| | | public function getOrderStatus($request): array |
| | | public function getOrderStatus(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $order_id = $request->get_param('order_id'); |
| | | |
| | |
| | | $wp_order_id = get_option(BASE . 'square_order_map_' . $order_id); |
| | | |
| | | if (!$wp_order_id) { |
| | | return ['success' => false, 'message' => 'Order not found']; |
| | | return $this->error('Order not found'); |
| | | } |
| | | |
| | | $meta = new \JVBase\meta\MetaManager($wp_order_id, 'post'); |
| | | $meta = Meta::forPost($wp_order_id); |
| | | $fields = $meta->getAll(['status', 'fulfillment_status', 'pickup_time', 'items']); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'order' => [ |
| | | 'status' => $meta->getValue('status'), |
| | | 'fulfillment_status' => $meta->getValue('fulfillment_status'), |
| | | 'pickup_time' => $meta->getValue('pickup_time'), |
| | | 'items' => $meta->getValue('items') |
| | | ] |
| | | ]; |
| | | return $this->success(['order' => $fields]); |
| | | } |
| | | } |