├── .gitignore ├── LICENSE ├── README.md ├── twocheckout-convert-plus-v2.3.1.zip ├── twocheckout-convert-plus ├── assets │ ├── css │ │ └── twocheckout.css │ └── images │ │ └── spinner.gif ├── bootstrap.php ├── src │ └── Twocheckout │ │ ├── Helpers │ │ └── class-convert-plus-helper.php │ │ └── TwoCheckoutApi.php ├── templates │ ├── admin-options.php │ └── init_form_fields.php ├── twocheckout.png └── wc-twocheckout-convert-plus.php ├── twocheckout-inline-v2.3.1.zip ├── twocheckout-inline ├── .gitignore ├── assets │ ├── css │ │ └── twocheckout.css │ ├── images │ │ └── spinner.gif │ └── js │ │ └── twocheckout_inline.js ├── autoload.php ├── bootstrap.php ├── src │ └── Twocheckout │ │ ├── Helpers │ │ └── class-two-checkout-inline-helper.php │ │ ├── TwoCheckoutApi.php │ │ └── class-two-checkout-ipn-helper.php ├── templates │ ├── admin-options.php │ ├── init_form_fields.php │ ├── payment-fields.php │ └── payment-inline-fields.php ├── twocheckout.png └── wc-twocheckout-inline.php ├── twocheckout-v2.3.1.zip └── twocheckout ├── .gitignore ├── assets ├── css │ └── twocheckout.css ├── images │ └── spinner.gif └── js │ └── twocheckout.js ├── autoload.php ├── bootstrap.php ├── src └── Twocheckout │ ├── TwoCheckoutApi.php │ └── TwoCheckoutIpnHelperApi.php ├── templates ├── admin-options.php ├── init_form_fields.php └── payment-fields.php ├── twocheckout.png └── wc-twocheckout.php /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | */tests/ 3 | *.DS_Store 4 | *.swp 5 | *phpunit.xml 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 2Checkout 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WooCommerce 2 | 2Checkout WooCommerce Connector 3 | 4 | ### _[Signup free with 2Checkout and start selling!](https://www.2checkout.com/signup)_ 5 | 6 | This repository includes modules for each 2Checkout inteface: 7 | * **twocheckout** : 2PayJS/API 8 | * **twocheckout-inline** : Inline Checkout 9 | * **twocheckout-convert-plus** : Hosted Checkout 10 | 11 | ### Integrate WooCommerce with 2Checkout 12 | ---------------------------------------- 13 | 14 | ### 2Checkout Payment Module Setup 15 | 16 | #### 2Checkout Settings 17 | 18 | 1. Sign in to your 2Checkout account. 19 | 2. Navigate to **Dashboard** → **Integrations** → **Webhooks & API section** 20 | 3. There you can find the 'Merchant Code', 'Secret key', and the 'Buy link secret word' 21 | 4. Navigate to **Dashboard** → **Integrations** → **Ipn Settings** 22 | 5. Set the IPN URL which should be https://{your-site-name.com}/?wc-api=2checkout_ipn_{variant} ( you can copy it from your 2Checkout Woocommerce payment settings page labeled as **IPN Callback URL**) 23 | 6. When adding the IPN URL make sure you check **SHA3** as **Hashing algorithm** 24 | 7. Enable 'Triggers' in the IPN section. It’s simpler to enable all the triggers. Those who are not required will simply not be used. 25 | 26 | #### WooCommerce Settings 27 | 28 | 1. Copy the directory for the module that you want to install to your WordPress plugins directory under '/wp-content/plugins'. 29 | 2. In your WordPress admin, navigate to **Plugins** and install the plugin. 30 | 3. Navigate to your WooCommerce settings page, click on **Payments** and click the module link. 31 | 4. Check to enable. 32 | 5. Enter the payment title and description. 33 | 6. Enter your **Seller ID** found in your 2Checkout panel Integrations section. 34 | 7. Enter your **Secret Key** found in your 2Checkout panel Integrations section. 35 | 8. Enter your **Secret Word** 2Checkout panel Integrations section _(Only used for Inline Checkout and Hosted Checkout modules)_ 36 | 9. Click **Save Changes**. 37 | -------------------------------------------------------------------------------- /twocheckout-convert-plus-v2.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-convert-plus-v2.3.1.zip -------------------------------------------------------------------------------- /twocheckout-convert-plus/assets/css/twocheckout.css: -------------------------------------------------------------------------------- 1 | 2 | #tcoApiForm .form-control:focus, .input-group.focus { 3 | outline: none; 4 | } 5 | 6 | #tcoApiForm .btn-primary:hover { 7 | background-color: #26A0BA; 8 | } 9 | 10 | #tcoApiForm .btn-primary { 11 | box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, .2); 12 | text-transform: uppercase; 13 | border-radius: 2px; 14 | font-weight: 600; 15 | margin-top: 25px; 16 | padding: .5rem 1.25rem; 17 | color: #FFF; 18 | background-color: #2FB5D2; 19 | border-color: transparent; 20 | } 21 | 22 | #tcoApiForm button:disabled, 23 | #tcoApiForm button[disabled] { 24 | cursor: not-allowed; 25 | } 26 | 27 | #tcoApiForm .btn:hover { 28 | text-decoration: none; 29 | } 30 | 31 | #tcoApiForm .btn { 32 | font-weight: 400; 33 | line-height: 1.25; 34 | text-align: center; 35 | white-space: nowrap; 36 | vertical-align: middle; 37 | cursor: pointer; 38 | -webkit-user-select: none; 39 | -moz-user-select: none; 40 | -ms-user-select: none; 41 | user-select: none; 42 | border: 1px solid transparent; 43 | padding: 10px 20px; 44 | font-size: 15px; 45 | border-radius: 0; 46 | } 47 | 48 | #tcoWait .text img { 49 | display: inline-block; 50 | margin-right: 10px; 51 | width: 30px; 52 | top: 10px; 53 | position: relative; 54 | } 55 | 56 | #tcoWait .text { 57 | position: absolute; 58 | top: calc(50% - 22px); 59 | left: calc(50% - 100px); 60 | color: #222; 61 | font-size: 14px; 62 | line-height: 40px; 63 | } 64 | 65 | #tcoWait { 66 | text-align: center; 67 | width: 100%; 68 | display: none; 69 | height: 100%; 70 | top: 0; 71 | left: 0; 72 | position: absolute; 73 | color: #4939E4; 74 | z-index: 99; 75 | background: rgba(255, 255, 255, 0.75); 76 | } 77 | 78 | #tcoApiForm button:focus { 79 | outline: none; 80 | } 81 | 82 | #placeOrderTco { 83 | display: none; 84 | } 85 | 86 | #load { 87 | font-size: 15px; 88 | text-align: center; 89 | color: #888; 90 | } 91 | 92 | #tcoApiForm { 93 | padding: 10px 15px; 94 | position: relative; 95 | border: 1px solid #DDD; 96 | border-radius: 5px; 97 | box-shadow: 3px 3px 7px #DDD; 98 | font-family: Helvetica, sans-serif; 99 | } 100 | 101 | .wc_payment_method .payment_box, 102 | .wc_payment_method .payment_box fieldset { 103 | background: #FFF; 104 | } 105 | 106 | .wc_payment_method .payment_box fieldset { 107 | padding: 0; 108 | } 109 | 110 | .woocommerce-checkout ul.woocommerce-error li { 111 | padding: 0; 112 | font-size: 12px; 113 | } 114 | 115 | @media (max-width: 400px) { 116 | #tcoApiForm { 117 | padding: 10px; 118 | border: 0; 119 | box-shadow: none; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /twocheckout-convert-plus/assets/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-convert-plus/assets/images/spinner.gif -------------------------------------------------------------------------------- /twocheckout-convert-plus/bootstrap.php: -------------------------------------------------------------------------------- 1 | request_params = $request_params; 64 | $this->secret_key = $secret_key; 65 | $this->complete_order_on_payment = $complete_order_on_payment; 66 | self::$log_enabled = $debug; 67 | $this->wc_order = $order; 68 | } 69 | 70 | 71 | /** 72 | * @param $merchant_id 73 | * @param $buy_link_secret_word 74 | * @param $payload 75 | * 76 | * @return mixed 77 | * @throws Exception 78 | */ 79 | public function get_signature( $merchant_id, $buy_link_secret_word ) { 80 | $jwtToken = $this->generate_JWT_token( 81 | $merchant_id, 82 | time(), 83 | time() + 3600, 84 | $buy_link_secret_word 85 | ); 86 | 87 | $curl = curl_init(); 88 | 89 | curl_setopt_array( $curl, [ 90 | CURLOPT_URL => self::CURLOPT_URL, 91 | CURLOPT_RETURNTRANSFER => true, 92 | CURLOPT_CUSTOMREQUEST => 'POST', 93 | CURLOPT_POSTFIELDS => json_encode( $this->request_params ), 94 | CURLOPT_HTTPHEADER => [ 95 | 'content-type: application/json', 96 | 'cache-control: no-cache', 97 | 'merchant-token: ' . $jwtToken, 98 | ], 99 | ] ); 100 | $response = curl_exec( $curl ); 101 | $err = curl_error( $curl ); 102 | curl_close( $curl ); 103 | $log = new WC_Logger(); 104 | 105 | if ( $err ) { 106 | wc_add_notice( 'Error when trying to place order', $notice_type = 'error' ); 107 | $log->add( 'twocheckout-convert-plus', 108 | sprintf( 'Unable to get proper response from signature generation API. In file %s at line %s', __FILE__, 109 | __LINE__ ) ); 110 | } 111 | 112 | $response = json_decode( $response, true ); 113 | if ( JSON_ERROR_NONE !== json_last_error() || ! isset( $response['signature'] ) ) { 114 | wc_add_notice( 'Error when trying to place order', $notice_type = 'error' ); 115 | $log->add( 'twocheckout-convert-plus', 116 | sprintf( 'Unable to get proper response from signature generation API. In file %s at line %s', __FILE__, 117 | __LINE__ ) ); 118 | } 119 | 120 | return $response['signature']; 121 | 122 | } 123 | 124 | /** 125 | * @param $sub 126 | * @param $iat 127 | * @param $exp 128 | * @param $buy_link_secret_word 129 | * 130 | * @return string 131 | */ 132 | public function generate_JWT_token( $sub, $iat, $exp, $buy_link_secret_word ) { 133 | $header = $this->encode( json_encode( [ 'alg' => 'HS512', 'typ' => 'JWT' ] ) ); 134 | $payload = $this->encode( json_encode( [ 'sub' => $sub, 'iat' => $iat, 'exp' => $exp ] ) ); 135 | $signature = $this->encode( 136 | hash_hmac( 'sha512', "$header.$payload", $buy_link_secret_word, true ) 137 | ); 138 | 139 | return implode( '.', [ 140 | $header, 141 | $payload, 142 | $signature 143 | ] ); 144 | } 145 | 146 | /** 147 | * @param $data 148 | * 149 | * @return string|string[] 150 | */ 151 | private function encode( $data ) { 152 | return str_replace( '=', '', strtr( base64_encode( $data ), '+/', '-_' ) ); 153 | } 154 | 155 | /** 156 | * @return array [hash, algo] 157 | */ 158 | protected function extractHashFromRequest():array { 159 | $receivedAlgo = 'sha3-256'; 160 | $receivedHash = $this->request_params['SIGNATURE_SHA3_256']; 161 | 162 | if (!$receivedHash) { 163 | $receivedAlgo = 'sha256'; 164 | $receivedHash = $this->request_params['SIGNATURE_SHA2_256']; 165 | } 166 | 167 | if (!$receivedHash) { 168 | $receivedAlgo = 'md5'; 169 | $receivedHash = $this->request_params['HASH']; 170 | } 171 | 172 | return ['hash' => $receivedHash, 'algo' => $receivedAlgo]; 173 | } 174 | 175 | /** 176 | * Validate Ipn request 177 | * @return bool 178 | */ 179 | public function is_ipn_response_valid() { 180 | $result = ''; 181 | 182 | $hash = $this->extractHashFromRequest(); 183 | 184 | foreach ( $this->request_params as $key => $val ) { 185 | if ( !in_array($key ,[ "HASH", "SIGNATURE_SHA2_256", "SIGNATURE_SHA3_256"]) ) { 186 | if ( is_array( $val ) ) { 187 | $result .= $this->array_expand( $val ); 188 | } else { 189 | $size = strlen( stripslashes( $val ) ); 190 | $result .= $size . stripslashes( $val ); 191 | } 192 | } 193 | } 194 | 195 | if ( isset( $this->request_params['REFNO'] ) && ! empty($this->request_params['REFNO'] ) ) { 196 | $calcHash = $this->generate_hash( $this->secret_key, $result, $hash['algo'] ); 197 | 198 | if ( $hash['hash'] === $calcHash ) { 199 | return true; 200 | } 201 | } 202 | 203 | return false; 204 | } 205 | 206 | /** 207 | * generates hmac 208 | * 209 | * @param string $key 210 | * @param string $data 211 | * 212 | * @return string 213 | */ 214 | public function generate_hash($key, $data, $algo = 'sha3-256') { 215 | if ('sha3-256' === $algo) { 216 | return hash_hmac($algo, $data, $key); 217 | } 218 | 219 | $b = 64; // byte length for hash 220 | if (strlen($key) > $b) { 221 | $key = pack("H*", hash($algo, $key)); 222 | } 223 | 224 | $key = str_pad($key, $b, chr(0x00)); 225 | $ipad = str_pad('', $b, chr(0x36)); 226 | $opad = str_pad('', $b, chr(0x5c)); 227 | $k_ipad = $key ^ $ipad; 228 | $k_opad = $key ^ $opad; 229 | 230 | return hash($algo, $k_opad . pack("H*", hash($algo, $k_ipad . $data))); 231 | } 232 | 233 | /** 234 | * @param $array 235 | * 236 | * @return string 237 | */ 238 | private function array_expand( $array ) { 239 | $retval = ''; 240 | foreach ( $array as $key => $value ) { 241 | $size = strlen( stripslashes( $value ) ); 242 | $retval .= $size . stripslashes( $value ); 243 | } 244 | 245 | return $retval; 246 | } 247 | 248 | /** 249 | * @return string 250 | */ 251 | public function process_ipn() { 252 | $hash = $this->extractHashFromRequest(); 253 | 254 | try { 255 | if ( ! isset( $this->request_params['REFNO'] ) && empty( $this->request_params['REFNO'] ) ) { 256 | self::log( 'Cannot identify order: "%s".', $this->request_params['REFNOEXT'] ); 257 | 258 | return; 259 | } 260 | $orderId = intval( $this->request_params['REFNOEXT'] ); 261 | $params['REFNOEXT_D'] = ! empty( $orderId ) ? $orderId : 0; 262 | 263 | if ( $this->wc_order->get_id() ) { 264 | $this->_process_fraud(); 265 | if ( ! $this->_is_fraud() ) { 266 | $this->processorder_status($this->request_params['ORDERSTATUS'], $this->request_params['REFNO']); 267 | } 268 | } 269 | } catch ( Exception $ex ) { 270 | self::log( 'Exception processing IPN: ' . $ex->getMessage() ); 271 | } 272 | echo $this->_calculate_ipn_response($hash['algo']); 273 | exit(); 274 | } 275 | 276 | /** 277 | * @return void 278 | */ 279 | protected function _process_fraud() { 280 | 281 | if ( isset( $this->request_params['FRAUD_STATUS'] ) ) { 282 | switch ( trim( $this->request_params['FRAUD_STATUS'] ) ) { 283 | case self::FRAUD_STATUS_DENIED: 284 | case self::ORDER_STATUS_INVALID: 285 | $this->wc_order->update_status( 'failed' ); 286 | $this->wc_order->add_order_note( __( "Order status changed to failed" ), false, false ); 287 | 288 | break; 289 | 290 | case self::FRAUD_STATUS_APPROVED: 291 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 292 | $this->wc_order->update_status( 'pending' ); 293 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 294 | } 295 | break; 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * @return bool 302 | */ 303 | protected function _is_fraud() { 304 | return ( isset( $this->request_params['FRAUD_STATUS'] ) 305 | && $this->request_params['FRAUD_STATUS'] === self::FRAUD_STATUS_DENIED ); 306 | } 307 | 308 | /** 309 | * @return bool 310 | */ 311 | protected function _is_order_pending() { 312 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PENDING; 313 | } 314 | 315 | /** 316 | * @return bool 317 | */ 318 | protected function _is_order_processing() { 319 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PROCESSING; 320 | } 321 | 322 | /** 323 | * @return bool 324 | */ 325 | protected function _is_order_completed() { 326 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_COMPLETE; 327 | } 328 | 329 | /** 330 | * @return bool 331 | */ 332 | protected function _is_order_refunded() { 333 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_REFUND; 334 | } 335 | 336 | /** 337 | * @return string 338 | */ 339 | private function _calculate_ipn_response($algo='sha3-256') { 340 | $resultResponse = ''; 341 | $ipn_params_response = []; 342 | // we're assuming that these always exist, if they don't then the problem is on avangate side 343 | $ipn_params_response['IPN_PID'][0] = $this->request_params['IPN_PID'][0]; 344 | $ipn_params_response['IPN_PNAME'][0] = $this->request_params['IPN_PNAME'][0]; 345 | $ipn_params_response['IPN_DATE'] = $this->request_params['IPN_DATE']; 346 | $ipn_params_response['DATE'] = date( 'YmdHis' ); 347 | 348 | foreach ( $ipn_params_response as $key => $val ) { 349 | $resultResponse .= $this->array_expand( (array) $val ); 350 | } 351 | 352 | if ('md5' === $algo) 353 | return sprintf( 354 | '%s|%s', 355 | $ipn_params_response['DATE'], 356 | $this->generate_hash($this->secret_key, $resultResponse, $algo) 357 | ); 358 | else 359 | return sprintf( 360 | '%s', 361 | $algo, 362 | $ipn_params_response['DATE'], 363 | $this->generate_hash($this->secret_key, $resultResponse, $algo) 364 | ); 365 | } 366 | 367 | /** 368 | * @return bool 369 | */ 370 | private function getCompleteOrderOnPayment() { 371 | return $this->complete_order_on_payment; 372 | } 373 | 374 | /** 375 | * @throws Exception 376 | */ 377 | public function processorder_status(string $order_status, string $refNo) { 378 | if ( ! empty( $order_status ) ) { 379 | switch ( trim( $order_status ) ) { 380 | case self::ORDER_STATUS_PENDING: 381 | case self::ORDER_STATUS_PURCHASE_PENDING: 382 | case self::ORDER_STATUS_PENDING_APPROVAL: 383 | if ( ! $this->_is_order_pending() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 384 | $this->wc_order->update_status( 'pending' ); 385 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 386 | } 387 | break; 388 | 389 | case self::ORDER_STATUS_PAYMENT_AUTHORIZED: 390 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 391 | $this->wc_order->update_status( 'pending' ); 392 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 393 | } 394 | break; 395 | 396 | case self::ORDER_STATUS_COMPLETE: 397 | case self::ORDER_STATUS_AUTH_RECEIVED: 398 | if ( ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 399 | $this->wc_order->update_meta_data( self::TCO_ORDER_REFERENCE, $refNo ); 400 | $this->wc_order->save_meta_data(); 401 | $this->wc_order->payment_complete(); 402 | if ( $this->getCompleteOrderOnPayment() ) { 403 | $this->wc_order->update_status( 'completed' ); 404 | } 405 | $this->wc_order->add_order_note( __( '2Checkout transaction ID: ' . $refNo ), false, false ); 406 | $this->wc_order->add_order_note( __( "Order payment is completed." ), false, false ); 407 | } 408 | break; 409 | 410 | default: 411 | throw new Exception( 'Cannot handle the order status ' . $order_status . '!' ); 412 | } 413 | 414 | $this->wc_order->save(); 415 | } 416 | } 417 | 418 | } 419 | -------------------------------------------------------------------------------- /twocheckout-convert-plus/src/Twocheckout/TwoCheckoutApi.php: -------------------------------------------------------------------------------- 1 | test_order; 37 | } 38 | 39 | /** 40 | * @param bool $test_order 41 | */ 42 | public function set_test_order( bool $test_order ) { 43 | $this->test_order = $test_order; 44 | } 45 | 46 | /** 47 | * @return null 48 | */ 49 | public function get_seller_id() { 50 | return $this->seller_id; 51 | } 52 | 53 | /** 54 | * @param null seller_id 55 | * 56 | * @return TwoCheckoutApi 57 | */ 58 | public function set_seller_id( $seller_id ) { 59 | $this->seller_id = $seller_id; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return null 66 | */ 67 | public function get_secret_key() { 68 | return $this->secret_key; 69 | } 70 | 71 | /** 72 | * @param null $secret_key 73 | * 74 | * @return TwoCheckoutApi 75 | */ 76 | public function set_secret_key( $secret_key ) { 77 | $this->secret_key = $secret_key; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * sets the header with the auth has and params 84 | * @return array 85 | * @throws Exception 86 | */ 87 | private function get_headers() { 88 | if ( ! $this->seller_id || ! $this->secret_key ) { 89 | throw new Exception( 'Merchandiser needs a valid 2Checkout SellerId and SecretKey to authenticate!' ); 90 | } 91 | $gmt_date = gmdate( 'Y-m-d H:i:s' ); 92 | $string = strlen( $this->seller_id ) . $this->seller_id . strlen( $gmt_date ) . $gmt_date; 93 | $hash = hash_hmac( 'sha3-256', $string, $this->secret_key ); 94 | 95 | $headers[] = 'Content-Type: application/json'; 96 | $headers[] = 'Accept: application/json'; 97 | $headers[] = 'X-Avangate-Authentication: code="' . $this->seller_id . '" date="' . $gmt_date . '" hash="' . $hash . '" algo="sha3-256"'; 98 | 99 | return $headers; 100 | } 101 | 102 | 103 | /** 104 | * @param $vendorCode 105 | * @param $secret 106 | * @param $requestDateTime 107 | * @return string 108 | * @throws Exception 109 | */ 110 | public function generate_hash($vendorCode, $secret, $requestDateTime) 111 | { 112 | $string = sprintf('%s%s%s%s', strlen($vendorCode), $vendorCode, strlen($requestDateTime), $requestDateTime); 113 | $hash = hash_hmac('sha3-256', $string, $secret); 114 | 115 | // the hash_hmac may fail for various reasons 116 | if (false === $hash) { 117 | $exception = new Exception('Unable to create the hash, hash_hmac returned false.'); 118 | throw $exception; 119 | } 120 | 121 | return $hash; 122 | } 123 | 124 | /** 125 | * @param string $endpoint 126 | * @param array $params 127 | * @param string $method 128 | * 129 | * @return mixed 130 | * @throws Exception 131 | */ 132 | public function call( string $endpoint, array $params, $method = 'POST' ) { 133 | // if endpoint does not starts or end with a '/' we add it, as the API needs it 134 | if ( $endpoint[0] !== '/' ) { 135 | $endpoint = '/' . $endpoint; 136 | } 137 | if ( $endpoint[ - 1 ] !== '/' ) { 138 | $endpoint = $endpoint . '/'; 139 | } 140 | 141 | try { 142 | $url = self::API_URL . self::API_VERSION . $endpoint; 143 | $ch = curl_init(); 144 | curl_setopt( $ch, CURLOPT_URL, $url ); 145 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $this->get_headers() ); 146 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); 147 | curl_setopt( $ch, CURLOPT_HEADER, false ); 148 | curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); 149 | if ( $method === 'POST' ) { 150 | curl_setopt( $ch, CURLOPT_POST, true ); 151 | curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $params, JSON_UNESCAPED_UNICODE ) ); 152 | } 153 | $response = curl_exec( $ch ); 154 | 155 | if ( $response === false ) { 156 | exit( curl_error( $ch ) ); 157 | } 158 | curl_close( $ch ); 159 | 160 | return json_decode( $response, true ); 161 | } catch ( Exception $e ) { 162 | throw new Exception( $e->getMessage() ); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /twocheckout-convert-plus/templates/admin-options.php: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | generate_settings_html(); 7 | ?> 8 | 9 | 10 | 13 | 20 | 21 |
11 | 12 | 14 |
15 | 16 | 17 |

18 |
19 |
22 | -------------------------------------------------------------------------------- /twocheckout-convert-plus/templates/init_form_fields.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'title' => __( 'Enable/Disable', 'woocommerce' ), 8 | 'type' => 'checkbox', 9 | 'label' => __( 'Enable 2Checkout Convert Plus', 'woocommerce' ), 10 | 'default' => 'yes' 11 | ], 12 | 'title' => [ 13 | 'title' => __( 'Title', 'woocommerce' ), 14 | 'type' => 'text', 15 | 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ), 16 | 'default' => __( '2Checkout Convert Plus Payment Gateway', 'woocommerce' ), 17 | 'desc_tip' => true, 18 | ], 19 | 'description' => [ 20 | 'title' => __( 'Description', 'woocommerce' ), 21 | 'type' => 'text', 22 | 'desc_tip' => true, 23 | 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce' ), 24 | 'default' => __( 'Safe payment solutions by 2Checkout ', 'woocommerce' ) 25 | ], 26 | 'seller_id' => [ 27 | 'title' => __( 'Seller ID', 'woocommerce' ), 28 | 'type' => 'text', 29 | 'description' => __( 'Please enter your 2Checkout account number; this is needed in order to take payment.', 'woocommerce' ), 30 | 'default' => '', 31 | 'desc_tip' => true, 32 | 'placeholder' => '' 33 | ], 34 | 'secret_key' => [ 35 | 'title' => __( 'Secret Key', 'woocommerce' ), 36 | 'type' => 'password', 37 | 'description' => __( 'Please enter your 2Checkout Secret Key; this is needed in order to take payment.', 'woocommerce' ), 38 | 'default' => '', 39 | 'desc_tip' => true, 40 | 'placeholder' => '' 41 | ], 42 | 'secret_word' => [ 43 | 'title' => __( 'Secret Word', 'woocommerce' ), 44 | 'type' => 'password', 45 | 'description' => __( 'Please enter your 2Checkout Secret Word; this is needed in order to update the payment status.', 'woocommerce' ), 46 | 'default' => '', 47 | 'desc_tip' => true, 48 | 'placeholder' => '' 49 | ], 50 | 'debug' => [ 51 | 'title' => __( 'Debug Log', 'woocommerce' ), 52 | 'type' => 'checkbox', 53 | 'label' => __( 'Enable logging', 'woocommerce' ), 54 | 'default' => 'no', 55 | 'desc_tip' => true, 56 | 'description' => sprintf( __( 'Log 2Checkout events', 'woocommerce' ), wc_get_log_file_path( 'twocheckout' ) ) 57 | ], 58 | 'demo' => [ 59 | 'title' => __( 'Demo order', 'woocommerce' ), 60 | 'type' => 'checkbox', 61 | 'label' => __( 'Create test orders', 'woocommerce' ), 62 | 'default' => 'no', 63 | 'desc_tip' => true, 64 | 'description' => sprintf( __( 'Not available yet for this method!', 'woocommerce' ), wc_get_log_file_path( 'twocheckout' ) ) 65 | ], 66 | 'complete_order_on_payment' => [ 67 | 'desc_tip' => true, 68 | 'title' => __( 'Complete Order on Payment', 'woocommerce' ), 69 | 'type' => 'select', 70 | 'label' => __( 'Complete Order on Payment', 'woocommerce' ), 71 | 'options' => [ 72 | 'Yes' => 'Yes', 73 | 'No' => 'No', 74 | ], 75 | 'description' => __( 'Set order status to complete on successful payment.', 'woocommerce' ), 76 | ], 77 | ]; 78 | } 79 | -------------------------------------------------------------------------------- /twocheckout-convert-plus/twocheckout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-convert-plus/twocheckout.png -------------------------------------------------------------------------------- /twocheckout-convert-plus/wc-twocheckout-convert-plus.php: -------------------------------------------------------------------------------- 1 | plugin_name = '2Checkout Convert Plus Payment Method Plugin'; 58 | $this->method_description = __( '2Checkout secured card payments with Buy Links.', 'woocommerce' ); 59 | $this->supports[] = 'refunds'; 60 | $this->version = '1.0.0'; 61 | 62 | //set the variable for the example endpoint. you can use a option plugin to store it and change it later in the admin page 63 | $this->css_filepath = 'assets/css/twocheckout.css'; 64 | 65 | $this->id = self::PAYMENT_METHOD_ID; 66 | $this->icon = apply_filters( 'woocommerce_twocheckout_icon', 67 | plugin_dir_url( __FILE__ ) . 'twocheckout.png' ); 68 | $this->has_fields = false; 69 | 70 | // Load the settings 71 | $this->init_form_fields(); 72 | $this->init_settings(); 73 | 74 | // Define user set variables 75 | $this->title = $this->get_option( 'title' ); 76 | $this->seller_id = $this->get_option( 'seller_id' ); 77 | $this->secret_key = $this->get_option( 'secret_key' ); 78 | $this->secret_word = $this->get_option( 'secret_word' ); 79 | $this->custom_style = $this->get_option( 'style' ); 80 | $this->test_order = $this->get_option( 'demo' ); 81 | $this->description = $this->get_option( 'description' ); 82 | $this->debug = $this->get_option( 'debug' ); 83 | $this->complete_order_on_payment = ( $this->get_option( 'complete_order_on_payment' ) == 'Yes' ) ? true : false; 84 | 85 | self::$log_enabled = $this->debug; 86 | 87 | 88 | if ( ! $this->is_valid_for_use() ) { 89 | $this->enabled = false; 90 | } 91 | 92 | $this->add_actions(); 93 | } 94 | 95 | /**** Plugin methods ****/ 96 | 97 | private function add_actions() { 98 | //put your actions here 99 | add_action( 'woocommerce_receipt_' . $this->id, [ $this, 'receipt_page' ] ); 100 | 101 | // Save options 102 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, 103 | [ 104 | $this, 105 | 'process_admin_options', 106 | ] ); 107 | 108 | // Payment listener/API hook 109 | add_action( 'woocommerce_api_2checkout_ipn_convert_plus', [ $this, 'check_ipn_response_convert_plus' ] ); 110 | add_action( 'woocommerce_api_payment_response', [ $this, 'check_payment_response' ] ); 111 | } 112 | 113 | /** 114 | * Logging method 115 | * 116 | * @param string $message 117 | */ 118 | public static function log( $message ) { 119 | if ( self::$log_enabled ) { 120 | if ( empty( self::$log ) ) { 121 | self::$log = new WC_Logger(); 122 | } 123 | self::$log->add( 'twocheckout', $message ); 124 | } 125 | } 126 | 127 | /** 128 | * Admin Panel Options 129 | * - Options for bits like 'title' and availability on a country-by-country basis 130 | * 131 | * @since 1.0.0 132 | */ 133 | public function admin_options() { 134 | require_once plugin_dir_path( __FILE__ ) . 'templates/admin-options.php'; 135 | } 136 | 137 | /** 138 | * Initialise Gateway Settings Form Fields 139 | * 140 | * @access public 141 | * @return void 142 | */ 143 | public function init_form_fields() { 144 | require_once plugin_dir_path( __FILE__ ) . 'templates/init_form_fields.php'; 145 | $this->form_fields = getTwoCheckoutConvertPlusFormFields(); 146 | } 147 | 148 | /** 149 | * Check if this gateway is enabled and available in the user's country 150 | * 151 | * @access public 152 | * @return bool 153 | */ 154 | function is_valid_for_use() { 155 | $supported_currencies = [ 156 | 'AFN', 157 | 'ALL', 158 | 'DZD', 159 | 'ARS', 160 | 'AUD', 161 | 'AZN', 162 | 'BSD', 163 | 'BDT', 164 | 'BBD', 165 | 'BZD', 166 | 'BMD', 167 | 'BOB', 168 | 'BWP', 169 | 'BRL', 170 | 'GBP', 171 | 'BND', 172 | 'BGN', 173 | 'CAD', 174 | 'CLP', 175 | 'CNY', 176 | 'COP', 177 | 'CRC', 178 | 'HRK', 179 | 'CZK', 180 | 'DKK', 181 | 'DOP', 182 | 'XCD', 183 | 'EGP', 184 | 'EUR', 185 | 'FJD', 186 | 'GTQ', 187 | 'HKD', 188 | 'HNL', 189 | 'HUF', 190 | 'INR', 191 | 'IDR', 192 | 'ILS', 193 | 'JMD', 194 | 'JPY', 195 | 'KZT', 196 | 'KES', 197 | 'LAK', 198 | 'MMK', 199 | 'LBP', 200 | 'LRD', 201 | 'MOP', 202 | 'MYR', 203 | 'MVR', 204 | 'MRO', 205 | 'MUR', 206 | 'MXN', 207 | 'MAD', 208 | 'NPR', 209 | 'TWD', 210 | 'NZD', 211 | 'NIO', 212 | 'NOK', 213 | 'PKR', 214 | 'PGK', 215 | 'PEN', 216 | 'PHP', 217 | 'PLN', 218 | 'QAR', 219 | 'RON', 220 | 'RUB', 221 | 'WST', 222 | 'SAR', 223 | 'SCR', 224 | 'SGF', 225 | 'SBD', 226 | 'ZAR', 227 | 'KRW', 228 | 'LKR', 229 | 'SEK', 230 | 'CHF', 231 | 'SYP', 232 | 'THB', 233 | 'TOP', 234 | 'TTD', 235 | 'TRY', 236 | 'UAH', 237 | 'AED', 238 | 'USD', 239 | 'VUV', 240 | 'VND', 241 | 'XOF', 242 | 'YER', 243 | ]; 244 | 245 | if ( ! in_array( get_woocommerce_currency(), 246 | apply_filters( 'woocommerce_twocheckout_supported_currencies', 247 | $supported_currencies ) ) ) { 248 | return false; 249 | } 250 | 251 | return true; 252 | } 253 | 254 | 255 | private function load_helper() { 256 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/Helpers/class-convert-plus-helper.php'; 257 | } 258 | 259 | /** 260 | * Process the payment and return the result 261 | * 262 | * @access public 263 | * 264 | * @param int $order_id 265 | * 266 | * @return array 267 | */ 268 | public function process_payment( $order_id ) { 269 | $order = new WC_Order( $order_id ); 270 | $order->update_meta_data( '_2co_order_type', 'convert_plus' ); 271 | $order->save_meta_data(); 272 | $order->save(); 273 | $this->load_helper(); 274 | 275 | try { 276 | $buy_link_params = []; 277 | $order_params = $this->build_checkout_parameters( $order ); 278 | 279 | $buy_link_params = array_merge( 280 | $buy_link_params, 281 | $order_params['setup_data'], 282 | $order_params['cart_data'], 283 | $order_params['products_data'], 284 | $order_params['shipping_data'], 285 | $order_params['billing_data'] 286 | ); 287 | $helper = new WC_Twocheckout_Convert_Plus_Helper( $buy_link_params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 288 | $buy_link_params['signature'] = $helper->get_signature( $this->seller_id, $this->secret_word ); 289 | $pay_url = 'https://secure.2checkout.com/checkout/buy?' . http_build_query( $buy_link_params ); 290 | 291 | return [ 292 | 'result' => 'success', 293 | 'redirect' => $pay_url, 294 | ]; 295 | 296 | } catch ( Exception $e ) { 297 | wc_add_notice( $e->getMessage(), $notice_type = 'error' ); 298 | 299 | return [ 300 | 'result' => 'failure', 301 | 'messages' => 'There has been an error processing your order', 302 | ]; 303 | } 304 | } 305 | 306 | /** 307 | * @param int $order_id 308 | * @param null $amount 309 | * @param string $reason 310 | * 311 | * @return bool|\WP_Error 312 | * @throws \Exception 313 | */ 314 | public function process_refund( $order_id, $amount = null, $reason = '' ) { 315 | $order = wc_get_order( $order_id ); 316 | if ( $order->get_payment_method() == self::PAYMENT_METHOD_ID ) { 317 | $transaction_id = $order->get_meta( '__2co_order_number' ); 318 | 319 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 320 | $api = new Two_Checkout_Api(); 321 | $api->set_seller_id( $this->seller_id ); 322 | $api->set_secret_key( $this->secret_key ); 323 | $tco_order = $api->call( 'orders/' . $transaction_id . '/', [], 'GET' ); 324 | if ( ! $order || ! $tco_order || ! $transaction_id ) { 325 | $this->log( sprintf( 'Tried to refund order with order ID %s but it has no registered transaction ID, aborting.', $order_id ) ); 326 | 327 | return new WP_Error( '2co_refund_error', 'Refund Error: Unable to refund transaction' ); 328 | } 329 | 330 | if ( $order->get_currency() !== $tco_order['PayoutCurrency'] ) { 331 | $this->log( sprintf( 'Attempted to refund order in other currency %s while the order was placed in currency %s. Aborting.', 332 | $order->get_currency(), 333 | $tco_order['PayoutCurrency'] 334 | ) ); 335 | 336 | return new WP_Error( '2co_refund_error', sprintf( 'Refund Error: Cannot refund order in currency %s as it was placed in another currency', 337 | strtoupper( $order->get_currency() ) 338 | ) ); 339 | } 340 | 341 | if ( $amount != $tco_order['GrossPrice'] ) { 342 | $this->log( 'Only full refund is supported!' ); 343 | 344 | return new WP_Error( '2co_refund_error', 'Refund Error: Only full refund is supported.' ); 345 | } 346 | 347 | if ( strtolower( get_woocommerce_currency() ) != strtolower( $tco_order['Currency'] ) ) { 348 | $this->log( 'Order currency not matching the 2checkout response!' ); 349 | 350 | return new WP_Error( '2co_refund_error', 'Refund Error: Order currency not matching the 2checkout response.' ); 351 | } 352 | 353 | 354 | $params = [ 355 | "amount" => $amount, 356 | "comment" => $reason, 357 | "reason" => 'Other' 358 | ]; 359 | 360 | $response = $api->call( '/orders/' . $transaction_id . '/refund/', $params, 'POST' ); 361 | 362 | if ( isset( $response['error_code'] ) && ! empty( $response['error_code'] ) ) { 363 | $this->log( 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 364 | 365 | return new WP_Error( '2co_refund_error', 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 366 | } 367 | 368 | $order->update_meta_data( '__2co_order_number', $response['id'] ); 369 | $order->add_order_note( __( sprintf( 'Refunded %s out of a total of %s from order', $amount, $order->get_total() ) ) ); 370 | $order->save_meta_data(); 371 | $order->save(); 372 | 373 | return true; 374 | } 375 | } 376 | 377 | /** 378 | * @param WC_Order $order 379 | * 380 | * @return array 381 | */ 382 | public function build_checkout_parameters( WC_Order $order ) { 383 | global $woocommerce; 384 | $woocommerce_version_formatted = str_replace( '.', '_', $woocommerce->version ); 385 | 386 | //1. Setup data 387 | $setup_data = []; 388 | $setup_data['merchant'] = $this->seller_id; 389 | $setup_data['dynamic'] = 1; 390 | //2. Set the BASE needed fields. 391 | $cart_data = []; 392 | $cart_data['src'] = 'WOOCOMMERCE_' . $woocommerce_version_formatted; 393 | $cart_data['return-url'] = add_query_arg( 'wc-api', 'payment_response', home_url( '/' ) ) . "&pm={$order->get_payment_method()}" . "&order-ext-ref={$order->get_id()}"; 394 | $cart_data['return-type'] = 'redirect'; 395 | $cart_data['expiration'] = time() + ( 3600 * 5 ); 396 | $cart_data['order-ext-ref'] = $order->get_id(); 397 | $cart_data['customer-ext-ref'] = $order->get_billing_email(); 398 | $cart_data['currency'] = get_woocommerce_currency(); 399 | $cart_data["test"] = strtolower( $this->test_order ) === 'yes' ? '1' : '0'; 400 | 401 | //3. Language config 402 | $current_locale_setting = get_option( 'WPLANG' ); 403 | $current_store_lang = get_locale(); 404 | $lang = ! $current_store_lang ? $current_locale_setting : $current_store_lang; 405 | $langCode = strstr( $lang, '_', true ); 406 | $cart_data['language'] = $langCode; 407 | 408 | //4. Products 409 | $products = $this->get_item( $order ); 410 | //dynamic products 411 | $products_data['prod'] = implode( ';', $products['prod'] ); 412 | $products_data['price'] = implode( 413 | ';', 414 | $products['price'] 415 | ); 416 | $products_data['qty'] = implode( ';', $products['qty'] ); 417 | $products_data['type'] = implode( ';', $products['type'] ); 418 | $products_data['tangible'] = implode( 419 | ';', 420 | $products['tangible'] 421 | ); 422 | 423 | return [ 424 | 'setup_data' => $setup_data, 425 | 'cart_data' => $cart_data, 426 | 'products_data' => $products_data, 427 | 'shipping_data' => $this->get_shipping_details( $order ), 428 | 'billing_data' => $this->get_billing_details( $order ), 429 | ]; 430 | } 431 | 432 | /** 433 | * @param WC_Order $order 434 | * 435 | * @return array 436 | */ 437 | private function get_billing_details( WC_Order $order ) { 438 | return [ 439 | 'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), 440 | 'phone' => $order->get_billing_phone(), 441 | 'country' => strtoupper( $order->get_billing_country() ), 442 | 'state' => $order->get_billing_state(), 443 | 'email' => $order->get_billing_email(), 444 | 'address' => $order->get_billing_address_1(), 445 | 'address2' => $order->get_billing_address_2(), 446 | 'city' => $order->get_billing_city(), 447 | 'zip' => $order->get_billing_postcode(), 448 | 'company-name' => $order->get_billing_company(), 449 | ]; 450 | } 451 | 452 | 453 | /** 454 | * @param WC_Order $order 455 | * 456 | * @return array 457 | */ 458 | private function get_shipping_details( WC_Order $order ) { 459 | return [ 460 | 'ship-name' => $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name(), 461 | 'ship-country' => strtoupper( $order->get_shipping_country() ), 462 | 'ship-state' => $order->get_shipping_state(), 463 | 'ship-email' => $order->get_billing_email(), 464 | //same as billing 465 | 'ship-address' => $order->get_shipping_address_1(), 466 | 'ship-address2' => $order->get_shipping_address_2(), 467 | 'ship-city' => $order->get_shipping_city(), 468 | ]; 469 | } 470 | 471 | /** 472 | * for safety reasons we only send one Item with the grand total and the Cart_id as ProductName (identifier) 473 | * sending products order as ONE we dont have to calculate the total fee of the order (product price, tax, discounts etc) 474 | * 475 | * @return array 476 | */ 477 | private function get_item( WC_Order $order ) { 478 | $items = []; 479 | $items['prod'][] = get_bloginfo(); //name 480 | $items['price'][] = number_format( $order->get_total(), 2, '.', '' ); 481 | $items['qty'][] = 1; 482 | $items['type'][] = 'PRODUCT'; 483 | $items['tangible'][] = 0; 484 | 485 | return $items; 486 | } 487 | 488 | /** 489 | * Validate & process 2Checkout request 490 | * 491 | * @access public 492 | * @return void|string 493 | */ 494 | public function check_ipn_response_convert_plus() { 495 | if ( $_SERVER['REQUEST_METHOD'] === 'GET' ) { 496 | return; 497 | } 498 | $params = $_POST; 499 | unset( $params['wc-api'] ); 500 | if ( isset( $params['REFNOEXT'] ) && ! empty( $params['REFNOEXT'] ) ) { 501 | $order = wc_get_order( $params['REFNOEXT'] ); 502 | if ( $order && $order->get_payment_method() == self::PAYMENT_METHOD_ID ) { 503 | $this->load_helper(); 504 | try { 505 | $helper = new WC_Twocheckout_Convert_Plus_Helper( $params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 506 | } catch ( Exception $ex ) { 507 | $this->log( 'Unable to find order with RefNo: ' . $params['REFNOEXT'] ); 508 | throw new Exception( 'An error occurred!' ); 509 | } 510 | if ( ! $helper->is_ipn_response_valid() ) { 511 | self::log( sprintf( 'SHA3 hash mismatch for 2Checkout IPN with date: "%s" . ', 512 | $params['IPN_DATE'] ) ); 513 | echo 'Bad Hash!'; 514 | 515 | return; 516 | } 517 | 518 | return $helper->process_ipn(); 519 | } 520 | } 521 | } 522 | 523 | /** 524 | * @return void 525 | */ 526 | public function check_payment_response() { 527 | $params = $_GET; 528 | if ( ! isset( $params['pm'] ) || (string) $params['pm'] !== self::PAYMENT_METHOD_ID ) { 529 | return; 530 | } 531 | 532 | if ( ! isset( $params['order-ext-ref'] ) 533 | || ! isset( $params['refno'] ) 534 | || empty( $params['order-ext-ref'] ) 535 | || empty( $params['refno'] ) 536 | ) { 537 | $this->go_to_404_page(); 538 | } 539 | 540 | $order = wc_get_order( (int) $params['order-ext-ref'] ); 541 | 542 | if ( ! $order instanceof WC_Order ) { 543 | $this->log( 'There was a request for an order that doesn\'t exist in current shop! Requested params: ' 544 | . strip_tags( http_build_query( $params ) ) ); 545 | $this->go_to_404_page(); 546 | } 547 | 548 | $refNo = $params['refno']; 549 | 550 | $this->load_helper(); 551 | $helper = new WC_Twocheckout_Convert_Plus_Helper( $params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 552 | 553 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 554 | $api = new Two_Checkout_Api(); 555 | 556 | $api->set_seller_id( $this->seller_id ); 557 | $api->set_secret_key( $this->secret_key ); 558 | $api_response = $api->call( 'orders/' . $refNo . '/', [], 'GET' ); 559 | 560 | if ( ! isset( $api_response['Status'] ) 561 | || empty( $api_response['Status'] ) 562 | || ! in_array( $api_response['Status'], [ 'AUTHRECEIVED', 'COMPLETE' ] ) 563 | ) { 564 | $this->log( 'Api did not respond with expected result' ); 565 | $this->go_to_404_page(); 566 | } 567 | $redirect_url = $order->get_checkout_order_received_url(); 568 | if ( wp_redirect( $redirect_url ) ) { 569 | try { 570 | $helper->processorder_status($api_response['Status'],(string)$api_response['RefNo']); 571 | } catch (\Exception $e) { 572 | self::$log->add('twocheckout', __CLASS__ . ' line ' . __LINE__ . ' : ' . $e->getMessage()); 573 | } 574 | global $woocommerce; 575 | $woocommerce->cart->empty_cart(); 576 | } 577 | } 578 | 579 | /** 580 | * Returns a 404 page 581 | */ 582 | private function go_to_404_page() { 583 | status_header( 404 ); 584 | nocache_headers(); 585 | include( get_query_template( '404' ) ); 586 | die; 587 | } 588 | } 589 | 590 | function add_twocheckout_gateway_convert_plus( $methods ) { 591 | $methods[] = 'WC_Twocheckout_Convert_Plus_Getaway'; 592 | 593 | return $methods; 594 | } 595 | 596 | add_filter( 'woocommerce_payment_gateways', 597 | 'add_twocheckout_gateway_convert_plus' ); 598 | 599 | } 600 | 601 | add_action( 'before_woocommerce_init', function() { 602 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 603 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __DIR__.'/'.__FILE__, true ); 604 | } 605 | } ); 606 | -------------------------------------------------------------------------------- /twocheckout-inline-v2.3.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-inline-v2.3.1.zip -------------------------------------------------------------------------------- /twocheckout-inline/.gitignore: -------------------------------------------------------------------------------- 1 | .phpunit.result.cache 2 | /vendor -------------------------------------------------------------------------------- /twocheckout-inline/assets/css/twocheckout.css: -------------------------------------------------------------------------------- 1 | .wc_payment_method .payment_box, 2 | .wc_payment_method .payment_box fieldset { 3 | background: #FFF; 4 | } 5 | 6 | .wc_payment_method .payment_box fieldset { 7 | padding: 0; 8 | } 9 | 10 | .woocommerce-checkout ul.woocommerce-error li { 11 | padding: 0; 12 | font-size: 12px; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /twocheckout-inline/assets/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-inline/assets/images/spinner.gif -------------------------------------------------------------------------------- /twocheckout-inline/assets/js/twocheckout_inline.js: -------------------------------------------------------------------------------- 1 | function inlinePay() { 2 | if (jQuery("#payment_method_twocheckout_inline").is(':checked')) { 3 | jQuery('#tco_inline_error').html(''); 4 | jQuery('#place_order').attr('disabled', true); 5 | var tco_ajax_url; 6 | var tco_data; 7 | if (jQuery('.checkout.woocommerce-checkout').length) { 8 | tco_ajax_url = wc_checkout_params.checkout_url; 9 | tco_data = jQuery('.checkout.woocommerce-checkout').serialize(); 10 | } else if (jQuery('form#order_review').length) { 11 | tco_ajax_url = wc_checkout_params.wc_ajax_url 12 | .toString() 13 | .replace('wc-ajax', 'wc-api') 14 | .replace('%%endpoint%%', 'twocheckout_inline_handle_payment_request'); 15 | tco_data = jQuery('form#order_review').serialize(); 16 | } 17 | jQuery.ajax({ 18 | type: 'POST', 19 | data: tco_data, 20 | url: tco_ajax_url, 21 | success: function (response) { 22 | if (response.result === "success") { 23 | if (typeof response.payload !== "undefined") { 24 | var payload = JSON.parse(response.payload); 25 | (function (document, src, libName, config) { 26 | var script = document.createElement('script'); 27 | script.src = src; 28 | script.async = true; 29 | var firstScriptElement = document.getElementsByTagName('script')[0]; 30 | script.onload = function () { 31 | for (var namespace in config) { 32 | if (config.hasOwnProperty(namespace)) { 33 | window[libName].setup.setConfig(namespace, config[namespace]); 34 | } 35 | } 36 | window[libName].register(); 37 | TwoCoInlineCart.setup.setMerchant(payload['merchant']); 38 | TwoCoInlineCart.setup.setMode(payload.mode); 39 | TwoCoInlineCart.register(); 40 | 41 | TwoCoInlineCart.cart.setCurrency(payload['currency']); 42 | TwoCoInlineCart.cart.setLanguage(payload['language']); 43 | TwoCoInlineCart.cart.setReturnMethod(payload['return-method']); 44 | TwoCoInlineCart.cart.setTest(payload['test']); 45 | TwoCoInlineCart.cart.setOrderExternalRef(payload['order-ext-ref']); 46 | TwoCoInlineCart.cart.setExternalCustomerReference(payload['customer-ext-ref']); 47 | TwoCoInlineCart.cart.setSource(payload['src']); 48 | 49 | TwoCoInlineCart.products.removeAll(); 50 | TwoCoInlineCart.products.addMany(payload['products']); 51 | TwoCoInlineCart.billing.setData(payload['billing_address']); 52 | TwoCoInlineCart.billing.setCompanyName(payload['billing_address']['company-name']); 53 | TwoCoInlineCart.shipping.setData(payload['shipping_address']); 54 | TwoCoInlineCart.cart.setSignature(payload['signature']); 55 | TwoCoInlineCart.cart.setAutoAdvance(true); 56 | TwoCoInlineCart.cart.checkout(); 57 | 58 | }; 59 | firstScriptElement.parentNode.insertBefore(script, firstScriptElement); 60 | })(document, 'https://secure.2checkout.com/checkout/client/twoCoInlineCart.js', 'TwoCoInlineCart', 61 | {"app": {"merchant": payload.merchant}, "cart": {"host": "https:\/\/secure.2checkout.com", "customization": payload.customization}} 62 | ); 63 | } else if (typeof response.redirect !== "undefined") { 64 | window.location.href = response.redirect; 65 | } 66 | } 67 | jQuery('#tcoWait').hide(); 68 | if (response.result === "failure") { 69 | if (response.step !== "undefined") { 70 | var notice_wrapper = jQuery('.woocommerce-notices-wrapper').first(); 71 | notice_wrapper.empty(); 72 | notice_wrapper.append(''); 73 | jQuery("html, body").animate({scrollTop: 0}, 1500); 74 | } else { 75 | jQuery('#tco_inline_error').html(response.messages); 76 | jQuery('#place_order').attr('disabled', false); 77 | } 78 | } 79 | }, 80 | error: function (response, data) { 81 | console.error("Error response: " + response + " && data: " + data); 82 | jQuery('#place_order').attr('disabled', false); 83 | return false; 84 | }, 85 | complete: function (xhr, status) { 86 | jQuery('#place_order').attr('disabled', false); 87 | } 88 | }); 89 | } else { 90 | if (jQuery('.checkout.woocommerce-checkout').length) { 91 | jQuery("form.woocommerce-checkout").unbind('submit').submit(); 92 | } else if (jQuery('form#order_review').length) { 93 | jQuery('form#order_review').unbind('submit').submit(); 94 | } 95 | } 96 | } 97 | 98 | function prepareInlinePay(e) { 99 | if(jQuery("#payment_method_twocheckout_inline").is(':checked')) { 100 | e.preventDefault(); 101 | e.stopPropagation(); 102 | inlinePay(); 103 | return false; 104 | } else { 105 | jQuery("form.woocommerce-checkout").off('submit', prepareInlinePay); 106 | jQuery("form#order_review").off('submit', prepareInlinePay); 107 | return true; 108 | } 109 | } 110 | 111 | 112 | 113 | jQuery(document).on("change", "form[name='checkout'] input[name='payment_method']", function () { 114 | 115 | if (jQuery(this).attr('id') == 'payment_method_twocheckout_inline') { 116 | jQuery("form.woocommerce-checkout").unbind('submit'); 117 | jQuery("form.woocommerce-checkout").on('submit', prepareInlinePay); 118 | } else { 119 | jQuery("form.woocommerce-checkout").off('submit', prepareInlinePay); 120 | } 121 | }); 122 | 123 | jQuery(document).on("change", "form[id='order_review'] input[name='payment_method']", function () { 124 | 125 | if (jQuery(this).attr('id') == 'payment_method_twocheckout_inline') { 126 | jQuery("form#order_review").unbind('submit'); 127 | jQuery("form#order_review").on('submit', prepareInlinePay); 128 | } else { 129 | jQuery("form#order_review").off('submit', prepareInlinePay); 130 | } 131 | }); 132 | 133 | jQuery(window).on('load', function () { 134 | if (jQuery('form.woocommerce-checkout').length) { 135 | jQuery(document.body).on('updated_checkout', function () { 136 | if (jQuery("#payment_method_twocheckout_inline").is(':checked')) { 137 | jQuery("form.woocommerce-checkout").unbind('submit'); 138 | jQuery("form.woocommerce-checkout").on('submit', prepareInlinePay); 139 | } 140 | }); 141 | } else if (jQuery('form#add_payment_method').length || jQuery('form#order_review').length) { 142 | if (jQuery("#payment_method_twocheckout_inline").is(':checked')) { 143 | jQuery("form#order_review").unbind('submit'); 144 | jQuery("form#order_review").on('submit', prepareInlinePay); 145 | } 146 | } 147 | }); 148 | -------------------------------------------------------------------------------- /twocheckout-inline/autoload.php: -------------------------------------------------------------------------------- 1 | generate_JWT_token( 19 | $merchant_id, 20 | time(), 21 | time() + 3600, 22 | $buy_link_secret_word 23 | ); 24 | 25 | $curl = curl_init(); 26 | 27 | curl_setopt_array($curl, [ 28 | CURLOPT_URL => self::CURLOPT_URL, 29 | CURLOPT_RETURNTRANSFER => true, 30 | CURLOPT_CUSTOMREQUEST => 'POST', 31 | CURLOPT_POSTFIELDS => json_encode($payload), 32 | CURLOPT_HTTPHEADER => [ 33 | 'content-type: application/json', 34 | 'cache-control: no-cache', 35 | 'merchant-token: ' . $jwtToken, 36 | ], 37 | ]); 38 | $response = curl_exec($curl); 39 | $err = curl_error($curl); 40 | curl_close($curl); 41 | $log = new WC_Logger(); 42 | 43 | if ($err) { 44 | wc_add_notice('Error when trying to place order', $notice_type = 'error' ); 45 | $log->add('twocheckout-inline',sprintf('Unable to get proper response from signature generation API. In file %s at line %s', __FILE__, __LINE__)); 46 | } 47 | 48 | $response = json_decode($response, true); 49 | if (JSON_ERROR_NONE !== json_last_error() || !isset($response['signature'])) { 50 | wc_add_notice('Error when trying to place order', $notice_type = 'error' ); 51 | $log->add('twocheckout-inline',sprintf('Unable to get proper response from signature generation API. In file %s at line %s', __FILE__, __LINE__)); 52 | } 53 | 54 | return $response['signature']; 55 | 56 | } 57 | 58 | /** 59 | * @param $sub 60 | * @param $iat 61 | * @param $exp 62 | * @param $buy_link_secret_word 63 | * 64 | * @return string 65 | */ 66 | public function generate_JWT_token($sub, $iat, $exp, $buy_link_secret_word) 67 | { 68 | $header = $this->encode(json_encode(['alg' => 'HS512', 'typ' => 'JWT'])); 69 | $payload = $this->encode(json_encode(['sub' => $sub, 'iat' => $iat, 'exp' => $exp])); 70 | $signature = $this->encode( 71 | hash_hmac('sha512', "$header.$payload", $buy_link_secret_word, true) 72 | ); 73 | 74 | return implode('.', [ 75 | $header, 76 | $payload, 77 | $signature 78 | ]); 79 | } 80 | 81 | 82 | /** 83 | * @param $data 84 | * 85 | * @return string|string[] 86 | */ 87 | private function encode($data) 88 | { 89 | return str_replace('=', '', strtr(base64_encode($data), '+/', '-_')); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /twocheckout-inline/src/Twocheckout/TwoCheckoutApi.php: -------------------------------------------------------------------------------- 1 | test_order; 37 | } 38 | 39 | /** 40 | * @param bool $test_order 41 | */ 42 | public function set_test_order( bool $test_order ) { 43 | $this->test_order = $test_order; 44 | } 45 | 46 | /** 47 | * @return null 48 | */ 49 | public function get_seller_id() { 50 | return $this->seller_id; 51 | } 52 | 53 | /** 54 | * @param null seller_id 55 | * 56 | * @return TwoCheckoutApi 57 | */ 58 | public function set_seller_id( $seller_id ) { 59 | $this->seller_id = $seller_id; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return null 66 | */ 67 | public function get_secret_key() { 68 | return $this->secret_key; 69 | } 70 | 71 | /** 72 | * @param null $secret_key 73 | * 74 | * @return TwoCheckoutApi 75 | */ 76 | public function set_secret_key( $secret_key ) { 77 | $this->secret_key = $secret_key; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * sets the header with the auth has and params 84 | * @return array 85 | * @throws Exception 86 | */ 87 | private function get_headers() { 88 | if ( ! $this->seller_id || ! $this->secret_key ) { 89 | throw new Exception( 'Merchandiser needs a valid 2Checkout SellerId and SecretKey to authenticate!' ); 90 | } 91 | $gmt_date = gmdate( 'Y-m-d H:i:s' ); 92 | $string = strlen( $this->seller_id ) . $this->seller_id . strlen( $gmt_date ) . $gmt_date; 93 | $hash = hash_hmac( 'sha3-256', $string, $this->secret_key ); 94 | 95 | $headers[] = 'Content-Type: application/json'; 96 | $headers[] = 'Accept: application/json'; 97 | $headers[] = 'X-Avangate-Authentication: code="' . $this->seller_id . '" date="' . $gmt_date . '" hash="' . $hash . '" algo="sha3-256"'; 98 | 99 | return $headers; 100 | } 101 | 102 | 103 | /** 104 | * @param $vendorCode 105 | * @param $secret 106 | * @param $requestDateTime 107 | * @return string 108 | * @throws Exception 109 | */ 110 | public function generate_hash($vendorCode, $secret, $requestDateTime) 111 | { 112 | $string = sprintf('%s%s%s%s', strlen($vendorCode), $vendorCode, strlen($requestDateTime), $requestDateTime); 113 | $hash = hash_hmac('sha3-256', $string, $secret); 114 | 115 | // the hash_hmac may fail for various reasons 116 | if (false === $hash) { 117 | $exception = new Exception('Unable to create the hash, hash_hmac returned false.'); 118 | throw $exception; 119 | } 120 | 121 | return $hash; 122 | } 123 | 124 | /** 125 | * @param string $endpoint 126 | * @param array $params 127 | * @param string $method 128 | * 129 | * @return mixed 130 | * @throws Exception 131 | */ 132 | public function call( string $endpoint, array $params, $method = 'POST' ) { 133 | // if endpoint does not starts or end with a '/' we add it, as the API needs it 134 | if ( $endpoint[0] !== '/' ) { 135 | $endpoint = '/' . $endpoint; 136 | } 137 | if ( $endpoint[ - 1 ] !== '/' ) { 138 | $endpoint = $endpoint . '/'; 139 | } 140 | 141 | try { 142 | $url = self::API_URL . self::API_VERSION . $endpoint; 143 | $ch = curl_init(); 144 | curl_setopt( $ch, CURLOPT_URL, $url ); 145 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $this->get_headers() ); 146 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); 147 | curl_setopt( $ch, CURLOPT_HEADER, false ); 148 | curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); 149 | if ( $method === 'POST' ) { 150 | curl_setopt( $ch, CURLOPT_POST, true ); 151 | curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $params, JSON_UNESCAPED_UNICODE ) ); 152 | } 153 | $response = curl_exec( $ch ); 154 | 155 | if ( $response === false ) { 156 | exit( curl_error( $ch ) ); 157 | } 158 | curl_close( $ch ); 159 | 160 | return json_decode( $response, true ); 161 | } catch ( Exception $e ) { 162 | throw new Exception( $e->getMessage() ); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /twocheckout-inline/src/Twocheckout/class-two-checkout-ipn-helper.php: -------------------------------------------------------------------------------- 1 | request_params = $request_params; 65 | $this->secret_key = $secret_key; 66 | $this->complete_order_on_payment = $complete_order_on_payment; 67 | self::$log_enabled = $debug; 68 | $this->wc_order = $order; 69 | } 70 | 71 | /** 72 | * @return void 73 | */ 74 | protected function _process_fraud(string $fraudStatus) { 75 | 76 | if ( isset( $fraudStatus ) ) { 77 | switch ( trim( $fraudStatus ) ) { 78 | case self::FRAUD_STATUS_DENIED: 79 | case self::ORDER_STATUS_INVALID: 80 | $this->wc_order->update_status( 'failed' ); 81 | $this->wc_order->add_order_note( __( "Order status changed to failed" ), false, false ); 82 | 83 | break; 84 | 85 | case self::FRAUD_STATUS_APPROVED: 86 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 87 | $this->wc_order->update_status( 'pending' ); 88 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 89 | } 90 | break; 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * @return bool 97 | */ 98 | protected function _is_fraud() { 99 | return ( isset( $this->request_params['FRAUD_STATUS'] ) 100 | && $this->request_params['FRAUD_STATUS'] === self::FRAUD_STATUS_DENIED ); 101 | } 102 | 103 | /** 104 | * @return bool 105 | */ 106 | protected function _is_order_pending() { 107 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PENDING; 108 | } 109 | 110 | /** 111 | * @return bool 112 | */ 113 | protected function _is_order_processing() { 114 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PROCESSING; 115 | } 116 | 117 | /** 118 | * @return bool 119 | */ 120 | protected function _is_order_completed() { 121 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_COMPLETE; 122 | } 123 | 124 | /** 125 | * @return bool 126 | */ 127 | protected function _is_order_refunded() { 128 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_REFUND; 129 | } 130 | 131 | /** 132 | * @return string 133 | */ 134 | private function _calculate_ipn_response($algo='sha3-256') { 135 | $result_response = ''; 136 | $ipn_params_response = []; 137 | // we're assuming that these always exist, if they don't then the problem is on avangate side 138 | $ipn_params_response['IPN_PID'][0] = $this->request_params['IPN_PID'][0]; 139 | $ipn_params_response['IPN_PNAME'][0] = $this->request_params['IPN_PNAME'][0]; 140 | $ipn_params_response['IPN_DATE'] = $this->request_params['IPN_DATE']; 141 | $ipn_params_response['DATE'] = date( 'YmdHis' ); 142 | 143 | foreach ( $ipn_params_response as $key => $val ) { 144 | $result_response .= $this->array_expand( (array) $val ); 145 | } 146 | 147 | if ('md5' === $algo) 148 | return sprintf( 149 | '%s|%s', 150 | $ipn_params_response['DATE'], 151 | $this->generate_hash($this->secret_key, $result_response, $algo) 152 | ); 153 | else 154 | return sprintf( 155 | '%s', 156 | $algo, 157 | $ipn_params_response['DATE'], 158 | $this->generate_hash($this->secret_key, $result_response, $algo) 159 | ); 160 | } 161 | 162 | /** 163 | * @return bool 164 | */ 165 | private function getCompleteOrderOnPayment() { 166 | return $this->complete_order_on_payment; 167 | } 168 | 169 | /** 170 | * @throws Exception 171 | */ 172 | function processorder_status(string $order_status,string $refNo) { 173 | if ( ! empty( $order_status ) ) { 174 | switch ( trim( $order_status ) ) { 175 | case self::ORDER_STATUS_PENDING: 176 | case self::ORDER_STATUS_PURCHASE_PENDING: 177 | case self::ORDER_STATUS_PENDING_APPROVAL: 178 | if ( ! $this->_is_order_pending() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 179 | $this->wc_order->update_status( 'pending' ); 180 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 181 | } 182 | break; 183 | 184 | 185 | case self::ORDER_STATUS_PAYMENT_AUTHORIZED: 186 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 187 | $this->wc_order->update_status( 'pending' ); 188 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 189 | } 190 | break; 191 | 192 | case self::ORDER_STATUS_COMPLETE: 193 | case self::ORDER_STATUS_AUTH_RECEIVED: 194 | if ( ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 195 | $this->wc_order->update_meta_data( self::TCO_ORDER_REFERENCE, $refNo ); 196 | $this->wc_order->save_meta_data(); 197 | $this->wc_order->payment_complete(); 198 | if ( $this->getCompleteOrderOnPayment() ) { 199 | $this->wc_order->update_status( 'completed' ); 200 | } 201 | $this->wc_order->add_order_note( __( '2Checkout transaction ID: ' . $refNo ), false, false ); 202 | $this->wc_order->add_order_note( __( "Order payment is completed." ), false, false ); 203 | } 204 | break; 205 | 206 | default: 207 | throw new Exception( 'Cannot handle Ipn message type for this request!' ); 208 | } 209 | 210 | $this->wc_order->save(); 211 | } 212 | } 213 | 214 | /** 215 | * Validate Ipn request 216 | * @return bool 217 | */ 218 | public function is_ipn_response_valid() { 219 | $result = ''; 220 | 221 | $receivedAlgo='sha3-256'; 222 | $receivedHash = $this->request_params['SIGNATURE_SHA3_256']; 223 | 224 | if(!$receivedHash){ 225 | $receivedAlgo='sha256'; 226 | $receivedHash = $this->request_params['SIGNATURE_SHA2_256']; 227 | } 228 | 229 | if(!$receivedHash){ 230 | $receivedAlgo='md5'; 231 | $receivedHash = $this->request_params['HASH']; 232 | } 233 | 234 | foreach ( $this->request_params as $key => $val ) { 235 | 236 | if ( !in_array($key, ["HASH","SIGNATURE_SHA2_256","SIGNATURE_SHA3_256"]) ) { 237 | if ( is_array( $val ) ) { 238 | $result .= $this->array_expand( $val ); 239 | } else { 240 | $size = strlen( stripslashes( $val ) ); 241 | $result .= $size . stripslashes( $val ); 242 | } 243 | } 244 | } 245 | 246 | if ( isset( $this->request_params['REFNO'] ) && ! empty( $this->request_params['REFNO'] ) ) { 247 | $calcHash = $this->generate_hash( $this->secret_key, $result, $receivedAlgo ); 248 | if ( $receivedHash === $calcHash ) { 249 | return true; 250 | } 251 | } 252 | 253 | return false; 254 | } 255 | 256 | /** 257 | * generates hmac 258 | * 259 | * @param string $key 260 | * @param string $data 261 | * 262 | * @return string 263 | */ 264 | public function generate_hash($key, $data, $algo = 'sha3-256') { 265 | if ('sha3-256' === $algo) { 266 | return hash_hmac($algo, $data, $key); 267 | } 268 | 269 | $b = 64; // byte length for hash 270 | if (strlen($key) > $b) { 271 | $key = pack("H*", hash($algo, $key)); 272 | } 273 | 274 | $key = str_pad($key, $b, chr(0x00)); 275 | $ipad = str_pad('', $b, chr(0x36)); 276 | $opad = str_pad('', $b, chr(0x5c)); 277 | $k_ipad = $key ^ $ipad; 278 | $k_opad = $key ^ $opad; 279 | 280 | return hash($algo, $k_opad . pack("H*", hash($algo, $k_ipad . $data))); 281 | } 282 | 283 | /** 284 | * @return string 285 | */ 286 | public function process_ipn() { 287 | $hash = $this->extractHashFromRequest(); 288 | try { 289 | if ( ! isset( $this->request_params['REFNO'] ) && empty( $this->request_params['REFNO'] ) ) { 290 | self::log( 'Cannot identify order: "%s".', $this->request_params['REFNOEXT'] ); 291 | 292 | return; 293 | } 294 | $order_id = intval( $this->request_params['REFNOEXT'] ); 295 | $this->request_params['REFNOEXT_D'] = ! empty( $order_id ) ? $order_id : 0; 296 | 297 | if ( $this->wc_order->get_id() ) { 298 | $this->_process_fraud($this->request_params['FRAUD_STATUS']); 299 | if ( ! $this->_is_fraud() ) { 300 | $this->processorder_status($this->request_params['ORDERSTATUS'],$this->request_params['REFNO']); 301 | } 302 | } 303 | } catch ( Exception $ex ) { 304 | self::log( 'Exception processing IPN: ' . $ex->getMessage() ); 305 | } 306 | echo $this->_calculate_ipn_response($hash['algo']); 307 | exit(); 308 | } 309 | 310 | /** 311 | * Logging method 312 | * 313 | * @param string $message 314 | */ 315 | public static function log( $message ) { 316 | if ( self::$log_enabled ) { 317 | if ( empty( self::$log ) ) { 318 | self::$log = new WC_Logger(); 319 | } 320 | self::$log->add( 'twocheckout', $message ); 321 | } 322 | } 323 | 324 | /** 325 | * @param $array 326 | * 327 | * @return string 328 | */ 329 | private function array_expand( $array ) { 330 | $retval = ''; 331 | foreach ( $array as $key => $value ) { 332 | $size = strlen( stripslashes( $value ) ); 333 | $retval .= $size . stripslashes( $value ); 334 | } 335 | 336 | return $retval; 337 | } 338 | 339 | /** 340 | * @return array [hash, algo] 341 | */ 342 | protected function extractHashFromRequest():array { 343 | $receivedAlgo = 'sha3-256'; 344 | $receivedHash = $this->request_params['SIGNATURE_SHA3_256']; 345 | 346 | if (!$receivedHash) { 347 | $receivedAlgo = 'sha256'; 348 | $receivedHash = $this->request_params['SIGNATURE_SHA2_256']; 349 | } 350 | 351 | if (!$receivedHash) { 352 | $receivedAlgo = 'md5'; 353 | $receivedHash = $this->request_params['HASH']; 354 | } 355 | 356 | return ['hash' => $receivedHash, 'algo' => $receivedAlgo]; 357 | } 358 | 359 | } 360 | -------------------------------------------------------------------------------- /twocheckout-inline/templates/admin-options.php: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | generate_settings_html(); 7 | ?> 8 | 9 | 10 | 13 | 20 | 21 |
11 | 12 | 14 |
15 | 16 | 17 |

18 |
19 |
22 | -------------------------------------------------------------------------------- /twocheckout-inline/templates/init_form_fields.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'title' => __( 'Enable/Disable', 'woocommerce' ), 8 | 'type' => 'checkbox', 9 | 'label' => __( 'Enable 2Checkout Inline', 'woocommerce' ), 10 | 'default' => 'yes' 11 | ], 12 | 'title' => [ 13 | 'title' => __( 'Title', 'woocommerce' ), 14 | 'type' => 'text', 15 | 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ), 16 | 'default' => __( '2Checkout Inline Payment Gateway', 'woocommerce' ), 17 | 'desc_tip' => true, 18 | ], 19 | 'description' => [ 20 | 'title' => __( 'Description', 'woocommerce' ), 21 | 'type' => 'text', 22 | 'desc_tip' => true, 23 | 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce' ), 24 | 'default' => __( 'Safe payment solutions by 2Checkout ', 'woocommerce' ) 25 | ], 26 | 'seller_id' => [ 27 | 'title' => __( 'Seller ID', 'woocommerce' ), 28 | 'type' => 'text', 29 | 'description' => __( 'Please enter your 2Checkout account number; this is needed in order to take payment.', 'woocommerce' ), 30 | 'default' => '', 31 | 'desc_tip' => true, 32 | 'placeholder' => '' 33 | ], 34 | 'secret_key' => [ 35 | 'title' => __( 'Secret Key', 'woocommerce' ), 36 | 'type' => 'password', 37 | 'description' => __( 'Please enter your 2Checkout Secret Key; this is needed in order to take payment.', 'woocommerce' ), 38 | 'default' => '', 39 | 'desc_tip' => true, 40 | 'placeholder' => '' 41 | ], 42 | 'secret_word' => [ 43 | 'title' => __( 'Secret Word', 'woocommerce' ), 44 | 'type' => 'password', 45 | 'description' => __( 'Please enter your 2Checkout Secret Word; this is needed in order to update the payment status.', 'woocommerce' ), 46 | 'default' => '', 47 | 'desc_tip' => true, 48 | 'placeholder' => '' 49 | ], 50 | 'debug' => [ 51 | 'title' => __( 'Debug Log', 'woocommerce' ), 52 | 'type' => 'checkbox', 53 | 'label' => __( 'Enable logging', 'woocommerce' ), 54 | 'default' => 'no', 55 | 'desc_tip' => true, 56 | 'description' => sprintf( __( 'Log 2Checkout events', 'woocommerce' ), wc_get_log_file_path( 'twocheckout' ) ) 57 | ], 58 | 'demo' => [ 59 | 'title' => __( 'Demo order', 'woocommerce' ), 60 | 'type' => 'checkbox', 61 | 'label' => __( 'Create test orders', 'woocommerce' ), 62 | 'default' => 'no', 63 | 'desc_tip' => true, 64 | 'description' => sprintf( __( 'Not available yet for this method!', 'woocommerce' ), wc_get_log_file_path( 'twocheckout' ) ) 65 | ], 66 | 'complete_order_on_payment' => [ 67 | 'desc_tip' => true, 68 | 'title' => __( 'Complete Order on Payment', 'woocommerce' ), 69 | 'type' => 'select', 70 | 'label' => __( 'Complete Order on Payment', 'woocommerce' ), 71 | 'options' => [ 72 | 'Yes' => 'Yes', 73 | 'No' => 'No', 74 | ], 75 | 'description' => __( 'Set order status to complete on successful payment.', 'woocommerce' ), 76 | ], 77 | 'inline_type' => [ 78 | 'title' => __( 'Template', 'verifone-hosted' ), 79 | 'type' => 'radio', // The type radio is handled by the custom function generate_radio_html 80 | 'description' => __( 'Choose between a multi-step or a one-step inline checkout.', 'verifone-hosted' ), 81 | 'label' => __( 'Template', 'verifone-hosted' ), 82 | 'options' => [ 83 | 'inline-one-step' => 'One step inline', 84 | 'inline' => 'Multi step inline', 85 | ], 86 | 'default' => 'inline-one-step', 87 | 'desc_tip' => true, 88 | ], 89 | ]; 90 | } 91 | -------------------------------------------------------------------------------- /twocheckout-inline/templates/payment-fields.php: -------------------------------------------------------------------------------- 1 | description) { 3 | echo '

' . $this->description . '

'; 4 | } 5 | ?> 6 |
7 |
8 |
9 |
10 | 11 | Processing, please wait... 12 |
13 |
14 | 15 | 16 |
17 |
custom_style); ?>"> 18 |
19 |
Loading, please wait...
20 | 21 |
22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /twocheckout-inline/templates/payment-inline-fields.php: -------------------------------------------------------------------------------- 1 | description) { 3 | echo '

' . $this->description . '

'; 4 | } 5 | ?> 6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /twocheckout-inline/twocheckout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout-inline/twocheckout.png -------------------------------------------------------------------------------- /twocheckout-inline/wc-twocheckout-inline.php: -------------------------------------------------------------------------------- 1 | id = 'twocheckout_inline'; 54 | $this->icon = apply_filters( 'woocommerce_twocheckout_icon', 55 | plugin_dir_url( __FILE__ ) . 'twocheckout.png' ); 56 | $this->plugin_name = '2Checkout secured Inline payments'; 57 | $this->method_description = __( 'Secured 2Checkout Inline payments without redirects', 'woocommerce' ); 58 | $this->supports[] = 'refunds'; 59 | $this->has_fields = true; 60 | 61 | // Load the settings 62 | $this->init_form_fields(); 63 | $this->init_settings(); 64 | 65 | // Define user set variables 66 | $this->title = $this->get_option( 'title' ); 67 | $this->seller_id = $this->get_option( 'seller_id' ); 68 | $this->secret_key = $this->get_option( 'secret_key' ); 69 | $this->secret_word = $this->get_option( 'secret_word' ); 70 | $this->custom_style = $this->get_option( 'style' ); 71 | $this->test_order = $this->get_option( 'demo' ); 72 | $this->description = $this->get_option( 'description' ); 73 | $this->debug = $this->get_option( 'debug' ); 74 | $this->complete_order_on_payment = ( $this->get_option( 'complete_order_on_payment' ) == 'Yes' ) ? true : false; 75 | $this->inline_type = ( $this::INLINE_TYPE_ONE_STEP == $this->get_option( 'inline_type' ) ) ? $this::INLINE_TYPE_ONE_STEP : $this::INLINE_TYPE_MULTI_STEP; 76 | 77 | self::$log_enabled = $this->debug; 78 | 79 | $this->add_actions(); 80 | 81 | if ( ! $this->is_valid_for_use() ) { 82 | $this->enabled = false; 83 | } 84 | 85 | } 86 | 87 | private function add_actions() { 88 | //put your actions here 89 | 90 | // Actions 91 | add_action( 'woocommerce_receipt_' . $this->id, 92 | [ $this, 'receipt_page' ] ); 93 | 94 | // Save options 95 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, 96 | [ 97 | $this, 98 | 'process_admin_options', 99 | ] ); 100 | 101 | add_action( 'woo_mp_payment_complete', function ( $order ) { 102 | $order->set_payment_method( 'inline' ); 103 | $order->save(); 104 | }, 10, 1 ); 105 | 106 | // Payment listener/API hook 107 | add_action( 'woocommerce_api_payment_response', [ $this, 'check_inline_payment_response' ] ); 108 | add_action( 'woocommerce_api_2checkout_ipn_inline', 109 | [ $this, 'check_ipn_response_inline' ] ); 110 | 111 | add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_style' ] ); 112 | add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_script' ] ); 113 | 114 | add_action( 'woocommerce_api_twocheckout_inline_handle_payment_request', [ 115 | $this, 116 | 'handle_payment_request' 117 | ] ); 118 | 119 | // Order Page filter 120 | add_action( 'woocommerce_pay_order_after_submit', array( $this, 'render_additional_order_page_fields' ) ); 121 | 122 | } 123 | 124 | function enqueue_style() { 125 | wp_enqueue_style( 'twocheckout_inline_style', 126 | '/wp-content/plugins/twocheckout-inline/assets/css/twocheckout.css' ); 127 | } 128 | 129 | //enqueue a script 130 | function enqueue_script() { 131 | wp_enqueue_script( 'twocheckout_inline_script', 132 | '/wp-content/plugins/twocheckout-inline/assets/js/twocheckout_inline.js', ['jquery'] ); 133 | } 134 | 135 | /** 136 | * Logging method 137 | * 138 | * @param string $message 139 | */ 140 | public static function log( $message ) { 141 | if ( self::$log_enabled ) { 142 | if ( empty( self::$log ) ) { 143 | self::$log = new WC_Logger(); 144 | } 145 | self::$log->add( 'twocheckout', $message ); 146 | } 147 | } 148 | 149 | /** 150 | * Admin Panel Options 151 | * - Options for bits like 'title' and availability on a country-by-country basis 152 | * 153 | * @since 1.0.0 154 | */ 155 | public function admin_options() { 156 | 157 | require_once plugin_dir_path( __FILE__ ) . 'templates/admin-options.php'; 158 | } 159 | 160 | /** 161 | * Initialise Gateway Settings Form Fields 162 | * 163 | * @access public 164 | * @return void 165 | */ 166 | public function init_form_fields() { 167 | require_once plugin_dir_path( __FILE__ ) . 'templates/init_form_fields.php'; 168 | $this->form_fields = get_two_checkout_inline_form_fields(); 169 | } 170 | 171 | /** 172 | * Generate the credit card payment form 173 | */ 174 | public function payment_fields() { 175 | require_once plugin_dir_path( __FILE__ ) . 'templates/payment-inline-fields.php'; 176 | } 177 | 178 | /** 179 | * Check if this gateway is enabled and available in the user's country 180 | * 181 | * @access public 182 | * @return bool 183 | */ 184 | function is_valid_for_use() { 185 | $supported_currencies = [ 186 | 'AFN', 187 | 'ALL', 188 | 'DZD', 189 | 'ARS', 190 | 'AUD', 191 | 'AZN', 192 | 'BSD', 193 | 'BDT', 194 | 'BBD', 195 | 'BZD', 196 | 'BMD', 197 | 'BOB', 198 | 'BWP', 199 | 'BRL', 200 | 'GBP', 201 | 'BND', 202 | 'BGN', 203 | 'CAD', 204 | 'CLP', 205 | 'CNY', 206 | 'COP', 207 | 'CRC', 208 | 'HRK', 209 | 'CZK', 210 | 'DKK', 211 | 'DOP', 212 | 'XCD', 213 | 'EGP', 214 | 'EUR', 215 | 'FJD', 216 | 'GTQ', 217 | 'HKD', 218 | 'HNL', 219 | 'HUF', 220 | 'INR', 221 | 'IDR', 222 | 'ILS', 223 | 'JMD', 224 | 'JPY', 225 | 'KZT', 226 | 'KES', 227 | 'LAK', 228 | 'MMK', 229 | 'LBP', 230 | 'LRD', 231 | 'MOP', 232 | 'MYR', 233 | 'MVR', 234 | 'MRO', 235 | 'MUR', 236 | 'MXN', 237 | 'MAD', 238 | 'NPR', 239 | 'TWD', 240 | 'NZD', 241 | 'NIO', 242 | 'NOK', 243 | 'PKR', 244 | 'PGK', 245 | 'PEN', 246 | 'PHP', 247 | 'PLN', 248 | 'QAR', 249 | 'RON', 250 | 'RUB', 251 | 'WST', 252 | 'SAR', 253 | 'SCR', 254 | 'SGF', 255 | 'SBD', 256 | 'ZAR', 257 | 'KRW', 258 | 'LKR', 259 | 'SEK', 260 | 'CHF', 261 | 'SYP', 262 | 'THB', 263 | 'TOP', 264 | 'TTD', 265 | 'TRY', 266 | 'UAH', 267 | 'AED', 268 | 'USD', 269 | 'VUV', 270 | 'VND', 271 | 'XOF', 272 | 'YER', 273 | ]; 274 | 275 | if ( ! in_array( get_woocommerce_currency(), 276 | apply_filters( 'woocommerce_twocheckout_supported_currencies', 277 | $supported_currencies ) ) ) { 278 | return false; 279 | } 280 | 281 | return true; 282 | } 283 | 284 | /** 285 | * Include inline helper file 286 | */ 287 | private function inline_helper() { 288 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/Helpers/class-two-checkout-inline-helper.php'; 289 | } 290 | 291 | /** 292 | * @param int $order_id 293 | * @param null $amount 294 | * @param string $reason 295 | * 296 | * @return bool|\WP_Error 297 | * @throws \Exception 298 | */ 299 | public function process_refund( $order_id, $amount = null, $reason = '' ) { 300 | $order = wc_get_order( $order_id ); 301 | if ( $order->get_payment_method() == 'twocheckout_inline' ) { 302 | $transaction_id = $order->get_meta( '__2co_order_number' ); 303 | 304 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 305 | $api = new Two_Checkout_Api(); 306 | $api->set_seller_id( $this->seller_id ); 307 | $api->set_secret_key( $this->secret_key ); 308 | $tco_order = $api->call( 'orders/' . $transaction_id . '/', [], 'GET' ); 309 | if ( ! $order || ! $tco_order || ! $transaction_id ) { 310 | $this->log( sprintf( 'Tried to refund order with order ID %s but it has no registered transaction ID, aborting.', $order_id ) ); 311 | 312 | return new WP_Error( '2co_refund_error', 'Refund Error: Unable to refund transaction' ); 313 | } 314 | 315 | if ( $order->get_currency() !== $tco_order['PayoutCurrency'] ) { 316 | $this->log( sprintf( 'Refund Error: Cannot refund order in currency %s as it was placed in currency %s', 317 | strtoupper( $order->get_currency() ), 318 | strtoupper( $tco_order['PayoutCurrency'] ) 319 | ) ); 320 | 321 | return new WP_Error( '2co_refund_error', sprintf( 'Attempted to refund order in other currency %s while the order was placed in a different currency', 322 | $order->get_currency() 323 | ) ); 324 | } 325 | 326 | if ( $amount != $tco_order['GrossPrice'] ) { 327 | $this->log( 'Only full refund is supported!' ); 328 | 329 | return new WP_Error( '2co_refund_error', 'Refund Error: Only full refund is supported.' ); 330 | } 331 | 332 | if ( strtolower( get_woocommerce_currency() ) != strtolower( $tco_order['Currency'] ) ) { 333 | $this->log( 'Order currency not matching the 2checkout response!' ); 334 | 335 | return new WP_Error( '2co_refund_error', 'Refund Error: Order currency not matching the 2checkout response.' ); 336 | } 337 | 338 | $params = [ 339 | "amount" => $amount, 340 | "comment" => $reason, 341 | "reason" => 'Other' 342 | ]; 343 | 344 | $response = $api->call( '/orders/' . $transaction_id . '/refund/', $params, 'POST' ); 345 | 346 | if ( isset( $response['error_code'] ) && ! empty( $response['error_code'] ) ) { 347 | $this->log( 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 348 | 349 | return new WP_Error( '2co_refund_error', 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 350 | } 351 | 352 | $order->update_meta_data( '__2co_order_number', $response['id'] ); 353 | $order->add_order_note( __( sprintf( 'Refunded %s out of a total of %s from order', $amount, $order->get_total() ) ) ); 354 | $order->save_meta_data(); 355 | $order->save(); 356 | 357 | return true; 358 | } 359 | } 360 | 361 | /** 362 | * Process the payment and return the result 363 | * 364 | * @access public 365 | * 366 | * @param int $order_id 367 | * 368 | * @return array 369 | */ 370 | public function process_payment( $order_id ) { 371 | $order = new WC_Order( $order_id ); 372 | $this->inline_helper(); 373 | $helper = new Two_Checkout_Inline_Helper(); 374 | global $woocommerce; 375 | $woocommerce_version_formatted = str_replace( '.', '_', $woocommerce->version ); 376 | 377 | try { 378 | $order_params = [ 379 | 'currency' => get_woocommerce_currency(), 380 | 'language' => strtoupper( substr( get_locale(), 0, 381 | 2 ) ), 382 | 'country' => strtoupper( $order->get_billing_country() ), 383 | 'products' => $this->get_item( $order ), 384 | 'return-method' => [ 385 | 'type' => 'redirect', 386 | 'url' => add_query_arg( 'wc-api', 'payment_response', home_url( '/' ) ) . "&pm={$order->get_payment_method()}" . "&order-ext-ref={$order->get_id()}", 387 | ], 388 | 'test' => strtolower( $this->test_order ) === 'yes' ? '1' : '0', 389 | 'order-ext-ref' => $order->get_id(), 390 | 'customer-ext-ref' => $order->get_billing_email(), 391 | 'src' => 'WOOCOMMERCE_' . $woocommerce_version_formatted, 392 | 'mode' => 'DYNAMIC', 393 | 'dynamic' => '1', 394 | 'merchant' => $this->seller_id, 395 | 'customization' => isset( $this->inline_type ) && !empty( $this->inline_type ) ? $this->inline_type : $this::INLINE_TYPE_ONE_STEP, 396 | ]; 397 | 398 | $order_params = array_merge( $order_params, 399 | $this->get_billing_details( $order ) ); 400 | $order_params = array_merge( $order_params, 401 | $this->get_shipping_details( $order ) ); 402 | 403 | $order_params['billing_address'] = $this->get_billing_details( $order ); 404 | $order_params['shipping_address'] = $this->get_shipping_details( $order ); 405 | 406 | $order_params['signature'] = $helper->get_inline_signature( 407 | $this->seller_id, 408 | $this->secret_word, 409 | $order_params ); 410 | 411 | 412 | return [ 413 | 'result' => 'success', 414 | 'payload' => wp_json_encode( $order_params ), 415 | ]; 416 | 417 | } catch ( Exception $e ) { 418 | wc_add_notice( $e->getMessage(), $notice_type = 'error' ); 419 | 420 | return [ 421 | 'result' => 'failure', 422 | 'messages' => 'There has been an error processing your order', 423 | ]; 424 | } 425 | } 426 | 427 | /** 428 | * @param WC_Order $order 429 | * 430 | * @return array 431 | */ 432 | private function get_billing_details( WC_Order $order ) { 433 | return [ 434 | 'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), 435 | 'phone' => $order->get_billing_phone(), 436 | 'country' => strtoupper( $order->get_billing_country() ), 437 | 'state' => $order->get_billing_state(), 438 | 'email' => $order->get_billing_email(), 439 | 'address' => $order->get_billing_address_1(), 440 | 'address2' => $order->get_billing_address_2(), 441 | 'city' => $order->get_billing_city(), 442 | 'zip' => $order->get_billing_postcode(), 443 | 'company-name' => $order->get_billing_company(), 444 | ]; 445 | } 446 | 447 | 448 | /** 449 | * @param WC_Order $order 450 | * 451 | * @return array 452 | */ 453 | private function get_shipping_details( WC_Order $order ) { 454 | return [ 455 | 'ship-name' => $order->get_shipping_first_name() . ' ' . $order->get_shipping_last_name(), 456 | 'ship-country' => strtoupper( $order->get_shipping_country() ), 457 | 'ship-state' => $order->get_shipping_state(), 458 | 'ship-email' => $order->get_billing_email(), 459 | //same as billing 460 | 'ship-address' => $order->get_shipping_address_1(), 461 | 'ship-address2' => $order->get_shipping_address_2(), 462 | 'ship-city' => $order->get_shipping_city(), 463 | ]; 464 | } 465 | 466 | /** 467 | * for safety reasons we only send one Item with the grand total and the Cart_id as ProductName (identifier) 468 | * sending products order as ONE we dont have to calculate the total fee of the order (product price, tax, discounts etc) 469 | * 470 | * @return array 471 | */ 472 | private function get_item( WC_Order $order ) { 473 | $items[] = [ 474 | 'type' => 'PRODUCT', 475 | 'name' => get_bloginfo(), 476 | 'price' => number_format( $order->get_total(), 2, '.', '' ), 477 | 'tangible' => 0, 478 | 'quantity' => 1, 479 | ]; 480 | 481 | return $items; 482 | } 483 | 484 | /** 485 | * @return void 486 | */ 487 | public function check_inline_payment_response() { 488 | global $woocommerce; 489 | $params = $_GET; 490 | if ( isset( $params['pm'] ) && ! empty( $params['pm'] ) ) { 491 | if ( $params['pm'] == 'twocheckout_inline' ) { 492 | if ( isset( $params['order-ext-ref'] ) && ! empty( $params['order-ext-ref'] ) ) { 493 | $order = wc_get_order( (int) $params['order-ext-ref'] ); 494 | if ( ! $order instanceof WC_Order ) { 495 | $this->log( 'There was a request for an order that doesn\'t exist in current shop! Requested params: ' . strip_tags( http_build_query( $params ) ) ); 496 | } else { 497 | if ( isset( $params['refno'] ) && ! empty( $params['refno'] ) ) { 498 | $refNo = $params['refno']; 499 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 500 | $api = new Two_Checkout_Api(); 501 | $api->set_seller_id( $this->seller_id ); 502 | $api->set_secret_key( $this->secret_key ); 503 | $api_response = $api->call( 'orders/' . $refNo . '/', [], 'GET' ); 504 | 505 | if ( ! empty( $api_response['Status'] ) && isset( $api_response['Status'] ) ) { 506 | if ( in_array( $api_response['Status'], [ 'AUTHRECEIVED', 'COMPLETE' ] ) ) { 507 | $order->add_order_note( __( '2Checkout transaction ID: ' . $api_response['RefNo'] ), false, false ); 508 | $redirect_url = $order->get_checkout_order_received_url(); 509 | 510 | try { 511 | $ipnHelper = new Two_Checkout_Ipn_Helper( $params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 512 | } catch ( Exception $ex ) { 513 | $this->log( 'Unable to find order with RefNo: ' . $params['order-ext-ref'] ); 514 | throw new Exception( 'An error occurred!' ); 515 | } 516 | 517 | if (wp_redirect($redirect_url)) { 518 | try { 519 | $ipnHelper->processorder_status($api_response['Status'],$api_response['RefNo']); 520 | }catch(\Exception $e){ 521 | $this->log( __CLASS__ . ' line ' . __LINE__ . ' : ' . $e->getMessage() ); 522 | throw new Exception( 'An error occurred!' ); 523 | } 524 | 525 | $woocommerce->cart->empty_cart(); 526 | exit; 527 | } 528 | } 529 | } 530 | } 531 | } 532 | } 533 | status_header( 404 ); 534 | nocache_headers(); 535 | include( get_query_template( '404' ) ); 536 | die(); 537 | } 538 | } 539 | } 540 | 541 | /** 542 | * Validate & process 2Checkout request 543 | * 544 | * @access public 545 | * @return void 546 | */ 547 | public function check_ipn_response_inline() { 548 | if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) { 549 | return; 550 | } 551 | 552 | $params = $_POST; 553 | if(empty($params)) 554 | $params=json_decode(file_get_contents('php://input'),true); 555 | 556 | unset( $params['wc-api'] ); 557 | if ( isset( $params['REFNOEXT'] ) && ! empty( $params['REFNOEXT'] ) ) { 558 | $order = wc_get_order( $params['REFNOEXT'] ); 559 | if ( $order && $order->get_payment_method() == 'twocheckout_inline' ) { 560 | try { 561 | $ipn_helper = new Two_Checkout_Ipn_Helper( $params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 562 | } catch ( Exception $ex ) { 563 | $this->log( 'Unable to find order with RefNo: ' . $params['REFNOEXT'] ); 564 | throw new Exception( 'An error occurred!' ); 565 | } 566 | if ( ! $ipn_helper->is_ipn_response_valid() ) { 567 | self::log( sprintf( 'SHA3 hash mismatch for 2Checkout IPN with date: "%s" . ', 568 | $params['IPN_DATE'] ) ); 569 | 570 | return; 571 | } 572 | $ipn_helper->process_ipn(); 573 | } 574 | } 575 | } 576 | 577 | /** 578 | * ajax callable wrapper for process_payment 579 | * 580 | * @access public 581 | * @return array 582 | */ 583 | public function handle_payment_request() { 584 | ob_start(); 585 | if ( ! empty( $_POST['terms-field'] ) && empty( $_POST['terms'] ) ) { 586 | wp_send_json( [ 587 | 'result' => 'failure', 588 | 'messages' => 'Please read and accept the terms and conditions to proceed with your order.', 589 | 'step' => 'existing_order' 590 | ] ); 591 | } 592 | 593 | if ( $_SERVER['REQUEST_METHOD'] !== 'POST' || ! isset( $_POST['woocommerce-pay-nonce'] ) || ! isset( $_POST['order_id'] ) ) { 594 | return; 595 | } 596 | 597 | $nonce_value = $_POST['woocommerce-pay-nonce']; 598 | 599 | if ( ! wp_verify_nonce( $nonce_value, 'woocommerce-pay' ) ) { 600 | return; 601 | } 602 | 603 | $response = $this->process_payment( $_POST['order_id'] ); 604 | 605 | wp_send_json( $response ); 606 | } 607 | 608 | /** 609 | * Render additional order page fields 610 | * 611 | * @access public 612 | * @return void 613 | */ 614 | public function render_additional_order_page_fields( $order = null ) { 615 | if ( ! isset( $order ) || empty( $order ) ) { 616 | $order = wc_get_order( absint( get_query_var( 'order-pay' ) ) ); 617 | } 618 | 619 | if ( isset( $order ) ) { 620 | echo ''; 621 | } 622 | } 623 | 624 | public function generate_radio_html($key, $data) 625 | { 626 | $field_key = $this->get_field_key( $key ); 627 | $defaults = array( 628 | 'title' => '', 629 | 'disabled' => false, 630 | 'class' => '', 631 | 'css' => '', 632 | 'placeholder' => '', 633 | 'type' => 'text', 634 | 'desc_tip' => false, 635 | 'description' => '', 636 | 'custom_attributes' => array(), 637 | ); 638 | 639 | $data = wp_parse_args( $data, $defaults ); 640 | 641 | $defaultValue = null; 642 | foreach( $data["options"] as $optionValue => $optionLabel ) { 643 | if( $defaultValue !== null && $optionValue === $this->get_option( $key ) ) { 644 | $defaultValue = $optionValue; 645 | } 646 | } 647 | 648 | ob_start(); 649 | ?> 650 | 651 | 652 | 653 | 654 | 655 |
656 | 657 | get_option( $key ) ) ) ? $this->get_field_default( $data ) : $this->get_option( $key ); 659 | foreach ( $data["options"] as $optionValue => $optionLabel ) { 660 | ?> 661 | > 662 | get_description_html($data); 665 | ?> 666 |
667 | 668 | 669 | test_order; 37 | } 38 | 39 | /** 40 | * @param bool $test_order 41 | */ 42 | public function set_test_order( bool $test_order ) { 43 | $this->test_order = $test_order; 44 | } 45 | 46 | /** 47 | * @return null 48 | */ 49 | public function get_seller_id() { 50 | return $this->seller_id; 51 | } 52 | 53 | /** 54 | * @param null seller_id 55 | * 56 | * @return TwoCheckoutApi 57 | */ 58 | public function set_seller_id( $seller_id ) { 59 | $this->seller_id = $seller_id; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @return null 66 | */ 67 | public function get_secret_key() { 68 | return $this->secret_key; 69 | } 70 | 71 | /** 72 | * @param null $secret_key 73 | * 74 | * @return TwoCheckoutApi 75 | */ 76 | public function set_secret_key( $secret_key ) { 77 | $this->secret_key = $secret_key; 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * sets the header with the auth has and params 84 | * @return array 85 | * @throws Exception 86 | */ 87 | public function get_headers() { 88 | if ( ! $this->seller_id || ! $this->secret_key ) { 89 | throw new Exception( 'Merchandiser needs a valid 2Checkout SellerId and SecretKey to authenticate!' ); 90 | } 91 | $gmt_date = gmdate( 'Y-m-d H:i:s' ); 92 | $string = strlen( $this->seller_id ) . $this->seller_id . strlen( $gmt_date ) . $gmt_date; 93 | $hash = hash_hmac( 'sha3-256', $string, $this->secret_key ); 94 | 95 | $headers[] = 'Content-Type: application/json'; 96 | $headers[] = 'Accept: application/json'; 97 | $headers[] = 'X-Avangate-Authentication: code="' . $this->seller_id . '" date="' . $gmt_date . '" hash="' . $hash . '" algo="sha3-256"'; 98 | 99 | return $headers; 100 | } 101 | 102 | /** 103 | * @param string $endpoint 104 | * @param array $params 105 | * @param string $method 106 | * 107 | * @return mixed 108 | * @throws Exception 109 | */ 110 | public function call( string $endpoint, array $params, $method = 'POST' ) { 111 | // if endpoint does not starts or end with a '/' we add it, as the API needs it 112 | if ( $endpoint[0] !== '/' ) { 113 | $endpoint = '/' . $endpoint; 114 | } 115 | if ( $endpoint[ - 1 ] !== '/' ) { 116 | $endpoint = $endpoint . '/'; 117 | } 118 | 119 | try { 120 | $url = self::API_URL . self::API_VERSION . $endpoint; 121 | $ch = curl_init(); 122 | curl_setopt( $ch, CURLOPT_URL, $url ); 123 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $this->get_headers() ); 124 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); 125 | curl_setopt( $ch, CURLOPT_HEADER, false ); 126 | curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); 127 | if ( $method === 'POST' ) { 128 | curl_setopt( $ch, CURLOPT_POST, true ); 129 | curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $params, JSON_UNESCAPED_UNICODE ) ); 130 | } 131 | $response = curl_exec( $ch ); 132 | 133 | if ( $response === false ) { 134 | exit( curl_error( $ch ) ); 135 | } 136 | curl_close( $ch ); 137 | 138 | return json_decode( $response, true ); 139 | } catch ( Exception $e ) { 140 | throw new Exception( $e->getMessage() ); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /twocheckout/src/Twocheckout/TwoCheckoutIpnHelperApi.php: -------------------------------------------------------------------------------- 1 | request_params = $request_params; 66 | $this->secret_key = $secret_key; 67 | $this->complete_order_on_payment = $complete_order_on_payment; 68 | self::$log_enabled = $debug; 69 | $this->wc_order = $order; 70 | } 71 | 72 | /** 73 | * @return bool 74 | */ 75 | protected function _is_fraud() { 76 | return ( isset( $this->request_params['FRAUD_STATUS'] ) 77 | && $this->request_params['FRAUD_STATUS'] === self::FRAUD_STATUS_DENIED ); 78 | } 79 | 80 | /** 81 | * @return bool 82 | */ 83 | protected function _is_order_pending() { 84 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PENDING; 85 | } 86 | 87 | /** 88 | * @return bool 89 | */ 90 | protected function _is_order_processing() { 91 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_PROCESSING; 92 | } 93 | 94 | /** 95 | * @return bool 96 | */ 97 | protected function _is_order_completed() { 98 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_COMPLETE; 99 | } 100 | 101 | /** 102 | * @return bool 103 | */ 104 | protected function _is_order_refunded() { 105 | return strtoupper( $this->wc_order->get_status() ) == self::WC_ORDER_STATUS_REFUND; 106 | } 107 | 108 | /** 109 | * @param $array 110 | * 111 | * @return string 112 | */ 113 | private function array_expand( array $array ) { 114 | $retval = ''; 115 | foreach ( $array as $key => $value ) { 116 | $size = strlen( stripslashes( $value ) ); 117 | $retval .= $size . stripslashes( $value ); 118 | } 119 | 120 | return $retval; 121 | } 122 | 123 | /** 124 | * @return bool 125 | */ 126 | private function getCompleteOrderOnPayment() { 127 | return $this->complete_order_on_payment; 128 | } 129 | 130 | /** 131 | * @throws Exception 132 | */ 133 | public function processorder_status(string $order_status,string $refNo) { 134 | if ( ! empty( $order_status ) ) { 135 | switch ( trim( $order_status ) ) { 136 | case self::ORDER_STATUS_PENDING: 137 | case self::ORDER_STATUS_PURCHASE_PENDING: 138 | case self::ORDER_STATUS_PENDING_APPROVAL: 139 | if ( ! $this->_is_order_pending() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 140 | $this->wc_order->update_status( 'pending' ); 141 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 142 | } 143 | break; 144 | 145 | case self::ORDER_STATUS_PAYMENT_AUTHORIZED: 146 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 147 | $this->wc_order->update_status( 'pending' ); 148 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 149 | } 150 | break; 151 | 152 | case self::ORDER_STATUS_COMPLETE: 153 | case self::ORDER_STATUS_AUTHRECEIVED: 154 | //woocommerce style :) 155 | if ( ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 156 | $this->wc_order->update_meta_data( self::TCO_ORDER_REFERENCE, $refNo ); 157 | $this->wc_order->save_meta_data(); 158 | $this->wc_order->payment_complete(); 159 | if ( $this->getCompleteOrderOnPayment() ) { 160 | $this->wc_order->update_status( 'completed' ); 161 | } 162 | $this->wc_order->add_order_note( __( '2Checkout transaction ID: ' . $refNo ), false, false ); 163 | $this->wc_order->add_order_note( __( "Order payment is completed." ), false, false ); 164 | $this->wc_order->save(); 165 | } 166 | break; 167 | 168 | default: 169 | throw new Exception( 'Cannot handle Ipn message type for this request!' ); 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * @return array [hash, algo] 176 | */ 177 | protected function extractHashFromRequest():array { 178 | $receivedAlgo = 'sha3-256'; 179 | $receivedHash = $this->request_params['SIGNATURE_SHA3_256']; 180 | 181 | if (!$receivedHash) { 182 | $receivedAlgo = 'sha256'; 183 | $receivedHash = $this->request_params['SIGNATURE_SHA2_256']; 184 | } 185 | 186 | if (!$receivedHash) { 187 | $receivedAlgo = 'md5'; 188 | $receivedHash = $this->request_params['HASH']; 189 | } 190 | 191 | return ['hash' => $receivedHash, 'algo' => $receivedAlgo]; 192 | } 193 | 194 | /** 195 | * Validate Ipn request 196 | * @return bool 197 | */ 198 | public function is_ipn_response_valid() { 199 | $result = ''; 200 | 201 | $hash = $this->extractHashFromRequest(); 202 | 203 | foreach ( $this->request_params as $key => $val ) { 204 | if ( !in_array($key ,["HASH", "SIGNATURE_SHA2_256", "SIGNATURE_SHA3_256"]) ) { 205 | if ( is_array( $val ) ) { 206 | $result .= $this->array_expand( $val ); 207 | } else { 208 | $size = strlen( StripSlashes( $val ) ); 209 | $result .= $size . StripSlashes( $val ); 210 | } 211 | } 212 | } 213 | 214 | if ( isset( $this->request_params['REFNO'] ) && ! empty( $this->request_params['REFNO'] ) ) { 215 | $calc_hash = $this->generate_hash( $this->secret_key, $result, $hash['algo'] ); 216 | if ( $hash['hash'] === $calc_hash ) { 217 | return true; 218 | } 219 | } 220 | 221 | return false; 222 | } 223 | 224 | /** 225 | * generates hmac 226 | * 227 | * @param string $key 228 | * @param string $data 229 | * 230 | * @return string 231 | */ 232 | public function generate_hash($key, $data, $receivedAlgo = 'sha3-256') { 233 | if ('sha3-256' === $receivedAlgo) { 234 | return hash_hmac($receivedAlgo, $data, $key); 235 | } 236 | 237 | $b = 64; // byte length for hash 238 | if (strlen($key) > $b) { 239 | $key = pack("H*", hash($receivedAlgo, $key)); 240 | } 241 | 242 | $key = str_pad($key, $b, chr(0x00)); 243 | $ipad = str_pad('', $b, chr(0x36)); 244 | $opad = str_pad('', $b, chr(0x5c)); 245 | $k_ipad = $key ^ $ipad; 246 | $k_opad = $key ^ $opad; 247 | 248 | return hash($receivedAlgo, $k_opad . pack("H*", hash($receivedAlgo, $k_ipad . $data))); 249 | } 250 | 251 | /** 252 | * Logging method 253 | * 254 | * @param string $message 255 | */ 256 | public static function log( $message ) { 257 | if ( self::$log_enabled ) { 258 | if ( empty( self::$log ) ) { 259 | self::$log = new WC_Logger(); 260 | } 261 | self::$log->add( 'twocheckout', $message ); 262 | } 263 | } 264 | 265 | /** 266 | * @return string 267 | */ 268 | public function process_ipn() { 269 | $hash = $this->extractHashFromRequest(); 270 | 271 | try { 272 | if ( ! isset( $this->request_params['REFNO'] ) && empty( $this->request_params['REFNO'] ) ) { 273 | self::log( 'Cannot identify order: "%s".', $this->request_params['REFNOEXT'] ); 274 | 275 | return; 276 | } 277 | $order_id = intval( $this->request_params['REFNOEXT'] ); 278 | $this->request_params['REFNOEXT_D'] = ! empty( $order_id ) ? $order_id : 0; 279 | 280 | if ( $this->wc_order->get_id() ) { 281 | $this->_process_fraud(); 282 | if ( ! $this->_is_fraud() ) { 283 | $this->processorder_status($this->request_params['ORDERSTATUS'],$this->request_params['REFNO']); 284 | } 285 | } 286 | } catch ( Exception $ex ) { 287 | self::log( 'Exception processing IPN: ' . $ex->getMessage() ); 288 | } 289 | echo $this->_calculate_ipn_response($hash['algo']); 290 | exit(); 291 | } 292 | 293 | /** 294 | * @return void 295 | */ 296 | protected function _process_fraud() { 297 | 298 | if ( isset( $this->request_params['FRAUD_STATUS'] ) ) { 299 | switch ( trim( $this->request_params['FRAUD_STATUS'] ) ) { 300 | case self::FRAUD_STATUS_DENIED: 301 | case self::ORDER_STATUS_INVALID: 302 | $this->wc_order->update_status( 'failed' ); 303 | $this->wc_order->add_order_note( __( "Order status changed to failed" ), false, false ); 304 | 305 | break; 306 | 307 | case self::FRAUD_STATUS_APPROVED: 308 | if ( ! $this->_is_order_pending() && ! $this->_is_order_processing() && ! $this->_is_order_completed() && ! $this->_is_order_refunded() ) { 309 | $this->wc_order->update_status( 'pending' ); 310 | $this->wc_order->add_order_note( __( "Order status changed to: Pending" ), false, false ); 311 | } 312 | break; 313 | 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * @return string 320 | */ 321 | private function _calculate_ipn_response($algo='sha3-256') { 322 | $resultResponse = ''; 323 | $ipn_params_response = []; 324 | // we're assuming that these always exist, if they don't then the problem is on avangate side 325 | $ipn_params_response['IPN_PID'][0] = $this->request_params['IPN_PID'][0]; 326 | $ipn_params_response['IPN_PNAME'][0] = $this->request_params['IPN_PNAME'][0]; 327 | $ipn_params_response['IPN_DATE'] = $this->request_params['IPN_DATE']; 328 | $ipn_params_response['DATE'] = date( 'YmdHis' ); 329 | 330 | foreach ( $ipn_params_response as $key => $val ) { 331 | $resultResponse .= $this->array_expand( (array) $val ); 332 | } 333 | 334 | if ('md5' === $algo) 335 | return sprintf( 336 | '%s|%s', 337 | $ipn_params_response['DATE'], 338 | $this->generate_hash($this->secret_key, $resultResponse, $algo) 339 | ); 340 | else 341 | return sprintf( 342 | '%s', 343 | $algo, 344 | $ipn_params_response['DATE'], 345 | $this->generate_hash($this->secret_key, $resultResponse, $algo) 346 | ); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /twocheckout/templates/admin-options.php: -------------------------------------------------------------------------------- 1 |

2 |

3 | 4 | generate_settings_html(); 7 | ?> 8 | 9 | 10 | 13 | 20 | 21 |
11 | 12 | 14 |
15 | 16 | 17 |

18 |
19 |
22 | -------------------------------------------------------------------------------- /twocheckout/templates/init_form_fields.php: -------------------------------------------------------------------------------- 1 | [ 161 | 'title' => __('Enable/Disable', 'woocommerce'), 162 | 'type' => 'checkbox', 163 | 'label' => __('Enable 2Checkout', 'woocommerce'), 164 | 'default' => 'yes' 165 | ], 166 | 'title' => [ 167 | 'title' => __('Title', 'woocommerce'), 168 | 'type' => 'text', 169 | 'description' => __('This controls the title which the user sees during checkout.', 'woocommerce'), 170 | 'default' => __('2Checkout 2Pay Api', 'woocommerce'), 171 | 'desc_tip' => true, 172 | ], 173 | 'description' => [ 174 | 'title' => __('Description', 'woocommerce'), 175 | 'type' => 'text', 176 | 'desc_tip' => true, 177 | 'description' => __('This controls the description which the user sees during checkout.', 'woocommerce'), 178 | 'default' => __('Safe payment solutions by 2Checkout ', 'woocommerce') 179 | ], 180 | 'seller_id' => [ 181 | 'title' => __('Seller ID', 'woocommerce'), 182 | 'type' => 'text', 183 | 'description' => __('Please enter your 2Checkout account number; this is needed in order to take payment.', 'woocommerce'), 184 | 'default' => '', 185 | 'desc_tip' => true, 186 | 'placeholder' => '' 187 | ], 188 | 'secret_key' => [ 189 | 'title' => __('Secret Key', 'woocommerce'), 190 | 'type' => 'password', 191 | 'description' => __('Please enter your 2Checkout Secret Key; this is needed in order to take payment.', 'woocommerce'), 192 | 'default' => '', 193 | 'desc_tip' => true, 194 | 'placeholder' => '' 195 | ], 196 | 'debug' => [ 197 | 'title' => __('Debug Log', 'woocommerce'), 198 | 'type' => 'checkbox', 199 | 'label' => __('Enable logging', 'woocommerce'), 200 | 'default' => 'no', 201 | 'desc_tip' => true, 202 | 'description' => sprintf(__('Log 2Checkout events', 'woocommerce'), wc_get_log_file_path('twocheckout')) 203 | ], 204 | 'demo' => [ 205 | 'title' => __('Demo order', 'woocommerce'), 206 | 'type' => 'checkbox', 207 | 'label' => __('Create test orders', 'woocommerce'), 208 | 'default' => 'no', 209 | 'desc_tip' => true, 210 | 'description' => sprintf(__('Not available yet for this method!', 'woocommerce'), wc_get_log_file_path('twocheckout')) 211 | ], 212 | 'complete_order_on_payment' => [ 213 | 'desc_tip' => true, 214 | 'title' => __( 'Complete Order on Payment', 'woocommerce' ), 215 | 'type' => 'select', 216 | 'label' => __( 'Complete Order on Payment', 'woocommerce' ), 217 | 'options' => [ 218 | 'Yes' => 'Yes', 219 | 'No' => 'No', 220 | ], 221 | 'description' => __( 'Set order status to complete on successful payment.', 'woocommerce' ), 222 | ], 223 | 'default' => [ 224 | 'title' => __('Use default style', 'woocommerce'), 225 | 'type' => 'checkbox', 226 | 'label' => __('Yes, I like the default style', 'woocommerce'), 227 | 'default' => 'yes', 228 | 'desc_tip' => true, 229 | 'description' => sprintf(__('If you uncheck this, the form will use the style from the bellow input!', 230 | 'woocommerce'), wc_get_log_file_path('twocheckout')) 231 | ], 232 | 'style' => [ 233 | 'title' => __('Custom style', 'woocommerce'), 234 | 'type' => 'textarea', 235 | 'description' => __('IMPORTANT!
This is the styling object that styles your form. 236 | Do not remove or add new classes. You can modify the existing ones. Use 237 | double quotes for all keys and values!
VALID JSON FORMAT REQUIRED (validate 238 | json before save here: https://jsonlint.com/)
.
239 | Also you can find more about styling your form here!', 'woocommerce'), 241 | 'default' => $default_style 242 | ], 243 | ]; 244 | } 245 | -------------------------------------------------------------------------------- /twocheckout/templates/payment-fields.php: -------------------------------------------------------------------------------- 1 | description) { 3 | echo '

' . $this->description . '

'; 4 | } 5 | ?> 6 |
7 |
8 |
9 |
10 | 11 | Processing, please wait... 12 |
13 |
14 | 15 | 16 |
17 |
custom_style); ?>"> 18 |
19 |
Loading, please wait...
20 | 21 |
22 |
23 |
24 | 25 | 30 |
31 | -------------------------------------------------------------------------------- /twocheckout/twocheckout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2Checkout/woocommerce/808a9ddcbda00252dcf5addb5113772c4db3c467/twocheckout/twocheckout.png -------------------------------------------------------------------------------- /twocheckout/wc-twocheckout.php: -------------------------------------------------------------------------------- 1 | id = 'twocheckout'; 56 | $this->icon = apply_filters( 'woocommerce_twocheckout_icon', 57 | plugin_dir_url( __FILE__ ) . 'twocheckout.png' ); 58 | $this->plugin_name = '2Checkout 2PayJs over API'; 59 | $this->method_description = __( 'Secured 2Checkout card payments over API.', 'woocommerce' ); 60 | $this->supports[] = 'refunds'; 61 | $this->has_fields = true; 62 | 63 | // Load the settings 64 | $this->init_form_fields(); 65 | $this->init_settings(); 66 | 67 | // Define user set variables 68 | $this->title = $this->get_option( 'title' ); 69 | $this->seller_id = $this->get_option( 'seller_id' ); 70 | $this->secret_key = $this->get_option( 'secret_key' ); 71 | $this->default_style = $this->get_option( 'default' ); 72 | $this->custom_style = $this->get_option( 'style' ); 73 | $this->test_order = $this->get_option( 'demo' ); 74 | $this->description = $this->get_option( 'description' ); 75 | $this->debug = $this->get_option( 'debug' ); 76 | $this->complete_order_on_payment = ( $this->get_option( 'complete_order_on_payment' ) == 'Yes' ) ? true : false; 77 | 78 | self::$log_enabled = $this->debug; 79 | 80 | // Actions 81 | add_action( 'woocommerce_receipt_' . $this->id, [ $this, 'receipt_page' ] ); 82 | 83 | // Save options 84 | add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, 85 | [ $this, 'process_admin_options' ] ); 86 | 87 | 88 | // Payment listener/API hook 89 | add_action( 'woocommerce_api_payment_response', [ $this, 'check_api_payment_response' ] ); 90 | add_action( 'woocommerce_api_2checkout_ipn_api', [ $this, 'check_ipn_response_api' ] ); 91 | 92 | // Order Page filter 93 | // add_filter( 'woocommerce_available_payment_gateways', array( $this, 'prepare_order_pay_page' ) ); 94 | add_action( 'woocommerce_pay_order_after_submit', array( $this, 'render_additional_order_page_fields' ) ); 95 | 96 | if ( ! $this->is_valid_for_use() ) { 97 | $this->enabled = false; 98 | } 99 | 100 | } 101 | 102 | /** 103 | * Logging method 104 | * 105 | * @param string $message 106 | */ 107 | public static function log( $message ) { 108 | if ( self::$log_enabled ) { 109 | if ( empty( self::$log ) ) { 110 | self::$log = new WC_Logger(); 111 | } 112 | self::$log->add( 'twocheckout', $message ); 113 | } 114 | } 115 | 116 | 117 | /** 118 | * Admin Panel Options 119 | * - Options for bits like 'title' and availability on a country-by-country basis 120 | * 121 | * @since 1.0.0 122 | */ 123 | public function admin_options() { 124 | 125 | require_once plugin_dir_path( __FILE__ ) . 'templates/admin-options.php'; 126 | } 127 | 128 | /** 129 | * Initialise Gateway Settings Form Fields 130 | * 131 | * @access public 132 | * @return void 133 | */ 134 | public function init_form_fields() { 135 | require_once plugin_dir_path( __FILE__ ) . 'templates/init_form_fields.php'; 136 | $this->form_fields = getTwoCheckoutFormFields(); 137 | } 138 | 139 | /** 140 | * Generate the credit card payment form 141 | */ 142 | public function payment_fields() { 143 | 144 | wp_enqueue_script( '2payjs', 'https://2pay-js.2checkout.com/v1/2pay.js', ['jquery'] ); 145 | wp_enqueue_script( 'twocheckout_script', '/wp-content/plugins/twocheckout/assets/js/twocheckout.js', ['jquery'] ); 146 | wp_enqueue_style( 'twocheckout_style', '/wp-content/plugins/twocheckout/assets/css/twocheckout.css' ); 147 | $twocheckout_is_checkout = ( is_checkout() && empty( $_GET['pay_for_order'] ) ) ? 'yes' : 'no'; 148 | require_once plugin_dir_path( __FILE__ ) . 'templates/payment-fields.php'; 149 | } 150 | 151 | 152 | /** 153 | * Check if this gateway is enabled and available in the user's country 154 | * 155 | * @access public 156 | * @return bool 157 | */ 158 | function is_valid_for_use() { 159 | $supported_currencies = [ 160 | 'AFN', 161 | 'ALL', 162 | 'DZD', 163 | 'ARS', 164 | 'AUD', 165 | 'AZN', 166 | 'BSD', 167 | 'BDT', 168 | 'BBD', 169 | 'BZD', 170 | 'BMD', 171 | 'BOB', 172 | 'BWP', 173 | 'BRL', 174 | 'GBP', 175 | 'BND', 176 | 'BGN', 177 | 'CAD', 178 | 'CLP', 179 | 'CNY', 180 | 'COP', 181 | 'CRC', 182 | 'HRK', 183 | 'CZK', 184 | 'DKK', 185 | 'DOP', 186 | 'XCD', 187 | 'EGP', 188 | 'EUR', 189 | 'FJD', 190 | 'GTQ', 191 | 'HKD', 192 | 'HNL', 193 | 'HUF', 194 | 'INR', 195 | 'IDR', 196 | 'ILS', 197 | 'JMD', 198 | 'JPY', 199 | 'KZT', 200 | 'KES', 201 | 'LAK', 202 | 'MMK', 203 | 'LBP', 204 | 'LRD', 205 | 'MOP', 206 | 'MYR', 207 | 'MVR', 208 | 'MRO', 209 | 'MUR', 210 | 'MXN', 211 | 'MAD', 212 | 'NPR', 213 | 'TWD', 214 | 'NZD', 215 | 'NIO', 216 | 'NOK', 217 | 'PKR', 218 | 'PGK', 219 | 'PEN', 220 | 'PHP', 221 | 'PLN', 222 | 'QAR', 223 | 'RON', 224 | 'RUB', 225 | 'WST', 226 | 'SAR', 227 | 'SCR', 228 | 'SGF', 229 | 'SBD', 230 | 'ZAR', 231 | 'KRW', 232 | 'LKR', 233 | 'SEK', 234 | 'CHF', 235 | 'SYP', 236 | 'THB', 237 | 'TOP', 238 | 'TTD', 239 | 'TRY', 240 | 'UAH', 241 | 'AED', 242 | 'USD', 243 | 'VUV', 244 | 'VND', 245 | 'XOF', 246 | 'YER' 247 | ]; 248 | 249 | if ( ! in_array( get_woocommerce_currency(), 250 | apply_filters( 'woocommerce_twocheckout_supported_currencies', $supported_currencies ) ) ) { 251 | return false; 252 | } 253 | 254 | return true; 255 | } 256 | 257 | /** 258 | * IPv6 is not supported by API 6.0 259 | * 260 | * @param $ip 261 | * 262 | * @return bool 263 | */ 264 | public function is_ipv6( $ip ) { 265 | return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ); 266 | } 267 | 268 | /** 269 | * @param int $order_id 270 | * @param null $amount 271 | * @param string $reason 272 | * 273 | * @return bool|\WP_Error 274 | * @throws \Exception 275 | */ 276 | public function process_refund( $order_id, $amount = null, $reason = '' ) { 277 | $order = wc_get_order( $order_id ); 278 | if ( $order->get_payment_method() == 'twocheckout' ) { 279 | $transaction_id = $order->get_meta( '__2co_order_number' ); 280 | 281 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 282 | $api = new Two_Checkout_Api(); 283 | $api->set_seller_id( $this->seller_id ); 284 | $api->set_secret_key( $this->secret_key ); 285 | $tco_order = $api->call( 'orders/' . $transaction_id . '/', [], 'GET' ); 286 | if ( ! $order || ! $tco_order || ! $transaction_id ) { 287 | $this->log( sprintf( 'Tried to refund order with order ID %s but it has no registered transaction ID, aborting.', $order_id ) ); 288 | 289 | return new WP_Error( '2co_refund_error', 'Refund Error: Unable to refund transaction' ); 290 | } 291 | 292 | if ( $amount != $tco_order['GrossPrice'] ) { 293 | $this->log( 'Only full refund is supported!' ); 294 | 295 | return new WP_Error( '2co_refund_error', 'Refund Error: Only full refund is supported.' ); 296 | } 297 | 298 | if ( strtolower( get_woocommerce_currency() ) != strtolower( $tco_order['Currency'] ) ) { 299 | $this->log( 'Order currency not matching the 2checkout response!' ); 300 | 301 | return new WP_Error( '2co_refund_error', 'Refund Error: Order currency not matching the 2checkout response.' ); 302 | } 303 | 304 | 305 | $params = [ 306 | "amount" => $amount, 307 | "comment" => $reason, 308 | "reason" => 'Other' 309 | ]; 310 | 311 | $response = $api->call( '/orders/' . $transaction_id . '/refund/', $params, 'POST' ); 312 | 313 | if ( isset( $response['error_code'] ) && ! empty( $response['error_code'] ) ) { 314 | $this->log( 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 315 | 316 | return new WP_Error( '2co_refund_error', 'Refund failed. Please login to your 2Checkout admin to issue the partial refund manually.' ); 317 | } 318 | 319 | $order->update_meta_data( '__2co_order_number', $response['id'] ); 320 | $order->add_order_note( __( sprintf( 'Refunded %s out of a total of %s from order', $amount, $order->get_total() ) ) ); 321 | $order->save_meta_data(); 322 | $order->save(); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Process the payment and return the result 330 | * 331 | * @access public 332 | * 333 | * @param int $order_id 334 | * 335 | * @return array 336 | */ 337 | public function process_payment( $order_id ) { 338 | $post_data = $_POST; 339 | 340 | $order = new WC_Order( $order_id ); 341 | $total = $order->get_total(); 342 | $customer_ip = $order->get_customer_ip_address(); 343 | 344 | $country_code = strtoupper( $order->get_billing_country() ); 345 | try { 346 | global $woocommerce; 347 | $woocommerce_version_formatted = str_replace( '.', '_', $woocommerce->version ); 348 | 349 | $order_params = [ 350 | 'Currency' => get_woocommerce_currency(), 351 | 'Language' => strtoupper( substr( get_locale(), 0, 2 ) ), 352 | 'Country' => $country_code, 353 | 'Source' => 'WOOCOMMERCE_' . $woocommerce_version_formatted, 354 | 'ExternalReference' => $order_id, 355 | 'Items' => $this->get_item( $total ), 356 | 'BillingDetails' => $this->get_billing_details( $post_data, $country_code ), 357 | 'PaymentDetails' => $this->get_payment_details( 358 | $post_data['ess_token'], 359 | $customer_ip, 360 | add_query_arg( 'wc-api', 'payment_response', home_url( '/' ) ) . "&pm={$order->get_payment_method()}" . "&order-ext-ref={$order->get_id()}", 361 | wc_get_cart_url() 362 | ) 363 | ]; 364 | 365 | if ( ! $this->is_ipv6( $customer_ip ) ) { 366 | $order_params['CustomerIP'] = $customer_ip; 367 | } 368 | 369 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 370 | $api = new Two_Checkout_Api(); 371 | $api->set_seller_id( $this->seller_id ); 372 | $api->set_secret_key( $this->secret_key ); 373 | $api_response = $api->call( 'orders', $order_params ); 374 | if ( ! $api_response || isset( $api_response['error_code'] ) && ! empty( $api_response['error_code'] ) ) { // we dont get any response from 2co or internal account related error 375 | if ( $api_response && isset( $api_response['message'] ) && ! empty( $api_response['message'] ) ) { 376 | $error_message = $api_response['message']; 377 | } else { 378 | $error_message = __( 'The payment could not be processed for order ' . $order_id . '! Please try again or contact us.' ); 379 | } 380 | wc_add_notice( __( 'Payment error:', 'woothemes' ) . $error_message, 'error' ); 381 | $json_response = [ 382 | 'result' => 'error', 383 | 'messages' => $error_message, 384 | 'refresh' => true, 385 | 'reload' => false, 386 | 'redirect' => null 387 | ]; 388 | } else { 389 | if ( $api_response['Errors'] ) { // errors that must be shown to the client 390 | $error_message = ''; 391 | foreach ( $api_response['Errors'] as $key => $value ) { 392 | $error_message .= $value . PHP_EOL; 393 | } 394 | wc_add_notice( __( 'Payment error: ' . $error_message, 'woothemes' ), 'error' ); 395 | $json_response = [ 396 | 'result' => 'error', 397 | 'messages' => $error_message, 398 | 'refresh' => true, 399 | 'reload' => false, 400 | 'redirect' => null 401 | ]; 402 | } else { 403 | $order->update_meta_data( '__2co_order_number', $api_response['RefNo'] ); 404 | $has3ds = false; 405 | if ( isset( $api_response['PaymentDetails']['PaymentMethod']['Authorize3DS'] ) ) { 406 | $has3ds = $this->has_authorize_3DS( $api_response['PaymentDetails']['PaymentMethod']['Authorize3DS'] ); 407 | } 408 | if ( $has3ds ) { 409 | $redirect_url = $has3ds; 410 | $json_response = [ 411 | 'result' => 'success', 412 | 'messages' => '3dSecure Redirect', 413 | 'type' => '3ds_redirect', 414 | 'refresh' => true, 415 | 'reload' => false, 416 | 'redirect' => $redirect_url, 417 | ]; 418 | } else { 419 | $json_response = [ 420 | 'result' => 'success', 421 | 'messages' => 'Order payment success', 422 | 'refresh' => true, 423 | 'reload' => false, 424 | 'redirect' => $this->get_return_url( $order ) 425 | ]; 426 | 427 | try { 428 | $ipn_helper = new Two_Checkout_Ipn_Helper_Api( [], $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 429 | $ipn_helper->processorder_status($api_response['Status'],$api_response['RefNo']); 430 | }catch(\Exception $ex){ 431 | self::log($ex->getMessage()); 432 | } 433 | } 434 | } 435 | } 436 | 437 | return $json_response; 438 | } catch ( Exception $e ) { 439 | wc_add_notice( $e->getMessage(), $notice_type = 'error' ); 440 | 441 | return false; 442 | } 443 | } 444 | 445 | 446 | /** 447 | * @param array $post_data 448 | * @param string $country_code 449 | * 450 | * @return array 451 | */ 452 | private function get_billing_details( array $post_data, string $country_code ) { 453 | $address = [ 454 | 'Address1' => $post_data['billing_address_1'], 455 | 'City' => $post_data['billing_city'], 456 | 'State' => $post_data['billing_state'], 457 | 'CountryCode' => $country_code, 458 | 'Email' => $post_data['billing_email'], 459 | 'FirstName' => $post_data['billing_first_name'], 460 | 'LastName' => $post_data['billing_last_name'], 461 | 'Phone' => $post_data['billing_phone'], 462 | 'Zip' => $post_data['billing_postcode'], 463 | 'Company' => $post_data['billing_company'] 464 | ]; 465 | 466 | if ( $post_data['billing_address_2'] ) { 467 | $address['Address2'] = $post_data['billing_address_2']; 468 | } 469 | 470 | return $address; 471 | } 472 | 473 | /** 474 | * for safety reasons we only send one Item with the grand total and the Cart_id as ProductName (identifier) 475 | * sending products order as ONE we dont have to calculate the total fee of the order (product price, tax, discounts etc) 476 | * 477 | * @param float $total 478 | * 479 | * @return array 480 | */ 481 | private function get_item( float $total ) { 482 | $items[] = [ 483 | 'Code' => null, 484 | 'Quantity' => 1, 485 | 'Name' => get_bloginfo(), 486 | 'Description' => 'N/A', 487 | 'RecurringOptions' => null, 488 | 'IsDynamic' => true, 489 | 'Tangible' => false, 490 | 'PurchaseType' => 'PRODUCT', 491 | 'Price' => [ 492 | 'Amount' => number_format( $total, 2, '.', '' ), 493 | 'Type' => 'CUSTOM' 494 | ] 495 | ]; 496 | 497 | return $items; 498 | } 499 | 500 | /** 501 | * @param string $token 502 | * @param string $customer_ip 503 | * @param string $success_url 504 | * @param string $cancel_url 505 | * 506 | * @return array 507 | */ 508 | private function get_payment_details( 509 | string $token, 510 | string $customer_ip, 511 | string $success_url, 512 | string $cancel_url 513 | ) { 514 | 515 | $payload = [ 516 | 'Type' => strtolower( $this->test_order ) === 'yes' ? 'TEST' : 'EES_TOKEN_PAYMENT', 517 | 'Currency' => get_woocommerce_currency(), 518 | 'PaymentMethod' => [ 519 | 'EesToken' => $token, 520 | 'Vendor3DSReturnURL' => $success_url, 521 | 'Vendor3DSCancelURL' => $cancel_url 522 | ], 523 | ]; 524 | 525 | if ( ! $this->is_ipv6( $customer_ip ) ) { 526 | $payload['CustomerIP'] = $customer_ip; 527 | } 528 | 529 | return $payload; 530 | } 531 | 532 | /** 533 | * @param mixed $has3ds 534 | * 535 | * @return string|null 536 | */ 537 | private function has_authorize_3DS( $has3ds ) { 538 | if ( isset( $has3ds ) && isset( $has3ds['Href'] ) && ! empty( $has3ds['Href'] ) ) { 539 | 540 | return $has3ds['Href'] . '?avng8apitoken=' . $has3ds['Params']['avng8apitoken']; 541 | } 542 | 543 | return null; 544 | } 545 | 546 | /** 547 | * @return void 548 | */ 549 | public function check_api_payment_response() { 550 | global $woocommerce; 551 | $params = $_GET; 552 | if ( isset( $params['pm'] ) && ! empty( $params['pm'] ) ) { 553 | if ( $params['pm'] == 'twocheckout' ) { 554 | if ( isset( $params['order-ext-ref'] ) && ! empty( $params['order-ext-ref'] ) ) { 555 | $order = wc_get_order( (int) $params['order-ext-ref'] ); 556 | if ( ! $order instanceof WC_Order ) { 557 | $this->log( 'There was a request for an order that doesn\'t exist in current shop! Requested params: ' . strip_tags( http_build_query( $params ) ) ); 558 | } else { 559 | if ( isset( $params['REFNO'] ) && ! empty( $params['REFNO'] ) ) { 560 | $refNo = $params['REFNO']; 561 | 562 | require_once plugin_dir_path( __FILE__ ) . 'src/Twocheckout/TwoCheckoutApi.php'; 563 | $api = new Two_Checkout_Api(); 564 | $api->set_seller_id( $this->seller_id ); 565 | $api->set_secret_key( $this->secret_key ); 566 | $api_response = $api->call( 'orders/' . $refNo . '/', [], 'GET' ); 567 | 568 | if ( ! empty( $api_response['Status'] ) && isset( $api_response['Status'] ) ) { 569 | if ( in_array( $api_response['Status'], [ 'AUTHRECEIVED', 'COMPLETE' ] ) ) { 570 | $redirect_url = $order->get_checkout_order_received_url(); 571 | if ( wp_redirect( $redirect_url ) ) { 572 | if ( $order->has_status( 'pending' ) ) { 573 | $order->update_meta_data( '__2co_order_number', $refNo ); 574 | $order->save_meta_data(); 575 | $order->save(); 576 | } 577 | $woocommerce->cart->empty_cart(); 578 | exit; 579 | } 580 | } 581 | } 582 | } 583 | } 584 | } 585 | status_header( 404 ); 586 | nocache_headers(); 587 | include( get_query_template( '404' ) ); 588 | die(); 589 | } 590 | } 591 | } 592 | 593 | 594 | /** 595 | * Validate & process 2Checkout request 596 | * 597 | * @access public 598 | * @return void 599 | */ 600 | public function check_ipn_response_api() { 601 | if ( $_SERVER['REQUEST_METHOD'] === 'GET' ) { 602 | return; 603 | } 604 | $params = $_POST; 605 | if(empty($params)) 606 | $params=json_decode(file_get_contents('php://input'),true); 607 | 608 | unset( $params['wc-api'] ); 609 | if ( isset( $params['REFNOEXT'] ) && ! empty( $params['REFNOEXT'] ) ) { 610 | $order = wc_get_order( $params['REFNOEXT'] ); 611 | 612 | if ( $order && $order->get_payment_method() == 'twocheckout' ) { 613 | try { 614 | $ipn_helper = new Two_Checkout_Ipn_Helper_Api( $params, $this->secret_key, $this->complete_order_on_payment, $this->debug, $order ); 615 | } catch ( Exception $ex ) { 616 | $this->log( 'Unable to find order with RefNo: ' . $params['REFNOEXT'] ); 617 | throw new Exception( 'An error occurred!' ); 618 | } 619 | 620 | if ( ! $ipn_helper->is_ipn_response_valid() ) { 621 | self::log( sprintf( 'SHA3 hash mismatch for 2Checkout IPN with date: "%s" . ', 622 | $params['IPN_DATE'] ) ); 623 | 624 | return; 625 | } 626 | $ipn_helper->process_ipn(); 627 | } 628 | } 629 | } 630 | 631 | /** 632 | * Render additional order page fields 633 | * 634 | * @access public 635 | * @return void 636 | */ 637 | public function render_additional_order_page_fields( $order = null ) { 638 | if ( ! isset( $order ) || empty( $order ) ) { 639 | $order = wc_get_order( absint( get_query_var( 'order-pay' ) ) ); 640 | } 641 | 642 | if ( isset( $order ) ) { 643 | echo ''; 644 | echo ''; 645 | echo ''; 646 | echo ''; 647 | echo ''; 648 | echo ''; 649 | echo ''; 650 | echo ''; 651 | echo ''; 652 | echo ''; 653 | } 654 | } 655 | } 656 | 657 | /** 658 | * @param $methods 659 | * 660 | * @return array 661 | */ 662 | function add_twocheckout_gateway_api( $methods ) { 663 | $methods[] = 'WC_Gateway_Twocheckout'; 664 | 665 | return $methods; 666 | } 667 | 668 | add_filter( 'woocommerce_payment_gateways', 'add_twocheckout_gateway_api' ); 669 | } 670 | 671 | add_action( 'before_woocommerce_init', function() { 672 | if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { 673 | \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __DIR__.'/'.__FILE__, true ); 674 | } 675 | } ); 676 | --------------------------------------------------------------------------------