PluginProbe
Easy Hotel – Powerful Hotel Booking / 2.0.2
Easy Hotel – Powerful Hotel Booking v2.0.2
2.0.8 2.0.7 2.0.6 2.0.5 2.0.4 2.0.3 2.0.2 2.0.1 2.0.0 1.9.9 1.9.8 1.9.7 1.9.6 1.9.5 1.9.4 1.9.3 1.9.2 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 All 110 releases
easy-hotel / admin / includes / native-checkout / gateways / class-paypal-gateway.php

class-paypal-gateway.php in Easy Hotel – Powerful Hotel Booking 2.0.2, at admin/includes/native-checkout/gateways/class-paypal-gateway.php

242 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PayPal gateway for the native checkout.
4 *
5 * Uses the PayPal Orders v2 REST API:
6 * 1. JS Smart Buttons call our `create_payment` endpoint, which
7 * creates a PayPal order server-side and returns its id.
8 * 2. After the buyer approves in the PayPal popup, the JS layer
9 * calls our `capture_payment` endpoint, which captures the
10 * order server-side. We trust the captured status returned by
11 * PayPal — not anything the client claims.
12 *
13 * Credentials live in the existing eshb_settings option:
14 * - paypal-client-id
15 * - paypal-client-secret
16 * - paypal-mode ('sandbox' or 'live')
17 */
18
19 if ( ! defined( 'ABSPATH' ) ) exit;
20
21 class ESHB_Native_PayPal_Gateway extends ESHB_Native_Abstract_Gateway {
22
23 protected $id = 'paypal';
24 protected $title = 'PayPal';
25 protected $description = '';
26
27 public function __construct() {
28 $this->title = __( 'PayPal', 'easy-hotel' );
29 $this->description = __( 'Pay securely using your PayPal account or a credit card.', 'easy-hotel' );
30 }
31
32 private function get_settings() {
33 return [
34 'client_id' => trim( (string) eshb_native_checkout_get_setting( 'paypal-client-id', '' ) ),
35 'client_secret' => trim( (string) eshb_native_checkout_get_setting( 'paypal-client-secret', '' ) ),
36 'mode' => ( eshb_native_checkout_get_setting( 'paypal-mode', 'sandbox' ) === 'live' ) ? 'live' : 'sandbox',
37 ];
38 }
39
40 private function api_base() {
41 $s = $this->get_settings();
42 return $s['mode'] === 'live'
43 ? 'https://api-m.paypal.com'
44 : 'https://api-m.sandbox.paypal.com';
45 }
46
47 public function is_enabled() {
48 // Defaults to enabled when the switch has never been saved, so
49 // existing installs that already configured credentials keep working.
50 $switch_on = ! empty( eshb_native_checkout_get_setting( 'gateway-paypal-enable', true ) );
51
52 $s = $this->get_settings();
53 return $switch_on && ! empty( $s['client_id'] ) && ! empty( $s['client_secret'] );
54 }
55
56 public function get_frontend_data() {
57 $s = $this->get_settings();
58 return array_merge( parent::get_frontend_data(), [
59 'clientId' => $s['client_id'],
60 'mode' => $s['mode'],
61 'currency' => $this->get_currency_code(),
62 ] );
63 }
64
65 private function get_currency_code() {
66 // PayPal needs an ISO-4217 currency code. Prefer WooCommerce's
67 // configured currency (since the plugin already integrates with
68 // it for currency symbol formatting), then fall back to a guess
69 // from the symbol setting, then USD.
70 if ( function_exists( 'get_woocommerce_currency' ) ) {
71 $code = get_woocommerce_currency();
72 if ( ! empty( $code ) ) return $code;
73 }
74
75 $settings = get_option( 'eshb_settings', [] );
76 $symbol = isset( $settings['currency_symbol'] ) ? trim( (string) $settings['currency_symbol'] ) : '';
77 $map = [
78 '$' => 'USD',
79 '' => 'EUR',
80 '£' => 'GBP',
81 '¥' => 'JPY',
82 'A$' => 'AUD',
83 'C$' => 'CAD',
84 '' => 'INR',
85 '' => 'TRY',
86 '' => 'RUB',
87 ];
88 if ( ! empty( $symbol ) && isset( $map[ $symbol ] ) ) {
89 return $map[ $symbol ];
90 }
91
92 return apply_filters( 'eshb_native_paypal_currency', 'USD' );
93 }
94
95 /**
96 * Fetch an OAuth2 token from PayPal. Cached for 8 minutes since
97 * PayPal tokens last ~9 minutes.
98 */
99 private function get_access_token() {
100 $s = $this->get_settings();
101 if ( empty( $s['client_id'] ) || empty( $s['client_secret'] ) ) {
102 return new WP_Error( 'paypal_not_configured', __( 'PayPal credentials are not configured.', 'easy-hotel' ) );
103 }
104
105 $cache_key = 'eshb_paypal_token_' . md5( $s['client_id'] . '|' . $s['mode'] );
106 $cached = get_transient( $cache_key );
107 if ( $cached ) return $cached;
108
109 $response = wp_remote_post( $this->api_base() . '/v1/oauth2/token', [
110 'timeout' => 20,
111 'headers' => [
112 'Authorization' => 'Basic ' . base64_encode( $s['client_id'] . ':' . $s['client_secret'] ),
113 'Accept' => 'application/json',
114 'Content-Type' => 'application/x-www-form-urlencoded',
115 ],
116 'body' => 'grant_type=client_credentials',
117 ] );
118
119 if ( is_wp_error( $response ) ) return $response;
120
121 $body = json_decode( wp_remote_retrieve_body( $response ), true );
122 if ( empty( $body['access_token'] ) ) {
123 return new WP_Error( 'paypal_token_failed', $body['error_description'] ?? __( 'Unable to authenticate with PayPal.', 'easy-hotel' ) );
124 }
125
126 set_transient( $cache_key, $body['access_token'], 8 * MINUTE_IN_SECONDS );
127 return $body['access_token'];
128 }
129
130 public function create_payment( array $reservation, array $customer, array $pricing ) {
131
132 $token = $this->get_access_token();
133 if ( is_wp_error( $token ) ) {
134 return [ 'success' => false, 'message' => $token->get_error_message() ];
135 }
136
137 $amount = number_format( (float) ( $pricing['grandTotal'] ?? $pricing['totalPrice'] ?? 0 ), 2, '.', '' );
138 $currency = $this->get_currency_code();
139
140 $payload = [
141 'intent' => 'CAPTURE',
142 'purchase_units' => [
143 [
144 'amount' => [
145 'currency_code' => $currency,
146 'value' => $amount,
147 ],
148 'description' => sprintf(
149 /* translators: %s: accommodation title */
150 __( 'Booking for %s', 'easy-hotel' ),
151 get_the_title( (int) ( $reservation['accomodation_id'] ?? 0 ) )
152 ),
153 ],
154 ],
155 'application_context' => [
156 'brand_name' => get_bloginfo( 'name' ),
157 'user_action' => 'PAY_NOW',
158 'shipping_preference' => 'NO_SHIPPING',
159 ],
160 ];
161
162 $response = wp_remote_post( $this->api_base() . '/v2/checkout/orders', [
163 'timeout' => 30,
164 'headers' => [
165 'Authorization' => 'Bearer ' . $token,
166 'Content-Type' => 'application/json',
167 ],
168 'body' => wp_json_encode( $payload ),
169 ] );
170
171 if ( is_wp_error( $response ) ) {
172 return [ 'success' => false, 'message' => $response->get_error_message() ];
173 }
174
175 $body = json_decode( wp_remote_retrieve_body( $response ), true );
176 if ( empty( $body['id'] ) ) {
177 $msg = $body['message'] ?? __( 'Failed to create PayPal order.', 'easy-hotel' );
178 return [ 'success' => false, 'message' => $msg ];
179 }
180
181 return [
182 'success' => true,
183 'data' => [
184 'order_id' => $body['id'],
185 ],
186 ];
187 }
188
189 public function capture_payment( array $params ) {
190 $order_id = sanitize_text_field( $params['order_id'] ?? '' );
191 if ( empty( $order_id ) ) {
192 return [ 'success' => false, 'message' => __( 'Missing PayPal order id.', 'easy-hotel' ) ];
193 }
194
195 $token = $this->get_access_token();
196 if ( is_wp_error( $token ) ) {
197 return [ 'success' => false, 'message' => $token->get_error_message() ];
198 }
199
200 $response = wp_remote_post( $this->api_base() . '/v2/checkout/orders/' . rawurlencode( $order_id ) . '/capture', [
201 'timeout' => 30,
202 'headers' => [
203 'Authorization' => 'Bearer ' . $token,
204 'Content-Type' => 'application/json',
205 // Idempotency key prevents accidental double-capture if the JS layer retries.
206 'PayPal-Request-Id' => 'eshb-' . $order_id,
207 ],
208 'body' => '{}',
209 ] );
210
211 if ( is_wp_error( $response ) ) {
212 return [ 'success' => false, 'message' => $response->get_error_message() ];
213 }
214
215 $body = json_decode( wp_remote_retrieve_body( $response ), true );
216 $status = $body['status'] ?? '';
217
218 if ( $status !== 'COMPLETED' ) {
219 $msg = $body['message'] ?? sprintf(
220 /* translators: %s: PayPal capture status code returned by the Orders v2 API */
221 __( 'PayPal capture failed (%s).', 'easy-hotel' ),
222 $status
223 );
224 return [ 'success' => false, 'message' => $msg, 'raw' => $body ];
225 }
226
227 $capture = $body['purchase_units'][0]['payments']['captures'][0] ?? [];
228 $amount = (float) ( $capture['amount']['value'] ?? 0 );
229 $currency = $capture['amount']['currency_code'] ?? $this->get_currency_code();
230 $txn_id = $capture['id'] ?? $order_id;
231
232 return [
233 'success' => true,
234 'transaction_id' => $txn_id,
235 'amount' => $amount,
236 'currency' => $currency,
237 'mode' => $this->get_settings()['mode'],
238 'raw' => $body,
239 ];
240 }
241 }
242