Commit inicial - WordPress Análisis de Precios Unitarios

- 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>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
<tr valign="top">
<th colspan=2>
<h3><?php _e( 'Braintree Settings', 'rcp' ); ?></h3>
</th>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_live_merchantId]"><?php _e( 'Live Merchant ID', 'rcp' ); ?></label>
</th>
<td>
<input type="text" class="regular-text" id="rcp_settings[braintree_live_merchantId]" style="width: 300px;"
name="rcp_settings[braintree_live_merchantId]"
value="<?php if ( isset( $rcp_options['braintree_live_merchantId'] ) ) {
echo esc_attr( $rcp_options['braintree_live_merchantId'] );
} ?>"/>
<p class="description"><?php _e( 'Enter your Braintree live merchant ID.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_live_publicKey]"><?php _e( 'Live Public Key', 'rcp' ); ?></label>
</th>
<td>
<input type="<?php echo isset( $rcp_options['braintree_live_publicKey'] ) ? 'password' : 'text'; ?>"
class="regular-text" id="rcp_settings[braintree_live_publicKey]"
style="width: 300px;" name="rcp_settings[braintree_live_publicKey]"
value="<?php if ( isset( $rcp_options['braintree_live_publicKey'] ) ) {
echo esc_attr( $rcp_options['braintree_live_publicKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_live_publicKey]"
class="dashicons dashicons-hidden toggle-credentials"></span>
</button>
<p class="description"><?php _e( 'Enter your Braintree live public key.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_live_privateKey]"><?php _e( 'Live Private Key', 'rcp' ); ?></label>
</th>
<td>
<input type="<?php echo isset( $rcp_options['braintree_live_privateKey'] ) ? 'password' : 'text'; ?>"
class="regular-text" id="rcp_settings[braintree_live_privateKey]"
style="width: 300px;" name="rcp_settings[braintree_live_privateKey]"
value="<?php if ( isset( $rcp_options['braintree_live_privateKey'] ) ) {
echo esc_attr( $rcp_options['braintree_live_privateKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_live_privateKey]"
class="dashicons dashicons-hidden toggle-credentials"></span>
</button>
<p class="description"><?php _e( 'Enter your Braintree live private key.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_live_encryptionKey]"><?php _e( 'Live Client Side Encryption Key', 'rcp' ); ?></label>
</th>
<td>
<?php if ( ! empty( $rcp_options['braintree_live_encryptionKey'] ) ) : ?>
<textarea
class="regular-text"
id="rcp_settings[braintree_live_encryptionKey]"
style="width: 300px;height: 100px; display: none"
name="rcp_settings[braintree_live_encryptionKey]"
/><?php if ( isset( $rcp_options['braintree_live_encryptionKey'] ) ) { echo esc_attr( trim($rcp_options['braintree_live_encryptionKey'] ) ); } ?></textarea>
<input
type="password"
id="rcp_settings[braintree_live_encryptionKey_input]"
style="width: 300px;height: 100px; display: inline-block;"
name="rcp_settings[braintree_live_encryptionKey_input]"
value="<?php echo isset( $rcp_options['braintree_live_encryptionKey'] ) ? esc_attr( $rcp_options['braintree_live_encryptionKey'] ) : '' ?>"
/>
<button type="button" class="button button-secondary">
<span
toggle="rcp_settings[braintree_live_encryptionKey]"
class="dashicons dashicons-visibility toggle-textarea"
id="rcp_setting_braintree_toggle_live"></span>
</button>
<?php else : ?>
<textarea
class="regular-text"
id="rcp_settings[braintree_live_encryptionKey]" style="width: 300px;height: 100px;"
name="rcp_settings[braintree_live_encryptionKey]"
/><?php echo isset( $rcp_options['braintree_live_encryptionKey'] ) ? esc_attr( trim($rcp_options['braintree_live_encryptionKey'] ) ) : ''; ?></textarea>
<input
type="password"
id="rcp_settings[braintree_live_encryptionKey_input]"
style="display:none; width: 300px;height: 100px;"
name="rcp_settings[braintree_live_encryptionKey_input]"
value="<?php echo isset( $rcp_options['braintree_live_encryptionKey'] ) ? esc_attr( $rcp_options['braintree_live_encryptionKey'] ) : '' ?>"
/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_live_encryptionKey]"
class="dashicons dashicons-hidden toggle-textarea"
id="rcp_setting_braintree_toggle_live"></span>
</button>
<?php endif; ?>
<p class="description"><?php _e( 'Enter your Braintree live client side encryption key.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_sandbox_merchantId]"><?php _e( 'Sandbox Merchant ID', 'rcp' ); ?></label>
</th>
<td>
<input type="text" class="regular-text" id="rcp_settings[braintree_sandbox_merchantId]"
style="width: 300px;" name="rcp_settings[braintree_sandbox_merchantId]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_merchantId'] ) ) {
echo esc_attr( $rcp_options['braintree_sandbox_merchantId'] );
} ?>"/>
<p class="description"><?php _e( 'Enter your Braintree sandbox merchant ID.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_sandbox_publicKey]"><?php _e( 'Sandbox Public Key', 'rcp' ); ?></label>
</th>
<td>
<?php if ( ! empty( $rcp_options['braintree_sandbox_publicKey'] ) ) : ?>
<input type="password" class="regular-text" id="rcp_settings[braintree_sandbox_publicKey]"
style="width: 300px;" name="rcp_settings[braintree_sandbox_publicKey]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_publicKey'] ) ) {
echo esc_attr( $rcp_options['braintree_sandbox_publicKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_sandbox_publicKey]"
class="dashicons dashicons-visibility toggle-credentials"></span>
</button>
<?php else : ?>
<input type="text" class="regular-text" id="rcp_settings[braintree_sandbox_publicKey]"
style="width: 300px;" name="rcp_settings[braintree_sandbox_publicKey]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_publicKey'] ) ) {
echo esc_attr( $rcp_options['braintree_sandbox_publicKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_sandbox_publicKey]"
class="dashicons dashicons-hidden toggle-credentials"></span>
</button>
<?php endif; ?>
<p class="description"><?php _e( 'Enter your Braintree sandbox public key.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_sandbox_privateKey]"><?php _e( 'Sandbox Private Key', 'rcp' ); ?></label>
</th>
<td>
<?php if ( ! empty( $rcp_options['braintree_sandbox_privateKey'] ) ) : ?>
<input type="password" class="regular-text" id="rcp_settings[braintree_sandbox_privateKey]"
style="width: 300px;" name="rcp_settings[braintree_sandbox_privateKey]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_privateKey'] ) ) {
echo esc_attr( $rcp_options['braintree_sandbox_privateKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_sandbox_privateKey]"
class="dashicons dashicons-visibility toggle-credentials"></span>
</button>
<?php else : ?>
<input type="text" class="regular-text" id="rcp_settings[braintree_sandbox_privateKey]"
style="width: 300px;" name="rcp_settings[braintree_sandbox_privateKey]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_privateKey'] ) ) {
echo esc_attr( $rcp_options['braintree_sandbox_privateKey'] );
} ?>"/>
<button type="button" class="button button-secondary">
<span toggle="rcp_settings[braintree_sandbox_privateKey]"
class="dashicons dashicons-hidden toggle-credentials"></span>
</button>
<?php endif; ?>
<p class="description"><?php _e( 'Enter your Braintree sandbox private key.', 'rcp' ); ?></p>
</td>
</tr>
<tr>
<th>
<label for="rcp_settings[braintree_sandbox_encryptionKey]"><?php _e( 'Sandbox Client Side Encryption Key', 'rcp' ); ?></label>
</th>
<td>
<?php if ( ! empty( $rcp_options['braintree_sandbox_encryptionKey'] ) ) : ?>
<textarea
class="regular-text"
id="rcp_settings[braintree_sandbox_encryptionKey]"
style="width: 300px;height: 100px; display: none"
name="rcp_settings[braintree_sandbox_encryptionKey]"
/><?php if ( isset( $rcp_options['braintree_sandbox_encryptionKey'] ) ) { echo esc_attr( $rcp_options['braintree_sandbox_encryptionKey'] ); } ?></textarea>
<input
type="password"
id="rcp_settings[braintree_sandbox_encryptionKey_input]"
style="width: 300px; height: 100px; display: inline-block"
name="rcp_settings[braintree_sandbox_encryptionKey_input]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_encryptionKey'] ) ) { echo esc_attr( $rcp_options['braintree_sandbox_encryptionKey'] ); } ?>"
/>
<button type="button" class="button button-secondary">
<span
toggle="rcp_settings[braintree_sandbox_encryptionKey]"
class="dashicons dashicons-visibility toggle-textarea"
id="rcp_setting_braintree_toggle_sandbox">
</span>
</button>
<?php else : ?>
<textarea
class="regular-text"
id="rcp_settings[braintree_sandbox_encryptionKey]"
style="width: 300px;height: 100px;"
name="rcp_settings[braintree_sandbox_encryptionKey]"
/><?php if ( isset( $rcp_options['braintree_sandbox_encryptionKey'] ) ) { echo esc_attr( $rcp_options['braintree_sandbox_encryptionKey'] ); } ?></textarea>
<input
type="password"
id="rcp_settings[braintree_sandbox_encryptionKey_input]"
style="display:none; width: 300px;"
name="rcp_settings[braintree_sandbox_encryptionKey_input]"
value="<?php if ( isset( $rcp_options['braintree_sandbox_encryptionKey'] ) ) { echo esc_attr( $rcp_options['braintree_sandbox_encryptionKey'] ); } ?>"
/>
<button type="button" class="button button-secondary">
<span
toggle="rcp_settings[braintree_sandbox_encryptionKey]"
class="dashicons dashicons-hidden toggle-textarea"
id="rcp_setting_braintree_toggle_sandbox">
</span>
</button>
<?php endif; ?>
<p class="description"><?php _e( 'Enter your Braintree sandbox client side encryption key.', 'rcp' ); ?></p>
</td>
</tr>
</table>

View File

@@ -0,0 +1,341 @@
<?php
/**
* Braintree Functions
*
* @package Restrict Content Pro
* @subpackage Gateways/Braintree/Functions
* @copyright Copyright (c) 2017, Sandhills Development
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.8
*/
/**
* Determines if a membership is Braintree subscription.
*
* @param int|RCP_Membership $membership_object_or_id Membership ID or object.
*
* @since 3.0
* @return bool
*/
function rcp_is_braintree_membership( $membership_object_or_id ) {
if ( ! is_object( $membership_object_or_id ) ) {
$membership = rcp_get_membership( $membership_object_or_id );
} else {
$membership = $membership_object_or_id;
}
$is_braintree = false;
if ( ! empty( $membership ) && $membership->get_id() > 0 ) {
$subscription_id = $membership->get_gateway_customer_id();
if ( false !== strpos( $subscription_id, 'bt_' ) ) {
$is_braintree = true;
}
}
/**
* Filters whether or not the membership is a Braintree subscription.
*
* @param bool $is_braintree
* @param RCP_Membership $membership
*
* @since 3.0
*/
return (bool) apply_filters( 'rcp_is_braintree_membership', $is_braintree, $membership );
}
/**
* Determines if all necessary Braintree API credentials are available.
*
* @since 2.7
* @return bool
*/
function rcp_has_braintree_api_access() {
global $rcp_options;
if ( rcp_is_sandbox() ) {
$merchant_id = ! empty( $rcp_options['braintree_sandbox_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_sandbox_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_sandbox_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_privateKey'] ) : '';
$encryption_key = ! empty( $rcp_options['braintree_sandbox_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_encryptionKey'] ) : '';
} else {
$merchant_id = ! empty( $rcp_options['braintree_live_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_live_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_live_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_live_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_privateKey'] ) : '';
$encryption_key = ! empty( $rcp_options['braintree_live_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_encryptionKey'] ) : '';
}
if ( ! empty( $merchant_id ) && ! empty( $public_key ) && ! empty( $private_key ) && ! empty( $encryption_key ) ) {
return true;
}
return false;
}
/**
* Cancel a Braintree membership by subscription ID.
*
* @param string $subscription_id Braintree subscription ID.
*
* @since 3.0
* @return true|WP_Error True on success, WP_Error on failure.
*/
function rcp_braintree_cancel_membership( $subscription_id ) {
global $rcp_options;
$ret = true;
if ( rcp_is_sandbox() ) {
$merchant_id = ! empty( $rcp_options['braintree_sandbox_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_sandbox_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_sandbox_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_privateKey'] ) : '';
$encryption_key = ! empty( $rcp_options['braintree_sandbox_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_encryptionKey'] ) : '';
$environment = 'sandbox';
} else {
$merchant_id = ! empty( $rcp_options['braintree_live_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_live_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_live_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_live_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_privateKey'] ) : '';
$encryption_key = ! empty( $rcp_options['braintree_live_encryptionKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_encryptionKey'] ) : '';
$environment = 'production';
}
if ( ! class_exists( 'Braintree\\Gateway' ) ) {
require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/braintree/lib/Braintree.php';
}
$gateway = new Braintree\Gateway( array(
'environment' => $environment,
'merchantId' => $merchant_id,
'publicKey' => $public_key,
'privateKey' => $private_key
) );
try {
$result = $gateway->subscription()->cancel( $subscription_id );
if ( ! $result->success ) {
$status = $result->errors->forKey( 'subscription' )->onAttribute( 'status' );
/**
* Don't throw an exception if the subscription is already cancelled.
*/
if ( '81905' != $status[0]->code ) {
$ret = new WP_Error( 'rcp_braintree_error', $result->message );
}
}
} catch ( Exception $e ) {
$ret = new WP_Error( 'rcp_braintree_error', $e->getMessage() );
}
return $ret;
}
/**
* Checks for the legacy Braintree gateway
* and deactivates it and shows a notice.
*
* @since 2.8
* @return void
*/
function rcp_braintree_detect_legacy_plugin() {
if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
return;
}
if ( is_plugin_active( 'rcp-braintree/rcp-braintree.php' ) ) {
deactivate_plugins( 'rcp-braintree/rcp-braintree.php', true );
}
}
add_action( 'admin_init', 'rcp_braintree_detect_legacy_plugin' );
/**
* Checks for legacy Braintree webhook endpoints
* and fires off the webhook processing for those requests.
*
* @since 2.8
* @return void
*/
add_action( 'init', function() {
if ( ! empty( $_GET['bt_challenge'] ) || ( ! empty( $_POST['bt_signature'] ) && ! empty( $_POST['bt_payload'] ) ) ) {
add_filter( 'rcp_process_gateway_webhooks', '__return_true' );
}
}, -100000 ); // Must run before rcp_process_gateway_webooks which is hooked on -99999
/**
* Add JS to the update card form
*
* @since 3.3
* @return void
*/
function rcp_braintree_update_card_form_js() {
global $rcp_membership;
if ( ! rcp_is_braintree_membership( $rcp_membership ) || ! rcp_has_braintree_api_access() ) {
return;
}
$gateway = new RCP_Payment_Gateway_Braintree();
$gateway->scripts();
}
add_action( 'rcp_before_update_billing_card_form', 'rcp_braintree_update_card_form_js' );
/**
* Update the billing card for a given membership
*
* @param RCP_Membership $membership
*
* @since 3.3
* @return void
*/
function rcp_braintree_update_membership_billing_card( $membership ) {
if ( ! $membership instanceof RCP_Membership ) {
return;
}
if ( ! rcp_is_braintree_membership( $membership ) ) {
return;
}
if ( empty( $_POST['payment_method_nonce'] ) ) {
wp_die( __( 'Missing payment method nonce.', 'rcp' ) );
}
$subscription_id = $membership->get_gateway_subscription_id();
if ( empty( $subscription_id ) ) {
wp_die( __( 'Invalid subscription.', 'rcp' ) );
}
global $rcp_options;
if ( rcp_is_sandbox() ) {
$merchant_id = ! empty( $rcp_options['braintree_sandbox_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_sandbox_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_sandbox_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_sandbox_privateKey'] ) : '';
$environment = 'sandbox';
} else {
$merchant_id = ! empty( $rcp_options['braintree_live_merchantId'] ) ? sanitize_text_field( $rcp_options['braintree_live_merchantId'] ) : '';
$public_key = ! empty( $rcp_options['braintree_live_publicKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_publicKey'] ) : '';
$private_key = ! empty( $rcp_options['braintree_live_privateKey'] ) ? sanitize_text_field( $rcp_options['braintree_live_privateKey'] ) : '';
$environment = 'production';
}
if ( ! class_exists( 'Braintree\\Gateway' ) ) {
require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/braintree/lib/Braintree.php';
}
$gateway = new Braintree\Gateway( array(
'environment' => $environment,
'merchantId' => $merchant_id,
'publicKey' => $public_key,
'privateKey' => $private_key
) );
try {
$gateway->subscription()->update( $subscription_id, array(
'paymentMethodNonce' => sanitize_text_field( $_POST['payment_method_nonce'] )
) );
wp_redirect( add_query_arg( 'card', 'updated' ) ); exit;
} catch ( \Exception $e ) {
wp_die( sprintf( __( 'An error occurred: %s', 'rcp' ), $e->getMessage() ) );
}
}
add_action( 'rcp_update_membership_billing_card', 'rcp_braintree_update_membership_billing_card' );
/**
* The origin of this function was the migration of 3DS V1 to 3DS V2.
*
* Output the additional fields needed by Braintree to fulfill the 3DS2 such as address fields.
*
* @return void
*/
function rcp_braintree_additional_fields() { ?>
<fieldset class="rcp_braintree_billing_info">
<h3><?php echo apply_filters ( 'rcp_braintree_billing_legend_label', __( 'Billing Information', 'rcp' ) ); ?></h3>
<p id="rcp_braintree_billing_phoneNumber_wrap">
<label for="rcp_braintree_billing_phoneNumber"><?php echo apply_filters ( 'rcp_braintree_billing_phoneNumber_label', __( 'Phone Number', 'rcp' ) ); ?></label>
<input name="rcp_braintree_billing_phoneNumber" id="rcp_braintree_billing_phoneNumber" class="required"
type="text" placeholder="1234567890"
<?php if( isset( $_POST['rcp_braintree_billing_phoneNumber'] ) ) { echo 'value="' . esc_attr( $_POST['rcp_braintree_billing_phoneNumber'] ) . '"'; } ?>/>
</p>
<p id="rcp_braintree_billing_firstname_wrap">
<label for="rcp_braintree_billing_firstname"><?php echo apply_filters ( 'rcp_braintree_billing_firstname_label', __( 'Given Name', 'rcp' ) ); ?></label>
<input name="rcp_braintree_billing_firstname" id="rcp_braintree_billing_firstname" class="required"
type="text" placeholder="First"
<?php if( isset( $_POST['rcp_braintree_billing_firstname'] ) ) { echo 'value="' . esc_attr( $_POST['rcp_braintree_billing_firstname'] ) . '"'; } ?>/>
</p>
<p id="rcp_braintree_billing_lastname_wrap">
<label for="rcp_braintree_billing_lastname"><?php echo apply_filters ( 'rcp_braintree_billing_lastname_label', __( 'Surname', 'rcp' ) ); ?></label>
<input name="rcp_braintree_billing_lastname" id="rcp_braintree_billing_lastname" class="required"
type="text" placeholder="Last"
<?php if( isset( $_POST['rcp_braintree_billing_lastname'] ) ) { echo 'value="' . esc_attr( $_POST['rcp_braintree_billing_lastname'] ) . '"'; } ?>/>
</p>
<input type="hidden" id="braintree_3ds_nonce" name="braintree_3ds_nonce" value="<?php echo esc_attr( wp_create_nonce( 'braintree_3ds' ) ); ?>">
</fieldset>
<?php
}
add_action( 'rcp_braintree_additional_fields', 'rcp_braintree_additional_fields' );
/**
* Sanitize the fields that the user enter and validate the nonce.
*
* @return void return json created by WordPress.
*/
function rcp_braintree_3ds_validation_fields() {
$post = wp_unslash( $_POST );
$nonce = wp_verify_nonce( sanitize_text_field( $post['nonce'] ),'braintree_3ds' );
// Bail if nonce is not valid.
if( false === $nonce ){
wp_send_json_error( [
'status' => 'failed',
'message' => 'Invalid Nonce. Consider reloading the page.',
], 401);
}
$billing_address = array_key_exists( 'billingAddress', $post) ? array_map( 'rcp_sanitize_fields', $post['billingAddress'] ) : $post;
if(array_key_exists( 'additionalInformation', $post) ) {
$additional_information = array_map( 'rcp_sanitize_fields', $post['additionalInformation'] );
$additional_information['shippingAddress'] = array_key_exists( 'shippingAddress', $additional_information ) ? array_map( 'rcp_sanitize_fields', $additional_information['shippingAddress'] ) : $additional_information;
}
$result = [
'billingAddress' => $billing_address,
'additionalInformation' => $billing_address,
];
wp_send_json_success( $result, 200 );
}
/**
* Function that will check if the current value if a string and sanitize it, otherwise it will just return the
* Array|Object.
*
* @param array|string $_field The field to check.
* @since 3.5.23.1
* @return array|string The Sanitized String or the Array|Object.
*/
function rcp_sanitize_fields( $_field ) {
if( is_object( $_field ) || is_array( $_field ) ) {
return $_field;
}
return sanitize_text_field( $_field );
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,361 @@
/* global rcp_braintree_script_options */
jQuery( function( $ ) {
/**
* Braintree registration
*/
var RCP_Braintree_Registration = {
/**
* Braintree drop-in UI instance
*/
dropinInstance: false,
/**
* Whether or not card details have been entered
*/
hasCardDetails: false,
/**
* Initialize
*/
init: function () {
$( 'body' ).on( 'rcp_gateway_loaded', RCP_Braintree_Registration.mountUI );
$( '#rcp_submit' ).on( 'click', RCP_Braintree_Registration.maybeBlockSubmit );
$( 'body' ).on( 'rcp_registration_form_processed', RCP_Braintree_Registration.tokenizePayment );
},
/**
* Mount the drop-in UI when the gateway is loaded
*
* @param e
* @param gateway
*/
mountUI: function( e, gateway ) {
if ( ! document.getElementById( 'rcp-braintree-client-token' ) ) {
return;
}
rcp_braintree_script_options.dropin_ui_config = {
authorization: $( '#rcp-braintree-client-token' ).val(),
container: '#rcp-braintree-dropin-container',
threeDSecure: true
};
braintree.dropin.create( rcp_braintree_script_options.dropin_ui_config ).then( function( dropinInstance ) {
RCP_Braintree_Registration.dropinInstance = dropinInstance;
// Flag as having payment details or not.
if ( dropinInstance.isPaymentMethodRequestable() ) {
RCP_Braintree_Registration.hasCardDetails = true;
}
dropinInstance.on( 'paymentMethodRequestable', function ( requestableEvent ) {
RCP_Braintree_Registration.hasCardDetails = true;
} );
dropinInstance.on( 'noPaymentMethodRequestable', function ( requestableEvent ) {
RCP_Braintree_Registration.hasCardDetails = false;
} );
} ).catch( function( error ) {
// Handle errors from creating drop-in.
rcpBraintreeHandleError( error );
} );
},
/**
* Prevent form submission if card details haven't been filled out yet
*
* @param e
*/
maybeBlockSubmit: function ( e ) {
if ( 'braintree' === rcp_get_gateway().val() && document.getElementById( 'rcp-braintree-client-token' ) && ! RCP_Braintree_Registration.hasCardDetails ) {
e.stopPropagation();
rcpBraintreeHandleError( rcp_script_options.enter_card_details );
return false;
}
},
registerTestInformation: function () {
// Using timeout since the elements are not loaded with the initial DOM elements.
setTimeout(function(){
$( '#rcp_braintree_test_check' ).on('click', function (event) {
if ( true === $( this ).prop('checked') ) {
$( '#rcp_braintree_billing_lastname' ).val('Doe');
$( '#rcp_braintree_billing_phoneNumber' ).val('1234567890');
$( '#rcp_braintree_billing_firstname' ).val('Santino');
}
else {
$( '#rcp_braintree_billing_lastname' ).val('');
$( '#rcp_braintree_billing_phoneNumber' ).val('');
$( '#rcp_braintree_billing_firstname' ).val('');
}
});
},2500);
},
/**
* Collect card details, handle 3D secure if available, and tokenize the payment method
*
* @param event
* @param form
* @param response
*/
tokenizePayment: function( event, form, response ) {
if ( ! document.getElementById( 'rcp-braintree-client-token' ) || 'braintree' !== rcp_get_gateway().val() ) {
return;
}
let paymentMethodOptions = rcp_braintree_script_options.payment_method_options;
let additionalInformation = {};
let billingAddress = {};
// Set email address(es) for logged out customers.
if ( 'undefined' !== typeof( paymentMethodOptions.threeDSecure ) && '' === paymentMethodOptions.threeDSecure.email ) {
paymentMethodOptions.threeDSecure.email = $( '#rcp_user_email' ).val();
}
if ( 'undefined' !== typeof( paymentMethodOptions.threeDSecure ) && 'undefined' !== typeof( paymentMethodOptions.threeDSecure.additionalInformation ) && '' === paymentMethodOptions.threeDSecure.additionalInformation.deliveryEmail ) {
additionalInformation.deliveryEmail = $( '#rcp_user_email' ).val();
// paymentMethodOptions.threeDSecure.additionalInformation.deliveryEmail = $( '#rcp_user_email' ).val();
}
// We need to collect the billing and additional information.
billingAddress = {
givenName: $( '#rcp_braintree_billing_firstname' ).val(),
surname: $( '#rcp_braintree_billing_lastname' ).val(),
phoneNumber: $( '#rcp_braintree_billing_phoneNumber' ).val(),
/* streetAddress: $( '#rcp_braintree_billing_streetAddress' ).val(),
extendedAddress: $( '#rcp_braintree_billing_extendedAddress' ).val(),
locality: $( '#rcp_braintree_billing_locality' ).val(),
region: $( '#rcp_braintree_billing_region' ).val(),
postalCode: $( '#rcp_braintree_billing_postalCode' ).val(),
countryCodeAlpha2: $( '#rcp_braintree_billing_countryCodeAlpha2' ).val()
*/
};
/**
* Make sure that the fields that user is entering are being sanitized by the backend.
*/
$.when ( $.ajax({
type: 'post',
dataType: 'json',
url: rcp_script_options.ajaxurl,
data: {
action: 'rcp_braintree_3ds_validation_fields',
nonce: $( '#braintree_3ds_nonce' ).val(),
billingAddress: billingAddress
}
} ) ).then( function( validationResponse) {
if( validationResponse.success ) {
// Let's check for empty fields.
for ( const key in validationResponse.data.billingAddress ) {
if( '' === validationResponse.data.billingAddress[key] ){
rcpBraintreeHandleError( rcp_script_options.braintree_empty_fields );
return false;
}
}
// Add to the threeDSecure object the billing fields and the additional fields.
paymentMethodOptions.threeDSecure.billingAddress = validationResponse.data.billingAddress;
paymentMethodOptions.threeDSecure.additionalInformation = additionalInformation;
// Set authorization amount.
if ( 'undefined' !== typeof paymentMethodOptions.threeDSecure ) {
paymentMethodOptions.threeDSecure.amount = (response.total > 0) ? response.total : response.recurring_total;
}
RCP_Braintree_Registration.dropinInstance.requestPaymentMethod( paymentMethodOptions ).then( function( payload ) {
if ( payload.liabilityShiftPossible && ! payload.liabilityShifted ) {
// 3D secure was possible, but failed.
// Clear the payment method.
RCP_Braintree_Registration.dropinInstance.clearSelectedPaymentMethod();
// Display error message.
rcpBraintreeHandleError( rcp_braintree_script_options.try_new_payment );
} else {
// Payment was successfully tokenized. Set up the nonce so we can use it for processing transactions server-side.
$( form ).find( '#rcp_submit_wrap' ).append( '<input type="hidden" name="payment_method_nonce" value="' + payload.nonce + '"/>' );
// Submit registration.
rcp_submit_registration_form( form, response );
}
} ).catch( function( error ) {
// Handle errors from payment method request.
rcpBraintreeHandleError( error );
} );
}
else {
throw 'RCP 3DS: There was an error validating you information.';
}
}).fail( function( error ) {
rcpBraintreeHandleError( rcp_script_options.braintree_invalid_nonce );
throw 'RCP 3DS: There was an error validating you information. Nonce expired. Reload the page.';
});
}
};
RCP_Braintree_Registration.init();
RCP_Braintree_Registration.registerTestInformation();
/**
* Update card details
*/
let RCP_Braintree_Update_Card = {
container: false,
recurringAmount: 0.00,
hasCardDetails: false,
init: function () {
RCP_Braintree_Update_Card.container = $( '#rcp_update_card_form' );
if ( ! RCP_Braintree_Update_Card.container.length ) {
return;
}
RCP_Braintree_Update_Card.mountUI();
RCP_Braintree_Update_Card.container.on( 'submit', RCP_Braintree_Update_Card.tokenizePayment );
},
/**
* Mount the drop-in UI
*/
mountUI: function() {
if ( ! document.getElementById( 'rcp-braintree-client-token' ) ) {
return;
}
rcp_braintree_script_options.dropin_ui_config.authorization = $( '#rcp-braintree-client-token' ).val();
let dropinArgs = rcp_braintree_script_options.dropin_ui_config;
/*
* Enabling this would allow customers to delete their saved payment methods. I've commented it out for now
* because if the customer deletes their CURRENT payment method then Braintree will automatically cancel
* the subscription, which is a bit annoying.
*/
//dropinArgs.vaultManager = true;
/*
* We set `preselectVaultedPaymentMethod` to false because we can't yet configure which one is pre-selected
* and we don't want to confuse anyone by having the wrong payment method pre-selected.
*/
dropinArgs.preselectVaultedPaymentMethod = false;
braintree.dropin.create( dropinArgs ).then( function( dropinInstance ) {
RCP_Braintree_Update_Card.dropinInstance = dropinInstance;
// Flag as having payment details or not.
if ( dropinInstance.isPaymentMethodRequestable() ) {
RCP_Braintree_Update_Card.hasCardDetails = true;
}
dropinInstance.on( 'paymentMethodRequestable', function ( requestableEvent ) {
RCP_Braintree_Update_Card.hasCardDetails = true;
} );
dropinInstance.on( 'noPaymentMethodRequestable', function ( requestableEvent ) {
RCP_Braintree_Update_Card.hasCardDetails = false;
} );
} ).catch( function( error ) {
// Handle errors from creating drop-in.
rcpBraintreeHandleError( error );
} );
},
/**
* Disable the submit button and change the text to "Please wait..."
*/
disableButton: function() {
let button = RCP_Braintree_Update_Card.container.find( '#rcp_submit' );
button.prop( 'disabled', true ).data( 'text', button.val() ).val( rcp_braintree_script_options.please_wait );
},
/**
* Enable the submit button and re-set the text back to the original value
*/
enableButton: function() {
let button = RCP_Braintree_Update_Card.container.find( '#rcp_submit' );
button.prop( 'disabled', false ).val( button.data( 'text' ) );
},
/**
* Tokenize the payment method
* @param e
*/
tokenizePayment: function ( e ) {
e.preventDefault();
if ( ! RCP_Braintree_Update_Card.hasCardDetails ) {
rcpBraintreeHandleError( rcp_script_options.enter_card_details );
return false;
}
// Clear errors.
$( '#rcp-braintree-dropin-errors' ).empty();
RCP_Braintree_Update_Card.disableButton();
let paymentMethodOptions = rcp_braintree_script_options.payment_method_options;
// Set authorization amount.
paymentMethodOptions.threeDSecure.amount = $( '#rcp-braintree-recurring-amount' ).val();
RCP_Braintree_Update_Card.dropinInstance.requestPaymentMethod( paymentMethodOptions ).then( function( payload ) {
if ( payload.liabilityShiftPossible && ! payload.liabilityShifted ) {
// 3D secure was possible, but failed.
// Clear the payment method.
RCP_Braintree_Update_Card.dropinInstance.clearSelectedPaymentMethod();
// Display error message.
throw rcp_braintree_script_options.try_new_payment;
} else {
// Payment was successfully tokenized. Set up the nonce so we can use it for processing transactions server-side.
RCP_Braintree_Update_Card.container.append( '<input type="hidden" name="payment_method_nonce" value="' + payload.nonce + '"/>' );
RCP_Braintree_Update_Card.container.off( 'submit', RCP_Braintree_Update_Card.tokenizePayment ).submit();
}
} ).catch( function( error ) {
// Handle errors from payment method request.
rcpBraintreeHandleError( error );
RCP_Braintree_Update_Card.enableButton();
return false;
} );
}
};
RCP_Braintree_Update_Card.init();
} );
/**
* Handle Braintree errors
* @param {string} error Error message.
*/
function rcpBraintreeHandleError( error ) {
let $ = jQuery;
let form = $( '#rcp_registration_form' );
let errorWrapper = $( '#rcp-braintree-dropin-errors' );
errorWrapper.empty().append( '<div class="rcp_message error" role="list"><p class="rcp_error" role="listitem">' + error + '</p>' );
if ( form.length > 0 ) {
form.unblock();
$( '#rcp_submit' ).val( rcp_script_options.register );
}
rcp_processing = false;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,536 @@
<?php
/**
* 2Checkout Payment Gateway
*
* @package Restrict Content Pro
* @subpackage Classes/Gateways/2Checkout
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.3
*/
class RCP_Payment_Gateway_2Checkout extends RCP_Payment_Gateway {
private $secret_word;
private $secret_key;
private $publishable_key;
private $seller_id;
private $environment;
/**
* Get things going
*
* @access public
* @since 2.3
* @return void
*/
public function init() {
global $rcp_options;
$this->supports[] = 'one-time';
$this->supports[] = 'recurring';
$this->supports[] = 'fees';
$this->supports[] = 'gateway-submits-form';
$this->secret_word = isset( $rcp_options['twocheckout_secret_word'] ) ? trim( $rcp_options['twocheckout_secret_word'] ) : '';
if( $this->test_mode ) {
$this->secret_key = isset( $rcp_options['twocheckout_test_private'] ) ? trim( $rcp_options['twocheckout_test_private'] ) : '';
$this->publishable_key = isset( $rcp_options['twocheckout_test_publishable'] ) ? trim( $rcp_options['twocheckout_test_publishable'] ) : '';
$this->seller_id = isset( $rcp_options['twocheckout_test_seller_id'] ) ? trim( $rcp_options['twocheckout_test_seller_id'] ) : '';
$this->environment = 'sandbox';
} else {
$this->secret_key = isset( $rcp_options['twocheckout_live_private'] ) ? trim( $rcp_options['twocheckout_live_private'] ) : '';
$this->publishable_key = isset( $rcp_options['twocheckout_live_publishable'] ) ? trim( $rcp_options['twocheckout_live_publishable'] ) : '';
$this->seller_id = isset( $rcp_options['twocheckout_live_seller_id'] ) ? trim( $rcp_options['twocheckout_live_seller_id'] ) : '';
$this->environment = 'production';
}
if( ! class_exists( 'Twocheckout' ) ) {
require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/twocheckout/Twocheckout.php';
}
} // end init
/**
* Process registration
*
* @access public
* @since 2.3
* @return void
*/
public function process_signup() {
Twocheckout::privateKey( $this->secret_key );
Twocheckout::sellerId( $this->seller_id );
Twocheckout::sandbox( $this->test_mode );
/**
* @var RCP_Payments $rcp_payments_db
*/
global $rcp_payments_db;
$member = new RCP_Member( $this->user_id ); // for backwards compatibility only
if( empty( $_POST['twoCheckoutToken'] ) ) {
rcp_errors()->add( 'missing_card_token', __( 'Missing 2Checkout token, please try again or contact support if the issue persists.', 'rcp' ), 'register' );
return;
}
$paid = false;
if ( $this->auto_renew ) {
$payment_type = 'Credit Card';
$line_items = array( array(
"recurrence" => $this->length . ' ' . ucfirst( $this->length_unit ),
"type" => 'product',
"price" => $this->amount,
"productId" => $this->subscription_id,
"name" => $this->subscription_name,
"quantity" => '1',
"tangible" => 'N',
"startupFee" => $this->initial_amount - $this->amount
) );
} else {
$payment_type = 'Credit Card One Time';
$line_items = array( array(
"recurrence" => 0,
"type" => 'product',
"price" => $this->initial_amount,
"productId" => $this->subscription_id,
"name" => $this->subscription_name,
"quantity" => '1',
"tangible" => 'N'
) );
}
try {
$charge = Twocheckout_Charge::auth( array(
'merchantOrderId' => $this->subscription_key,
'token' => $_POST['twoCheckoutToken'],
'currency' => strtolower( $this->currency ),
'billingAddr' => array(
'name' => sanitize_text_field( $_POST['rcp_card_name'] ),
'addrLine1' => sanitize_text_field( $_POST['rcp_card_address'] ),
'city' => sanitize_text_field( $_POST['rcp_card_city'] ),
'state' => sanitize_text_field( $_POST['rcp_card_state'] ),
'zipCode' => sanitize_text_field( $_POST['rcp_card_zip'] ),
'country' => sanitize_text_field( $_POST['rcp_card_country'] ),
'email' => $this->email,
),
"lineItems" => $line_items,
));
if( $charge['response']['responseCode'] == 'APPROVED' ) {
// This activates the user's account.
$rcp_payments_db->update( $this->payment->id, array(
'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ),
'payment_type' => $payment_type,
'transaction_id' => $charge['response']['orderNumber'],
'status' => 'complete'
) );
do_action( 'rcp_gateway_payment_processed', $member, $this->payment->id, $this );
$paid = true;
}
} catch ( Twocheckout_Error $e ) {
$this->error_message = $e->getMessage();
do_action( 'rcp_registration_failed', $this );
wp_die( $e->getMessage(), __( 'Error', 'rcp' ), array( 'response' => '401' ) );
}
if ( $paid ) {
$this->membership->add_note( __( 'Subscription started in 2Checkout', 'rcp' ) );
$this->membership->set_gateway_subscription_id( '2co_' . $charge['response']['orderNumber'] );
do_action( 'rcp_2co_signup', $this->user_id, $this );
}
// redirect to the success page, or error page if something went wrong
wp_redirect( $this->return_url ); exit;
}
/**
* Proccess webhooks
*
* @access public
* @since 2.3
* @return void
*/
public function process_webhooks() {
if ( isset( $_GET['listener'] ) && $_GET['listener'] == '2checkout' ) {
rcp_log( 'Starting to process 2Checkout webhook.' );
global $wpdb;
$hash = strtoupper( md5( $_POST['sale_id'] . $this->seller_id . $_POST['invoice_id'] . $this->secret_word ) );
if ( ! hash_equals( $hash, $_POST['md5_hash'] ) ) {
rcp_log( 'Exiting 2Checkout webhook - invalid MD5 hash.', true );
die('-1');
}
if ( empty( $_POST['message_type'] ) ) {
rcp_log( 'Exiting 2Checkout webhook - empty message_type.', true );
die( '-2' );
}
if ( empty( $_POST['vendor_id'] ) ) {
rcp_log( 'Exiting 2Checkout webhook - empty vendor_id.', true );
die( '-3' );
}
$subscription_key = sanitize_text_field( $_POST['vendor_order_id'] );
$this->membership = rcp_get_membership_by( 'subscription_key', $subscription_key );
if ( empty( $this->membership ) ) {
rcp_log( sprintf( 'Exiting 2Checkout webhook - membership not found from order ID %s.', $subscription_key ), true );
die( '-4' );
}
$member = new RCP_Member( $this->membership->get_user_id() ); // for backwards compatibility
if( 'twocheckout' != $this->membership->get_gateway() ) {
rcp_log( 'Exiting 2Checkout webhook - membership is not a 2Checkout subscription.' );
return;
}
rcp_log( sprintf( 'Processing webhook for membership #%d.', $this->membership->get_id() ) );
$payments = new RCP_Payments();
switch( strtoupper( $_POST['message_type'] ) ) {
case 'ORDER_CREATED' :
rcp_log( 'Processing 2Checkout ORDER_CREATED webhook.' );
break;
case 'REFUND_ISSUED' :
rcp_log( sprintf( 'Processing 2Checkout REFUND_ISSUED webhook for invoice ID %s.', $_POST['invoice_id'] ) );
$payment = $payments->get_payment_by( 'transaction_id', $_POST['invoice_id'] );
$payments->update( $payment->id, array( 'status' => 'refunded' ) );
if( ! empty( $_POST['recurring'] ) ) {
$this->membership->cancel();
$this->membership->add_note( __( 'Membership cancelled via refund 2Checkout', 'rcp' ) );
}
break;
case 'RECURRING_INSTALLMENT_SUCCESS' :
rcp_log( sprintf( 'Processing 2Checkout RECURRING_INSTALLMENT_SUCCESS webhook for membership #%d.', $this->membership->get_id() ) );
$payment_data = array(
'date' => date( 'Y-m-d H:i:s', strtotime( $_POST['timestamp'], current_time( 'timestamp' ) ) ),
'subscription' => $this->membership->get_membership_level_name(),
'payment_type' => sanitize_text_field( $_POST['payment_type'] ),
'transaction_type' => 'renewal',
'subscription_key' => $subscription_key,
'amount' => sanitize_text_field( $_POST['item_list_amount_1'] ), // don't have a total from this call, but this should be safe
'subtotal' => sanitize_text_field( $_POST['item_list_amount_1'] ),
'user_id' => $this->membership->get_user_id(),
'customer_id' => $this->membership->get_customer()->get_id(),
'membership_id' => $this->membership->get_id(),
'transaction_id' => sanitize_text_field( $_POST['invoice_id'] ),
'gateway' => 'twocheckout'
);
$recurring = ! empty( $_POST['recurring'] );
$this->membership->renew( $recurring );
$payment_id = $payments->insert( $payment_data );
$this->membership->add_note( __( 'Membership renewed in 2Checkout', 'rcp' ) );
do_action( 'rcp_webhook_recurring_payment_processed', $member, $payment_id, $this );
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
break;
case 'RECURRING_INSTALLMENT_FAILED' :
rcp_log( 'Processing 2Checkout RECURRING_INSTALLMENT_FAILED webhook.' );
if ( ! empty( $_POST['sale_id'] ) ) {
$this->webhook_event_id = sanitize_text_field( $_POST['sale_id'] );
}
do_action( 'rcp_recurring_payment_failed', $member, $this );
break;
case 'RECURRING_STOPPED' :
rcp_log( 'Processing 2Checkout RECURRING_STOPPED webhook.' );
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() ) );
} else {
if ( $this->membership->is_active() ) {
$this->membership->cancel();
$this->membership->add_note( __( 'Membership cancelled via 2Checkout webhook.', 'rcp' ) );
} else {
rcp_log( sprintf( 'Membership #%d is not active - not cancelling.', $this->membership->get_id() ) );
}
do_action( 'rcp_webhook_cancel', $member, $this );
}
break;
case 'RECURRING_COMPLETE' :
rcp_log( 'Processing 2Checkout RECURRING_COMPLETE webhook.' );
break;
case 'RECURRING_RESTARTED' :
rcp_log( 'Processing 2Checkout RECURRING_RESTARTED webhook.' );
$this->membership->set_status( 'active' );
$this->membership->add_note( __( 'Subscription restarted in 2Checkout', 'rcp' ) );
do_action( 'rcp_webhook_recurring_payment_profile_created', $member, $this );
break;
case 'FRAUD_STATUS_CHANGED' :
rcp_log( sprintf( 'Processing 2Checkout FRAUD_STATUS_CHANGED webhook. Status: %s', $_POST['fraud_status'] ) );
switch ( $_POST['fraud_status'] ) {
case 'pass':
break;
case 'fail':
$this->membership->set_status( 'pending' );
$this->membership->add_note( __( 'Payment flagged as fraudulent in 2Checkout', 'rcp' ) );
break;
case 'wait':
break;
}
break;
}
do_action( 'rcp_2co_' . strtolower( $_POST['message_type'] ) . '_ins', $member );
die( 'success');
}
}
/**
* Display fields and add extra JavaScript
*
* @access public
* @since 2.3
* @return void
*/
public function fields() {
ob_start();
?>
<script type="text/javascript">
// Called when token created successfully.
var successCallback = function(data) {
// re-enable the submit button
jQuery('#rcp_registration_form #rcp_submit').attr("disabled", false);
// Remove loding overlay
jQuery('#rcp_ajax_loading').hide();
var form$ = jQuery('#rcp_registration_form');
// token contains id, last4, and card type
var token = data.response.token.token;
// insert the token into the form so it gets submitted to the server
form$.append("<input type='hidden' name='twoCheckoutToken' value='" + token + "' />");
form$.get(0).submit();
};
// Called when token creation fails.
var errorCallback = function(data) {
if (data.errorCode === 200) {
tokenRequest();
} else {
jQuery('#rcp_registration_form').unblock();
jQuery('#rcp_submit').before( '<div class="rcp_message error"><p class="rcp_error"><span>' + data.errorMsg + '</span></p></div>' );
jQuery('#rcp_submit').val( rcp_script_options.register );
}
};
var tokenRequest = function() {
// Setup token request arguments
var args = {
sellerId: '<?php echo $this->seller_id; ?>',
publishableKey: '<?php echo $this->publishable_key; ?>',
ccNo: jQuery('.rcp_card_number').val(),
cvv: jQuery('.rcp_card_cvc').val(),
expMonth: jQuery('.rcp_card_exp_month').val(),
expYear: jQuery('.rcp_card_exp_year').val()
};
// Make the token request
TCO.requestToken(successCallback, errorCallback, args);
};
jQuery(document).ready(function($) {
// Pull in the public encryption key for our environment
TCO.loadPubKey('<?php echo $this->environment; ?>');
jQuery('body').on('rcp_register_form_submission', function rcp_2co_register_form_submission_handler(event, response, form_id) {
if ( response.gateway.slug !== 'twocheckout' ) {
return;
}
event.preventDefault();
/*
* Create token if the amount due today is greater than $0, or if the recurring
* amount is greater than $0 and auto renew is enabled.
*/
if( response.total > 0 || ( response.recurring_total > 0 && true == response.auto_renew ) ) {
// Call our token request function
tokenRequest();
// Prevent form from submitting
return false;
}
});
});
</script>
<?php
rcp_get_template_part( 'card-form', 'full' );
return ob_get_clean();
}
/**
* Validate additional fields during registration submission
*
* @access public
* @since 2.3
* @return void
*/
public function validate_fields() {
if( empty( $_POST['rcp_card_cvc'] ) ) {
rcp_errors()->add( 'missing_card_code', __( 'The security code you have entered is invalid', 'rcp' ), 'register' );
}
if( empty( $_POST['rcp_card_address'] ) ) {
rcp_errors()->add( 'missing_card_address', __( 'The address you have entered is invalid', 'rcp' ), 'register' );
}
if( empty( $_POST['rcp_card_city'] ) ) {
rcp_errors()->add( 'missing_card_city', __( 'The city you have entered is invalid', 'rcp' ), 'register' );
}
if( empty( $_POST['rcp_card_state'] ) && $this->card_needs_state_and_zip() ) {
rcp_errors()->add( 'missing_card_state', __( 'The state you have entered is invalid', 'rcp' ), 'register' );
}
if( empty( $_POST['rcp_card_country'] ) ) {
rcp_errors()->add( 'missing_card_country', __( 'The country you have entered is invalid', 'rcp' ), 'register' );
}
if( empty( $_POST['rcp_card_zip'] ) && $this->card_needs_state_and_zip() ) {
rcp_errors()->add( 'missing_card_zip', __( 'The zip / postal code you have entered is invalid', 'rcp' ), 'register' );
}
}
/**
* Load 2Checkout JS
*
* @access public
* @since 2.3
* @return void
*/
public function scripts() {
wp_enqueue_script( 'twocheckout', 'https://www.2checkout.com/checkout/api/2co.min.js', array( 'jquery' ) );
}
/**
* Determine if zip / state are required
*
* @access private
* @since 2.3
* @return bool
*/
private function card_needs_state_and_zip() {
$ret = true;
if( ! empty( $_POST['rcp_card_country'] ) ) {
$needs_zip = array(
'AR',
'AU',
'BG',
'CA',
'CH',
'CY',
'EG',
'FR',
'IN',
'ID',
'IT',
'JP',
'MY',
'ME',
'NL',
'PA',
'PH',
'PO',
'RO',
'RU',
'SR',
'SG',
'ZA',
'ES',
'SW',
'TH',
'TU',
'GB',
'US'
);
if( ! in_array( $_POST['rcp_card_country'], $needs_zip ) ) {
$ret = false;
}
}
return $ret;
}
}

View File

@@ -0,0 +1,841 @@
<?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 subscriptions 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;
}
}

View File

@@ -0,0 +1,782 @@
<?php
use RCP\Membership_Level;
/**
* PayPal Express Gateway class
*
* @package Restrict Content Pro
* @subpackage Classes/Gateways/PayPal Express
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.1
*/
class RCP_Payment_Gateway_PayPal_Express extends RCP_Payment_Gateway {
private $api_endpoint;
private $checkout_url;
protected $username;
protected $password;
protected $signature;
/**
* Get things going
*
* @access public
* @since 2.1
* @return void
*/
public function init() {
global $rcp_options;
$this->supports[] = 'one-time';
$this->supports[] = 'recurring';
$this->supports[] = 'fees';
$this->supports[] = 'trial';
$this->supports[] = 'expiration-extension-on-renewals'; // @link https://github.com/restrictcontentpro/restrict-content-pro/issues/1259
if( $this->test_mode ) {
$this->api_endpoint = 'https://api-3t.sandbox.paypal.com/nvp';
$this->checkout_url = 'https://www.sandbox.paypal.com/webscr&cmd=_express-checkout&token=';
} else {
$this->api_endpoint = 'https://api-3t.paypal.com/nvp';
$this->checkout_url = 'https://www.paypal.com/webscr&cmd=_express-checkout&token=';
}
if( rcp_has_paypal_api_access() ) {
$creds = rcp_get_paypal_api_credentials();
$this->username = $creds['username'];
$this->password = $creds['password'];
$this->signature = $creds['signature'];
}
}
/**
* Process registration
*
* @access public
* @since 2.1
* @return void
*/
public function process_signup() {
global $rcp_options;
if( $this->auto_renew ) {
$amount = $this->amount;
} else {
$amount = $this->initial_amount;
}
$cancel_url = wp_get_referer();
if ( empty( $cancel_url ) ) {
$cancel_url = get_permalink( $rcp_options['registration_page'] );
}
$args = array(
'USER' => $this->username,
'PWD' => $this->password,
'SIGNATURE' => $this->signature,
'VERSION' => '124',
'METHOD' => 'SetExpressCheckout',
'PAYMENTREQUEST_0_AMT' => $amount,
'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
'PAYMENTREQUEST_0_CURRENCYCODE' => strtoupper( $this->currency ),
'PAYMENTREQUEST_0_ITEMAMT' => $amount,
'PAYMENTREQUEST_0_SHIPPINGAMT' => 0,
'PAYMENTREQUEST_0_TAXAMT' => 0,
'PAYMENTREQUEST_0_DESC' => html_entity_decode( substr( $this->subscription_name, 0, 127 ), ENT_COMPAT, 'UTF-8' ),
'PAYMENTREQUEST_0_CUSTOM' => $this->user_id . '|' . absint( $this->membership->get_id() ),
'PAYMENTREQUEST_0_NOTIFYURL' => add_query_arg( 'listener', 'EIPN', home_url( 'index.php' ) ),
'EMAIL' => $this->email,
'RETURNURL' => add_query_arg( array( 'rcp-confirm' => 'paypal_express', 'membership_id' => urlencode( $this->membership->get_id() ) ), get_permalink( $rcp_options['registration_page'] ) ),
'CANCELURL' => $cancel_url,
'REQCONFIRMSHIPPING' => 0,
'NOSHIPPING' => 1,
'ALLOWNOTE' => 0,
'ADDROVERRIDE' => 0,
'PAGESTYLE' => ! empty( $rcp_options['paypal_page_style'] ) ? trim( $rcp_options['paypal_page_style'] ) : '',
'SOLUTIONTYPE' => 'Sole',
'LANDINGPAGE' => 'Billing',
);
if( $this->auto_renew && ! empty( $this->length ) ) {
$args['L_BILLINGAGREEMENTDESCRIPTION0'] = html_entity_decode( substr( $this->subscription_name, 0, 127 ), ENT_COMPAT, 'UTF-8' );
$args['L_BILLINGTYPE0'] = 'RecurringPayments';
$args['RETURNURL'] = add_query_arg( array( 'rcp-recurring' => '1' ), $args['RETURNURL'] );
}
$request = wp_remote_post( $this->api_endpoint, array(
'timeout' => 45,
'httpversion' => '1.1',
'body' => $args
) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
if( is_wp_error( $request ) ) {
$this->error_message = $request->get_error_message();
do_action( 'rcp_registration_failed', $this );
do_action( 'rcp_paypal_express_signup_payment_failed', $request, $this );
$error = '<p>' . __( 'An unidentified error occurred.', 'rcp' ) . '</p>';
$error .= '<p>' . $request->get_error_message() . '</p>';
wp_die( $error, __( 'Error', 'rcp' ), array( 'response' => '401' ) );
} elseif ( 200 == $code && 'OK' == $message ) {
if( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if( 'failure' === strtolower( $body['ACK'] ) ) {
$this->error_message = $body['L_LONGMESSAGE0'];
do_action( 'rcp_registration_failed', $this );
$error = '<p>' . __( 'PayPal token creation failed.', 'rcp' ) . '</p>';
$error .= '<p>' . __( 'Error message:', 'rcp' ) . ' ' . $body['L_LONGMESSAGE0'] . '</p>';
$error .= '<p>' . __( 'Error code:', 'rcp' ) . ' ' . $body['L_ERRORCODE0'] . '</p>';
wp_die( $error, __( 'Error', 'rcp' ), array( 'response' => '401' ) );
} else {
// Successful token
wp_redirect( $this->checkout_url . $body['TOKEN'] );
exit;
}
} else {
do_action( 'rcp_registration_failed', $this );
wp_die( __( 'Something has gone wrong, please try again', 'rcp' ), __( 'Error', 'rcp' ), array( 'back_link' => true, 'response' => '401' ) );
}
}
/**
* Validate additional fields during registration submission
*
* @access public
* @since 2.1
* @return void
*/
public function validate_fields() {
if( ! rcp_has_paypal_api_access() ) {
rcp_errors()->add( 'no_paypal_api', __( 'You have not configured PayPal API access. Please configure it in Restrict &rarr; Settings', 'rcp' ), 'register' );
}
}
/**
* Process payment confirmation after returning from PayPal
*
* @access public
* @since 2.1
* @return void
*/
public function process_confirmation() {
if ( isset( $_POST['rcp_ppe_confirm_nonce'] ) && wp_verify_nonce( $_POST['rcp_ppe_confirm_nonce'], 'rcp-ppe-confirm-nonce' ) ) {
$details = $this->get_checkout_details( $_POST['token'] );
$membership = rcp_get_membership( absint( $details['membership_id'] ) );
/**
* Always process a one-time payment if the initial amount is > 0.
*/
if ( $details['initial_amount'] > 0 ) {
$args = array(
'USER' => $this->username,
'PWD' => $this->password,
'SIGNATURE' => $this->signature,
'VERSION' => '124',
'METHOD' => 'DoExpressCheckoutPayment',
'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
'TOKEN' => $_POST['token'],
'PAYERID' => $_POST['payer_id'],
'PAYMENTREQUEST_0_AMT' => $details['initial_amount'],
'PAYMENTREQUEST_0_ITEMAMT' => $details['initial_amount'],
'PAYMENTREQUEST_0_SHIPPINGAMT' => 0,
'PAYMENTREQUEST_0_TAXAMT' => 0,
'PAYMENTREQUEST_0_CURRENCYCODE' => $details['CURRENCYCODE'],
'BUTTONSOURCE' => 'EasyDigitalDownloads_SP'
);
$request = wp_remote_post( $this->api_endpoint, array(
'timeout' => 45,
'httpversion' => '1.1',
'body' => $args
) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
try {
if ( is_wp_error( $request ) ) {
$error = '<p>' . __( 'An unidentified error occurred.', 'rcp' ) . '</p>';
$error .= '<p>' . $request->get_error_message() . '</p>';
throw new Exception( $error );
}
if ( 200 != $code || 'OK' != $message ) {
throw new Exception( __( 'Something has gone wrong, please try again', 'rcp' ) );
}
if ( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if ( 'failure' === strtolower( $body['ACK'] ) ) {
$error = '<p>' . __( 'PayPal payment processing failed.', 'rcp' ) . '</p>';
$error .= '<p>' . __( 'Error message:', 'rcp' ) . ' ' . $body['L_LONGMESSAGE0'] . '</p>';
$error .= '<p>' . __( 'Error code:', 'rcp' ) . ' ' . $body['L_ERRORCODE0'] . '</p>';
throw new Exception( $error );
}
// At this point we know we're successful!
$payment_data = array(
'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ),
'subscription' => $membership->get_membership_level_name(),
'payment_type' => 'PayPal Express One Time',
'subscription_key' => $membership->get_subscription_key(),
'amount' => $body['PAYMENTINFO_0_AMT'],
'user_id' => $membership->get_user_id(),
'transaction_id' => $body['PAYMENTINFO_0_TRANSACTIONID'],
'status' => 'complete'
);
$rcp_payments = new RCP_Payments;
$pending_payment_id = rcp_get_membership_meta( $membership->get_id(), 'pending_payment_id', true );
if ( ! empty( $pending_payment_id ) ) {
$rcp_payments->update( $pending_payment_id, $payment_data );
}
// Membership is activated via rcp_complete_registration()
// If we're non-recurring, bail now.
if ( empty( $_GET['rcp-recurring'] ) ) {
wp_redirect( esc_url_raw( rcp_get_return_url() ) );
exit;
}
} catch ( Exception $e ) {
wp_die( $e->getMessage(), __( 'Error', 'rcp' ), array( 'response' => '401' ) );
}
}
/**
* Successful initial payment, now create the recurring profile
*/
// Fetch new membership object to refresh expiration date.
$this->membership = rcp_get_membership( $membership->get_id() );
// Get expiration date to use as subscription start date.
$base_date = $this->membership->get_expiration_date( false );
$timezone = get_option( 'timezone_string' );
$timezone = ! empty( $timezone ) ? $timezone : 'UTC';
$datetime = new DateTime( $base_date, new DateTimeZone( $timezone ) );
$current_time = getdate();
$datetime->setTime( $current_time['hours'], $current_time['minutes'], $current_time['seconds'] );
$args = array(
'USER' => $this->username,
'PWD' => $this->password,
'SIGNATURE' => $this->signature,
'VERSION' => '124',
'TOKEN' => $_POST['token'],
'METHOD' => 'CreateRecurringPaymentsProfile',
'PROFILESTARTDATE' => date( 'Y-m-d\TH:i:s', $datetime->getTimestamp() ),
'BILLINGPERIOD' => ucwords( $details['subscription']['duration_unit'] ),
'BILLINGFREQUENCY' => $details['subscription']['duration'],
'AMT' => $details['AMT'],
'CURRENCYCODE' => $details['CURRENCYCODE'],
'FAILEDINITAMTACTION' => 'CancelOnFailure',
'L_BILLINGTYPE0' => 'RecurringPayments',
'DESC' => html_entity_decode( substr( $details['subscription']['name'], 0, 127 ), ENT_COMPAT, 'UTF-8' ),
'BUTTONSOURCE' => 'EasyDigitalDownloads_SP'
);
$request = wp_remote_post( $this->api_endpoint, array(
'timeout' => 45,
'httpversion' => '1.1',
'body' => $args
) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
try {
if ( is_wp_error( $request ) ) {
$error = '<p>' . __( 'An unidentified error occurred.', 'rcp' ) . '</p>';
$error .= '<p>' . $request->get_error_message() . '</p>';
throw new Exception( $error );
}
if ( 200 != $code || 'OK' != $message ) {
throw new Exception( __( 'Something has gone wrong, please try again', 'rcp' ) );
}
if ( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if ( 'failure' === strtolower( $body['ACK'] ) ) {
$error = '<p>' . __( 'PayPal payment processing failed.', 'rcp' ) . '</p>';
$error .= '<p>' . __( 'Error message:', 'rcp' ) . ' ' . $body['L_LONGMESSAGE0'] . '</p>';
$error .= '<p>' . __( 'Error code:', 'rcp' ) . ' ' . $body['L_ERRORCODE0'] . '</p>';
throw new Exception( $error );
}
if ( empty( $body['PROFILEID'] ) ) {
$error = '<p>' . __( 'PayPal payment processing failed.', 'rcp' ) . '</p>';
$error .= '<p>' . __( 'Error message: Unable to retrieve subscription ID', 'rcp' ) . '</p>';
throw new Exception( $error );
}
// At this point, we know it was successful!
$this->membership->set_gateway_subscription_id( $body['PROFILEID'] );
} catch ( Exception $e ) {
// Only show the customer an error if the initial amount was 0.
if ( empty( $details['initial_amount'] ) ) {
wp_die( $e->getMessage(), __( 'Error', 'rcp' ), array( 'response' => '401' ) );
} else {
// Initial payment was successful, it's just the subscription that failed, so let's unset auto renew.
$this->membership->set_recurring( false );
$this->membership->add_note( sprintf( __( 'PayPal Standard Gateway: An error occurred while creating the subscription: %s', 'rcp'), wp_strip_all_tags( $e->getMessage() ) ) );
}
}
wp_redirect( esc_url_raw( rcp_get_return_url() ) ); exit;
} elseif ( ! empty( $_GET['token'] ) && ! empty( $_GET['PayerID'] ) ) {
/**
* Show confirmation page.
*/
add_filter( 'the_content', array( $this, 'confirmation_form' ), 9999999 );
}
}
/**
* Display the confirmation form
*
* @since 2.1
* @return string
*/
public function confirmation_form() {
global $rcp_checkout_details;
$token = sanitize_text_field( $_GET['token'] );
$rcp_checkout_details = $this->get_checkout_details( $token );
if ( ! is_array( $rcp_checkout_details ) ) {
$error = is_wp_error( $rcp_checkout_details ) ? $rcp_checkout_details->get_error_message() : __( 'Invalid response code from PayPal', 'rcp' );
return '<p>' . sprintf( __( 'An unexpected PayPal error occurred. Error message: %s.', 'rcp' ), $error ) . '</p>';
}
ob_start();
rcp_get_template_part( 'paypal-express-confirm' );
return ob_get_clean();
}
/**
* Process PayPal IPN
*
* @access public
* @since 2.1
* @return void
*/
public function process_webhooks() {
if( ! isset( $_GET['listener'] ) || strtoupper( $_GET['listener'] ) != 'EIPN' ) {
return;
}
rcp_log( 'Starting to process PayPal Express IPN.' );
$user_id = 0;
$posted = apply_filters('rcp_ipn_post', $_POST ); // allow $_POST to be modified
$membership = false;
$custom = ! empty( $posted['custom'] ) ? explode( '|', $posted['custom'] ) : false;
if( ! empty( $posted['recurring_payment_id'] ) ) {
$membership = rcp_get_membership_by( 'gateway_subscription_id', $posted['recurring_payment_id'] );
}
if( empty( $membership ) && ! empty( $custom[1] ) ) {
$membership = rcp_get_membership( absint( $custom[1] ) );
}
if( empty( $membership ) || ! $membership->get_id() > 0 ) {
rcp_log( 'Exiting PayPal Express IPN - membership ID not found.', true );
die( 'no membership found' );
}
$this->membership = $membership;
rcp_log( sprintf( 'Processing IPN for membership #%d.', $membership->get_id() ) );
if ( empty( $user_id ) ) {
$user_id = $membership->get_user_id();
}
$member = new RCP_Member( $membership->get_user_id() ); // for backwards compatibility
$membership_level_id = $membership->get_object_id();
if( ! $membership_level_id ) {
rcp_log( 'Exiting PayPal Express IPN - no membership level ID.', true );
die( 'no membership level found' );
}
$membership_level = rcp_get_membership_level( $membership_level_id );
if ( ! $membership_level instanceof Membership_Level ) {
rcp_log( 'Exiting PayPal Express IPN - no membership level found.', true );
die( 'no membership level found' );
}
$amount = isset( $posted['mc_gross'] ) ? number_format( (float) $posted['mc_gross'], 2, '.', '' ) : false;
$membership_gateway = $membership->get_gateway();
// setup the payment info in an array for storage
$payment_data = array(
'subscription' => $membership_level->get_name(),
'payment_type' => $posted['txn_type'],
'subscription_key' => $membership->get_subscription_key(),
'user_id' => $user_id,
'customer_id' => $membership->get_customer()->get_id(),
'membership_id' => $membership->get_id(),
'status' => 'complete',
'gateway' => ! empty( $membership_gateway ) && 'paypal_pro' == $membership_gateway ? 'paypal_pro' : 'paypal_express'
);
if ( false !== $amount ) {
$payment_data['amount'] = $amount;
}
if ( ! empty( $posted['payment_date'] ) ) {
$payment_data['date'] = date( 'Y-m-d H:i:s', strtotime( $posted['payment_date'] ) );
}
if ( ! empty( $posted['txn_id'] ) ) {
$payment_data['transaction_id'] = sanitize_text_field( $posted['txn_id'] );
}
do_action( 'rcp_valid_ipn', $payment_data, $user_id, $posted );
/* now process the kind of subscription/payment */
$rcp_payments = new RCP_Payments();
$pending_payment_id = rcp_get_membership_meta( $membership->get_id(), 'pending_payment_id', true );
// Subscriptions
switch ( $posted['txn_type'] ) :
case "recurring_payment_profile_created":
rcp_log( 'Processing PayPal Express recurring_payment_profile_created IPN.' );
if ( isset( $posted['initial_payment_txn_id'] ) ) {
$transaction_id = ( 'Completed' == $posted['initial_payment_status'] ) ? $posted['initial_payment_txn_id'] : '';
} else {
$transaction_id = $posted['ipn_track_id'];
}
if ( empty( $transaction_id ) || $rcp_payments->payment_exists( $transaction_id ) ) {
rcp_log( sprintf( 'Breaking out of PayPal Express IPN recurring_payment_profile_created. Transaction ID not given or payment already exists. TXN ID: %s', $transaction_id ), true );
break;
}
// setup the payment info in an array for storage
$payment_data['date'] = date( 'Y-m-d H:i:s', strtotime( $posted['time_created'] ) );
$payment_data['amount'] = number_format( (float) $posted['initial_payment_amount'], 2, '.', '' );
$payment_data['transaction_id'] = sanitize_text_field( $transaction_id );
if ( ! empty( $pending_payment_id ) ) {
$payment_id = $pending_payment_id;
// This activates the membership.
$rcp_payments->update( $pending_payment_id, $payment_data );
} elseif( floatval( $payment_data['amount'] ) > 0 ) {
$payment_data['subtotal'] = $payment_data['amount'];
$payment_id = $rcp_payments->insert( $payment_data );
$expiration = date( 'Y-m-d 23:59:59', strtotime( $posted['next_payment_date'] ) );
$membership->renew( $membership->is_recurring(), 'active', $expiration );
}
do_action( 'rcp_webhook_recurring_payment_profile_created', $member, $this );
if ( isset( $payment_id ) ) {
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
}
break;
case "recurring_payment" :
rcp_log( 'Processing PayPal Express recurring_payment IPN.' );
// when a user makes a recurring payment
update_user_meta( $user_id, 'rcp_paypal_subscriber', $posted['payer_id'] );
$membership->set_gateway_subscription_id( $posted['recurring_payment_id'] );
if ( 'failed' == strtolower( $posted['payment_status'] ) ) {
// Recurring payment failed.
$membership->add_note( sprintf( __( 'Transaction ID %s failed in PayPal.', 'rcp' ), $posted['txn_id'] ) );
die( 'Subscription payment failed' );
} elseif ( 'pending' == strtolower( $posted['payment_status'] ) ) {
// Recurring payment pending (such as echeck).
$pending_reason = ! empty( $posted['pending_reason'] ) ? $posted['pending_reason'] : __( 'unknown', 'rcp' );
$membership->add_note( sprintf( __( 'Transaction ID %s is pending in PayPal for reason: %s', 'rcp' ), $posted['txn_id'], $pending_reason ) );
die( 'Subscription payment pending' );
}
// Recurring payment succeeded.
$membership->renew( true );
$payment_data['transaction_type'] = 'renewal';
// record this payment in the database
$payment_id = $rcp_payments->insert( $payment_data );
do_action( 'rcp_ipn_subscr_payment', $user_id );
do_action( 'rcp_webhook_recurring_payment_processed', $member, $payment_id, $this );
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
die( 'successful recurring_payment' );
break;
case "recurring_payment_profile_cancel" :
rcp_log( 'Processing PayPal Express recurring_payment_profile_cancel IPN.' );
if( ! $member->just_upgraded() ) {
if( isset( $posted['initial_payment_status'] ) && 'Failed' == $posted['initial_payment_status'] ) {
// Initial payment failed, so set the user back to pending.
$membership->set_status( 'pending' );
$membership->add_note( __( 'Initial payment failed in PayPal Express.', 'rcp' ) );
$this->error_message = __( 'Initial payment failed.', 'rcp' );
do_action( 'rcp_registration_failed', $this );
do_action( 'rcp_paypal_express_initial_payment_failed', $member, $posted, $this );
} else {
// If this is a completed payment plan, we can skip any cancellation actions. This is handled in renewals.
if ( $membership->has_payment_plan() && $membership->at_maximum_renewals() ) {
rcp_log( sprintf( 'Membership #%d has completed its payment plan - not cancelling.', $membership->get_id() ) );
die( 'membership payment plan completed' );
}
// user is marked as cancelled but retains access until end of term
$membership->cancel();
$membership->add_note( __( 'Membership cancelled via PayPal Express IPN.', 'rcp' ) );
// set the use to no longer be recurring
delete_user_meta( $user_id, 'rcp_paypal_subscriber' );
do_action( 'rcp_ipn_subscr_cancel', $user_id );
do_action( 'rcp_webhook_cancel', $member, $this );
}
die( 'successful recurring_payment_profile_cancel' );
}
break;
case "recurring_payment_failed" :
case "recurring_payment_suspended_due_to_max_failed_payment" :
rcp_log( 'Processing PayPal Express recurring_payment_failed or recurring_payment_suspended_due_to_max_failed_payment IPN.' );
if( ! in_array( $membership->get_status(), array( 'cancelled', 'expired' ) ) ) {
$membership->set_status( 'expired' );
}
if ( ! empty( $posted['txn_id'] ) ) {
$this->webhook_event_id = sanitize_text_field( $posted['txn_id'] );
} elseif ( ! empty( $posted['ipn_track_id'] ) ) {
$this->webhook_event_id = sanitize_text_field( $posted['ipn_track_id'] );
}
do_action( 'rcp_ipn_subscr_failed' );
do_action( 'rcp_recurring_payment_failed', $member, $this );
die( 'successful recurring_payment_failed or recurring_payment_suspended_due_to_max_failed_payment' );
break;
case "web_accept" :
rcp_log( sprintf( 'Processing PayPal Express web_accept IPN. Payment status: %s', $posted['payment_status'] ) );
switch ( strtolower( $posted['payment_status'] ) ) :
case 'completed' :
if ( empty( $payment_data['transaction_id'] ) || $rcp_payments->payment_exists( $payment_data['transaction_id'] ) ) {
rcp_log( sprintf( 'Not inserting PayPal Express web_accept payment. Transaction ID not given or payment already exists. TXN ID: %s', $payment_data['transaction_id'] ), true );
} else {
$rcp_payments->insert( $payment_data );
}
// Member was already activated.
break;
case 'denied' :
case 'expired' :
case 'failed' :
case 'voided' :
if ( $membership->is_active() ) {
$membership->cancel();
} else {
rcp_log( sprintf( 'Membership #%d is not active - not cancelling account.', $membership->get_id() ) );
}
break;
endswitch;
die( 'successful web_accept' );
break;
endswitch;
}
/**
* Get checkout details
*
* @param string $token
*
* @return array|bool|string|WP_Error
*/
public function get_checkout_details( $token = '' ) {
$args = array(
'USER' => $this->username,
'PWD' => $this->password,
'SIGNATURE' => $this->signature,
'VERSION' => '124',
'METHOD' => 'GetExpressCheckoutDetails',
'TOKEN' => $token
);
$request = wp_remote_post( $this->api_endpoint, array(
'timeout' => 45,
'httpversion' => '1.1',
'body' => $args
) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
if( is_wp_error( $request ) ) {
return $request;
} elseif ( 200 == $code && 'OK' == $message ) {
if( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
$payments = new RCP_Payments();
$membership = rcp_get_membership( absint( $_GET['membership_id'] ) );
$membership_level_id = $membership->get_object_id();
$pending_payment_id = rcp_get_membership_meta( $membership->get_id(), 'pending_payment_id', true );
$pending_payment = ! empty( $pending_payment_id ) ? $payments->get_payment( $pending_payment_id ) : false;
if ( ! empty( $pending_payment ) ) {
$pending_amount = $pending_payment->amount;
} elseif ( 0 == $membership->get_times_billed() ) {
$pending_amount = $membership->get_initial_amount();
} else {
$pending_amount = $membership->get_recurring_amount();
}
$membership_level = rcp_get_membership_level( $membership_level_id );
$body['subscription'] = $membership_level instanceof Membership_Level ? $membership_level->export_vars() : array();
$body['initial_amount'] = $pending_amount;
$custom = explode( '|', $body['PAYMENTREQUEST_0_CUSTOM'] );
$body['membership_id'] = ! empty( $custom[1] ) ? absint( $custom[1] ) : 0;
return $body;
}
return false;
}
}

View File

@@ -0,0 +1,288 @@
<?php
/**
* PayPal Pro Gateway class
*
* @package Restrict Content Pro
* @subpackage Classes/Gateways/PayPal Pro
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.1
*/
class RCP_Payment_Gateway_PayPal_Pro extends RCP_Payment_Gateway {
private $api_endpoint;
protected $username;
protected $password;
protected $signature;
/**
* Get things going
*
* @access public
* @since 2.1
* @return void
*/
public function init() {
$this->supports[] = 'one-time';
$this->supports[] = 'recurring';
$this->supports[] = 'fees';
$this->supports[] = 'trial';
$this->supports[] = 'card-updates';
if( $this->test_mode ) {
$this->api_endpoint = 'https://api-3t.sandbox.paypal.com/nvp';
} else {
$this->api_endpoint = 'https://api-3t.paypal.com/nvp';
}
if( rcp_has_paypal_api_access() ) {
$creds = rcp_get_paypal_api_credentials();
$this->username = $creds['username'];
$this->password = $creds['password'];
$this->signature = $creds['signature'];
}
}
/**
* Process registration
*
* @access public
* @since 2.1
* @return void
*/
public function process_signup() {
global $rcp_options;
/**
* @var RCP_Payments $rcp_payments_db
*/
global $rcp_payments_db;
if ( is_user_logged_in() ) {
$user_data = get_userdata( $this->user_id );
$first_name = $user_data->first_name;
$last_name = $user_data->last_name;
} else {
$first_name = $_POST['rcp_user_first'];
$last_name = $_POST['rcp_user_last'];
}
$descriptor = get_bloginfo( 'name' ) . ' - ' . $this->subscription_name;
if ( strlen( $descriptor ) > 23 ) {
$descriptor = substr( $descriptor, 0, 23 );
}
$descriptor_city = get_bloginfo( 'admin_email' );
if ( strlen( $descriptor_city ) > 13 ) {
$descriptor_city = substr( $descriptor_city, 0, 13 );
}
$args = array(
'USER' => $this->username,
'PWD' => $this->password,
'SIGNATURE' => $this->signature,
'VERSION' => '124',
'METHOD' => $this->auto_renew ? 'CreateRecurringPaymentsProfile' : 'DoDirectPayment',
'AMT' => $this->auto_renew ? $this->amount : $this->initial_amount,
'CURRENCYCODE' => strtoupper( $this->currency ),
'SHIPPINGAMT' => 0,
'TAXAMT' => 0,
'DESC' => $this->subscription_name,
'SOFTDESCRIPTOR' => $descriptor,
'SOFTDESCRIPTORCITY' => $descriptor_city,
'CUSTOM' => $this->user_id . '|' . $this->membership->get_id(),
'NOTIFYURL' => add_query_arg( 'listener', 'EIPN', home_url( 'index.php' ) ),
'EMAIL' => $this->email,
'FIRSTNAME' => sanitize_text_field( $first_name ),
'LASTNAME' => sanitize_text_field( $last_name ),
'STREET' => sanitize_text_field( $_POST['rcp_card_address'] ),
'CITY' => sanitize_text_field( $_POST['rcp_card_city'] ),
'STATE' => sanitize_text_field( $_POST['rcp_card_state'] ),
'COUNTRYCODE' => sanitize_text_field( $_POST['rcp_card_country'] ),
'CREDITCARDTYPE' => '',
'ACCT' => sanitize_text_field( $_POST['rcp_card_number'] ),
'EXPDATE' => sanitize_text_field( $_POST['rcp_card_exp_month'] . $_POST['rcp_card_exp_year'] ), // needs to be in the format 062019
'CVV2' => sanitize_text_field( $_POST['rcp_card_cvc'] ),
'ZIP' => sanitize_text_field( $_POST['rcp_card_zip'] ),
'BUTTONSOURCE' => 'EasyDigitalDownloads_SP',
'PROFILESTARTDATE' => date( 'Y-m-d\TH:i:s', strtotime( '+' . $this->length . ' ' . $this->length_unit, time() ) ),
'BILLINGPERIOD' => ucwords( $this->length_unit ),
'BILLINGFREQUENCY' => $this->length,
'FAILEDINITAMTACTION'=> 'CancelOnFailure',
'TOTALBILLINGCYCLES' => $this->auto_renew ? 0 : 1
);
if ( $this->auto_renew ) {
if ( $this->initial_amount >= 0 ) {
$args['INITAMT'] = $this->initial_amount;
}
}
if ( $this->auto_renew && ! empty( $this->subscription_start_date ) ) {
// Set profile start date.
$args['PROFILESTARTDATE'] = date( 'Y-m-d\TH:i:s', strtotime( $this->subscription_start_date, current_time( 'timestamp' ) ) );
unset( $args['INITAMT'] );
}
$request = wp_remote_post( $this->api_endpoint, array( 'timeout' => 45, 'sslverify' => false, 'httpversion' => '1.1', 'body' => apply_filters( 'rcp_paypal_pro_args', $args, $this ) ) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
if( is_wp_error( $request ) ) {
$this->error_message = $request->get_error_message();
do_action( 'rcp_registration_failed', $this );
do_action( 'rcp_paypal_pro_signup_payment_failed', $request, $this );
$error = '<p>' . __( 'An unidentified error occurred.', 'rcp' ) . '</p>';
$error .= '<p>' . $request->get_error_message() . '</p>';
wp_die( $error, __( 'Error', 'rcp' ), array( 'response' => '401' ) );
} elseif ( 200 == $code && 'OK' == $message ) {
if( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if( false !== strpos( strtolower( $body['ACK'] ), 'failure' ) ) {
$this->error_message = $body['L_LONGMESSAGE0'];
do_action( 'rcp_registration_failed', $this );
do_action( 'rcp_paypal_pro_signup_payment_failed', $request, $this );
$error = '<p>' . __( 'PayPal subscription creation failed.', 'rcp' ) . '</p>';
$error .= '<p>' . __( 'Error message:', 'rcp' ) . ' ' . $body['L_LONGMESSAGE0'] . '</p>';
$error .= '<p>' . __( 'Error code:', 'rcp' ) . ' ' . $body['L_ERRORCODE0'] . '</p>';
wp_die( $error, __( 'Error', 'rcp' ), array( 'response' => '401' ) );
} else {
// Successful signup
if ( isset( $body['PROFILEID'] ) ) {
$this->membership->set_gateway_subscription_id( $body['PROFILEID'] );
}
if ( isset( $body['TRANSACTIONID'] ) && false !== strpos( strtolower( $body['ACK'] ), 'success' ) ) {
// Confirm a one-time payment. Updating the payment activates the account.
$payment_data = array(
'payment_type' => 'Credit Card One Time',
'transaction_id' => sanitize_text_field( $body['TRANSACTIONID'] ),
'status' => 'complete'
);
$rcp_payments_db->update( $this->payment->id, $payment_data );
}
wp_redirect( esc_url_raw( rcp_get_return_url() ) ); exit;
exit;
}
} else {
do_action( 'rcp_registration_failed', $this );
wp_die( __( 'Something has gone wrong, please try again', 'rcp' ), __( 'Error', 'rcp' ), array( 'back_link' => true, 'response' => '401' ) );
}
}
/**
* Add credit card form
*
* @since 2.1
* @return string
*/
public function fields() {
ob_start();
rcp_get_template_part( 'card-form', 'full' );
return ob_get_clean();
}
/**
* Validate additional fields during registration submission
*
* @access public
* @since 2.1
* @return void
*/
public function validate_fields() {
if( ! rcp_has_paypal_api_access() ) {
$this->add_error( 'no_paypal_api', __( 'You have not configured PayPal API access. Please configure it in Restrict &rarr; Settings', 'rcp' ) );
}
if( empty( $_POST['rcp_card_address'] ) ) {
$this->add_error( 'missing_card_address', __( 'The address you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_city'] ) ) {
$this->add_error( 'missing_card_city', __( 'The city you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_state'] ) ) {
$this->add_error( 'missing_card_state', __( 'The state you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_country'] ) ) {
$this->add_error( 'missing_card_country', __( 'The country you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_number'] ) ) {
$this->add_error( 'missing_card_number', __( 'The card number you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_cvc'] ) ) {
$this->add_error( 'missing_card_code', __( 'The security code you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_zip'] ) ) {
$this->add_error( 'missing_card_zip', __( 'The zip / postal code you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_name'] ) ) {
$this->add_error( 'missing_card_name', __( 'The card holder name you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_exp_month'] ) ) {
$this->add_error( 'missing_card_exp_month', __( 'The card expiration month you have entered is invalid', 'rcp' ) );
}
if( empty( $_POST['rcp_card_exp_year'] ) ) {
$this->add_error( 'missing_card_exp_year', __( 'The card expiration year you have entered is invalid', 'rcp' ) );
}
}
/**
* Process webhooks
*
* @access public
* @since 2.1
* @return void
*/
public function process_webhooks() {
// These are processed through PayPal Express gateway
}
}

View File

@@ -0,0 +1,663 @@
<?php
use RCP\Membership_Level;
/**
* PayPal Standard Payment Gateway
*
* @package Restrict Content Pro
* @subpackage Classes/Gateways/PayPal
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.1
*/
class RCP_Payment_Gateway_PayPal extends RCP_Payment_Gateway {
private $api_endpoint;
private $checkout_url;
protected $username;
protected $password;
protected $signature;
/**
* Get things going
*
* @access public
* @since 2.1
* @return void
*/
public function init() {
global $rcp_options;
$this->supports[] = 'one-time';
$this->supports[] = 'recurring';
$this->supports[] = 'fees';
$this->supports[] = 'trial';
$this->supports[] = 'expiration-extension-on-renewals'; // @link https://github.com/restrictcontentpro/restrict-content-pro/issues/1259
if ( $this->test_mode ) {
$this->api_endpoint = 'https://api-3t.sandbox.paypal.com/nvp';
$this->checkout_url = 'https://www.sandbox.paypal.com/webscr&cmd=_express-checkout&token=';
} else {
$this->api_endpoint = 'https://api-3t.paypal.com/nvp';
$this->checkout_url = 'https://www.paypal.com/webscr&cmd=_express-checkout&token=';
}
if ( rcp_has_paypal_api_access() ) {
$creds = rcp_get_paypal_api_credentials();
$this->username = $creds['username'];
$this->password = $creds['password'];
$this->signature = $creds['signature'];
}
}
/**
* Process registration
*
* @access public
* @since 2.1
* @return void
*/
public function process_signup() {
global $rcp_options;
if ( $this->test_mode ) {
$paypal_redirect = 'https://www.sandbox.paypal.com/cgi-bin/webscr/?';
} else {
$paypal_redirect = 'https://www.paypal.com/cgi-bin/webscr/?';
}
$cancel_url = wp_get_referer();
if ( empty( $cancel_url ) ) {
$cancel_url = get_permalink( $rcp_options['registration_page'] );
}
// Setup PayPal arguments
$paypal_args = array(
'business' => trim( $rcp_options['paypal_email'] ),
'email' => $this->email,
'item_number' => $this->subscription_key,
'item_name' => $this->subscription_name,
'no_shipping' => '1',
'shipping' => '0',
'no_note' => '1',
'currency_code' => $this->currency,
'charset' => get_bloginfo( 'charset' ),
'custom' => $this->user_id,
'rm' => '2',
'return' => $this->return_url,
'cancel_return' => $cancel_url,
'notify_url' => add_query_arg( 'listener', 'IPN', home_url( 'index.php' ) ),
'cbt' => get_bloginfo( 'name' ),
'tax' => 0,
'page_style' => ! empty( $rcp_options['paypal_page_style'] ) ? trim( $rcp_options['paypal_page_style'] ) : '',
'bn' => 'RestrictContentPro_SP_PPCP'
);
// recurring paypal payment
if ( $this->auto_renew && ! empty( $this->length ) ) {
// recurring paypal payment
$paypal_args['cmd'] = '_xclick-subscriptions';
$paypal_args['src'] = '1';
$paypal_args['sra'] = '1';
$paypal_args['a3'] = $this->amount;
$paypal_args['p3'] = $this->length;
switch ( $this->length_unit ) {
case "day" :
$paypal_args['t3'] = 'D';
break;
case "month" :
$paypal_args['t3'] = 'M';
break;
case "year" :
$paypal_args['t3'] = 'Y';
break;
}
if ( 'renewal' === $this->payment->transaction_type && $this->membership->is_active() ) {
/*
* If this is a renewal then we want to charge the customer immediately, but then delay the
* first renewal payment until the RCP expiration date.
*
* @link https://github.com/restrictcontentpro/restrict-content-pro/issues/1259
*/
$current_date = new DateTime( 'now' );
$expiration = new DateTime( date( 'Y-m-d', strtotime( $this->membership->calculate_expiration( false ) ) ) );
$date_diff = $current_date->diff( $expiration );
$paypal_args['a1'] = $this->initial_amount;
$paypal_args['t1'] = 'D';
$paypal_args['p1'] = $date_diff->days;
/*
* PayPal has a maximum of 90 days for trial periods.
* If the difference between today & the next bill date is greater than 90 days then we need to
* split it into two trial periods.
*/
if ( $date_diff->days > 90 ) {
// Set up the default period times.
$first_period = $date_diff->days;
$second_period = 0;
$unit = 'D';
if ( ( $date_diff->days - 90 ) <= 90 ) {
// t1 = D, t2 = D
$unit = 'D';
$second_period = $date_diff->days - 90;
$first_period = 90;
} elseif ( $date_diff->days / 7 <= 52 ) {
// t1 = D, t2 = W
$unit = 'W';
$total_weeks = $date_diff->days / 7;
$second_period = (int) floor( $total_weeks );
$first_period = (int) absint( round( ( 7 * ( $total_weeks - $second_period ) ) ) );
} elseif ( $date_diff->days / 7 > 52 ) {
// t1 = D, t2 = M
$unit = 'M';
$first_period = $date_diff->d;
$second_period = $date_diff->m;
}
// Reudce things to be a bit more human readable.
switch ( $unit ) {
case 'W' :
if ( 52 === $second_period ) {
$unit = 'Y';
$second_period = 1;
} elseif ( 4 === $second_period ) {
$unit = 'M';
$second_period = 1;
}
break;
case 'M' :
if ( 12 === $second_period ) {
$unit = 'Y';
$second_period = 1;
}
break;
}
// Only create two trials if necessary.
if ( ! empty( $first_period ) ) {
$paypal_args['p1'] = $first_period;
$paypal_args['t1'] = 'D';
$paypal_args['a2'] = 0;
$paypal_args['p2'] = absint( $second_period );
$paypal_args['t2'] = $unit;
} else {
$paypal_args['p1'] = absint( $second_period );
$paypal_args['t1'] = $unit;
}
}
} elseif ( $this->initial_amount != $this->amount ) {
/*
* Add a trial period to charge the different "initial amount".
* This will be used for free trials, one-time discount codes, signup fees,
* and prorated credits.
*/
// By default we use the same values as the normal subscription period.
$paypal_args['a1'] = $this->initial_amount;
$paypal_args['p1'] = $this->length;
$paypal_args['t1'] = $paypal_args['t3'];
/*
* If this is not a free trial then the trial duration would have already been set above
* using the normal duration fields.
*
* If this is a free trial, then we'll override the values using the trial duration fields.
*/
if ( $this->is_trial() ) {
$paypal_args['a1'] = 0;
$paypal_args['p1'] = $this->subscription_data['trial_duration'];
switch ( $this->subscription_data['trial_duration_unit'] ) {
case 'day':
$paypal_args['t1'] = 'D';
break;
case 'month':
$paypal_args['t1'] = 'M';
break;
case 'year':
$paypal_args['t1'] = 'Y';
break;
}
}
}
} else {
// one time payment
$paypal_args['cmd'] = '_xclick';
$paypal_args['amount'] = $this->initial_amount;
}
$paypal_args = apply_filters( 'rcp_paypal_args', $paypal_args, $this );
// Build query
$paypal_redirect .= http_build_query( $paypal_args );
// Fix for some sites that encode the entities
$paypal_redirect = str_replace( '&amp;', '&', $paypal_redirect );
// Redirect to paypal
header( 'Location: ' . $paypal_redirect );
exit;
}
/**
* Process PayPal IPN
*
* @access public
* @since 2.1
* @return void
*/
public function process_webhooks() {
if ( ! isset( $_GET['listener'] ) || strtoupper( $_GET['listener'] ) != 'IPN' ) {
return;
}
rcp_log( 'Starting to process PayPal Standard IPN.' );
global $rcp_options;
nocache_headers();
if ( ! class_exists( 'IpnListener' ) ) {
// instantiate the IpnListener class
include( RCP_PLUGIN_DIR . 'pro/includes/gateways/paypal/paypal-ipnlistener.php' );
}
$listener = new IpnListener();
$verified = false;
if ( $this->test_mode ) {
$listener->use_sandbox = true;
}
/*
if( isset( $rcp_options['ssl'] ) ) {
$listener->use_ssl = true;
} else {
$listener->use_ssl = false;
}
*/
//To post using the fsockopen() function rather than cURL, use:
if ( isset( $rcp_options['disable_curl'] ) ) {
$listener->use_curl = false;
}
if ( ! isset( $rcp_options['disable_ipn_verify'] ) ) {
try {
$listener->requirePostMethod();
$verified = $listener->processIpn();
} catch ( Exception $e ) {
status_header( 402 );
//die( 'IPN exception: ' . $e->getMessage() );
}
}
/*
The processIpn() method returned true if the IPN was "VERIFIED" and false if it
was "INVALID".
*/
if ( $verified || isset( $_POST['verification_override'] ) || ( $this->test_mode || isset( $rcp_options['disable_ipn_verify'] ) ) ) {
status_header( 200 );
$user_id = 0;
$posted = apply_filters( 'rcp_ipn_post', $_POST ); // allow $_POST to be modified
if ( ! empty( $posted['subscr_id'] ) ) {
$this->membership = rcp_get_membership_by( 'gateway_subscription_id', $posted['subscr_id'] );
}
// Get by subscription key.
if ( empty( $this->membership ) && ! empty( $posted['item_number'] ) ) {
$membership = rcp_get_membership_by( 'subscription_key', sanitize_text_field( $posted['item_number'] ) );
if ( ! empty( $membership ) ) {
$this->membership = $membership;
}
}
if ( empty( $this->membership ) ) {
rcp_log( sprintf( 'PayPal IPN Failed: unable to find associated membership in RCP. Item Name: %s; Item Number: %d; TXN Type: %s; TXN ID: %s', $posted['item_name'], $posted['item_number'], $posted['txn_type'], $posted['txn_id'] ), true );
die( 'no membership found' );
}
if ( empty( $user_id ) ) {
$user_id = $this->membership->get_user_id();
}
$member = new RCP_Member( $this->membership->get_user_id() ); // for backwards compat
rcp_log( sprintf( 'Processing IPN for membership #%d.', $this->membership->get_id() ) );
if ( ! $this->membership->get_object_id() ) {
die( 'no membership level found' );
}
if ( ! rcp_get_membership_level( $this->membership->get_object_id() ) instanceof Membership_Level ) {
die( 'no membership level found' );
}
$rcp_payments = new RCP_Payments();
$subscription_key = $posted['item_number'];
$has_trial = isset( $posted['mc_amount1'] ) && '0.00' == $posted['mc_amount1'];
if ( ! $has_trial && isset( $posted['mc_gross'] ) ) {
$amount = number_format( (float) $posted['mc_gross'], 2, '.', '' );
} elseif ( $has_trial && isset( $posted['mc_amount1'] ) ) {
$amount = number_format( (float) $posted['mc_amount1'], 2, '.', '' );
} else {
$amount = false;
}
$payment_status = ! empty( $posted['payment_status'] ) ? $posted['payment_status'] : false;
$currency_code = $posted['mc_currency'];
$pending_payment_id = rcp_get_membership_meta( $this->membership->get_id(), 'pending_payment_id', true );
$pending_payment = ! empty( $pending_payment_id ) ? $rcp_payments->get_payment( $pending_payment_id ) : false;
// Check for invalid amounts in the IPN data
if ( ! empty( $pending_payment ) && ! empty( $pending_payment->amount ) && ! empty( $amount ) && in_array( $posted['txn_type'], array( 'web_accept', 'subscr_payment' ) ) ) {
if ( $amount < $pending_payment->amount ) {
$this->membership->add_note( sprintf( __( 'Incorrect amount received in the IPN. Amount received was %s. The amount should have been %s. PayPal Transaction ID: %s', 'rcp' ), $amount, $pending_payment->amount, sanitize_text_field( $posted['txn_id'] ) ) );
die( 'incorrect amount' );
}
}
// setup the payment info in an array for storage
$payment_data = array(
'date' => ! empty( $posted['payment_date'] ) ? date( 'Y-m-d H:i:s', strtotime( $posted['payment_date'], current_time( 'timestamp' ) ) ) : date( 'Y-m-d H:i:s', strtotime( 'now', current_time( 'timestamp' ) ) ),
'subscription' => $posted['item_name'],
'payment_type' => $posted['txn_type'],
'subscription_key' => $subscription_key,
'user_id' => $this->membership->get_user_id(),
'customer_id' => $this->membership->get_customer_id(),
'membership_id' => $this->membership->get_id(),
'transaction_id' => ! empty( $posted['txn_id'] ) ? $posted['txn_id'] : false,
'status' => 'complete',
'gateway' => 'paypal'
);
if ( false !== $amount ) {
$payment_data['amount'] = $amount;
}
// We don't want any empty values in the array in order to avoid deleting a transaction ID or other data.
foreach ( $payment_data as $payment_key => $payment_value ) {
if ( empty( $payment_value ) ) {
unset( $payment_data[ $payment_key ] );
}
}
do_action( 'rcp_valid_ipn', $payment_data, $user_id, $posted );
if ( $posted['txn_type'] == 'web_accept' || $posted['txn_type'] == 'subscr_payment' ) {
// only check for an existing payment if this is a payment IPD request
if ( ! empty( $posted['txn_id'] ) && $rcp_payments->payment_exists( $posted['txn_id'] ) ) {
do_action( 'rcp_ipn_duplicate_payment', $posted['txn_id'], $member, $this );
die( 'duplicate IPN detected' );
}
}
/* now process the kind of subscription/payment */
// Subscriptions
switch ( $posted['txn_type'] ) :
case "subscr_signup" :
// when a new user signs up
rcp_log( 'Processing PayPal Standard subscr_signup IPN.' );
$this->membership->set_gateway_subscription_id( $posted['subscr_id'] );
$this->membership->set_recurring( true );
if ( $has_trial && ! empty( $pending_payment_id ) ) {
// This activates the trial.
$rcp_payments->update( $pending_payment_id, $payment_data );
}
do_action( 'rcp_ipn_subscr_signup', $user_id );
do_action( 'rcp_webhook_recurring_payment_profile_created', $member, $this );
die( 'successful subscr_signup' );
break;
case "subscr_payment" :
// when a user makes a recurring payment
rcp_log( 'Processing PayPal Standard subscr_payment IPN.' );
if ( 'failed' == strtolower( $posted['payment_status'] ) ) {
// Recurring payment failed.
$this->membership->add_note( sprintf( __( 'Transaction ID %s failed in PayPal.', 'rcp' ), $posted['txn_id'] ) );
die( 'Subscription payment failed' );
} elseif ( 'pending' == strtolower( $posted['payment_status'] ) ) {
// Recurring payment pending (such as echeck).
$pending_reason = ! empty( $posted['pending_reason'] ) ? $posted['pending_reason'] : __( 'unknown', 'rcp' );
$this->membership->add_note( sprintf( __( 'Transaction ID %s is pending in PayPal for reason: %s', 'rcp' ), $posted['txn_id'], $pending_reason ) );
die( 'Subscription payment pending' );
}
// Payment completed.
if ( ! empty( $pending_payment_id ) ) {
$this->membership->set_recurring( true );
// This activates the membership.
$rcp_payments->update( $pending_payment_id, $payment_data );
$payment_id = $pending_payment_id;
} else {
$this->membership->renew( true );
$payment_data['subtotal'] = $payment_data['amount'];
$payment_data['transaction_type'] = 'renewal';
// record this payment in the database
$payment_id = $rcp_payments->insert( $payment_data );
do_action( 'rcp_webhook_recurring_payment_processed', $member, $payment_id, $this );
}
do_action( 'rcp_ipn_subscr_payment', $user_id );
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
die( 'successful subscr_payment' );
break;
case "subscr_cancel" :
rcp_log( 'Processing PayPal Standard subscr_cancel IPN.' );
if ( isset( $posted['subscr_id'] ) && $posted['subscr_id'] == $this->membership->get_gateway_subscription_id() && 'cancelled' !== $this->membership->get_status() ) {
// 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' );
}
// user is marked as cancelled but retains access until end of term
if ( $this->membership->is_active() ) {
$this->membership->cancel();
$this->membership->add_note( __( 'Membership cancelled via PayPal Standard IPN.', 'rcp' ) );
} else {
rcp_log( sprintf( 'Membership #%d is not active - not cancelling.', $this->membership->get_id() ) );
}
do_action( 'rcp_ipn_subscr_cancel', $user_id );
do_action( 'rcp_webhook_cancel', $member, $this );
die( 'successful subscr_cancel' );
}
break;
case "subscr_failed" :
rcp_log( 'Processing PayPal Standard subscr_failed IPN.' );
if ( ! empty( $posted['txn_id'] ) ) {
$this->webhook_event_id = sanitize_text_field( $posted['txn_id'] );
} elseif ( ! empty( $posted['ipn_track_id'] ) ) {
$this->webhook_event_id = sanitize_text_field( $posted['ipn_track_id'] );
}
do_action( 'rcp_recurring_payment_failed', $member, $this );
do_action( 'rcp_ipn_subscr_failed' );
die( 'successful subscr_failed' );
break;
case "subscr_eot" :
// user's subscription has reached the end of its term
rcp_log( 'Processing PayPal Standard subscr_eot IPN.' );
if ( isset( $posted['subscr_id'] ) && $posted['subscr_id'] == $this->membership->get_gateway_subscription_id() && 'cancelled' !== $this->membership->get_status() ) {
$this->membership->set_status( 'expired' );
}
do_action( 'rcp_ipn_subscr_eot', $user_id );
die( 'successful subscr_eot' );
break;
case "web_accept" :
rcp_log( sprintf( 'Processing PayPal Standard web_accept IPN. Payment status: %s', $payment_status ) );
switch ( strtolower( $payment_status ) ) :
case 'completed' :
if ( ! empty( $pending_payment_id ) ) {
// Complete the pending payment. This activates the membership.
$rcp_payments->update( $pending_payment_id, $payment_data );
$payment_id = $pending_payment_id;
} else {
// Renew the account.
$this->membership->renew();
$payment_id = $rcp_payments->insert( $payment_data );
}
do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this );
break;
case 'denied' :
case 'expired' :
case 'failed' :
case 'voided' :
$this->membership->cancel();
break;
endswitch;
die( 'successful web_accept' );
break;
case "cart" :
case "express_checkout" :
default :
break;
endswitch;
} else {
rcp_log( 'Invalid PayPal IPN attempt.', true );
status_header( 400 );
die( 'invalid IPN' );
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Payment Gateway For Stripe Checkout
*
* @deprecated 3.2
*
* @package Restrict Content Pro
* @subpackage Classes/Gateways/Stripe Checkout
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.5
*/
class RCP_Payment_Gateway_Stripe_Checkout extends RCP_Payment_Gateway_Stripe {
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,380 @@
<?php
/**
* PayPal Functions
*
* @package Restrict Content Pro
* @subpackage Gateways/PayPal/Functions
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
/**
* Determine if a member is a PayPal subscriber
*
* @deprecated 3.0 Use `rcp_is_paypal_membership()` instead.
* @see rcp_is_paypal_membership()
*
* @param int $user_id The ID of the user to check
*
* @since 2.0
* @access public
* @return bool
*/
function rcp_is_paypal_subscriber( $user_id = 0 ) {
if( empty( $user_id ) ) {
$user_id = get_current_user_id();
}
$ret = false;
$customer = rcp_get_customer_by_user_id( $user_id );
if ( ! empty( $customer ) ) {
$membership = rcp_get_customer_single_membership( $customer->get_id() );
if ( ! empty( $membership ) ) {
$ret = rcp_is_paypal_membership( $membership );
}
}
return (bool) apply_filters( 'rcp_is_paypal_subscriber', $ret, $user_id );
}
/**
* Determines if a membership is a PayPal subscription.
*
* @param int|RCP_Membership $membership_object_or_id Membership ID or object.
*
* @since 3.0
* @return bool
*/
function rcp_is_paypal_membership( $membership_object_or_id ) {
if ( ! is_object( $membership_object_or_id ) ) {
$membership = rcp_get_membership( $membership_object_or_id );
} else {
$membership = $membership_object_or_id;
}
$is_paypal = false;
if ( ! empty( $membership ) && $membership->get_id() > 0 ) {
$subscription_id = $membership->get_gateway_subscription_id();
if ( false !== strpos( $subscription_id, 'I-' ) ) {
$is_paypal = true;
}
}
/**
* Filters whether or not the membership is a PayPal subscription.
*
* @param bool $is_paypal
* @param RCP_Membership $membership
*
* @since 3.0
*/
return (bool) apply_filters( 'rcp_is_paypal_membership', $is_paypal, $membership );
}
/**
* Determine if PayPal API access is enabled
*
* @access public
* @since 2.1
* @return bool
*/
function rcp_has_paypal_api_access() {
global $rcp_options;
$ret = false;
$prefix = 'live_';
if( rcp_is_sandbox() ) {
$prefix = 'test_';
}
$username = $prefix . 'paypal_api_username';
$password = $prefix . 'paypal_api_password';
$signature = $prefix . 'paypal_api_signature';
if( ! empty( $rcp_options[ $username ] ) && ! empty( $rcp_options[ $password ] ) && ! empty( $rcp_options[ $signature ] ) ) {
$ret = true;
}
return $ret;
}
/**
* Retrieve PayPal API credentials
*
* @access public
* @since 2.1
* @return array Array of credentials.
*/
function rcp_get_paypal_api_credentials() {
global $rcp_options;
$ret = false;
$prefix = 'live_';
if( rcp_is_sandbox() ) {
$prefix = 'test_';
}
$creds = array(
'username' => $rcp_options[ $prefix . 'paypal_api_username' ],
'password' => $rcp_options[ $prefix . 'paypal_api_password' ],
'signature' => $rcp_options[ $prefix . 'paypal_api_signature' ]
);
return apply_filters( 'rcp_get_paypal_api_credentials', $creds );
}
/**
* Process an update card form request
*
* @deprecated 3.0 Use `rcp_paypal_update_membership_billing_card()` instead.
* @see rcp_paypal_update_membership_billing_card()
*
* @param int $member_id ID of the member.
* @param RCP_Member $member_obj Member object.
*
* @access private
* @since 2.6
* @return void
*/
function rcp_paypal_update_billing_card( $member_id, $member_obj ) {
if( empty( $member_id ) ) {
return;
}
if( ! is_a( $member_obj, 'RCP_Member' ) ) {
return;
}
$customer = rcp_get_customer_by_user_id( $member_id );
if ( empty( $customer ) ) {
return;
}
$membership = rcp_get_customer_single_membership( $customer->get_id() );
if ( empty( $membership ) ) {
return;
}
rcp_paypal_update_membership_billing_card( $membership );
}
//add_action( 'rcp_update_billing_card', 'rcp_paypal_update_billing_card', 10, 2 );
/**
* Update the billing card for a given membership.
*
* @param RCP_Membership $membership
*
* @since 3.0
* @return void
*/
function rcp_paypal_update_membership_billing_card( $membership ) {
if ( ! is_a( $membership, 'RCP_Membership' ) ) {
return;
}
if ( ! rcp_is_paypal_membership( $membership ) ) {
return;
}
if( rcp_is_sandbox() ) {
$api_endpoint = 'https://api-3t.sandbox.paypal.com/nvp';
} else {
$api_endpoint = 'https://api-3t.paypal.com/nvp';
}
$error = '';
$subscription_id = $membership->get_gateway_subscription_id();
$credentials = rcp_get_paypal_api_credentials();
$card_number = isset( $_POST['rcp_card_number'] ) && is_numeric( $_POST['rcp_card_number'] ) ? $_POST['rcp_card_number'] : '';
$card_exp_month = isset( $_POST['rcp_card_exp_month'] ) && is_numeric( $_POST['rcp_card_exp_month'] ) ? $_POST['rcp_card_exp_month'] : '';
$card_exp_year = isset( $_POST['rcp_card_exp_year'] ) && is_numeric( $_POST['rcp_card_exp_year'] ) ? $_POST['rcp_card_exp_year'] : '';
$card_cvc = isset( $_POST['rcp_card_cvc'] ) && is_numeric( $_POST['rcp_card_cvc'] ) ? $_POST['rcp_card_cvc'] : '';
$card_zip = isset( $_POST['rcp_card_zip'] ) ? sanitize_text_field( $_POST['rcp_card_zip'] ) : '' ;
if ( empty( $card_number ) || empty( $card_exp_month ) || empty( $card_exp_year ) || empty( $card_cvc ) || empty( $card_zip ) ) {
$error = __( 'Please enter all required fields.', 'rcp' );
}
if ( empty( $error ) ) {
$args = array(
'USER' => $credentials['username'],
'PWD' => $credentials['password'],
'SIGNATURE' => $credentials['signature'],
'VERSION' => '124',
'METHOD' => 'UpdateRecurringPaymentsProfile',
'PROFILEID' => $subscription_id,
'ACCT' => $card_number,
'EXPDATE' => $card_exp_month . $card_exp_year,
// needs to be in the format 062019
'CVV2' => $card_cvc,
'ZIP' => $card_zip,
'BUTTONSOURCE' => 'EasyDigitalDownloads_SP',
);
$request = wp_remote_post( $api_endpoint, array(
'timeout' => 45,
'sslverify' => false,
'body' => $args,
'httpversion' => '1.1',
) );
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
if ( is_wp_error( $request ) ) {
$error = $request->get_error_message();
} elseif ( 200 == $code && 'OK' == $message ) {
if( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if ( 'failure' === strtolower( $body['ACK'] ) ) {
$error = $body['L_ERRORCODE0'] . ': ' . $body['L_LONGMESSAGE0'];
} else {
// Request was successful, but verify the profile ID that came back matches
if ( $subscription_id !== $body['PROFILEID'] ) {
$error = __( 'Error updating subscription', 'rcp' );
rcp_log( sprintf( 'Invalid PayPal subscription ID. Expected: %s; Provided: %s.', $subscription_id, $body['PROFILEID'] ), true );
}
}
} else {
$error = __( 'Something has gone wrong, please try again', 'rcp' );
}
}
if( ! empty( $error ) ) {
wp_redirect( add_query_arg( array( 'card' => 'not-updated', 'msg' => urlencode( $error ) ) ) ); exit;
}
wp_redirect( add_query_arg( array( 'card' => 'updated', 'msg' => '' ) ) ); exit;
}
add_action( 'rcp_update_membership_billing_card', 'rcp_paypal_update_membership_billing_card' );
/**
* Log the start of a valid IPN request
*
* @param array $payment_data Payment information to be stored in the DB.
* @param int $user_id ID of the user the payment is for.
* @param array $posted Data sent via the IPN.
*
* @since 2.9
* @return void
*/
function rcp_log_valid_paypal_ipn( $payment_data, $user_id, $posted ) {
rcp_log( sprintf( 'Started processing valid PayPal IPN request for user #%d. Payment Data: %s', $user_id, var_export( $payment_data, true ) ) );
}
add_action( 'rcp_valid_ipn', 'rcp_log_valid_paypal_ipn', 10, 3 );
/**
* Cancel a PayPal membership by profile ID.
*
* @param string $payment_profile_id Gateway payment profile ID.
*
* @since 3.0
* @return true|WP_Error True on success, WP_Error on failure.
*/
function rcp_paypal_cancel_membership( $payment_profile_id ) {
global $rcp_options;
if ( ! rcp_has_paypal_api_access() ) {
return new WP_Error( 'paypal_cancel_failed_no_api', __( 'PayPal cancellation failed - no API access.', 'rcp' ) );
}
// Set PayPal API key credentials.
$api_username = rcp_is_sandbox() ? 'test_paypal_api_username' : 'live_paypal_api_username';
$api_password = rcp_is_sandbox() ? 'test_paypal_api_password' : 'live_paypal_api_password';
$api_signature = rcp_is_sandbox() ? 'test_paypal_api_signature' : 'live_paypal_api_signature';
$api_endpoint = rcp_is_sandbox() ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp';
$args = array(
'USER' => trim( $rcp_options[$api_username] ),
'PWD' => trim( $rcp_options[$api_password] ),
'SIGNATURE' => trim( $rcp_options[$api_signature] ),
'VERSION' => '124',
'METHOD' => 'ManageRecurringPaymentsProfileStatus',
'PROFILEID' => $payment_profile_id,
'ACTION' => 'Cancel'
);
$error_msg = '';
$request = wp_remote_post( $api_endpoint, array( 'body' => $args, 'timeout' => 30, 'httpversion' => '1.1' ) );
if ( is_wp_error( $request ) ) {
$success = false;
$error_msg = $request->get_error_message();
} else {
$body = wp_remote_retrieve_body( $request );
$code = wp_remote_retrieve_response_code( $request );
$message = wp_remote_retrieve_response_message( $request );
if ( is_string( $body ) ) {
wp_parse_str( $body, $body );
}
if ( 200 !== (int) $code ) {
$success = false;
}
if ( 'OK' !== $message ) {
$success = false;
}
if ( isset( $body['ACK'] ) && 'success' === strtolower( $body['ACK'] ) ) {
$success = true;
} else {
$success = false;
if ( isset( $body['L_LONGMESSAGE0'] ) ) {
$error_msg = $body['L_LONGMESSAGE0'];
}
}
}
if ( ! $success ) {
$success = new WP_Error( 'paypal_cancel_fail', $error_msg );
}
return $success;
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,300 @@
<?php
/**
* PayPal IPN Listener
*
* A class to listen for and handle Instant Payment Notifications (IPN) from
* the PayPal server.
*
* https://github.com/Quixotix/PHP-PayPal-IPN
*
* @package PHP-PayPal-IPN
* @author Micah Carrick
* @copyright (c) 2011 - Micah Carrick
* @version 2.0.3
* @license http://opensource.org/licenses/gpl-3.0.html
*/
class IpnListener {
/**
* If true, the recommended cURL PHP library is used to send the post back
* to PayPal. If flase then fsockopen() is used. Default true.
*
* @var boolean
*/
public $use_curl = true;
/**
* If true, explicitly sets cURL to use SSL version 3. Use this if cURL
* is compiled with GnuTLS SSL.
*
* @var boolean
*/
public $force_ssl_v3 = false;
/**
* If true, an SSL secure connection (port 443) is used for the post back
* as recommended by PayPal. If false, a standard HTTP (port 80) connection
* is used. Default true.
*
* @var boolean
*/
public $use_ssl = true;
/**
* If true, the paypal sandbox URI www.sandbox.paypal.com is used for the
* post back. If false, the live URI www.paypal.com is used. Default false.
*
* @var boolean
*/
public $use_sandbox = false;
/**
* The amount of time, in seconds, to wait for the PayPal server to respond
* before timing out. Default 30 seconds.
*
* @var int
*/
public $timeout = 30;
private $post_data = array();
private $post_uri = '';
private $response_status = '';
private $response = '';
const PAYPAL_HOST = 'www.paypal.com';
const SANDBOX_HOST = 'www.sandbox.paypal.com';
/**
* Post Back Using cURL
*
* Sends the post back to PayPal using the cURL library. Called by
* the processIpn() method if the use_curl property is true. Throws an
* exception if the post fails. Populates the response, response_status,
* and post_uri properties on success.
*
* @param string The post data as a URL encoded string
*/
protected function curlPost($encoded_data) {
if ($this->use_ssl) {
$uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr';
$this->post_uri = $uri;
} else {
$uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr';
$this->post_uri = $uri;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
if ($this->force_ssl_v3) {
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
}
$this->response = curl_exec($ch);
$this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
if ($this->response === false || $this->response_status == '0') {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
throw new Exception("cURL error: [$errno] $errstr");
}
}
/**
* Post Back Using fsockopen()
*
* Sends the post back to PayPal using the fsockopen() function. Called by
* the processIpn() method if the use_curl property is false. Throws an
* exception if the post fails. Populates the response, response_status,
* and post_uri properties on success.
*
* @param string The post data as a URL encoded string
*/
protected function fsockPost($encoded_data) {
if ($this->use_ssl) {
$uri = 'ssl://'.$this->getPaypalHost();
$port = '443';
$this->post_uri = $uri.'/cgi-bin/webscr';
} else {
$uri = $this->getPaypalHost(); // no "http://" in call to fsockopen()
$port = '80';
$this->post_uri = 'http://'.$uri.'/cgi-bin/webscr';
}
$fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout);
if (!$fp) {
// fsockopen error
throw new Exception("fsockopen error: [$errno] $errstr");
}
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: ".strlen($encoded_data)."\r\n";
$header .= "Connection: Close\r\n\r\n";
fputs($fp, $header.$encoded_data."\r\n\r\n");
while(!feof($fp)) {
if (empty($this->response)) {
// extract HTTP status from first line
$this->response .= $status = fgets($fp, 1024);
$this->response_status = trim(substr($status, 9, 4));
} else {
$this->response .= fgets($fp, 1024);
}
}
fclose($fp);
}
private function getPaypalHost() {
if ($this->use_sandbox) return IpnListener::SANDBOX_HOST;
else return IpnListener::PAYPAL_HOST;
}
/**
* Get POST URI
*
* Returns the URI that was used to send the post back to PayPal. This can
* be useful for troubleshooting connection problems. The default URI
* would be "ssl://www.sandbox.paypal.com:443/cgi-bin/webscr"
*
* @return string
*/
public function getPostUri() {
return $this->post_uri;
}
/**
* Get Response
*
* Returns the entire response from PayPal as a string including all the
* HTTP headers.
*
* @return string
*/
public function getResponse() {
return $this->response;
}
/**
* Get Response Status
*
* Returns the HTTP response status code from PayPal. This should be "200"
* if the post back was successful.
*
* @return string
*/
public function getResponseStatus() {
return $this->response_status;
}
/**
* Get Text Report
*
* Returns a report of the IPN transaction in plain text format. This is
* useful in emails to order processors and system administrators. Override
* this method in your own class to customize the report.
*
* @return string
*/
public function getTextReport() {
$r = '';
// date and POST url
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "\n[".date('m/d/Y g:i A').'] - '.$this->getPostUri();
if ($this->use_curl) $r .= " (curl)\n";
else $r .= " (fsockopen)\n";
// HTTP Response
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "\n{$this->getResponse()}\n";
// POST vars
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "\n";
foreach ($this->post_data as $key => $value) {
$r .= str_pad($key, 25)."$value\n";
}
$r .= "\n\n";
return $r;
}
/**
* Process IPN
*
* Handles the IPN post back to PayPal and parsing the response. Call this
* method from your IPN listener script. Returns true if the response came
* back as "VERIFIED", false if the response came back "INVALID", and
* throws an exception if there is an error.
*
* @param array
*
* @return boolean
*/
public function processIpn($post_data=null) {
$encoded_data = 'cmd=_notify-validate';
if ($post_data === null) {
// use raw POST data
if (!empty($_POST)) {
$this->post_data = $_POST;
$encoded_data .= '&'.file_get_contents('php://input');
} else {
throw new Exception("No POST data found.");
}
} else {
// use provided data array
$this->post_data = $post_data;
foreach ($this->post_data as $key => $value) {
$encoded_data .= "&$key=".urlencode($value);
}
}
if ($this->use_curl) $this->curlPost($encoded_data);
else $this->fsockPost($encoded_data);
if (strpos($this->response_status, '200') === false) {
throw new Exception("Invalid response status: ".$this->response_status);
}
if (strpos($this->response, "VERIFIED") !== false) {
return true;
} elseif (strpos($this->response, "INVALID") !== false) {
return false;
} else {
throw new Exception("Unexpected response from PayPal.");
}
}
/**
* Require Post Method
*
* Throws an exception and sets a HTTP 405 response header if the request
* method was not POST.
*/
public function requirePostMethod() {
// require POST requests
if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') {
header('Allow: POST', true, 405);
throw new Exception("Invalid HTTP request method.");
}
}
}
?>

View File

@@ -0,0 +1,184 @@
<?php
/**
* Checkout Functions
*
* @package Restrict Content Pro
* @subpackage Gateways/2Checkout/Functions
* @copyright Copyright (c) 2017, Pippin Williamson
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
/**
* Cancel a 2Checkout membership, given a gateway payment profile ID.
*
* @param string $payment_profile_id Membership payment profile ID.
*
* @since 3.0
* @return true|WP_Error True on success, WP_Error on failure.
*/
function rcp_2checkout_cancel_membership( $payment_profile_id ) {
global $rcp_options;
$user_name = defined( 'TWOCHECKOUT_ADMIN_USER' ) ? TWOCHECKOUT_ADMIN_USER : '';
$password = defined( 'TWOCHECKOUT_ADMIN_PASSWORD' ) ? TWOCHECKOUT_ADMIN_PASSWORD : '';
if( empty( $user_name ) || empty( $password ) ) {
return new WP_Error( 'missing_username_or_password', __( 'The 2Checkout API username and password must be defined', 'rcp' ) );
}
if( ! class_exists( 'Twocheckout' ) ) {
require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/twocheckout/Twocheckout.php';
}
$secret_word = rcp_is_sandbox() ? trim( $rcp_options['twocheckout_secret_word'] ) : '';;
$test_mode = rcp_is_sandbox();
if( $test_mode ) {
$secret_key = isset( $rcp_options['twocheckout_test_private'] ) ? trim( $rcp_options['twocheckout_test_private'] ) : '';
$publishable_key = isset( $rcp_options['twocheckout_test_publishable'] ) ? trim( $rcp_options['twocheckout_test_publishable'] ) : '';
$seller_id = isset( $rcp_options['twocheckout_test_seller_id'] ) ? trim( $rcp_options['twocheckout_test_seller_id'] ) : '';
$environment = 'sandbox';
} else {
$secret_key = isset( $rcp_options['twocheckout_live_private'] ) ? trim( $rcp_options['twocheckout_live_private'] ) : '';
$publishable_key = isset( $rcp_options['twocheckout_live_publishable'] ) ? trim( $rcp_options['twocheckout_live_publishable'] ) : '';
$seller_id = isset( $rcp_options['twocheckout_live_seller_id'] ) ? trim( $rcp_options['twocheckout_live_seller_id'] ) : '';
$environment = 'production';
}
try {
Twocheckout::privateKey( $secret_key );
Twocheckout::sellerId( $seller_id );
Twocheckout::username( TWOCHECKOUT_ADMIN_USER );
Twocheckout::password( TWOCHECKOUT_ADMIN_PASSWORD );
Twocheckout::sandbox( $test_mode );
$sale_id = str_replace( '2co_', '', $payment_profile_id );
$cancelled = Twocheckout_Sale::stop( array( 'sale_id' => $sale_id ) );
if( $cancelled['response_code'] == 'OK' ) {
return true;
}
} catch ( Twocheckout_Error $e) {
return new WP_Error( '2checkout_cancel_failed', $e->getMessage() );
}
return new WP_Error( '2checkout_cancel_failed', __( 'Unexpected error cancelling 2Checkout payment profile.', 'rcp' ) );
}
/**
* Cancel a 2checkout subscriber
*
* @deprecated 3.0 Use `rcp_2checkout_cancel_membership()` instead.
* @see rcp_2checkout_cancel_membership()
*
* @param int $member_id ID of the member to cancel.
*
* @access private
* @since 2.4
* @return bool|WP_Error
*/
function rcp_2checkout_cancel_member( $member_id = 0 ) {
$customer = rcp_get_customer_by_user_id( $member_id );
if ( empty( $customer ) ) {
return new WP_Error( '2checkout_cancel_failed', __( 'Unable to find customer from member ID.', 'rcp' ) );
}
$membership = rcp_get_customer_single_membership( $customer->get_id() );
if ( empty( $membership ) ) {
return new WP_Error( '2checkout_cancel_failed', __( 'Invalid membership.', 'rcp' ) );
}
$payment_profile = $membership->get_gateway_subscription_id();
if ( empty( $payment_profile ) ) {
return new WP_Error( '2checkout_cancel_failed', __( 'Invalid membership.', 'rcp' ) );
}
return rcp_2checkout_cancel_membership( $payment_profile );
}
/**
* Determine if a member is a 2Checkout Customer
*
* @deprecated 3.0 Use `rcp_is_2checkout_membership()` instead.
* @see rcp_is_2checkout_membership()
*
* @param int $user_id The ID of the user to check
*
* @since 2.4
* @access public
* @return bool
*/
function rcp_is_2checkout_subscriber( $user_id = 0 ) {
if( empty( $user_id ) ) {
$user_id = get_current_user_id();
}
$ret = false;
$customer = rcp_get_customer_by_user_id( $user_id );
if ( ! empty( $customer ) ) {
$membership = rcp_get_customer_single_membership( $customer->get_id() );
if ( ! empty( $membership ) ) {
$ret = rcp_is_2checkout_membership( $membership );
}
}
return (bool) apply_filters( 'rcp_is_2checkout_subscriber', $ret, $user_id );
}
/**
* Determines if a membership is a 2Checkout subscription.
*
* @param int|RCP_Membership $membership_object_or_id Membership ID or object.
*
* @since 3.0
* @return bool
*/
function rcp_is_2checkout_membership( $membership_object_or_id ) {
if ( ! is_object( $membership_object_or_id ) ) {
$membership = rcp_get_membership( $membership_object_or_id );
} else {
$membership = $membership_object_or_id;
}
$is_2checkout = false;
if ( ! empty( $membership ) && $membership->get_id() > 0 ) {
$subscription_id = $membership->get_gateway_subscription_id();
if ( false !== strpos( $subscription_id, '2co_' ) ) {
$is_2checkout = true;
}
}
/**
* Filters whether or not the membership is a 2Checkout subscription.
*
* @param bool $is_2checkout
* @param RCP_Membership $membership
*
* @since 3.0
*/
return (bool) apply_filters( 'rcp_is_2checkout_membership', $is_2checkout, $membership );
}

View File

@@ -0,0 +1 @@
<?php // Silence is golden.