├── .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 |
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('- ' + response.messages + '
');
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 |
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 |
25 |
26 |
--------------------------------------------------------------------------------
/twocheckout-inline/templates/payment-inline-fields.php:
--------------------------------------------------------------------------------
1 | description) {
3 | echo '' . $this->description . '
';
4 | }
5 | ?>
6 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------