- WordPress core y plugins - Tema Twenty Twenty-Four configurado - Plugin allow-unfiltered-html.php simplificado - .gitignore configurado para excluir wp-config.php y uploads 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
842 lines
26 KiB
PHP
842 lines
26 KiB
PHP
<?php
|
||
/**
|
||
* Braintree Payment Gateway Class
|
||
*
|
||
* @package Restrict Content Pro
|
||
* @subpackage Classes/Gateways/Braintree
|
||
* @copyright Copyright (c) 2017, Sandhills Development
|
||
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
|
||
* @since 2.8
|
||
*/
|
||
|
||
class RCP_Payment_Gateway_Braintree extends RCP_Payment_Gateway {
|
||
|
||
/**
|
||
* @var Braintree\Gateway
|
||
*/
|
||
protected $braintree;
|
||
protected $merchantId;
|
||
protected $publicKey;
|
||
protected $privateKey;
|
||
protected $encryptionKey;
|
||
protected $environment;
|
||
|
||
/**
|
||
* Initializes the gateway configuration.
|
||
*
|
||
* @since 2.8
|
||
* @return void
|
||
*/
|
||
public function init() {
|
||
|
||
if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) {
|
||
return;
|
||
}
|
||
|
||
global $rcp_options;
|
||
|
||
$this->supports[] = 'one-time';
|
||
$this->supports[] = 'recurring';
|
||
$this->supports[] = 'fees';
|
||
$this->supports[] = 'trial';
|
||
$this->supports[] = 'gateway-submits-form';
|
||
$this->supports[] = 'card-updates';
|
||
$this->supports[] = 'expiration-extension-on-renewals'; // @link https://github.com/restrictcontentpro/restrict-content-pro/issues/1259
|
||
|
||
if ( $this->test_mode ) {
|
||
$this->merchantId = ! empty( $rcp_options['braintree_sandbox_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_merchantId'] ) : '';
|
||
$this->publicKey = ! empty( $rcp_options['braintree_sandbox_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_publicKey'] ) : '';
|
||
$this->privateKey = ! empty( $rcp_options['braintree_sandbox_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_privateKey'] ) : '';
|
||
$this->encryptionKey = ! empty( $rcp_options['braintree_sandbox_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_encryptionKey'] ) : '';
|
||
$this->environment = 'sandbox';
|
||
} else {
|
||
$this->merchantId = ! empty( $rcp_options['braintree_live_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_live_merchantId'] ) : '';
|
||
$this->publicKey = ! empty( $rcp_options['braintree_live_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_publicKey'] ) : '';
|
||
$this->privateKey = ! empty( $rcp_options['braintree_live_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_privateKey'] ) : '';
|
||
$this->encryptionKey = ! empty( $rcp_options['braintree_live_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_encryptionKey'] ) : '';
|
||
$this->environment = 'production';
|
||
}
|
||
|
||
if ( ! class_exists( 'Braintree\\Gateway' ) ) {
|
||
require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/braintree/lib/Braintree.php';
|
||
}
|
||
|
||
$this->braintree = new Braintree\Gateway( array(
|
||
'environment' => $this->environment,
|
||
'merchantId' => $this->merchantId,
|
||
'publicKey' => $this->publicKey,
|
||
'privateKey' => $this->privateKey
|
||
) );
|
||
|
||
}
|
||
|
||
/**
|
||
* Validates the form fields.
|
||
* If there are any errors, it creates a new WP_Error instance
|
||
* via the rcp_errors() function.
|
||
*
|
||
* @see WP_Error::add()
|
||
* @uses rcp_errors()
|
||
* @return void
|
||
*/
|
||
public function validate_fields() {}
|
||
|
||
/**
|
||
* Processes a registration payment.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function process_signup() {
|
||
|
||
if ( empty( $_POST['payment_method_nonce'] ) ) {
|
||
$this->handle_processing_error(
|
||
new Exception(
|
||
__( 'Missing Braintree payment nonce. Please try again. Contact support if the issue persists.', 'rcp' )
|
||
)
|
||
);
|
||
}
|
||
|
||
$payment_method_nonce = $_POST['payment_method_nonce'];
|
||
|
||
/**
|
||
* @var RCP_Payments $rcp_payments_db
|
||
*/
|
||
global $rcp_payments_db;
|
||
|
||
$txn_args = array();
|
||
$member = new RCP_Member( $this->user_id ); // For backwards compatibility only.
|
||
$user = get_userdata( $this->user_id );
|
||
|
||
/**
|
||
* Set up the customer object.
|
||
*
|
||
* Get the customer record from Braintree if it already exists,
|
||
* otherwise create a new customer record.
|
||
*/
|
||
$customer = false;
|
||
$payment_profile_id = rcp_get_customer_gateway_id( $this->membership->get_customer_id(), 'braintree' );
|
||
|
||
|
||
if ( $payment_profile_id ) {
|
||
try {
|
||
$customer = $this->braintree->customer()->find( $payment_profile_id );
|
||
} catch ( Braintree_Exception_NotFound $e ) {
|
||
$customer = false;
|
||
} catch ( Exception $e ) {
|
||
$this->handle_processing_error( $e );
|
||
}
|
||
}
|
||
|
||
if ( ! $customer ) {
|
||
// Search for existing customer by ID.
|
||
$collection = $this->braintree->customer()->search( array(
|
||
Braintree_CustomerSearch::id()->is( 'bt_' . $this->user_id )
|
||
) );
|
||
|
||
if ( $collection ) {
|
||
foreach ( $collection as $record ) {
|
||
if ( $record->id === 'bt_' . $this->user_id ) {
|
||
$customer = $record;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if ( ! $customer ) {
|
||
|
||
try {
|
||
$result = $this->braintree->customer()->create( array(
|
||
'id' => 'bt_' . $this->user_id,
|
||
'firstName' => ! empty( $user->first_name ) ? sanitize_text_field( $user->first_name ) : '',
|
||
'lastName' => ! empty( $user->last_name ) ? sanitize_text_field( $user->last_name ) : '',
|
||
'email' => $user->user_email,
|
||
'riskData' => array(
|
||
'customerBrowser' => $_SERVER['HTTP_USER_AGENT'],
|
||
'customerIp' => rcp_get_ip()
|
||
)
|
||
) );
|
||
|
||
if ( $result->success && $result->customer ) {
|
||
$customer = $result->customer;
|
||
}
|
||
|
||
} catch ( Exception $e ) {
|
||
// Customer lookup/creation failed
|
||
$this->handle_processing_error( $e );
|
||
}
|
||
}
|
||
|
||
if ( empty( $customer ) ) {
|
||
$this->handle_processing_error( new Exception( __( 'Unable to locate or create customer record. Please try again. Contact support if the problem persists.', 'rcp' ) ) );
|
||
}
|
||
|
||
// Set the customer ID.
|
||
$this->membership->set_gateway_customer_id( $customer->id );
|
||
|
||
$payment_method_token = false;
|
||
|
||
if ( $this->initial_amount > 0 ) {
|
||
|
||
/**
|
||
* Always process a one-time payment for the first transaction.
|
||
*/
|
||
try {
|
||
$single_payment = $this->braintree->transaction()->sale( array(
|
||
'amount' => $this->initial_amount,
|
||
'customerId' => $customer->id,
|
||
'paymentMethodNonce' => $payment_method_nonce,
|
||
'options' => array(
|
||
'submitForSettlement' => true,
|
||
'storeInVaultOnSuccess' => true
|
||
)
|
||
) );
|
||
|
||
if ( $single_payment->success ) {
|
||
|
||
$payment_method_token = $single_payment->transaction->creditCardDetails->token;
|
||
|
||
$rcp_payments_db->update( $this->payment->id, array(
|
||
'date' => date( 'Y-m-d H:i:s', time() ),
|
||
'payment_type' => __( 'Braintree Credit Card Initial Payment', 'rcp' ),
|
||
'transaction_id' => $single_payment->transaction->id,
|
||
'status' => 'complete'
|
||
) );
|
||
|
||
/**
|
||
* Triggers when a gateway payment is completed.
|
||
*
|
||
* @param RCP_Member $member Deprecated member object.
|
||
* @param int $payment_id ID of the payment record in RCP.
|
||
* @param RCP_Payment_Gateway_Braintree $this Gateway object.
|
||
*/
|
||
do_action( 'rcp_gateway_payment_processed', $member, $this->payment->id, $this );
|
||
|
||
} else {
|
||
throw new Exception( sprintf( __( 'There was a problem processing your payment. Message: %s', 'rcp' ), $single_payment->message ) );
|
||
}
|
||
|
||
} catch ( Exception $e ) {
|
||
$this->handle_processing_error( $e );
|
||
}
|
||
|
||
} elseif ( empty( $this->initial_amount ) && $this->auto_renew ) {
|
||
|
||
/**
|
||
* Vault the payment method.
|
||
*
|
||
* Setting up a subscription requires a vaulted payment method first.
|
||
* This is done automatically when doing a one-time transaction, so we only need to do this
|
||
* separately if we haven't done a one-time charge.
|
||
*/
|
||
try {
|
||
$vaulted_payment_method = $this->braintree->paymentMethod()->create( array(
|
||
'customerId' => $customer->id,
|
||
'paymentMethodNonce' => $payment_method_nonce
|
||
) );
|
||
|
||
if ( $vaulted_payment_method->success && isset( $vaulted_payment_method->paymentMethod->token ) ) {
|
||
$payment_method_token = $vaulted_payment_method->paymentMethod->token;
|
||
}
|
||
|
||
} catch ( Exception $e ) {
|
||
$error = sprintf( 'Braintree Gateway: Error occurred while vaulting the payment method. Message: %s', $e->getMessage() );
|
||
rcp_log( $error, true );
|
||
$this->membership->add_note( $error );
|
||
}
|
||
|
||
// Complete the pending payment.
|
||
$rcp_payments_db->update( $this->payment->id, array(
|
||
'date' => date( 'Y-m-d H:i:s', time() ),
|
||
'payment_type' => __( 'Braintree Credit Card Initial Payment', 'rcp' ),
|
||
'status' => 'complete'
|
||
) );
|
||
|
||
}
|
||
|
||
/**
|
||
* Set up the subscription values and create the subscription.
|
||
*/
|
||
if ( $this->auto_renew ) {
|
||
|
||
try {
|
||
|
||
// Failure if we don't have a token.
|
||
if ( empty( $payment_method_token ) ) {
|
||
throw new Exception( __( 'Missing payment method token.', 'rcp' ) );
|
||
}
|
||
|
||
$txn_args['planId'] = $this->subscription_data['subscription_id'];
|
||
$txn_args['price'] = $this->amount;
|
||
|
||
if ( $this->is_3d_secure_enabled() ) {
|
||
// If 3D secure is enabled, we need a nonce from the vaulted payment method.
|
||
$nonce_result = $this->braintree->paymentMethodNonce()->create( $payment_method_token );
|
||
$txn_args['paymentMethodNonce'] = $nonce_result->paymentMethodNonce->nonce;
|
||
} else {
|
||
// Otherwise we can use a token, which doesn't have 3D secure data.
|
||
$txn_args['paymentMethodToken'] = $payment_method_token;
|
||
}
|
||
|
||
/**
|
||
* Start the subscription at the end of the trial period (if applicable) or the end of the first billing period.
|
||
*/
|
||
if ( ! empty( $this->subscription_start_date ) ) {
|
||
$txn_args['firstBillingDate'] = $this->subscription_start_date;
|
||
} else {
|
||
// Now set the firstBillingDate to the expiration date of the membership, modified to current time instead of 23:59.
|
||
$timezone = get_option( 'timezone_string' );
|
||
$timezone = ! empty( $timezone ) ? $timezone : 'UTC';
|
||
$datetime = new DateTime( $this->membership->get_expiration_date( false ), new DateTimeZone( $timezone ) );
|
||
$current_time = getdate( current_time( 'timestamp' ) );
|
||
$datetime->setTime( $current_time['hours'], $current_time['minutes'], $current_time['seconds'] );
|
||
$txn_args['firstBillingDate'] = $datetime->format( 'Y-m-d H:i:s' );
|
||
}
|
||
|
||
rcp_log( sprintf( 'Braintree Gateway: Creating subscription with start date: %s', $txn_args['firstBillingDate'] ) );
|
||
|
||
$result = $this->braintree->subscription()->create( $txn_args );
|
||
|
||
if ( $result->success ) {
|
||
$this->membership->set_gateway_subscription_id( $result->subscription->id );
|
||
} else {
|
||
throw new Exception( sprintf( __( 'Failed to create the subscription. Message: %s.', 'rcp' ), esc_html( $result->message ) ) );
|
||
}
|
||
|
||
} catch ( Exception $e ) {
|
||
$error = sprintf( 'Braintree Gateway: Error occurred while creating the subscription. Message: %s', $e->getMessage() );
|
||
rcp_log( $error, true );
|
||
$this->membership->add_note( $error );
|
||
$this->membership->set_recurring( false );
|
||
}
|
||
|
||
}
|
||
|
||
wp_redirect( $this->return_url ); exit;
|
||
|
||
}
|
||
|
||
/**
|
||
* Processes the Braintree webhooks.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function process_webhooks() {
|
||
|
||
if ( isset( $_GET['bt_challenge'] ) ) {
|
||
try {
|
||
$verify = $this->braintree->webhookNotification()->verify( $_GET['bt_challenge'] );
|
||
die( $verify );
|
||
} catch ( Exception $e ) {
|
||
rcp_log( 'Exiting Braintree webhook - verification failed.', true );
|
||
|
||
wp_die( 'Verification failed' );
|
||
}
|
||
}
|
||
|
||
if ( ! isset( $_POST['bt_signature'] ) || ! isset( $_POST['bt_payload'] ) ) {
|
||
return;
|
||
}
|
||
|
||
rcp_log( 'Starting to process Braintree webhook.' );
|
||
|
||
$data = false;
|
||
|
||
try {
|
||
$data = $this->braintree->webhookNotification()->parse( $_POST['bt_signature'], $_POST['bt_payload'] );
|
||
} catch ( Exception $e ) {
|
||
rcp_log( 'Exiting Braintree webhook - invalid signature.', true );
|
||
|
||
die( 'Invalid signature' );
|
||
}
|
||
|
||
if ( empty( $data->kind ) ) {
|
||
rcp_log( 'Exiting Braintree webhook - invalid webhook.', true );
|
||
|
||
die( 'Invalid webhook' );
|
||
}
|
||
|
||
/**
|
||
* Return early if this is a test webhook.
|
||
*/
|
||
if ( 'check' === $data->kind ) {
|
||
rcp_log( 'Exiting Braintree webhook - this is a test webhook.' );
|
||
|
||
die( 200 );
|
||
}
|
||
|
||
/**
|
||
* Get the membership from the subscription ID.
|
||
* @todo is subscription ID unique enough?? Should check for customer ID too.
|
||
*/
|
||
if ( empty( $user_id ) && ! empty( $data->subscription->id ) ) {
|
||
$this->membership = rcp_get_membership_by( 'gateway_subscription_id', $data->subscription->id );
|
||
|
||
}
|
||
|
||
if ( ! empty( $data->subscription->transactions ) ) {
|
||
$transaction = $data->subscription->transactions[0];
|
||
}
|
||
|
||
/**
|
||
* For backwards compatibility with the old Braintree add-on,
|
||
* find a user with this subscription ID stored in the meta
|
||
* `rcp_recurring_payment_id`.
|
||
*/
|
||
if ( empty( $this->membership ) && ! empty( $data->subscription->id ) ) {
|
||
|
||
global $wpdb;
|
||
|
||
$user_id = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = 'rcp_recurring_payment_id' AND meta_value = %s LIMIT 1", $data->subscription->id ) );
|
||
|
||
if ( ! empty( $user_id ) ) {
|
||
$customer = rcp_get_customer_by_user_id( $user_id );
|
||
|
||
if ( ! empty( $customer ) ) {
|
||
/*
|
||
* We can only use this function if:
|
||
* - Multiple memberships is disabled; or
|
||
* - The customer only has one membership anyway.
|
||
*/
|
||
if ( ! rcp_multiple_memberships_enabled() || 1 === count( $customer->get_memberships() ) ) {
|
||
$this->membership = rcp_get_customer_single_membership( $customer->get_id() );
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
if ( empty( $this->membership ) ) {
|
||
rcp_log( 'Exiting Braintree webhook - membership not found.', true );
|
||
|
||
die( 'no membership found' );
|
||
}
|
||
|
||
$member = new RCP_Member( $this->membership->get_user_id() ); // for backwards compat
|
||
|
||
rcp_log( sprintf( 'Processing webhook for membership #%d.', $this->membership->get_id() ) );
|
||
|
||
if ( empty( $this->membership->get_object_id() ) ) {
|
||
rcp_log( 'Exiting Braintree webhook - no membership level associated with membership.', true );
|
||
|
||
die( 'no membership level found' );
|
||
}
|
||
|
||
$pending_payment_id = rcp_get_membership_meta( $this->membership->get_id(), 'pending_payment_id', true );
|
||
|
||
$rcp_payments = new RCP_Payments;
|
||
|
||
/**
|
||
* Process the webhook.
|
||
*
|
||
* Descriptions of the webhook kinds below come from the Braintree developer docs.
|
||
* @see https://developers.braintreepayments.com/reference/general/webhooks/subscription/php
|
||
*/
|
||
switch ( $data->kind ) {
|
||
|
||
/**
|
||
* A subscription is canceled.
|
||
*/
|
||
case 'subscription_canceled':
|
||
|
||
rcp_log( 'Processing Braintree subscription_canceled webhook.' );
|
||
|
||
// If this is a completed payment plan, we can skip any cancellation actions. This is handled in renewals.
|
||
if ( $this->membership->has_payment_plan() && $this->membership->at_maximum_renewals() ) {
|
||
rcp_log( sprintf( 'Membership #%d has completed its payment plan - not cancelling.', $this->membership->get_id() ) );
|
||
die( 'membership payment plan completed' );
|
||
}
|
||
|
||
if ( $this->membership->is_active() ) {
|
||
$this->membership->cancel();
|
||
} else {
|
||
rcp_log( sprintf( 'Membership #%d is not active - not cancelling.', $this->membership->get_id() ) );
|
||
}
|
||
|
||
/**
|
||
* There won't be a paidThroughDate if a trial user cancels,
|
||
* so we need to check that it exists.
|
||
*/
|
||
if ( ! empty( $data->subscription->paidThroughDate ) ) {
|
||
$this->membership->set_expiration_date( $data->subscription->paidThroughDate->format( 'Y-m-d 23:59:59' ) );
|
||
}
|
||
|
||
$this->membership->add_note( __( 'Subscription cancelled in Braintree via webhook.', 'rcp' ) );
|
||
|
||
do_action( 'rcp_webhook_cancel', $member, $this );
|
||
|
||
die( 'braintree subscription cancelled' );
|
||
|
||
break;
|
||
|
||
/**
|
||
* A subscription successfully moves to the next billing cycle.
|
||
* This occurs if a new transaction is created. It will also occur
|
||
* when a billing cycle is skipped due to the presence of a
|
||
* negative balance that covers the cost of the subscription.
|
||
*/
|
||
case 'subscription_charged_successfully':
|
||
|
||
rcp_log( 'Processing Braintree subscription_charged_successfully webhook.' );
|
||
|
||
if ( $rcp_payments->payment_exists( $transaction->id ) ) {
|
||
do_action( 'rcp_ipn_duplicate_payment', $transaction->id, $member, $this );
|
||
|
||
die( 'duplicate payment found' );
|
||
}
|
||
|
||
if ( ! empty( $pending_payment_id ) ) {
|
||
|
||
// First payment on a new membership.
|
||
|
||
$rcp_payments->update( $pending_payment_id, array(
|
||
'date' => date( $transaction->createdAt->format( 'Y-m-d H:i:s' ) ),
|
||
'payment_type' => 'Braintree Credit Card',
|
||
'transaction_id' => $transaction->id,
|
||
'status' => 'complete'
|
||
) );
|
||
|
||
$this->membership->add_note( __( 'Subscription started in Braintree', 'rcp' ) );
|
||
|
||
$payment_id = $pending_payment_id;
|
||
|
||
} else {
|
||
|
||
// Renewing an existing membership.
|
||
|
||
$this->membership->renew( true, 'active', $data->subscription->paidThroughDate->format( 'Y-m-d 23:59:59' ) );
|
||
|
||
$payment_id = $rcp_payments->insert( array(
|
||
'date' => date( $transaction->createdAt->format( 'Y-m-d H:i:s' ) ),
|
||
'payment_type' => 'Braintree Credit Card',
|
||
'transaction_type' => 'renewal',
|
||
'user_id' => $this->membership->get_user_id(),
|
||
'customer_id' => $this->membership->get_customer_id(),
|
||
'membership_id' => $this->membership->get_id(),
|
||
'amount' => $transaction->amount,
|
||
'subtotal' => $transaction->subtotal,
|
||
'transaction_id' => $transaction->id,
|
||
'subscription' => $this->membership->get_membership_level_name(),
|
||
'subscription_key' => $member->get_subscription_key(),
|
||
'object_type' => 'subscription',
|
||
'object_id' => $this->membership->get_object_id(),
|
||
'gateway' => 'braintree'
|
||
) );
|
||
|
||
$member->add_note( sprintf( __( 'Payment %s collected in Braintree', 'rcp' ), $payment_id ) );
|
||
|
||
do_action( 'rcp_webhook_recurring_payment_processed', $member, $payment_id, $this );
|
||
}
|
||
|
||
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
|
||
|
||
die( 'braintree payment recorded' );
|
||
break;
|
||
|
||
/**
|
||
* A subscription already exists and fails to create a successful charge.
|
||
* This will not trigger on manual retries or if the attempt to create a
|
||
* subscription fails due to an unsuccessful transaction.
|
||
*/
|
||
case 'subscription_charged_unsuccessfully':
|
||
rcp_log( 'Processing Braintree subscription_charged_unsuccessfully webhook.' );
|
||
|
||
do_action( 'rcp_recurring_payment_failed', $member, $this );
|
||
|
||
die( 'subscription_charged_unsuccessfully' );
|
||
break;
|
||
|
||
/**
|
||
* A subscription reaches the specified number of billing cycles and expires.
|
||
*/
|
||
case 'subscription_expired':
|
||
|
||
rcp_log( 'Processing Braintree subscription_expired webhook.' );
|
||
|
||
$this->membership->set_status( 'expired' );
|
||
|
||
$this->membership->set_expiration_date( $data->subscription->paidThroughDate->format( 'Y-m-d H:i:s' ) );
|
||
|
||
$this->membership->add_note( __( 'Subscription expired in Braintree', 'rcp' ) );
|
||
|
||
die( 'member expired' );
|
||
break;
|
||
|
||
/**
|
||
* A subscription's trial period ends.
|
||
*/
|
||
case 'subscription_trial_ended':
|
||
|
||
rcp_log( 'Processing Braintree subscription_trial_ended webhook.' );
|
||
|
||
$this->membership->renew( $member->is_recurring(), '', $data->subscription->billingPeriodEndDate->format( 'Y-m-d H:i:s' ) );
|
||
$this->membership->add_note( __( 'Trial ended in Braintree', 'rcp' ) );
|
||
die( 'subscription_trial_ended processed' );
|
||
break;
|
||
|
||
/**
|
||
* A subscription's first authorized transaction is created.
|
||
* Subscriptions with trial periods will never trigger this notification.
|
||
*/
|
||
case 'subscription_went_active':
|
||
|
||
rcp_log( 'Processing Braintree subscription_went_active webhook.' );
|
||
|
||
if ( ! empty( $pending_payment_id ) ) {
|
||
$rcp_payments->update( $pending_payment_id, array(
|
||
'date' => $transaction->createdAt->format( 'Y-m-d H:i:s' ),
|
||
'payment_type' => 'Braintree Credit Card',
|
||
'transaction_id' => $transaction->id,
|
||
'status' => 'complete'
|
||
) );
|
||
|
||
$this->membership->add_note( sprintf( __( 'Subscription %s started in Braintree', 'rcp' ), $pending_payment_id ) );
|
||
}
|
||
|
||
do_action( 'rcp_webhook_recurring_payment_profile_created', $member, $this );
|
||
|
||
die( 'subscription went active' );
|
||
break;
|
||
|
||
/**
|
||
* A subscription has moved from the active status to the past due status.
|
||
* This occurs when a subscription’s initial transaction is declined.
|
||
*/
|
||
case 'subscription_went_past_due':
|
||
|
||
rcp_log( 'Processing Braintree subscription_went_past_due webhook.' );
|
||
|
||
$this->membership->set_status( 'pending' );
|
||
$this->membership->add_note( __( 'Subscription went past due in Braintree', 'rcp' ) );
|
||
die( 'subscription past due: member pending' );
|
||
break;
|
||
|
||
default:
|
||
die( 'unrecognized webhook kind' );
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handles the error processing.
|
||
*
|
||
* @param Exception $exception
|
||
*/
|
||
protected function handle_processing_error( $exception ) {
|
||
|
||
$this->error_message = $exception->getMessage();
|
||
|
||
do_action( 'rcp_registration_failed', $this );
|
||
|
||
wp_die( $exception->getMessage(), __( 'Error', 'rcp' ), array( 'response' => 401 ) );
|
||
|
||
}
|
||
|
||
/**
|
||
* Load the registration fields
|
||
*
|
||
* Outputs a placeholder for the Drop-in UI and a hidden field for the client token.
|
||
*
|
||
* @return string
|
||
*/
|
||
public function fields() {
|
||
ob_start();
|
||
|
||
$args = array();
|
||
$customer = rcp_get_customer();
|
||
|
||
if ( ! empty( $customer ) ) {
|
||
$braintree_customer_id = rcp_get_customer_gateway_id( $customer->get_id(), 'braintree' );
|
||
|
||
if ( ! empty( $braintree_customer_id ) ) {
|
||
$args['customerId'] = $braintree_customer_id;
|
||
}
|
||
}
|
||
|
||
try {
|
||
$token = $this->braintree->clientToken()->generate( $args );
|
||
} catch ( Exception $e ) {
|
||
return __( 'Failed to create client token.', 'rcp' );
|
||
}
|
||
?>
|
||
<div id="rcp-braintree-dropin-container"></div>
|
||
<div id="rcp-braintree-dropin-errors"></div>
|
||
<input type="hidden" id="rcp-braintree-client-token" name="rcp-braintree-client-token" value="<?php echo esc_attr( $token ); ?>" />
|
||
<?php
|
||
return ob_get_clean();
|
||
}
|
||
|
||
/**
|
||
* Load fields for the Update Billing Card form
|
||
*
|
||
* Outputs a placeholder for the Drop-in UI and a hidden field for the client token.
|
||
*
|
||
* @access public
|
||
* @since 3.3
|
||
* @return void
|
||
*/
|
||
public function update_card_fields() {
|
||
|
||
global $rcp_membership;
|
||
|
||
$args = array();
|
||
|
||
if ( ! $rcp_membership instanceof RCP_Membership ) {
|
||
return; // @todo message?
|
||
}
|
||
|
||
$braintree_customer_id = $rcp_membership->get_gateway_customer_id();
|
||
$braintree_subscription_id = $rcp_membership->get_gateway_subscription_id();
|
||
|
||
if ( empty( $braintree_customer_id ) || empty( $braintree_subscription_id ) ) {
|
||
echo '<p>' . __( 'You do not have an active subscription.', 'rcp' ) . '</p>';
|
||
return;
|
||
}
|
||
|
||
if ( ! empty( $braintree_customer_id ) ) {
|
||
$args['customerId'] = $braintree_customer_id;
|
||
}
|
||
|
||
try {
|
||
$token = $this->braintree->clientToken()->generate( $args );
|
||
} catch ( Exception $e ) {
|
||
echo '<p>' . sprintf( __( 'An unexpected error occurred: %s', 'rcp' ), esc_html( $e->getMessage() ) ) . '</p>';
|
||
return;
|
||
}
|
||
?>
|
||
<div id="rcp-braintree-dropin-container"></div>
|
||
<div id="rcp-braintree-dropin-errors"></div>
|
||
<input type="hidden" id="rcp-braintree-client-token" name="rcp-braintree-client-token" value="<?php echo esc_attr( $token ); ?>"/>
|
||
<input type="hidden" id="rcp-braintree-recurring-amount" value="<?php echo esc_attr( $rcp_membership->get_recurring_amount() ); ?>"/>
|
||
<?php
|
||
|
||
}
|
||
|
||
/**
|
||
* Loads the Braintree javascript library.
|
||
*/
|
||
public function scripts() {
|
||
$suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
|
||
|
||
wp_enqueue_script( 'rcp-braintree-dropin', 'https://js.braintreegateway.com/web/dropin/1.33.4/js/dropin.min.js', [], '1.33.4' );
|
||
|
||
wp_enqueue_script(
|
||
'rcp-braintree',
|
||
RCP_PLUGIN_URL . 'pro/includes/gateways/braintree/js/braintree' . $suffix . '.js',
|
||
array(
|
||
'jquery',
|
||
'rcp-braintree-dropin',
|
||
),
|
||
RCP_PLUGIN_VERSION
|
||
);
|
||
|
||
$array = array(
|
||
'dropin_ui_config' => $this->get_dropin_ui_config(),
|
||
'payment_method_options' => $this->get_payment_method_options(),
|
||
'please_wait' => esc_html__( 'Please wait...', 'rcp' ),
|
||
'user_email' => is_user_logged_in() ? wp_get_current_user()->user_email : '',
|
||
'try_new_payment' => __( 'Please try a new payment method.', 'rcp' )
|
||
);
|
||
|
||
wp_localize_script( 'rcp-braintree', 'rcp_braintree_script_options', $array );
|
||
}
|
||
|
||
/**
|
||
* Determines whether or not 3D secure is enabled on the merchant account.
|
||
*
|
||
* @since 3.3
|
||
* @return bool
|
||
*/
|
||
protected function is_3d_secure_enabled() {
|
||
|
||
try {
|
||
$token = $this->braintree->clientToken()->generate();
|
||
|
||
if ( empty( $token ) ) {
|
||
throw new Exception();
|
||
}
|
||
|
||
$data = json_decode( base64_decode( $token ) );
|
||
|
||
if ( empty( $data ) || empty( $data->threeDSecureEnabled ) ) {
|
||
throw new Exception();
|
||
}
|
||
|
||
$enabled = true;
|
||
} catch ( Exception $e ) {
|
||
$enabled = false;
|
||
}
|
||
|
||
return $enabled;
|
||
|
||
}
|
||
|
||
/**
|
||
* Get drop-in UI default configuration.
|
||
*
|
||
* @link https://braintree.github.io/braintree-web-drop-in/docs/current/module-braintree-web-drop-in.html#.create
|
||
*
|
||
* @since 3.3
|
||
* @return array
|
||
*/
|
||
protected function get_dropin_ui_config() {
|
||
|
||
$config = array(
|
||
'container' => '#rcp-braintree-dropin-container',
|
||
'locale' => get_locale(),
|
||
'threeDSecure' => $this->is_3d_secure_enabled()
|
||
);
|
||
|
||
/**
|
||
* Filters the default drop-in UI configuration.
|
||
*
|
||
* @since 3.3
|
||
*/
|
||
$config = apply_filters( 'rcp_braintree_dropin_ui_config', $config );
|
||
|
||
return $config;
|
||
|
||
}
|
||
|
||
/**
|
||
* Get default options for `requestPaymentMethod()` call.
|
||
*
|
||
* @link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod
|
||
*
|
||
* @since 3.3
|
||
* @return array
|
||
*/
|
||
protected function get_payment_method_options() {
|
||
|
||
$options = array();
|
||
|
||
if ( $this->is_3d_secure_enabled() ) {
|
||
$options['threeDSecure'] = array(
|
||
'amount' => 0.00,
|
||
// This gets set in the JavaScript.
|
||
'email' => is_user_logged_in() ? wp_get_current_user()->user_email : '',
|
||
// If user is not logged in, JS will set this.
|
||
'additionalInformation' => array(
|
||
'productCode' => 'DIG',
|
||
// Digital product
|
||
'deliveryTimeframe' => '01',
|
||
// Immediate delivery
|
||
'deliveryEmail' => is_user_logged_in() ? wp_get_current_user()->user_email : '',
|
||
// If user is not logged in, JS will set this.
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Filters the payment method options.
|
||
*
|
||
* @since 3.3
|
||
*/
|
||
$options = apply_filters( 'rcp_braintree_payment_method_options', $options );
|
||
|
||
return $options;
|
||
|
||
}
|
||
|
||
}
|