Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/paid-memberships-pro/services/braintree-webhook.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
/**
3
+
* The Braintree webhook handler
4
+
*
5
+
* @since 1.9.5 - Various updates to how we log & process requests from Braintree
6
+
*/
7
+
8
+
use Braintree\WebhookNotification as Braintree_WebhookNotification;
9
+
10
+
//in case the file is loaded directly
11
+
if( ! defined( 'ABSPATH' ) ) {
12
+
exit;
13
+
}
14
+
15
+
//globals
16
+
global $wpdb;
17
+
18
+
// Sets the PMPRO_DOING_WEBHOOK constant and fires the pmpro_doing_webhook action.
19
+
pmpro_doing_webhook( 'braintree', true );
20
+
21
+
// Debug log
22
+
global $logstr;
23
+
$logstr = array( "Logged On: " . date_i18n( "m/d/Y H:i:s", current_time( 'timestamp' ) ) );
24
+
$logstr[] = "\nREQUEST:";
25
+
$logstr[] = var_export( $_REQUEST, true );
26
+
$logstr[] = "\n";
27
+
28
+
// Don't run this with wrong PHP version
29
+
if ( version_compare( PHP_VERSION, '5.4.45', '<' ) ) {
30
+
return;
31
+
}
32
+
33
+
//load Braintree library, gateway class constructor does config
34
+
if ( ! class_exists( '\Braintree' ) ) {
35
+
require_once( PMPRO_DIR . "/classes/gateways/class.pmprogateway_braintree.php" );
36
+
}
37
+
38
+
$gateway = new PMProGateway_braintree();
39
+
$webhookNotification = null;
40
+
41
+
if ( empty( $_REQUEST['bt_payload'] ) ) {
42
+
$logstr[] = "No payload in request?!? " . print_r( $_REQUEST, true );
43
+
pmpro_braintreeWebhookExit();
44
+
}
45
+
46
+
if ( isset( $_POST['bt_signature'] ) && ! isset( $_POST['bt_payload'] ) ) {
47
+
$logstr[] = "No payload and signature included in the request!";
48
+
pmpro_braintreeWebhookExit();
49
+
}
50
+
51
+
//get notification
52
+
try {
53
+
/**
54
+
* @since 1.9.5 - BUG FIX: Unable to identify Braintree Webhook messages
55
+
* Expecting Braintree library to sanitize signature & payload
56
+
* since using sanitize_text_field() breaks Braintree parser
57
+
*
58
+
* NOTE: The Braintree API needs the unsanitized input.
59
+
*/
60
+
$webhookNotification = Braintree_WebhookNotification::parse( $_POST['bt_signature'], $_POST['bt_payload'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
61
+
62
+
$logstr[] = "\webhookNotification:";
63
+
$logstr[] = var_export( $webhookNotification, true );
64
+
$logstr[] = "\n";
65
+
} catch ( Exception $e ) {
66
+
$logstr[] = "Couldn't extract notification from payload: {$_REQUEST['bt_payload']}";
67
+
$logstr[] = "Error message: " . $e->getMessage();
68
+
69
+
pmpro_braintreeWebhookExit();
70
+
}
71
+
72
+
/**
73
+
* @since 1.9.5 - ENHANCEMENT: Log if notification object has unexpected format
74
+
*/
75
+
if ( ! isset( $webhookNotification->kind ) ) {
76
+
$logstr[] = "Unexpected webhook message: " . print_r( $webhookNotification, true ) . "\n";
77
+
pmpro_braintreeWebhookExit();
78
+
}
79
+
80
+
/**
81
+
* Only verifying?
82
+
* @since 1.9.5 - Log Webhook tests with webhook supplied timestamp (verifies there's no caching).
83
+
*/
84
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::CHECK ) {
85
+
$when = $webhookNotification->timestamp->format( 'Y-m-d H:i:s.u' );
86
+
87
+
$logstr[] = "Since you are just testing the URL, check that the timestamp updates on refresh to make sure this isn't being cached.";
88
+
$logstr[] = "Braintree gateway timestamp: {$when}";
89
+
pmpro_braintreeWebhookExit();
90
+
}
91
+
92
+
//subscription charged successfully
93
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::SUBSCRIPTION_CHARGED_SUCCESSFULLY ) {
94
+
$logstr[] = "The Braintree gateway received payment for a recurring billing plan";
95
+
96
+
//need a subscription id
97
+
if ( empty( $webhookNotification->subscription->id ) ) {
98
+
$logstr[] = "No subscription ID.";
99
+
pmpro_braintreeWebhookExit();
100
+
}
101
+
102
+
//figure out which order to attach to
103
+
$old_order = new \MemberOrder();
104
+
$old_order->getLastMemberOrderBySubscriptionTransactionID( $webhookNotification->subscription->id );
105
+
106
+
//no order?
107
+
if ( empty( $old_order->id ) ) {
108
+
$logstr[] = "Couldn't find the original subscription with ID={$webhookNotification->subscription->id}.";
109
+
pmpro_braintreeWebhookExit();
110
+
}
111
+
112
+
//create new order
113
+
$user_id = $old_order->user_id;
114
+
$user = get_userdata( $user_id );
115
+
116
+
if ( empty( $user ) ) {
117
+
$logstr[] = "Couldn't find the old order's user. Order ID = {$old_order->id}.";
118
+
pmpro_braintreeWebhookExit();
119
+
} else {
120
+
$user->membership_level = pmpro_getMembershipLevelForUser( $user_id );
121
+
}
122
+
123
+
//data about this transaction
124
+
$transaction = $webhookNotification->subscription->transactions[0];
125
+
126
+
//log it for debug email
127
+
$logstr[] = var_export( $transaction, true );
128
+
129
+
//alright. create a new order
130
+
$morder = new \MemberOrder();
131
+
$morder->user_id = $old_order->user_id;
132
+
$morder->membership_id = $old_order->membership_id;
133
+
$morder->subtotal = $transaction->amount;
134
+
$morder->total = $transaction->amount;
135
+
$morder->payment_transaction_id = $transaction->id;
136
+
$morder->subscription_transaction_id = $webhookNotification->subscription->id;
137
+
138
+
//Assume no tax for now. Add ons will handle it later.
139
+
$morder->tax = 0;
140
+
141
+
$morder->gateway = $old_order->gateway;
142
+
$morder->gateway_environment = $old_order->gateway_environment;
143
+
144
+
$morder->billing = new stdClass();
145
+
146
+
if (! empty( $transaction->billing_details) ) {
147
+
$morder->billing->name = trim( $transaction->billing_details->first_name . " " . $transaction->billing_details->last_name );
148
+
$morder->billing->street = $transaction->billing_details->street_address;
149
+
$morder->billing->city = $transaction->billing_details->locality;
150
+
$morder->billing->state = $transaction->billing_details->region;
151
+
$morder->billing->zip = $transaction->billing_details->postal_code;
152
+
$morder->billing->country = $transaction->billing_details->country_code_alpha2;
153
+
} else {
154
+
$morder->billing->name = $old_order->billing->name;
155
+
$morder->billing->street = $old_order->billing->street;
156
+
$morder->billing->street2 = $old_order->billing->street2;
157
+
$morder->billing->city = $old_order->billing->city;
158
+
$morder->billing->state = $old_order->billing->state;
159
+
$morder->billing->zip = $old_order->billing->zip;
160
+
$morder->billing->country = $old_order->billing->country;
161
+
}
162
+
163
+
$morder->billing->phone = $old_order->billing->phone;
164
+
165
+
//Updates this order with the most recent orders payment method information and saves it.
166
+
pmpro_update_order_with_recent_payment_method( $morder );
167
+
168
+
//save
169
+
$morder->status = "success";
170
+
$morder->saveOrder();
171
+
$morder->getMemberOrderByID( $morder->id );
172
+
173
+
//email the user their order
174
+
$pmproemail = new \PMProEmail();
175
+
$pmproemail->sendInvoiceEmail( $user, $morder );
176
+
177
+
do_action( 'pmpro_subscription_payment_completed', $morder );
178
+
179
+
$logstr[] = "Triggered pmpro_subscription_payment_completed actions and returned";
180
+
181
+
/**
182
+
* @since 1.9.5 - Didn't terminate & save debug loggins for Webhook
183
+
*/
184
+
pmpro_braintreeWebhookExit();
185
+
}
186
+
187
+
/*
188
+
Note here: These next three checks all work the same way and send the same
189
+
"billing failed" email, but kick off different actions based on the kind.
190
+
*/
191
+
192
+
//subscription charged unsuccessfully
193
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::SUBSCRIPTION_CHARGED_UNSUCCESSFULLY ) {
194
+
$logstr[] = "The Braintree gateway let us know there's a problem with the payment";
195
+
196
+
//need a subscription id
197
+
if ( empty( $webhookNotification->subscription->id ) ) {
198
+
$logstr[] = "No subscription ID.";
199
+
pmpro_braintreeWebhookExit();
200
+
}
201
+
202
+
//figure out which order to attach to
203
+
$old_order = new \MemberOrder();
204
+
$old_order->getLastMemberOrderBySubscriptionTransactionID( $webhookNotification->subscription->id );
205
+
206
+
if ( empty( $old_order ) ) {
207
+
$logstr[] = "Couldn't find old order for failed payment with subscription id={$webhookNotification->subscription->id}";
208
+
pmpro_braintreeWebhookExit();
209
+
}
210
+
211
+
$user_id = $old_order->user_id;
212
+
$user = get_userdata( $user_id );
213
+
$user->membership_level = pmpro_getMembershipLevelForUser( $user_id );
214
+
215
+
//generate billing failure email
216
+
do_action( "pmpro_subscription_payment_failed", $old_order );
217
+
218
+
$transaction = isset( $webhookNotification->transactions ) && is_array( $webhookNotification->transactions ) ?
219
+
$webhookNotification->transactions[0] :
220
+
null;
221
+
222
+
//prep this order for the failure emails
223
+
$morder = new \MemberOrder();
224
+
$morder->user_id = $user_id;
225
+
$morder->membership_id = $old_order->membership_id;
226
+
227
+
$morder->billing = new stdClass();
228
+
$morder->billing->name = isset( $transaction->billing_details->first_name ) && isset( $transaction->billing_details->last_name ) ?
229
+
trim( $transaction->billing_details->first_name . " " . $transaction->billing_details->first_name ) :
230
+
$old_order->billing->name;
231
+
232
+
$morder->billing->street = isset( $transaction->billing_details->street_address ) ?
233
+
$transaction->billing_details->street_address :
234
+
$old_order->billing->street;
235
+
236
+
$morder->billing->city = isset( $transaction->billing_details->locality ) ?
237
+
$transaction->billing_details->locality :
238
+
$old_order->billing->city;
239
+
240
+
$morder->billing->state = isset( $transaction->billing_details->region ) ?
241
+
$transaction->billing_details->region :
242
+
$old_order->billing->state;
243
+
244
+
$morder->billing->zip = isset( $transaction->billing_details->postal_code ) ?
245
+
$transaction->billing_details->postal_code :
246
+
$old_order->billing->zip;
247
+
248
+
$morder->billing->country = isset( $transaction->billing_details->country_code_alpha2 ) ?
249
+
$transaction->billing_details->country_code_alpha2 :
250
+
$old_order->billing->country;
251
+
252
+
$morder->billing->phone = $old_order->billing->phone;
253
+
254
+
//Updates this order with the most recent orders payment method information and saves it.
255
+
pmpro_update_order_with_recent_payment_method( $morder );
256
+
257
+
// Email the user and ask them to update their credit card information
258
+
$pmproemail = new \PMProEmail();
259
+
$pmproemail->sendBillingFailureEmail( $user, $morder );
260
+
261
+
// Email admin so they are aware of the failure
262
+
$pmproemail = new \PMProEmail();
263
+
$pmproemail->sendBillingFailureAdminEmail( get_bloginfo( "admin_email" ), $morder );
264
+
265
+
$logstr[] = "Sent email to the member and site admin. Thanks.";
266
+
pmpro_braintreeWebhookExit();
267
+
}
268
+
269
+
//subscription went past due
270
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::SUBSCRIPTION_WENT_PAST_DUE ) {
271
+
272
+
$logstr[] = "The Braintree gateway informed us the subscription payment is past due";
273
+
274
+
//need a subscription id
275
+
if ( empty( $webhookNotification->subscription->id ) ) {
276
+
$logstr[] = "No subscription ID.";
277
+
pmpro_braintreeWebhookExit();
278
+
}
279
+
280
+
//figure out which order to attach to
281
+
$old_order = new \MemberOrder();
282
+
$old_order->getLastMemberOrderBySubscriptionTransactionID( $webhookNotification->subscription->id );
283
+
284
+
if ( empty( $old_order ) ) {
285
+
$logstr[] = "Couldn't find old order for failed payment with subscription id=" . $webhookNotification->subscription->id;
286
+
pmpro_braintreeWebhookExit();
287
+
}
288
+
289
+
$user_id = $old_order->user_id;
290
+
$user = get_userdata( $user_id );
291
+
$user->membership_level = pmpro_getMembershipLevelForUser( $user_id );
292
+
293
+
//generate billing failure email
294
+
do_action( "pmpro_subscription_payment_failed", $old_order );
295
+
do_action( "pmpro_subscription_payment_went_past_due", $old_order );
296
+
297
+
$transaction = isset( $webhookNotification->transactions ) && is_array( $webhookNotification->transactions ) ?
298
+
$webhookNotification->transactions[0] :
299
+
null;
300
+
301
+
//prep this order for the failure emails
302
+
$morder = new \MemberOrder();
303
+
$morder->user_id = $user_id;
304
+
$morder->membership_id = $old_order->membership_id;
305
+
306
+
$morder->billing->name = isset( $transaction->billing_details->first_name ) && isset( $transaction->billing_details->last_name ) ?
307
+
trim( $transaction->billing_details->first_name . " " . $transaction->billing_details->first_name ) :
308
+
$old_order->billing->name;
309
+
310
+
$morder->billing->street = isset( $transaction->billing_details->street_address ) ?
311
+
$transaction->billing_details->street_address :
312
+
$old_order->billing->street;
313
+
314
+
$morder->billing->city = isset( $transaction->billing_details->locality ) ?
315
+
$transaction->billing_details->locality :
316
+
$old_order->billing->city;
317
+
318
+
$morder->billing->state = isset( $transaction->billing_details->region ) ?
319
+
$transaction->billing_details->region :
320
+
$old_order->billing->state;
321
+
322
+
$morder->billing->zip = isset( $transaction->billing_details->postal_code ) ?
323
+
$transaction->billing_details->postal_code :
324
+
$old_order->billing->zip;
325
+
326
+
$morder->billing->country = isset( $transaction->billing_details->country_code_alpha2 ) ?
327
+
$transaction->billing_details->country_code_alpha2 :
328
+
$old_order->billing->country;
329
+
330
+
$morder->billing->phone = $old_order->billing->phone;
331
+
332
+
//Updates this order with the most recent orders payment method information and saves it.
333
+
pmpro_update_order_with_recent_payment_method( $morder );
334
+
335
+
// Email the user and ask them to update their credit card information
336
+
$pmproemail = new \PMProEmail();
337
+
$pmproemail->sendBillingFailureEmail( $user, $morder );
338
+
339
+
// Email admin so they are aware of the failure
340
+
$pmproemail = new \PMProEmail();
341
+
$pmproemail->sendBillingFailureAdminEmail( get_bloginfo( "admin_email" ), $morder );
342
+
343
+
$logstr[] = "Sent email to the member and site admin. Thanks.";
344
+
pmpro_braintreeWebhookExit();
345
+
}
346
+
347
+
//subscription expired
348
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::SUBSCRIPTION_EXPIRED ) {
349
+
$logstr[] = "The Braintree gateway informed us the recurring payment plan has completed its required number of payments";
350
+
351
+
//need a subscription id
352
+
if ( empty( $webhookNotification->subscription->id ) ) {
353
+
$logstr[] = "No subscription ID.";
354
+
pmpro_braintreeWebhookExit();
355
+
}
356
+
357
+
//figure out which order to attach to
358
+
$old_order = new \MemberOrder();
359
+
$old_order->getLastMemberOrderBySubscriptionTransactionID( $webhookNotification->subscription->id );
360
+
361
+
if ( empty( $old_order ) ) {
362
+
$logstr[] = "Couldn't find old order for failed payment with subscription id=" . $webhookNotification->subscription->id;
363
+
pmpro_braintreeWebhookExit();
364
+
}
365
+
366
+
$user_id = $old_order->user_id;
367
+
$user = get_userdata( $user_id );
368
+
$user->membership_level = pmpro_getMembershipLevelForUser( $user_id );
369
+
370
+
//generate billing failure email
371
+
do_action( "pmpro_subscription_expired", $old_order );
372
+
373
+
$transaction = isset( $webhookNotification->transactions ) && is_array( $webhookNotification->transactions ) ?
374
+
$webhookNotification->transactions[0] :
375
+
null;
376
+
377
+
// We don't currently allow billing limits (number_of_billing_cycles) on Braintree subscriptions.
378
+
// But in case we get here, let's send the correct email to the admin.
379
+
$myemail = new PMProEmail();
380
+
$body = sprintf( __( "<p>A member's Braintree subscription has expired at your site. This typically happens if you've set up billing limits on your levels.</p><p>We have not removed the user's membership level.</p><p>You can view details on this user here: %s</p>", 'paid-memberships-pro' ), esc_url( admin_url( 'user-edit.php?user_id=' . $user_id ) ) );
381
+
$myemail->template = 'braintree_subscription_expired';
382
+
$myemail->subject = sprintf( __( "A member's Braintree subscription has expired at %s", 'paid-memberships-pro' ), get_bloginfo( 'name' ) );
383
+
$myemail->data = array( 'body' => $body );
384
+
$myemail->sendEmail( get_bloginfo( 'admin_email' ) );
385
+
386
+
$logstr[] = "Sent email to the site admin. Thanks.";
387
+
pmpro_braintreeWebhookExit();
388
+
}
389
+
390
+
//subscription cancelled (they used one l canceled)
391
+
if ( $webhookNotification->kind === Braintree_WebhookNotification::SUBSCRIPTION_CANCELED ) {
392
+
$logstr[] = 'The Braintree gateway cancelled the subscription plan';
393
+
394
+
// Need a subscription id.
395
+
if ( empty( $webhookNotification->subscription->id ) ) {
396
+
$logstr[] = 'No subscription ID.';
397
+
pmpro_braintreeWebhookExit();
398
+
}
399
+
$logstr[] = pmpro_handle_subscription_cancellation_at_gateway( $webhookNotification->subscription->id, 'braintree', get_option( 'pmpro_gateway_environment' ) );
400
+
pmpro_braintreeWebhookExit();
401
+
}
402
+
403
+
pmpro_unhandled_webhook();
404
+
405
+
/**
406
+
* @since 1.9.5 - BUG FIX: Didn't terminate & save debug log for webhook event
407
+
*/
408
+
pmpro_braintreeWebhookExit();
409
+
410
+
/**
411
+
* Fix address info for order/transaction
412
+
*
413
+
* @deprecated 3.2
414
+
*
415
+
* @param int $user_id
416
+
* @param \MemberOrder $old_order
417
+
*
418
+
* @return \stdClass
419
+
*/
420
+
function pmpro_braintreeAddressInfo( $user_id, $old_order ) {
421
+
_deprecated_function( __FUNCTION__, '3.2' );
422
+
423
+
// Grab billing info from the saved metadata as needed
424
+
425
+
if ( ! isset( $old_order->billing ) ) {
426
+
$old_order->billing = new \stdClass();
427
+
}
428
+
429
+
if ( empty ( $old_order->billing->name ) ) {
430
+
$first_name = get_user_meta( $user_id, 'pmpro_bfirstname', true );
431
+
$last_name = get_user_meta( $user_id, 'pmpro_blastname', true );
432
+
433
+
if ( ! empty( $first_name ) && ! empty( $last_name ) ) {
434
+
$old_order->billing->name = trim( "{$first_name} {$last_name}" );
435
+
}
436
+
}
437
+
438
+
if ( empty( $old_order->billing->street ) ) {
439
+
$address1 = get_user_meta( $user_id, 'pmpro_baddress', true );
440
+
$address2 = get_user_meta( $user_id, 'pmpro_baddress2', true );
441
+
$old_order->billing->street = ! empty( $address1 ) ? trim( $address1 ) : '';
442
+
$old_order->billing->street .= ! empty( $address2 ) ? "\n" . trim( $address2 ) : '';
443
+
}
444
+
445
+
if ( empty( $old_order->billing->city ) ) {
446
+
$city = get_user_meta( $user_id, 'pmpro_bcity', true );
447
+
$old_order->billing->city = ! empty( $city ) ? trim( $city ) : '';
448
+
}
449
+
450
+
if ( empty( $old_order->billing->state ) ) {
451
+
$state = get_user_meta( $user_id, 'pmpro_bstate', true );
452
+
$old_order->billing->state = ! empty( $state ) ? trim( $state ) : '';
453
+
}
454
+
455
+
if ( empty( $old_order->billing->zip ) ) {
456
+
$zip = get_user_meta( $user_id, 'pmpro_bzipcode', true );
457
+
$old_order->billing->zip = ! empty( $zip ) ? trim( $zip ) : '';
458
+
}
459
+
460
+
if ( empty( $old_order->billing->country ) ) {
461
+
$country = get_user_meta( $user_id, 'pmpro_bcountry', true );
462
+
$old_order->billing->country = ! empty( $country ) ? trim( $country ) : '';
463
+
}
464
+
465
+
$old_order->updateBilling();
466
+
467
+
return $old_order->billing;
468
+
}
469
+
470
+
/**
471
+
* Exit the Webhook handler, and save the debug log (if needed)
472
+
*/
473
+
function pmpro_braintreeWebhookExit() {
474
+
475
+
global $logstr;
476
+
477
+
//Log the info (if there is any)
478
+
if ( ! empty( $logstr ) ) {
479
+
480
+
$logstr[] = "\n-------------\n";
481
+
482
+
$debuglog = implode( "\n", $logstr );
483
+
484
+
//log in file or email?
485
+
if ( defined( 'PMPRO_BRAINTREE_WEBHOOK_DEBUG' ) && PMPRO_BRAINTREE_WEBHOOK_DEBUG === "log" ) {
486
+
//file
487
+
$logfile = apply_filters( 'pmpro_braintree_webhook_logfile', pmpro_get_restricted_file_path( 'logs', 'braintree-webhook.txt' ) );
488
+
$loghandle = fopen( $logfile, "a+" );
489
+
fwrite( $loghandle, $debuglog );
490
+
fclose( $loghandle );
491
+
} else if ( defined( 'PMPRO_BRAINTREE_WEBHOOK_DEBUG' ) ) {
492
+
/**
493
+
* @since 1.9.5 - BUG FIX: We specifically care about errors, not strings at position 0
494
+
*/
495
+
//email
496
+
if ( false !== strpos( PMPRO_BRAINTREE_WEBHOOK_DEBUG, "@" ) ) {
497
+
$log_email = PMPRO_BRAINTREE_WEBHOOK_DEBUG; //constant defines a specific email address
498
+
} else {
499
+
$log_email = get_option( "admin_email" );
500
+
}
501
+
502
+
wp_mail( $log_email, get_option( "blogname" ) . " Braintree Webhook Log", nl2br( esc_html( $debuglog ) ) );
503
+
}
504
+
}
505
+
506
+
exit;
507
+
}
508
+