GetPaid_Worldpay_Gateway::verify_ipn()   B
last analyzed

Complexity

Conditions 9
Paths 26

Size

Total Lines 45
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 23
nc 26
nop 0
dl 0
loc 45
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * Worldpay payment gateway
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * Worldpay Payment Gateway class.
11
 *
12
 */
13
class GetPaid_Worldpay_Gateway extends GetPaid_Payment_Gateway {
14
15
    /**
16
	 * Payment method id.
17
	 *
18
	 * @var string
19
	 */
20
    public $id = 'worldpay';
21
22
    /**
23
	 * Payment method order.
24
	 *
25
	 * @var int
26
	 */
27
    public $order = 5;
28
29
    /**
30
	 * Endpoint for requests from Worldpay.
31
	 *
32
	 * @var string
33
	 */
34
	protected $notify_url;
35
36
	/**
37
	 * Endpoint for requests to Worldpay.
38
	 *
39
	 * @var string
40
	 */
41
    protected $endpoint;
42
43
    /**
44
	 * An array of features that this gateway supports.
45
	 *
46
	 * @var array
47
	 */
48
    protected $supports = array( 'sandbox' );
49
50
    /**
51
	 * Currencies this gateway is allowed for.
52
	 *
53
	 * @var array
54
	 */
55
	public $currencies = array( 'AUD', 'ARS', 'CAD', 'CHF', 'DKK', 'EUR', 'HKD', 'MYR', 'GBP', 'NZD', 'NOK', 'SGD', 'LKR', 'SEK', 'TRY', 'USD', 'ZAR' );
56
57
    /**
58
	 * URL to view a transaction.
59
	 *
60
	 * @var string
61
	 */
62
    public $view_transaction_url = 'https://www.{sandbox}paypal.com/activity/payment/%s';
63
64
    /**
65
	 * URL to view a subscription.
66
	 *
67
	 * @var string
68
	 */
69
	public $view_subscription_url = 'https://www.{sandbox}paypal.com/cgi-bin/webscr?cmd=_profile-recurring-payments&encrypted_profile_id=%s';
70
71
    /**
72
	 * Class constructor.
73
	 */
74
	public function __construct() {
75
76
        $this->method_title         = __( 'Worldpay', 'invoicing' );
77
        $this->title                = __( 'Worldpay - Credit Card / Debit Card', 'invoicing' );
78
        $this->checkout_button_text = __( 'Proceed to Worldpay', 'invoicing' );
79
        $this->notify_url           = wpinv_get_ipn_url( $this->id );
80
81
        add_filter( 'wpinv_gateway_description', array( $this, 'sandbox_notice' ), 10, 2 );
82
        add_filter( 'getpaid_worldpay_args', array( $this, 'hash_args' ) );
83
84
        parent::__construct();
85
    }
86
87
    /**
88
	 * Process Payment.
89
	 *
90
	 *
91
	 * @param WPInv_Invoice $invoice Invoice.
92
	 * @param array $submission_data Posted checkout fields.
93
	 * @param GetPaid_Payment_Form_Submission $submission Checkout submission.
94
	 * @return array
95
	 */
96
	public function process_payment( $invoice, $submission_data, $submission ) {
97
98
        // Get redirect url.
99
        $worldpay_redirect = esc_url( $this->get_request_url( $invoice ) );
100
101
        // Get submission args.
102
        $worldpay_args     = $this->get_worldpay_args( $invoice );
103
104
        $form = "<form action='$worldpay_redirect' name='wpi_worldpay_form' method='POST'>";
105
106
        foreach ( $worldpay_args as $key => $value ) {
107
108
            if ( false === $value || '' === trim( $value ) ) {
109
                continue;
110
            }
111
112
            $value = esc_attr( $value );
113
            $key   = wpinv_clean( $key );
114
            $form .= "<input type='hidden' name='$key' value='$value'>";
115
        }
116
117
        $form .= '</form>';
118
119
        wp_send_json_success(
120
            array(
121
                'action' => 'auto_submit_form',
122
                'form'   => $form,
123
            )
124
        );
125
126
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
127
128
    }
129
130
    /**
131
	 * Get the Worldpay request URL for an invoice.
132
	 *
133
	 * @param  WPInv_Invoice $invoice Invoice object.
134
	 * @return string
135
	 */
136
	public function get_request_url( $invoice ) {
137
138
        // Endpoint for this request
139
		$this->endpoint = $this->is_sandbox( $invoice ) ? 'https://secure-test.worldpay.com/wcc/purchase' : 'https://secure.worldpay.com/wcc/purchase';
140
141
        return $this->endpoint;
142
143
	}
144
145
    /**
146
	 * Get Worldpay Args for passing to Worldpay.
147
	 *
148
	 * @param  WPInv_Invoice $invoice Invoice object.
149
	 * @return array
150
	 */
151
	protected function get_worldpay_args( $invoice ) {
152
153
		return apply_filters(
154
			'getpaid_worldpay_args',
155
			array(
156
                'amount'         => wpinv_sanitize_amount( $invoice->get_total() ), // mandatory
157
                'cartId'         => wpinv_clean( $invoice->get_number() ), // mandatory reference for the item purchased
158
                'currency'       => wpinv_clean( $invoice->get_currency() ), // mandatory
159
                'instId'         => wpinv_clean( $this->get_option( 'instId', '' ) ), // mandatory
160
                'testMode'       => $this->is_sandbox( $invoice ) ? 100 : 0, // mandatory
161
                'name'           => wpinv_clean( $invoice->get_full_name() ),
162
                'address'        => wpinv_clean( $invoice->get_address() ),
163
                'postcode'       => wpinv_clean( $invoice->get_zip() ),
164
                'tel'            => wpinv_clean( $invoice->get_phone() ),
165
                'email'          => sanitize_email( $invoice->get_email() ),
166
                'country'        => wpinv_clean( $invoice->get_country() ),
167
                'desc'           => sprintf( __( 'Payment for invoice %s.', 'invoicing' ), wpinv_clean( $invoice->get_number() ) ),
168
                'MC_description' => sprintf( __( 'Payment for invoice %s.', 'invoicing' ), wpinv_clean( $invoice->get_number() ) ),
169
                'MC_callback'    => esc_url_raw( $this->notify_url ),
170
                'resultfile'     => esc_url_raw( $this->get_return_url( $invoice ) ),
171
                'MC_key'         => wpinv_clean( $invoice->get_key() ),
172
                'MC_invoice_id'  => $invoice->get_id(),
173
                'address1'       => wpinv_clean( $invoice->get_address() ),
174
                'town'           => wpinv_clean( $invoice->get_city() ),
175
                'region'         => wpinv_clean( $invoice->get_state() ),
176
                'amountString'   => wpinv_price( $invoice->get_total(), $invoice->get_currency() ),
177
                'countryString'  => wpinv_clean( wpinv_country_name( $invoice->get_country() ) ),
178
                'compName'       => wpinv_clean( $invoice->get_company() ),
179
            ),
180
			$invoice
181
		);
182
183
    }
184
185
    /**
186
	 * Secures worldpay args with an md5 hash.
187
	 *
188
	 * @param  array $args Gateway args.
189
	 * @return array
190
	 */
191
	public function hash_args( $args ) {
192
193
        $md5_secret = $this->get_option( 'md5_secret' );
194
195
        // Abort if there is no secret.
196
        if ( empty( $md5_secret ) ) {
197
            return $args;
198
        }
199
200
        // Hash the args.
201
        $args['signature'] = md5( "$md5_secret:{$args['instId']}:{$args['amount']}:{$args['currency']}:{$args['cartId']}" );
202
203
        return $args;
204
    }
205
206
    /**
207
	 * Processes ipns and marks payments as complete.
208
	 *
209
	 * @return void
210
	 */
211
	public function verify_ipn() {
212
213
        // Validate the IPN.
214
        if ( empty( $_POST ) || ! $this->validate_ipn() ) {
215
		    wp_die( 'Worldpay IPN Request Failure', 'Worldpay IPN', array( 'response' => 500 ) );
216
		}
217
218
        // Process the IPN.
219
        $posted  = wp_kses_post_deep( wp_unslash( $_POST ) );
220
        $invoice = wpinv_get_invoice( $posted['MC_invoice_id'] );
221
222
        if ( $invoice && $this->id == $invoice->get_gateway() ) {
223
224
            wpinv_error_log( 'Found invoice #' . $invoice->get_number() );
225
            wpinv_error_log( 'Payment status:' . $posted['transStatus'] );
226
227
            // Update the transaction id.
228
            if ( ! empty( $posted['transId'] ) ) {
229
                $invoice->set_transaction_id( wpinv_clean( $posted['transId'] ) );
230
            }
231
232
             // Update the ip address.
233
             if ( ! empty( $posted['ipAddress'] ) ) {
234
                $invoice->set_ip( wpinv_clean( $posted['ipAddress'] ) );
235
            }
236
237
            if ( $posted['transStatus'] == 'Y' ) {
238
                $invoice->set_completed_date( date( 'Y-m-d H:i:s', $posted['transTime'] ) );
239
                $invoice->mark_paid();
240
                return;
241
            }
242
243
            if ( $posted['transStatus'] == 'C' ) {
244
                $invoice->set_status( 'wpi-failed' );
245
                $invoice->add_note( __( 'Payment transaction failed while processing Worldpay payment.', 'invoicing' ), false, false, true );
246
                $invoice->save();
247
                return;
248
            }
249
250
            wpinv_error_log( 'Aborting, Invalid transaction status:' . $posted['transStatus'] );
251
            $invoice->save();
252
253
        }
254
255
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
256
257
    }
258
259
    /**
260
	 * Check Worldpay IPN validity.
261
	 */
262
	public function validate_ipn() {
263
264
        wpinv_error_log( 'Validating Worldpay IPN response' );
265
266
        $data = wp_kses_post_deep( wp_unslash( $_POST ) );
267
268
        // Verify installation.
269
        if ( empty( $data['instId'] ) || $data['instId'] != wpinv_clean( $this->get_option( 'instId', '' ) ) ) {
270
            wpinv_error_log( 'Received invalid installation ID from Worldpay IPN' );
271
            return false;
272
        }
273
274
        // Verify invoice.
275
        if ( empty( $data['cartId'] ) || ! wpinv_get_id_by_invoice_number( $data['cartId'] ) ) {
276
            wpinv_error_log( 'Received invalid invoice number from Worldpay IPN' );
277
            return false;
278
        }
279
280
        // (maybe) verify password.
281
        $password = $this->get_option( 'callback_password' );
282
283
        if ( ! empty( $password ) && ( empty( $data['callbackPW'] ) || $password != $data['callbackPW'] ) ) {
284
            wpinv_error_log( 'Received invalid invoice number from Worldpay IPN' );
285
            return false;
286
        }
287
288
        return true;
289
290
    }
291
292
    /**
293
     * Displays a notice on the checkout page if sandbox is enabled.
294
     */
295
    public function sandbox_notice( $description, $gateway ) {
296
        if ( 'worldpay' == $gateway && wpinv_is_test_mode( 'worldpay' ) ) {
297
            $description .= '<br>' . sprintf(
298
                __( 'SANDBOX ENABLED. See the %1$sWorldpay Sandbox Testing Guide%2$s for more details.', 'invoicing' ),
299
                '<a href="https://developer.worldpay.com/docs/wpg/directintegration/abouttesting">',
300
                '</a>'
301
            );
302
        }
303
        return $description;
304
305
    }
306
307
    /**
308
	 * Filters the gateway settings.
309
	 *
310
	 * @param array $admin_settings
311
	 */
312
	public function admin_settings( $admin_settings ) {
313
314
        $currencies = sprintf(
315
            __( 'Supported Currencies: %s', 'invoicing' ),
316
            implode( ', ', $this->currencies )
317
        );
318
319
        $admin_settings['worldpay_active']['desc'] = $admin_settings['worldpay_active']['desc'] . " ($currencies)";
320
        $admin_settings['worldpay_desc']['std']    = __( 'Pay securely via Worldpay using your PayPal account, credit or debit card.', 'invoicing' );
321
322
        $admin_settings['worldpay_instId'] = array(
323
            'type' => 'text',
324
            'id'   => 'worldpay_instId',
325
            'name' => __( 'Installation Id', 'invoicing' ),
326
            'desc' => __( 'Your installation id. Ex: 211616', 'invoicing' ),
327
        );
328
329
        $admin_settings['worldpay_md5_secret'] = array(
330
            'type' => 'text',
331
            'id'   => 'worldpay_md5_secret',
332
            'name' => __( 'MD5 secret', 'invoicing' ),
333
            'desc' => __( 'Optionally enter your MD5 secret here. Next, open your installation settings and ensure that your SignatureFields parameter is set to ', 'invoicing' ) . '<code>instId:amount:currency:cartId</code>',
334
        );
335
336
        $admin_settings['worldpay_callbackPW'] = array(
337
            'type' => 'text',
338
            'id'   => 'worldpay_callbackPW',
339
            'name' => __( 'Payment Response password', 'invoicing' ),
340
            'desc' => __( 'Recommended. Enter your WorldPay response password to validate payment notifications.', 'invoicing' ),
341
        );
342
343
        $admin_settings['worldpay_ipn_url'] = array(
344
            'type'     => 'ipn_url',
345
            'id'       => 'worldpay_ipn_url',
346
            'name'     => __( 'Payment Response URL', 'invoicing' ),
347
            'std'      => $this->notify_url,
348
            'desc'     => __( 'Login to your Worldpay Merchant Interface then enable Payment Response & Shopper Response. Next, go to the Payment Response URL field and enter the above URL.', 'invoicing' ),
349
            'custom'   => 'worldpay',
350
            'readonly' => true,
351
        );
352
353
		return $admin_settings;
354
	}
355
356
}
357