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 @@
<?php // Silence is golden.

View File

@@ -0,0 +1,91 @@
<?php
/**
* Membership Level settings
*
* @package RCP\Custom_Redirects\Admin\Subscription\Fields
* @since 1.0.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Add per-level setting fields
*
* @since 1.0.0
* @return void
*/
function rcp_custom_redirects_add_redirect_settings() {
?>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-custom-redirects-subscription-url"><?php _e( 'Registration Redirect URL', 'rcp' ); ?></label>
</th>
<td>
<?php
$subscription_url = ( isset( $_GET['edit_subscription'] ) ? rcp_custom_redirects_get_url( 'subscription', $_GET['edit_subscription'] ) : '' );
echo '<input type="text" name="rcp-custom-redirects-subscription-url" id="rcp-custom-redirects-subscription-url" value="' . esc_attr( $subscription_url ) . '" style="width: 300px;" />';
echo '<p class="description">' . __( 'The URL to redirect customers to after registration.', 'rcp' ) . '</p>';
?>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-custom-redirects-login-url"><?php _e( 'Login Redirect URL', 'rcp' ); ?></label>
</th>
<td>
<?php
$login_url = ( isset( $_GET['edit_subscription'] ) ? rcp_custom_redirects_get_url( 'login', $_GET['edit_subscription'] ) : '' );
echo '<input type="text" name="rcp-custom-redirects-login-url" id="rcp-custom-redirects-login-url" value="' . esc_attr( $login_url ) . '" style="width: 300px;" />';
echo '<p class="description">' . __( 'The URL to redirect customers to after login.', 'rcp' ) . '</p>';
?>
</td>
</tr>
<?php
}
add_action( 'rcp_add_subscription_form', 'rcp_custom_redirects_add_redirect_settings' );
add_action( 'rcp_edit_subscription_form', 'rcp_custom_redirects_add_redirect_settings' );
/**
* Store the redirect URL in subscription meta
*
* @since 1.0.0
* @param int $level_id The subscription ID
* @param array $args Arguements passed to the action
*/
function rcp_custom_redirects_save( $level_id = 0, $args = array() ) {
if ( isset( $_POST['rcp-custom-redirects-subscription-url'] ) ) {
$url = $_POST['rcp-custom-redirects-subscription-url'];
$subscription_urls = get_option( 'rcp_custom_redirects_subscription_urls', array() );
if ( ! empty( $url ) ) {
$subscription_urls[ $level_id ] = sanitize_text_field( $url );
} elseif ( is_array( $subscription_urls ) && array_key_exists( $level_id, $subscription_urls ) ) {
unset( $subscription_urls[ $level_id ] );
}
update_option( 'rcp_custom_redirects_subscription_urls', $subscription_urls );
}
if ( isset( $_POST['rcp-custom-redirects-login-url'] ) ) {
$url = $_POST['rcp-custom-redirects-login-url'];
$login_urls = get_option( 'rcp_custom_redirects_login_urls', array() );
if ( ! empty( $url ) ) {
$login_urls[ $level_id ] = sanitize_text_field( $url );
} elseif ( is_array( $login_urls ) && array_key_exists( $level_id, $login_urls ) ) {
unset( $login_urls[ $level_id ] );
}
update_option( 'rcp_custom_redirects_login_urls', $login_urls );
}
}
add_action( 'rcp_add_subscription', 'rcp_custom_redirects_save', 10, 2 );
add_action( 'rcp_edit_subscription_level', 'rcp_custom_redirects_save', 10, 2 );

View File

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

View File

@@ -0,0 +1,130 @@
<?PHP
if ( ! class_exists( 'RCP_Custom_Redirects' ) ) {
/**
* Main RCP_Custom_Redirects class
*
* @since 1.0.0
*/
class RCP_Custom_Redirects {
/**
* @var RCP_Custom_Redirects $instance The one true RCP_Custom_Redirects
* @since 1.0.0
*/
private static $instance;
/**
* Get active instance
*
* @access public
* @since 1.0.0
* @return object self::$instance The one true RCP_Custom_Redirects
*/
public static function instance() {
if ( ! self::$instance ) {
self::$instance = new RCP_Custom_Redirects();
self::$instance->setup_constants();
self::$instance->includes();
self::$instance->hooks();
}
return self::$instance;
}
/**
* Setup plugin constants
*
* @access private
* @since 1.0.0
* @return void
*/
public function setup_constants() {
// Plugin version
define( 'RCP_CUSTOM_REDIRECTS_VER', '1.0.6' );
// Plugin path
define( 'RCP_CUSTOM_REDIRECTS_DIR', plugin_dir_path( __FILE__ ) );
// Plugin URL
define( 'RCP_CUSTOM_REDIRECTS_URL', plugin_dir_url( __FILE__ ) );
}
/**
* Include necessary files
*
* @access private
* @since 1.0.0
* @return void
*/
private function includes() {
require_once RCP_CUSTOM_REDIRECTS_DIR . 'includes/functions.php';
require_once RCP_CUSTOM_REDIRECTS_DIR . 'includes/filters.php';
require_once RCP_CUSTOM_REDIRECTS_DIR . 'includes/actions.php';
if ( is_admin() ) {
require_once RCP_CUSTOM_REDIRECTS_DIR . 'admin/subscription/fields.php';
}
}
/**
* Run action and filter hooks
*
* @access private
* @since 1.0.0
* @return void
*/
private function hooks() {
if ( class_exists( 'RCP_Add_On_Updater' ) ) {
$updater = new RCP_Add_On_Updater( 449, __FILE__, RCP_CUSTOM_REDIRECTS_VER );
}
}
}
}
/**
* The main function responsible for returning the one true RCP_Custom_Redirects
* instance to functions everywhere
*
* @since 1.0.0
* @return RCP_Custom_Redirects The one true RCP_Custom_Redirects
*/
function rcp_check_and_deactivate_cr_plugin() {
// Check if Custom redirects plugin is activated and deactivates if it is
if ( is_plugin_active( 'rcp-custom-redirects/rcp-custom-redirects.php' ) ) {
deactivate_plugins( 'rcp-custom-redirects/rcp-custom-redirects.php' );
add_action( 'admin_notices', 'rcp_cr_plugin_deactivated_notice' );
}
}
function rcp_cr_plugin_deactivated_notice() {
?>
<div class="notice notice-warning is-dismissible">
<p><?php _e( 'The Custom Redirects addon has been deactivated since its functionality is now included in the RCP core plugin.', 'rcp' ); ?></p>
</div>
<?php
}
add_action( 'plugins_loaded', 'rcp_check_and_deactivate_cr_plugin' );
function rcp_custom_redirects_addon() {
if ( is_plugin_active( 'rcp-custom-redirects/rcp-custom-redirects.php' ) ) {
return;
}
return RCP_Custom_Redirects::instance();
}
add_action( 'plugins_loaded', 'rcp_custom_redirects_addon' );

View File

@@ -0,0 +1,98 @@
<?php
/**
* Filters
*
* @package RCP\Custom_Redirects\Actions
* @since 1.0.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Override the RCP login form processing
*
* @since 1.0.0
* @return void
*/
function rcp_custom_redirects_process_login_form() {
if ( ! isset( $_POST['rcp_action'] ) || 'login' != $_POST['rcp_action'] ) {
return;
}
if ( ! isset( $_POST['rcp_login_nonce'] ) || ! wp_verify_nonce( $_POST['rcp_login_nonce'], 'rcp-login-nonce' ) ) {
return;
}
if ( is_email( $_POST['rcp_user_login'] ) && ! username_exists( $_POST['rcp_user_login'] ) ) {
$user = get_user_by( 'email', $_POST['rcp_user_login'] );
} else {
// This returns the user ID and other info from the user name
$user = get_user_by( 'login', $_POST['rcp_user_login'] );
}
do_action( 'rcp_before_form_errors', $_POST );
if ( ! $user ) {
// If the user name doesn't exist
rcp_errors()->add( 'empty_username', __( 'Invalid username or email', 'rcp' ), 'login' );
}
if ( ! isset( $_POST['rcp_user_pass'] ) || $_POST['rcp_user_pass'] == '' ) {
// If no password was entered
rcp_errors()->add( 'empty_password', __( 'Please enter a password', 'rcp' ), 'login' );
}
if ( $user ) {
// Check the user's login with their password
if ( ! wp_check_password( $_POST['rcp_user_pass'], $user->user_pass, $user->ID ) ) {
// If the password is incorrect for the specified user
rcp_errors()->add( 'empty_password', __( 'Incorrect password', 'rcp' ), 'login' );
}
}
if ( function_exists( 'is_limit_login_ok' ) && ! is_limit_login_ok() ) {
rcp_errors()->add( 'limit_login_failed', limit_login_error_msg(), 'login' );
}
do_action( 'rcp_login_form_errors', $_POST );
// Retrieve all error messages
$errors = rcp_errors()->get_error_messages();
// Only log the user in if there are no errors
if ( empty( $errors ) ) {
$remember = isset( $_POST['rcp_user_remember'] );
$redirect = ! empty( $_POST['rcp_redirect'] ) ? $_POST['rcp_redirect'] : home_url();
$level_id = rcp_get_subscription_id( $user->ID );
$redirect_urls = get_option( 'rcp_custom_redirects_login_urls' );
if ( is_array( $redirect_urls ) && array_key_exists( $level_id, $redirect_urls ) ) {
if ( $redirect_urls[ $level_id ] !== '' ) {
$redirect = $redirect_urls[ $level_id ];
}
}
rcp_login_user_in( $user->ID, $_POST['rcp_user_login'], $remember );
// Redirect the user back to the appropriate page
wp_redirect( $redirect );
exit;
} else {
if ( function_exists( 'limit_login_failed' ) ) {
limit_login_failed( $_POST['rcp_user_login'] );
}
}
}
// Only use our login override if not on version 2.8.2+.
if ( ! defined( 'RCP_PLUGIN_VERSION' ) || version_compare( RCP_PLUGIN_VERSION, '2.8.2', '<' ) ) {
remove_action( 'init', 'rcp_process_login_form' );
add_action( 'init', 'rcp_custom_redirects_process_login_form' );
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* Filters
*
* @package RCP\Custom_Redirects\Filters
* @since 1.0.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Filter registration return URLs
*
* @since 1.0.0
* @param string $redirect The current redirect URL
* @param int $user_id The ID for the logged in user
* @return string $redirect The redirect URL
*/
function rcp_custom_redirects_get_return_url( $redirect, $user_id ) {
$level_id = rcp_get_registration()->get_membership_level_id();
$redirect_urls = get_option( 'rcp_custom_redirects_subscription_urls' );
// Level ID won't be available via the registration class in PayPal Express due to the confirmation page.
if ( empty( $level_id ) ) {
if ( function_exists( 'rcp_get_customer_by_user_id' ) ) {
/**
* RCP 3.0 and higher
*/
$customer = rcp_get_customer_by_user_id( $user_id );
if ( empty( $customer ) ) {
return $redirect;
}
// Get most recently modified membership.
$memberships = $customer->get_memberships(
array(
'status__in' => array( 'pending', 'active' ),
'orderby' => 'date_modified',
'order' => 'DESC',
)
);
if ( empty( $memberships ) || empty( $memberships[0] ) ) {
return $redirect;
}
$level_id = $memberships[0]->get_object_id();
} else {
/**
* RCP 2.9 and lower
*/
$member = new RCP_Member( $user_id );
$level_id = $member->get_pending_subscription_id();
if ( empty( $level_id ) ) {
$level_id = $member->get_subscription_id();
}
}
}
if ( empty( $level_id ) ) {
return $redirect;
}
if ( is_array( $redirect_urls ) && array_key_exists( $level_id, $redirect_urls ) ) {
if ( $redirect_urls[ $level_id ] !== '' ) {
$redirect = $redirect_urls[ $level_id ];
}
}
return $redirect;
}
add_filter( 'rcp_return_url', 'rcp_custom_redirects_get_return_url', 10, 2 );
/**
* Filter login redirect URL
*
* @param string $redirect The current redirect URL.
* @param WP_User $user Object for the user logging in.
*
* @since 1.0.1
* @return string $redirect The new redirect URL.
*/
function rcp_custom_redirects_get_login_redirect_url( $redirect, $user ) {
if ( function_exists( 'rcp_get_customer_by_user_id' ) ) {
/**
* RCP 3.0+
*/
$customer = rcp_get_customer_by_user_id( $user->ID );
if ( empty( $customer ) ) {
return $redirect;
}
// Order by price so that the highest price one takes priority.
$memberships = $customer->get_memberships(
array(
'status__in' => array( 'active', 'cancelled' ),
)
);
if ( empty( $memberships ) || empty( $memberships[0] ) ) {
return $redirect;
}
/**
* @var RCP_Membership $membership
*/
$membership = false;
// Determine the highest priced membership.
foreach ( $memberships as $this_membership ) {
/**
* @var RCP_Membership $this_membership
*/
if ( empty( $membership ) ) {
$membership = $this_membership;
continue;
} else {
$high_value = max( $this_membership->get_initial_amount(), $this_membership->get_recurring_amount() );
if ( $high_value > max( $membership->get_initial_amount(), $membership->get_recurring_amount() ) ) {
$membership = $this_membership;
}
}
}
$level_id = $membership->get_object_id();
} else {
/**
* RCP 2.9 and lower
*/
$level_id = rcp_get_subscription_id( $user->ID );
}
$redirect_urls = get_option( 'rcp_custom_redirects_login_urls' );
if ( empty( $level_id ) ) {
return $redirect;
}
if ( is_array( $redirect_urls ) && array_key_exists( $level_id, $redirect_urls ) ) {
if ( ! empty( $redirect_urls[ $level_id ] ) ) {
$redirect = $redirect_urls[ $level_id ];
}
}
return $redirect;
}
add_filter( 'rcp_login_redirect_url', 'rcp_custom_redirects_get_login_redirect_url', 10, 2 );

View File

@@ -0,0 +1,46 @@
<?php
/**
* Helper functions
*
* @package RCP\Custom_Redirects\Functions
* @since 1.0.0
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Retrieve the URL for a given level
*
* @since 1.0.0
* @param string $url_type Whether to retrieve a subscription or login URL
* @param int $level_id The ID of the level to retrieve the URL for
* @return string $url The URL for the given level
*/
function rcp_custom_redirects_get_url( $url_type = 'subscription', $level_id = false ) {
$url = '';
if ( $level_id ) {
switch ( $url_type ) {
case 'subscription':
$redirect_urls = get_option( 'rcp_custom_redirects_subscription_urls' );
break;
case 'login':
$redirect_urls = get_option( 'rcp_custom_redirects_login_urls' );
break;
default:
$redirect_urls = apply_filters( 'rcp_custom_redirects_urls', array(), $url_type, $level_id );
break;
}
if ( is_array( $redirect_urls ) && array_key_exists( $level_id, $redirect_urls ) ) {
$url = $redirect_urls[ $level_id ];
}
}
return $url;
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,473 @@
<?php
/**
* Discount Codes List Table
*
* @package restrict-content-pro
* @copyright Copyright (c) 2019, Restrict Content Pro
* @license GPL2+
* @since 3.1
*/
namespace RCP\Admin;
use \RCP_Discount;
/**
* Class Discount_Codes_Table
*
* @since 3.1
* @package RCP\Admin
*/
class Discount_Codes_Table extends List_Table {
/**
* Constructor.
*
* @since 3.1
* @see WP_List_Table::__construct()
*/
public function __construct() {
parent::__construct( [
'singular' => 'Discount Code',
'plural' => 'Discount Codes',
'ajax' => false,
] );
$this->process_bulk_action();
$this->get_counts();
}
/**
* Get the base URL for the discount codes list table.
*
* @since 3.1
* @return string Base URL.
*/
public function get_base_url() {
$args = array(
'page' => 'rcp-discounts'
);
$discounts_page = add_query_arg( $args, admin_url( 'admin.php' ) );
return $discounts_page;
}
/**
* Retrieve the table columns.
*
* @since 3.1
* @return array
*/
public function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />',
'name' => __( 'Name', 'rcp' ),
'description' => __( 'Description', 'rcp' ),
'code' => __( 'Code', 'rcp' ),
'membership_levels' => __( 'Membership Level(s)', 'rcp' ),
'amount' => __( 'Amount', 'rcp' ),
'type' => __( 'Type', 'rcp' ),
'status' => __( 'Status', 'rcp' ),
'use_count' => __( 'Uses', 'rcp' ),
'uses_left' => __( 'Uses Left', 'rcp' ),
'expiration' => __( 'Expiration', 'rcp' ),
'one_time' => __( 'One Time', 'rcp' )
);
/*
* Backwards compatibility: add an "extra" column if someone is hooking into the old action to add
* their own column. Everything gets bundled into one column because this is the only way we can realistically
* do it.
*/
if ( has_action( 'rcp_discounts_page_table_header' ) ) {
$columns['custom'] = __( 'Extra', 'rcp' );
}
/**
* Filters the table columns.
*
* @param array $columns
*
* @since 3.1
*/
$columns = apply_filters( 'rcp_discount_codes_list_table_columns', $columns );
return $columns;
}
/**
* Retrieve the sortable columns.
*
* @since 3.1
* @return array
*/
public function get_sortable_columns() {
return array(
'name' => array( 'name', false ),
'code' => array( 'code', false ),
'use_count' => array( 'use_count', false ),
'expiration' => array( 'expiration', false )
);
}
/**
* Gets the name of the primary column.
*
* @since 3.1
* @return string
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* This function renders any other columns in the list table.
*
* @param RCP_Discount $discount Discount code object object.
* @param string $column_name The name of the column
*
* @since 3.1
* @return string Column Name
*/
public function column_default( $discount, $column_name ) {
$value = '';
switch ( $column_name ) {
case 'description' :
$value = $discount->get_description();
break;
case 'code' :
$value = esc_html( $discount->get_code() );
break;
case 'membership_levels' :
$membership_levels = $discount->get_membership_level_ids();
if ( is_array( $membership_levels ) && count( $membership_levels ) > 1 ) {
$value = __( 'Multiple Levels', 'rcp' );
} elseif ( is_array( $membership_levels ) && 1 === count( $membership_levels ) ) {
$value = rcp_get_subscription_name( $membership_levels[0] );
} else {
$value = __( 'All Levels', 'rcp' );
}
break;
case 'amount' :
$value = rcp_discount_sign_filter( $discount->get_amount(), $discount->get_unit() );
break;
case 'type' :
$value = '%' == $discount->get_unit() ? __( 'Percentage', 'rcp' ) : __( 'Flat', 'rcp' );
break;
case 'status' :
if ( rcp_is_discount_not_expired( $discount->get_id() ) ) {
$value = 'active' === $discount->get_status() ? __( 'active', 'rcp' ) : __( 'disabled', 'rcp' );
} else {
$value = __( 'expired', 'rcp' );
}
break;
case 'use_count' :
if ( $discount->get_max_uses() > 0 ) {
$value = absint( $discount->get_use_count() ) . '/' . absint( $discount->get_max_uses() );
} else {
$value = absint( $discount->get_use_count() );
}
break;
case 'uses_left' :
$value = rcp_discount_has_uses_left( $discount->get_id() ) ? __( 'yes', 'rcp' ) : __( 'no', 'rcp' );
break;
case 'expiration' :
$expiration = $discount->get_expiration();
$value = ! empty( $expiration ) ? date_i18n( 'Y-m-d H:i:s', strtotime( $expiration, current_time( 'timestamp' ) ) ) : __( 'none', 'rcp' );
break;
case 'one_time' :
$value = $discount->is_one_time() ? __( 'yes', 'rcp' ) : __( 'no', 'rcp' );
break;
}
/*
* Backwards compatibility: show content of custom columns from old action hook.
*/
if ( 'custom' == $column_name && has_action( 'rcp_discounts_page_table_column' ) ) {
ob_start();
do_action( 'rcp_discounts_page_table_column', $discount->get_id() );
$column_content = ob_get_clean();
$value = wp_strip_all_tags( $column_content );
}
/**
* Filters the column value.
*
* @param string $value Column value.
* @param object $discount Discount code object.
*
* @since 3.1
*/
$value = apply_filters( 'rcp_discount_codes_list_table_column_' . $column_name, $value, $discount );
return $value;
}
/**
* Render the checkbox column.
*
* @param object $discount
*
* @since 3.1
* @return string
*/
public function column_cb( $discount ) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
'discount_id',
$discount->id
);
}
/**
* Render the "Name" column.
*
* @param RCP_Discount $discount
*
* @since 3.1
* @return string
*/
public function column_name( $discount ) {
$edit_discount_url = add_query_arg( 'edit_discount', urlencode( $discount->get_id() ), $this->get_base_url() );
// Edit discount.
$actions = array(
'edit' => '<a href="' . esc_url( $edit_discount_url ) . '">' . __( 'Edit', 'rcp' ) . '</a>',
);
if ( 'active' == $discount->get_status() ) {
// Deactivate discount.
$actions['deactivate'] = '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
'rcp-action' => 'deactivate_discount',
'discount_id' => urlencode( $discount->get_id() )
), $this->get_base_url() ), 'rcp-deactivate-discount' ) ) . '">' . __( 'Deactivate', 'rcp' ) . '</a>';
} else {
// Activate discount.
$actions['activate'] = '<a href="' . esc_url( wp_nonce_url( add_query_arg( array(
'rcp-action' => 'activate_discount',
'discount_id' => urlencode( $discount->get_id() )
), $this->get_base_url() ), 'rcp-activate-discount' ) ) . '">' . __( 'Activate', 'rcp' ) . '</a>';
}
// Delete discount.
$actions['delete'] = '<span class="trash"><a href="' . esc_url( wp_nonce_url( add_query_arg( array(
'rcp-action' => 'delete_discount_code',
'discount_id' => urlencode( $discount->get_id() )
), $this->get_base_url() ), 'rcp-delete-discount' ) ) . '" class="rcp_delete_discount">' . __( 'Delete', 'rcp' ) . '</a></span>';
// Discount ID.
$actions['discount_id'] = '<span class="id rcp-id-col">' . sprintf( __( 'ID: %d', 'rcp' ), $discount->get_id() ) . '</span>';
/**
* Filters the row actions.
*
* @param array $actions Default actions.
* @param object $discount Discount object.
*
* @since 3.1
*/
$actions = apply_filters( 'rcp_discount_codes_list_table_row_actions', $actions, $discount );
$final = '<strong><a class="row-title" href="' . esc_url( $edit_discount_url ) . '">' . esc_html( $discount->get_name() ) . '</a></strong>';
if ( current_user_can( 'rcp_manage_discounts' ) ) {
$final .= $this->row_actions( $actions );
}
return $final;
}
/**
* Message to be displayed when there are no discount codes.
*
* @since 3.1
* @return void
*/
public function no_items() {
esc_html_e( 'No discount codes found.', 'rcp' );
}
/**
* Retrieve the bulk actions.
*
* @since 3.1
* @return array
*/
public function get_bulk_actions() {
return array(
'activate' => __( 'Activate', 'rcp' ),
'deactivate' => __( 'Deactivate', 'rcp' ),
'delete' => __( 'Permanently Delete', 'rcp' )
);
}
/**
* Process bulk actions.
*
* @since 3.1
* @return void
*/
public function process_bulk_action() {
// Bail if a nonce was not supplied.
if ( ! isset( $_REQUEST['_wpnonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'bulk-discountcodes' ) ) {
return;
}
$ids = wp_parse_id_list( (array) $this->get_request_var( 'discount_id', false ) );
// Bail if no IDs
if ( empty( $ids ) ) {
return;
}
foreach ( $ids as $discount_id ) {
switch ( $this->current_action() ) {
case 'activate':
rcp_update_discount( absint( $discount_id ), array( 'status' => 'active' ) );
break;
case 'deactivate':
rcp_update_discount( absint( $discount_id ), array( 'status' => 'disabled' ) );
break;
case 'delete':
rcp_delete_discount( absint( $discount_id ) );
break;
}
}
$this->show_admin_notice( $this->current_action(), count( $ids ) );
}
/**
* Show admin notice for bulk actions.
*
* @param string $action The action to show the notice for.
* @param int $number Number of objects processed.
*
* @access private
* @since 3.1
* @return void
*/
private function show_admin_notice( $action, $number = 1 ) {
$message = '';
switch ( $action ) {
case 'activate' :
$message = _n( 'Discount code activated.', 'Discount codes activated.', $number, 'rcp' );
break;
case 'deactivate' :
$message = _n( 'Discount code deactivated.', 'Discount codes deactivated.', $number, 'rcp' );
break;
case 'delete' :
$message = _n( 'Discount code deleted.', 'Discount codes deleted.', $number, 'rcp' );
break;
}
if ( empty( $message ) ) {
return;
}
echo '<div class="updated"><p>' . $message . '</p></div>';
}
/**
* Retrieve the discount code counts.
*
* @since 3.1
* @return void
*/
public function get_counts() {
$this->counts = array(
'total' => rcp_count_discounts(),
'active' => rcp_count_discounts( array( 'status' => 'active' ) ),
'inactive' => rcp_count_discounts( array( 'status' => 'disabled' ) )
);
}
/**
* Retrieve discount codes data.
*
* @param bool $count Whether or not to get discount code objects (false) or just count the total number (true).
*
* @since 3.1
* @return RCP_Discount[]|int
*/
public function discounts_data( $count = false ) {
$args = array(
'number' => $this->per_page,
'offset' => $this->get_offset(),
'status' => $this->get_status(),
'search' => $this->get_search(),
'orderby' => $this->get_request_var( 'orderby', 'date_modified' ),
'order' => strtoupper( $this->get_request_var( 'order', 'DESC' ) )
);
// Use `disabled` instead of `inactive`.
if ( 'inactive' === $args['status'] ) {
$args['status'] = 'disabled';
}
if ( $count ) {
return rcp_count_discounts( $args );
}
return rcp_get_discounts( $args );
}
/**
* Setup the final data for the table.
*
* @since 3.1
* @return void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->discounts_data();
$total = $this->discounts_data( true );
// Setup pagination
$this->set_pagination_args( array(
'total_items' => $total,
'per_page' => $this->per_page,
'total_pages' => ceil( $total / $this->per_page )
) );
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* Discount Actions
*
* @package restrict-content-pro
* @subpackage Admin/Discount Actions
* @copyright Copyright (c) 2017, Restrict Content Pro
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 2.9
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) exit;
/**
* Add a new discount code
*
* @since 2.9
* @return void
*/
function rcp_process_add_discount() {
if ( ! wp_verify_nonce( $_POST['rcp_add_discount_nonce'], 'rcp_add_discount_nonce' ) ) {
wp_die( __( 'Nonce verification failed.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! current_user_can( 'rcp_manage_discounts' ) ) {
wp_die( __( 'You do not have permission to perform this action.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
// Setup data
$data = array(
'name' => sanitize_text_field( wp_unslash( $_POST['name'] ) ),
'description' => wp_kses_post( wp_unslash( $_POST['description'] ) ),
'amount' => sanitize_text_field( $_POST['amount'] ),
'unit' => isset( $_POST['unit'] ) && $_POST['unit'] == '%' ? '%' : 'flat',
'code' => sanitize_text_field( wp_unslash( $_POST['code'] ) ),
'status' => 'active',
'expiration' => sanitize_text_field( $_POST['expiration'] ),
'max_uses' => absint( $_POST['max'] ),
'membership_level_ids' => ( ! empty( $_POST['membership_levels'] ) && is_array( $_POST['membership_levels'] ) ) ? array_map( 'absint', $_POST['membership_levels'] ) : array(),
'one_time' => ! empty( $_POST['one_time'] ) ? 1 : 0
);
$add = rcp_add_discount( $data );
if ( is_wp_error( $add ) ) {
rcp_log( sprintf( 'Error creating new discount code: %s', $add->get_error_message() ), true );
$error_code = ( 'discount' === substr( $add->get_error_code(), 0, 8 ) ) ? $add->get_error_code() : 'discount_' . $add->get_error_code();
$url = add_query_arg( array(
'rcp_message' => urlencode( $error_code ),
'discount_code' => urlencode( strtolower( $data['code'] ) )
), admin_url( 'admin.php?page=rcp-discounts' ) );
} elseif ( $add ) {
rcp_log( sprintf( 'Successfully added discount #%d.', $add ) );
$url = admin_url( 'admin.php?page=rcp-discounts&rcp_message=discount_added' );
} else {
rcp_log( 'Error inserting new discount code into the database.', true );
$url = admin_url( 'admin.php?page=rcp-discounts&rcp_message=discount_not_added' );
}
wp_safe_redirect( $url );
exit;
}
add_action( 'rcp_action_add-discount', 'rcp_process_add_discount' );
/**
* Edit an existing discount code
*
* @since 2.9
* @return void
*/
function rcp_process_edit_discount() {
if ( ! wp_verify_nonce( $_POST['rcp_edit_discount_nonce'], 'rcp_edit_discount_nonce' ) ) {
wp_die( __( 'Nonce verification failed.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! current_user_can( 'rcp_manage_discounts' ) ) {
wp_die( __( 'You do not have permission to perform this action.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
// Setup data
$data = array(
'name' => sanitize_text_field( wp_unslash( $_POST['name'] ) ),
'description' => wp_kses_post( wp_unslash( $_POST['description'] ) ),
'amount' => sanitize_text_field( $_POST['amount'] ),
'unit' => isset( $_POST['unit'] ) && $_POST['unit'] == '%' ? '%' : 'flat',
'code' => sanitize_text_field( wp_unslash( $_POST['code'] ) ),
'status' => 'active' == $_POST['status'] ? 'active' : 'disabled',
'expiration' => sanitize_text_field( $_POST['expiration'] ),
'max_uses' => absint( $_POST['max'] ),
'membership_level_ids' => ( ! empty( $_POST['membership_levels'] ) && is_array( $_POST['membership_levels'] ) ) ? array_map( 'absint', $_POST['membership_levels'] ) : array(),
'one_time' => ! empty( $_POST['one_time'] ) ? 1 : 0,
);
$update = rcp_update_discount( absint( $_POST['discount_id'] ), $data );
if ( is_wp_error( $update ) ) {
rcp_log( sprintf( 'Error updating discount code: %s', $update->get_error_message() ), true );
wp_die( $update );
}
if ( $update ) {
rcp_log( sprintf( 'Successfully edited discount #%d.', $_POST['discount_id'] ) );
$url = admin_url( 'admin.php?page=rcp-discounts&discount-updated=1' );
} else {
rcp_log( sprintf( 'Error editing discount #%d.', $_POST['discount_id'] ), true );
$url = admin_url( 'admin.php?page=rcp-discounts&discount-updated=0' );
}
wp_safe_redirect( $url );
exit;
}
add_action( 'rcp_action_edit-discount', 'rcp_process_edit_discount' );
/**
* Delete a discount code
*
* @since 2.9
* @return void
*/
function rcp_process_delete_discount() {
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rcp-delete-discount' ) ) {
wp_die( __( 'Nonce verification failed.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! current_user_can( 'rcp_manage_discounts' ) ) {
wp_die( __( 'You do not have permission to perform this action.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! isset( $_GET['discount_id'] ) ) {
wp_die( __( 'Please select a discount.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 400 ) );
}
$discount_id = absint( $_GET['discount_id'] );
rcp_delete_discount( $discount_id );
rcp_log( sprintf( 'Deleted discount #%d.', $discount_id ) );
wp_safe_redirect( add_query_arg( 'rcp_message', 'discount_deleted', 'admin.php?page=rcp-discounts' ) );
exit;
}
add_action( 'rcp_action_delete_discount_code', 'rcp_process_delete_discount' );
/**
* Activate a discount code
*
* @since 2.9
* @return void
*/
function rcp_process_activate_discount() {
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rcp-activate-discount' ) ) {
wp_die( __( 'Nonce verification failed.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! current_user_can( 'rcp_manage_discounts' ) ) {
wp_die( __( 'You do not have permission to perform this action.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! isset( $_GET['discount_id'] ) ) {
wp_die( __( 'Please select a discount.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 400 ) );
}
$discount_id = absint( $_GET['discount_id'] );
rcp_update_discount( $discount_id, array(
'status' => 'active'
) );
rcp_log( sprintf( 'Successfully activated discount #%d.', $discount_id ) );
wp_safe_redirect( add_query_arg( 'rcp_message', 'discount_activated', 'admin.php?page=rcp-discounts' ) );
exit;
}
add_action( 'rcp_action_activate_discount', 'rcp_process_activate_discount' );
/**
* Deactivate a discount code
*
* @since 2.9
* @return void
*/
function rcp_process_deactivate_discount() {
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'rcp-deactivate-discount' ) ) {
wp_die( __( 'Nonce verification failed.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! current_user_can( 'rcp_manage_discounts' ) ) {
wp_die( __( 'You do not have permission to perform this action.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 403 ) );
}
if ( ! isset( $_GET['discount_id'] ) ) {
wp_die( __( 'Please select a discount.', 'rcp' ), __( 'Error', 'rcp' ), array( 'response' => 400 ) );
}
$discount_id = absint( $_GET['discount_id'] );
rcp_update_discount( $discount_id, array(
'status' => 'disabled'
) );
rcp_log( sprintf( 'Successfully deactivated discount #%d.', $discount_id ) );
wp_safe_redirect( add_query_arg( 'rcp_message', 'discount_deactivated', 'admin.php?page=rcp-discounts' ) );
exit;
}
add_action( 'rcp_action_deactivate_discount', 'rcp_process_deactivate_discount' );

View File

@@ -0,0 +1,156 @@
<?php
/**
* Discount Codes Page
*
* @package Restrict Content Pro
* @subpackage Admin/Discount Codes
* @copyright Copyright (c) 2017, Restrict Content Pro
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
/**
* Render the discounts table
*
* @return void
*/
function rcp_discounts_page() {
include_once RCP_PLUGIN_DIR . 'pro/includes/admin/discounts/class-discount-codes-table.php';
$table_class = new \RCP\Admin\Discount_Codes_Table();
$table_class->prepare_items();
do_action( 'stellarwp/telemetry/restrict-content-pro/optin' );
?>
<div class="wrap">
<?php if( isset( $_GET['edit_discount'] ) ) :
include('edit-discount.php');
else : ?>
<h1><?php _e( 'Discount Codes', 'rcp' ); ?></h1>
<form id="rcp-discount-codes-filter" method="GET" action="<?php echo esc_url( add_query_arg( 'page', 'rcp-discounts', admin_url( 'admin.php' ) ) ); ?>">
<input type="hidden" name="page" value="rcp-discounts"/>
<?php
$table_class->views();
$table_class->search_box( __( 'Search discount codes', 'rcp' ), 'rcp-membership-levels' );
$table_class->display();
?>
</form>
<?php do_action( 'rcp_discounts_below_table' ); ?>
<?php if( current_user_can( 'rcp_manage_discounts' ) ) : ?>
<h2><?php _e( 'Add New Discount', 'rcp' ); ?></h2>
<form id="rcp-discounts" action="" method="POST">
<table class="form-table">
<tbody>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-name"><?php _e( 'Name', 'rcp' ); ?></label>
</th>
<td>
<input name="name" id="rcp-name" type="text" value=""/>
<p class="description"><?php _e( 'The name of this discount', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-description"><?php _e( 'Description', 'rcp' ); ?></label>
</th>
<td>
<textarea name="description" id="rcp-description"></textarea>
<p class="description"><?php _e( 'The description of this discount code', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-code"><?php _e( 'Code', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-code" name="code" value=""/>
<p class="description"><?php _e( 'Enter a code for this discount, such as 10PERCENT. Excluding special characters', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-unit"><?php _e( 'Type', 'rcp' ); ?></label>
</th>
<td>
<select name="unit" id="rcp-duration-unit">
<option value="%"><?php _e( 'Percentage', 'rcp' ); ?></option>
<option value="flat"><?php _e( 'Flat amount', 'rcp' ); ?></option>
</select>
<p class="description"><?php _e( 'The kind of discount to apply for this discount.', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-amount"><?php _e( 'Amount', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-amount" name="amount" value=""/>
<p class="description"><?php _e( 'The amount of this discount code.', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-discount-one-time"><?php _e( 'One Time', 'rcp' ); ?></label>
</th>
<td>
<input type="checkbox" value="1" name="one_time" id="rcp-discount-one-time"/>
<span class="description"><?php _e( 'Check this to make this discount only apply to the first payment in a membership. Note one-time discounts cannot be used in conjunction with free trials. When this option is not enabled, the discount code will apply to all payments in a membership instead of just the initial payment.', 'rcp' ); ?></span>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-subscription"><?php _e( 'Membership Levels', 'rcp' ); ?></label>
</th>
<td>
<?php
$levels = rcp_get_membership_levels( array( 'number' => 999 ) );
if( $levels ) {
foreach ( $levels as $level ) : ?>
<input type="checkbox" id="rcp-membership-levels-<?php echo esc_attr( $level->get_id() ); ?>" name="membership_levels[]" value="<?php echo esc_attr( $level->id ) ?>">
<label for="rcp-membership-levels-<?php echo esc_attr( $level->get_id() ); ?>"><?php echo esc_html( $level->get_name() ); ?></label>
<br>
<?php
endforeach;
?>
<p class="description"><?php _e( 'The membership levels this discount code can be used for. Leave blank for all levels.', 'rcp' ); ?></p>
<?php
} else {
echo '<p class="description">' . __( 'No membership levels created yet. This discount will be available to use with all future membership levels.', 'rcp' ) . '</p>';
}
?>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-expiration"><?php _e( 'Expiration date', 'rcp' ); ?></label>
</th>
<td>
<input name="expiration" id="rcp-expiration" type="text" class="rcp-datetimepicker"/>
<p class="description"><?php _e( 'Enter the expiration date for this discount code in the format of yyyy-mm-dd hh:mm:ss. For no expiration, leave blank', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-max-uses"><?php _e( 'Max Uses', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-max-uses" name="max" value=""/>
<p class="description"><?php _e( 'The maximum number of times this discount can be used. Leave blank for unlimited.', 'rcp' ); ?></p>
</td>
</tr>
<?php do_action( 'rcp_add_discount_form' ); ?>
</tbody>
</table>
<p class="submit">
<input type="hidden" name="rcp-action" value="add-discount"/>
<input type="submit" value="<?php _e( 'Add Discount Code', 'rcp' ); ?>" class="button-primary"/>
</p>
<?php wp_nonce_field( 'rcp_add_discount_nonce', 'rcp_add_discount_nonce' ); ?>
</form>
<?php endif; ?>
<?php endif; ?>
</div><!--end wrap-->
<?php
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* Edit Discount Code
*
* @package Restrict Content Pro
* @subpackage Admin/Edit Discount
* @copyright Copyright (c) 2017, Restrict Content Pro
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
*/
$code = rcp_get_discount( urldecode( $_GET['edit_discount'] ) );
?>
<h1>
<?php _e( 'Edit Discount Code:', 'rcp' ); echo ' ' . $code->get_name(); ?>
<a href="<?php echo admin_url( '/admin.php?page=rcp-discounts' ); ?>" class="add-new-h2">
<?php _e( 'Cancel', 'rcp' ); ?>
</a>
</h1>
<form id="rcp-edit-discount" action="" method="post">
<table class="form-table">
<tbody>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-name"><?php _e(' Name', 'rcp' ); ?></label>
</th>
<td>
<input name="name" id="rcp-name" type="text" value="<?php echo esc_html( $code->get_name() ); ?>"/>
<p class="description"><?php _e(' The name of this discount', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-description"><?php _e(' Description', 'rcp' ); ?></label>
</th>
<td>
<textarea name="description" id="rcp-description"><?php echo esc_html( $code->get_description() ); ?></textarea>
<p class="description"><?php _e(' The description of this discount code', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-code"><?php _e(' Code', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-code" name="code" value="<?php echo esc_attr( $code->get_code() ); ?>"/>
<p class="description"><?php _e(' Enter a code for this discount, such as 10PERCENT. Excluding special characters', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-unit"><?php _e(' Type', 'rcp' ); ?></label>
</th>
<td>
<select name="unit" id="rcp-unit">
<option value="%" <?php selected( $code->get_unit(), '%' ); ?>><?php _e(' Percentage', 'rcp' ); ?></option>
<option value="flat" <?php selected( $code->get_unit(), 'flat' ); ?>><?php _e(' Flat amount', 'rcp' ); ?></option>
</select>
<p class="description"><?php _e(' The kind of discount to apply for this discount.', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-amount"><?php _e(' Amount', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-amount" name="amount" value="<?php echo esc_attr( $code->get_amount() ); ?>"/>
<p class="description"><?php _e(' The amount of this discount code.', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-discount-one-time"><?php _e( 'One Time', 'rcp' ); ?></label>
</th>
<td>
<input type="checkbox" value="1" name="one_time" id="rcp-discount-one-time" <?php checked( ! empty( $code->one_time ) ); ?>/>
<span class="description"><?php _e( 'Check this to make this discount only apply to the first payment in a membership. Note one-time discounts cannot be used in conjunction with free trials. When this option is not enabled, the discount code will apply to all payments in a membership instead of just the initial payment.', 'rcp' ); ?></span>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-subscription"><?php _e( 'Membership Levels', 'rcp' ); ?></label>
</th>
<td>
<?php
$levels = rcp_get_membership_levels( array( 'number' => 999 ) );
if( $levels ) {
$current = $code->get_membership_level_ids();
foreach ( $levels as $level ) : ?>
<input type="checkbox" id="rcp-membership-levels-<?php echo esc_attr( $level->get_id() ); ?>" name="membership_levels[]" value="<?php echo esc_attr( $level->get_id() ) ?>" <?php checked( true, in_array( $level->get_id(), $current ) ); ?>>
<label for="rcp-membership-levels-<?php echo esc_attr( $level->get_id() ); ?>"><?php echo esc_html( $level->get_name() ); ?></label>
<br>
<?php
endforeach;
?>
<p class="description"><?php _e( 'The membership levels this discount code can be used for. Leave blank for all levels.', 'rcp' ); ?></p>
<?php
} else {
echo '<p class="description">' . __( 'No membership levels created yet. This discount will be available to use with all future membership levels.', 'rcp' ) . '</p>';
}
?>
</td>
</tr>
<tr valign="top">
<th scope="row" valign="top">
<label for="rcp-expiration"><?php _e(' Expiration date', 'rcp' ); ?></label>
</th>
<td>
<input name="expiration" id="rcp-expiration" type="text" class="rcp-datetimepicker" value="<?php echo empty( $code->get_expiration() ) ? '' : esc_attr( date( 'Y-m-d H:i:s', strtotime( $code->get_expiration(), current_time( 'timestamp' ) ) ) ); ?>"/>
<p class="description"><?php _e(' Enter the expiration date for this discount code in the format of yyyy-mm-dd hh:mm:ss. Leave blank for no expiration', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-status"><?php _e(' Status', 'rcp' ); ?></label>
</th>
<td>
<select name="status" id="rcp-status">
<option value="active" <?php selected( $code->get_status(), '%' ); ?>><?php _e(' Active', 'rcp' ); ?></option>
<option value="disabled" <?php selected( $code->get_status(), 'disabled' ); ?>><?php _e(' Disabled', 'rcp' ); ?></option>
</select>
<p class="description"><?php _e(' The status of this discount code.', 'rcp' ); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row" valign="top">
<label for="rcp-max-uses"><?php _e(' Max Uses', 'rcp' ); ?></label>
</th>
<td>
<input type="text" id="rcp-max-uses" name="max" value="<?php echo esc_attr( absint( $code->get_max_uses() ) ); ?>"/>
<p class="description"><?php _e(' The maximum number of times this discount can be used. Leave blank for unlimited.', 'rcp' ); ?></p>
</td>
</tr>
<?php do_action( 'rcp_edit_discount_form', $code->get_id() ); ?>
</tbody>
</table>
<p class="submit">
<input type="hidden" name="rcp-action" value="edit-discount"/>
<input type="hidden" name="discount_id" value="<?php echo absint( urldecode( $_GET['edit_discount'] ) ); ?>"/>
<input type="submit" value="<?php _e(' Update Discount', 'rcp' ); ?>" class="button-primary"/>
</p>
<?php wp_nonce_field( 'rcp_edit_discount_nonce', 'rcp_edit_discount_nonce' ); ?>
</form>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
<?php
/**
* Report Actions
*
* @package restrict-content-pro
* @copyright Copyright (c) 2019, Sandhills Development, LLC
* @license GPL2+
* @since 3.3
*/
/**
* Get the membership counts report data
*
* @since 3.3
* @return void
*/
function rcp_get_membership_counts_report_data() {
check_ajax_referer( 'rcp_load_reports', 'nonce' );
$membership_level_id = ! empty( $_POST['level_id'] ) ? absint( $_POST['level_id'] ) : 0;
$status = ! empty( $_POST['membership_status'] ) ? $_POST['membership_status'] : '';
$config = array(
'type' => 'line',
'data' => array(
'labels' => array(), // Date intervals
'datasets' => array(
'active' => array(
'label' => __( 'Active Memberships', 'rcp' ),
'backgroundColor' => 'rgba(12, 194, 25, 0.1)',
'borderColor' => 'rgb(12, 194, 25)',
//'fill' => false
),
'expired' => array(
'label' => __( 'Expired Memberships', 'rcp' ),
'backgroundColor' => 'rgba(255, 99, 132, 0.1)',
'borderColor' => 'rgb(255, 99, 132)',
//'fill' => false
),
'cancelled' => array(
'label' => __( 'Cancelled Memberships', 'rcp' ),
'backgroundColor' => 'rgba(178, 178, 178, 0.1)',
'borderColor' => 'rgb(178, 178, 178)',
//'fill' => false
),
'pending' => array(
'label' => __( 'Pending Memberships', 'rcp' ),
'backgroundColor' => 'rgba(59, 168, 245, 0.1)',
'borderColor' => 'rgb(59, 168, 245)',
//'fill' => false
)
)
),
'options' => array(
'scales' => array(
'xAxes' => array(
array(
'ticks' => array(
'autoSkipPadding' => 10,
'maxLabels' => 52
)
)
),
'yAxes' => array(
array(
'ticks' => array(
'min' => 0
)
)
),
)
)
);
if ( ! empty( $status ) ) {
foreach ( $config['data']['datasets'] as $status_key => $status_name ) {
if ( $status_key !== $status ) {
unset( $config['data']['datasets'][ $status_key ] );
}
}
}
$statuses = array_keys( $config['data']['datasets'] );
$dates = rcp_get_report_dates();
$ranges = rcp_get_graph_dates_by_range();
$from = date_create_from_format( 'j n Y', sprintf( '%d %d %d', $dates['day'], $dates['m_start'], $dates['year'] ) );
$to = date_create_from_format( 'j n Y', sprintf( '%d %d %d', $dates['day_end'], $dates['m_end'], $dates['year_end'] ) );
$config['data']['labels'] = $ranges;
$membership_counts_table_name = restrict_content_pro()->membership_counts_table->get_table_name();
global $wpdb;
$where = $wpdb->prepare( "WHERE date_created >= %s AND date_created <= %s", $from->format( 'Y-m-d 00:00:00' ), $to->format( 'Y-m-d 23:59:59' ) );
if ( ! empty( $membership_level_id ) ) {
$where .= $wpdb->prepare( " AND level_id = %d ", $membership_level_id );
}
$query = "SELECT SUM(active_count) AS active, SUM(pending_count) AS pending, SUM(cancelled_count) AS cancelled, SUM(expired_count) AS expired, DATE_FORMAT(date_created, '%Y-%m-%d') as date FROM {$membership_counts_table_name} {$where} GROUP BY date";
$results = $wpdb->get_results( $query );
$formatted_results = array();
foreach ( $results as $result ) {
$formatted_results[ $result->date ] = array(
'active' => $result->active,
'pending' => $result->pending,
'cancelled' => $result->cancelled,
'expired' => $result->expired
);
}
foreach ( $ranges as $range ) {
$exists = false;
if ( array_key_exists( $range, $formatted_results ) ) {
$exists = true;
}
foreach ( $statuses as $status ) {
$config['data']['datasets'][ $status ]['data'][] = $exists ? absint( $formatted_results[ $range ][ $status ] ) : 0;
}
}
$config['data']['datasets'] = array_values( $config['data']['datasets'] );
wp_send_json_success( $config );
exit;
}
add_action( 'wp_ajax_rcp_get_membership_counts_report_data', 'rcp_get_membership_counts_report_data' );

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.

View File

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

View File

@@ -0,0 +1,22 @@
Copyright (c) 2017 Braintree, a division of PayPal, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

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

View File

@@ -0,0 +1,24 @@
<?php
/**
* Braintree PHP Library
* Creates class_aliases for old class names replaced by PSR-4 Namespaces
*/
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'autoload.php');
if (version_compare(PHP_VERSION, '5.4.0', '<')) {
throw new Braintree_Exception('PHP version >= 5.4.0 required');
}
class Braintree {
public static function requireDependencies() {
$requiredExtensions = ['xmlwriter', 'openssl', 'dom', 'hash', 'curl'];
foreach ($requiredExtensions AS $ext) {
if (!extension_loaded($ext)) {
throw new Braintree_Exception('The Braintree library requires the ' . $ext . ' extension.');
}
}
}
}
Braintree::requireDependencies();

View File

@@ -0,0 +1,43 @@
<?php
namespace Braintree;
/**
* Creates an instance of AccountUpdaterDailyReport
*
*
* @package Braintree
*
* @property-read string $reportUrl
* @property-read date $reportDate
*/
class AccountUpdaterDailyReport extends Base
{
protected $_attributes = [];
protected function _initialize($disputeAttribs)
{
$this->_attributes = $disputeAttribs;
}
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
public function __toString()
{
$display = [
'reportDate', 'reportUrl'
];
$displayAttributes = [];
foreach ($display AS $attrib) {
$displayAttributes[$attrib] = $this->$attrib;
}
return __CLASS__ . '[' .
Util::attributesToString($displayAttributes) .']';
}
}
class_alias('Braintree\AccountUpdaterDailyReport', 'Braintree_AccountUpdaterDailyReport');

View File

@@ -0,0 +1,55 @@
<?php
namespace Braintree;
/**
* Braintree AchMandate module
* PHP Version 5
*
* @package Braintree
*
* @property-read string $text
* @property-read string $acceptedAt
*/
class AchMandate extends Base
{
/**
* create a printable representation of the object as:
* ClassName[property=value, property=value]
* @ignore
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) . ']';
}
/**
* sets instance properties from an array of values
*
* @ignore
* @access protected
* @param array $achAttribs array of achMandate data
* @return void
*/
protected function _initialize($achAttribs)
{
// set the attributes
$this->_attributes = $achAttribs;
}
/**
* factory method: returns an instance of AchMandate
* to the requesting method, with populated properties
* @ignore
* @return AchMandate
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
}
class_alias('Braintree\AchMandate', 'Braintree_Mandate');

View File

@@ -0,0 +1,43 @@
<?php
namespace Braintree;
/**
* @property-read string $amount
* @property-read \DateTime $createdAt
* @property-read int|null $currentBillingCycle
* @property-read string $description
* @property-read string $id
* @property-read string|null $kind
* @property-read string $merchantId
* @property-read string $name
* @property-read boolean $neverExpires
* @property-read int|null $numberOfBillingCycles
* @property-read int|null $quantity
* @property-read \DateTime $updatedAt
*/
class AddOn extends Modification
{
/**
*
* @param array $attributes
* @return AddOn
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
/**
* static methods redirecting to gateway
*
* @return AddOn[]
*/
public static function all()
{
return Configuration::gateway()->addOn()->all();
}
}
class_alias('Braintree\AddOn', 'Braintree_AddOn');

View File

@@ -0,0 +1,53 @@
<?php
namespace Braintree;
class AddOnGateway
{
/**
*
* @var Gateway
*/
private $_gateway;
/**
*
* @var Configuration
*/
private $_config;
/**
*
* @var Http
*/
private $_http;
/**
*
* @param Gateway $gateway
*/
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
/**
*
* @return AddOn[]
*/
public function all()
{
$path = $this->_config->merchantPath() . '/add_ons';
$response = $this->_http->get($path);
$addOns = ["addOn" => $response['addOns']];
return Util::extractAttributeAsArray(
$addOns,
'addOn'
);
}
}
class_alias('Braintree\AddOnGateway', 'Braintree_AddOnGateway');

View File

@@ -0,0 +1,150 @@
<?php
namespace Braintree;
/**
* Braintree Address module
* PHP Version 5
* Creates and manages Braintree Addresses
*
* An Address belongs to a Customer. It can be associated to a
* CreditCard as the billing address. It can also be used
* as the shipping address when creating a Transaction.
*
* @package Braintree
*
* @property-read string $company
* @property-read string $countryName
* @property-read \DateTime $createdAt
* @property-read string $customerId
* @property-read string $extendedAddress
* @property-read string $firstName
* @property-read string $id
* @property-read string $lastName
* @property-read string $locality
* @property-read string $postalCode
* @property-read string $region
* @property-read string $streetAddress
* @property-read \DateTime $updatedAt
*/
class Address extends Base
{
/**
* returns false if comparing object is not a Address,
* or is a Address with a different id
*
* @param object $other address to compare against
* @return boolean
*/
public function isEqual($other)
{
return !($other instanceof self) ?
false :
($this->id === $other->id && $this->customerId === $other->customerId);
}
/**
* create a printable representation of the object as:
* ClassName[property=value, property=value]
* @ignore
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) . ']';
}
/**
* sets instance properties from an array of values
*
* @ignore
* @access protected
* @param array $addressAttribs array of address data
* @return void
*/
protected function _initialize($addressAttribs)
{
// set the attributes
$this->_attributes = $addressAttribs;
}
/**
* factory method: returns an instance of Address
* to the requesting method, with populated properties
* @ignore
* @return Address
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
// static methods redirecting to gateway
/**
*
* @param array $attribs
* @return Address
*/
public static function create($attribs)
{
return Configuration::gateway()->address()->create($attribs);
}
/**
*
* @param array $attribs
* @return Address
*/
public static function createNoValidate($attribs)
{
return Configuration::gateway()->address()->createNoValidate($attribs);
}
/**
*
* @param Customer|int $customerOrId
* @param int $addressId
* @throws InvalidArgumentException
* @return Result\Successful
*/
public static function delete($customerOrId = null, $addressId = null)
{
return Configuration::gateway()->address()->delete($customerOrId, $addressId);
}
/**
*
* @param Customer|int $customerOrId
* @param int $addressId
* @throws Exception\NotFound
* @return Address
*/
public static function find($customerOrId, $addressId)
{
return Configuration::gateway()->address()->find($customerOrId, $addressId);
}
/**
*
* @param Customer|int $customerOrId
* @param int $addressId
* @param array $attributes
* @throws Exception\Unexpected
* @return Result\Successful|Result\Error
*/
public static function update($customerOrId, $addressId, $attributes)
{
return Configuration::gateway()->address()->update($customerOrId, $addressId, $attributes);
}
public static function updateNoValidate($customerOrId, $addressId, $attributes)
{
return Configuration::gateway()->address()->updateNoValidate($customerOrId, $addressId, $attributes);
}
}
class_alias('Braintree\Address', 'Braintree_Address');

View File

@@ -0,0 +1,314 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Braintree AddressGateway module
* PHP Version 5
* Creates and manages Braintree Addresses
*
* An Address belongs to a Customer. It can be associated to a
* CreditCard as the billing address. It can also be used
* as the shipping address when creating a Transaction.
*
* @package Braintree
*/
class AddressGateway
{
/**
*
* @var Gateway
*/
private $_gateway;
/**
*
* @var Configuration
*/
private $_config;
/**
*
* @var Http
*/
private $_http;
/**
*
* @param Gateway $gateway
*/
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
/* public class methods */
/**
*
* @access public
* @param array $attribs
* @return Result\Successful|Result\Error
*/
public function create($attribs)
{
Util::verifyKeys(self::createSignature(), $attribs);
$customerId = isset($attribs['customerId']) ?
$attribs['customerId'] :
null;
$this->_validateCustomerId($customerId);
unset($attribs['customerId']);
try {
return $this->_doCreate(
'/customers/' . $customerId . '/addresses',
['address' => $attribs]
);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound(
'Customer ' . $customerId . ' not found.'
);
}
}
/**
* attempts the create operation assuming all data will validate
* returns a Address object instead of a Result
*
* @access public
* @param array $attribs
* @return self
* @throws Exception\ValidationError
*/
public function createNoValidate($attribs)
{
$result = $this->create($attribs);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
* delete an address by id
*
* @param mixed $customerOrId
* @param string $addressId
*/
public function delete($customerOrId = null, $addressId = null)
{
$this->_validateId($addressId);
$customerId = $this->_determineCustomerId($customerOrId);
$path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId;
$this->_http->delete($path);
return new Result\Successful();
}
/**
* find an address by id
*
* Finds the address with the given <b>addressId</b> that is associated
* to the given <b>customerOrId</b>.
* If the address cannot be found, a NotFound exception will be thrown.
*
*
* @access public
* @param mixed $customerOrId
* @param string $addressId
* @return Address
* @throws Exception\NotFound
*/
public function find($customerOrId, $addressId)
{
$customerId = $this->_determineCustomerId($customerOrId);
$this->_validateId($addressId);
try {
$path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId;
$response = $this->_http->get($path);
return Address::factory($response['address']);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound(
'address for customer ' . $customerId .
' with id ' . $addressId . ' not found.'
);
}
}
/**
* updates the address record
*
* if calling this method in context,
* customerOrId is the 2nd attribute, addressId 3rd.
* customerOrId & addressId are not sent in object context.
*
*
* @access public
* @param array $attributes
* @param mixed $customerOrId (only used in call)
* @param string $addressId (only used in call)
* @return Result\Successful|Result\Error
*/
public function update($customerOrId, $addressId, $attributes)
{
$this->_validateId($addressId);
$customerId = $this->_determineCustomerId($customerOrId);
Util::verifyKeys(self::updateSignature(), $attributes);
$path = $this->_config->merchantPath() . '/customers/' . $customerId . '/addresses/' . $addressId;
$response = $this->_http->put($path, ['address' => $attributes]);
return $this->_verifyGatewayResponse($response);
}
/**
* update an address record, assuming validations will pass
*
* if calling this method in context,
* customerOrId is the 2nd attribute, addressId 3rd.
* customerOrId & addressId are not sent in object context.
*
* @access public
* @param array $transactionAttribs
* @param string $customerId
* @return Transaction
* @throws Exception\ValidationsFailed
* @see Address::update()
*/
public function updateNoValidate($customerOrId, $addressId, $attributes)
{
$result = $this->update($customerOrId, $addressId, $attributes);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
* creates a full array signature of a valid create request
* @return array gateway create request format
*/
public static function createSignature()
{
return [
'company', 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric',
'countryName', 'customerId', 'extendedAddress', 'firstName',
'lastName', 'locality', 'postalCode', 'region', 'streetAddress'
];
}
/**
* creates a full array signature of a valid update request
* @return array gateway update request format
*/
public static function updateSignature()
{
// TODO: remove customerId from update signature
return self::createSignature();
}
/**
* verifies that a valid address id is being used
* @ignore
* @param string $id address id
* @throws InvalidArgumentException
*/
private function _validateId($id = null)
{
if (empty($id) || trim($id) == "") {
throw new InvalidArgumentException(
'expected address id to be set'
);
}
if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) {
throw new InvalidArgumentException(
$id . ' is an invalid address id.'
);
}
}
/**
* verifies that a valid customer id is being used
* @ignore
* @param string $id customer id
* @throws InvalidArgumentException
*/
private function _validateCustomerId($id = null)
{
if (empty($id) || trim($id) == "") {
throw new InvalidArgumentException(
'expected customer id to be set'
);
}
if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) {
throw new InvalidArgumentException(
$id . ' is an invalid customer id.'
);
}
}
/**
* determines if a string id or Customer object was passed
* @ignore
* @param mixed $customerOrId
* @return string customerId
*/
private function _determineCustomerId($customerOrId)
{
$customerId = ($customerOrId instanceof Customer) ? $customerOrId->id : $customerOrId;
$this->_validateCustomerId($customerId);
return $customerId;
}
/* private class methods */
/**
* sends the create request to the gateway
* @ignore
* @param string $subPath
* @param array $params
* @return Result\Successful|Result\Error
*/
private function _doCreate($subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->post($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
* generic method for validating incoming gateway responses
*
* creates a new Address object and encapsulates
* it inside a Result\Successful object, or
* encapsulates an Errors object inside a Result\Error
* alternatively, throws an Unexpected exception if the response is invalid
*
* @ignore
* @param array $response gateway response values
* @return Result\Successful|Result\Error
* @throws Exception\Unexpected
*/
private function _verifyGatewayResponse($response)
{
if (isset($response['address'])) {
// return a populated instance of Address
return new Result\Successful(
Address::factory($response['address'])
);
} else if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
} else {
throw new Exception\Unexpected(
"Expected address or apiErrorResponse"
);
}
}
}
class_alias('Braintree\AddressGateway', 'Braintree_AddressGateway');

View File

@@ -0,0 +1,80 @@
<?php
namespace Braintree;
/**
* Braintree AmexExpressCheckoutCard module
* Creates and manages Braintree Amex Express Checkout cards
*
* <b>== More information ==</b>
*
* See {@link https://developers.braintreepayments.com/javascript+php}<br />
*
* @package Braintree
* @category Resources
*
* @property-read string $bin
* @property-read string $cardMemberExpiryDate
* @property-read string $cardMemberNumber
* @property-read string $cardType
* @property-read \DateTime $createdAt
* @property-read string $customerId
* @property-read boolean $default
* @property-read string $expirationMonth
* @property-read string $expirationYear
* @property-read string $imageUrl
* @property-read string $token
* @property-read string $sourceDescription
* @property-read \Braintree\Subscription[] $subscriptions
* @property-read \DateTime $updatedAt
*/
class AmexExpressCheckoutCard extends Base
{
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* factory method: returns an instance of AmexExpressCheckoutCard
* to the requesting method, with populated properties
*
* @ignore
* @return AmexExpressCheckoutCard
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $amexExpressCheckoutCardAttribs array of Amex Express Checkout card properties
* @return void
*/
protected function _initialize($amexExpressCheckoutCardAttribs)
{
// set the attributes
$this->_attributes = $amexExpressCheckoutCardAttribs;
$subscriptionArray = [];
if (isset($amexExpressCheckoutCardAttribs['subscriptions'])) {
foreach ($amexExpressCheckoutCardAttribs['subscriptions'] AS $subscription) {
$subscriptionArray[] = Subscription::factory($subscription);
}
}
$this->_set('subscriptions', $subscriptionArray);
}
}
class_alias('Braintree\AmexExpressCheckoutCard', 'Braintree_AmexExpressCheckoutCard');

View File

@@ -0,0 +1,90 @@
<?php
namespace Braintree;
/**
* Braintree AndroidPayCard module
* Creates and manages Braintree Android Pay cards
*
* <b>== More information ==</b>
*
* See {@link https://developers.braintreepayments.com/javascript+php}<br />
*
* @package Braintree
* @category Resources
*
* @property-read string $bin
* @property-read string $cardType
* @property-read \DateTime $createdAt
* @property-read string $customerId
* @property-read boolean $default
* @property-read string $expirationMonth
* @property-read string $expirationYear
* @property-read string $googleTransactionId
* @property-read string $imageUrl
* @property-read string $last4
* @property-read string $sourceCardLast4
* @property-read string $sourceCardType
* @property-read string $sourceDescription
* @property-read \Braintree\Subscription[] $subscriptions
* @property-read string $token
* @property-read \DateTime $updatedAt
* @property-read string $virtualCardLast4
* @property-read string $virtualCardType
*/
class AndroidPayCard extends Base
{
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* factory method: returns an instance of AndroidPayCard
* to the requesting method, with populated properties
*
* @ignore
* @return AndroidPayCard
*/
public static function factory($attributes)
{
$defaultAttributes = [
'expirationMonth' => '',
'expirationYear' => '',
'last4' => $attributes['virtualCardLast4'],
'cardType' => $attributes['virtualCardType'],
];
$instance = new self();
$instance->_initialize(array_merge($defaultAttributes, $attributes));
return $instance;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $androidPayCardAttribs array of Android Pay card properties
* @return void
*/
protected function _initialize($androidPayCardAttribs)
{
// set the attributes
$this->_attributes = $androidPayCardAttribs;
$subscriptionArray = [];
if (isset($androidPayCardAttribs['subscriptions'])) {
foreach ($androidPayCardAttribs['subscriptions'] AS $subscription) {
$subscriptionArray[] = Subscription::factory($subscription);
}
}
$this->_set('subscriptions', $subscriptionArray);
}
}
class_alias('Braintree\AndroidPayCard', 'Braintree_AndroidPayCard');

View File

@@ -0,0 +1,103 @@
<?php
namespace Braintree;
/**
* Braintree ApplePayCard module
* Creates and manages Braintree Apple Pay cards
*
* <b>== More information ==</b>
*
* See {@link https://developers.braintreepayments.com/javascript+php}<br />
*
* @package Braintree
* @category Resources
*
* @property-read string $bin
* @property-read string $cardType
* @property-read \DateTime $createdAt
* @property-read string $customerId
* @property-read boolean $default
* @property-read string $expirationDate
* @property-read string $expirationMonth
* @property-read string $expirationYear
* @property-read boolean $expired
* @property-read string $imageUrl
* @property-read string $last4
* @property-read string $token
* @property-read string $paymentInstrumentName
* @property-read string $sourceDescription
* @property-read \Braintree\Subscription[] $subscriptions
* @property-read \DateTime $updatedAt
*/
class ApplePayCard extends Base
{
// Card Type
const AMEX = 'Apple Pay - American Express';
const MASTER_CARD = 'Apple Pay - MasterCard';
const VISA = 'Apple Pay - Visa';
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* checks whether the card is expired based on the current date
*
* @return boolean
*/
public function isExpired()
{
return $this->expired;
}
/**
* factory method: returns an instance of ApplePayCard
* to the requesting method, with populated properties
*
* @ignore
* @return ApplePayCard
*/
public static function factory($attributes)
{
$defaultAttributes = [
'expirationMonth' => '',
'expirationYear' => '',
'last4' => '',
];
$instance = new self();
$instance->_initialize(array_merge($defaultAttributes, $attributes));
return $instance;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $applePayCardAttribs array of Apple Pay card properties
* @return void
*/
protected function _initialize($applePayCardAttribs)
{
// set the attributes
$this->_attributes = $applePayCardAttribs;
$subscriptionArray = [];
if (isset($applePayCardAttribs['subscriptions'])) {
foreach ($applePayCardAttribs['subscriptions'] AS $subscription) {
$subscriptionArray[] = Subscription::factory($subscription);
}
}
$this->_set('subscriptions', $subscriptionArray);
$this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear);
}
}
class_alias('Braintree\ApplePayCard', 'Braintree_ApplePayCard');

View File

@@ -0,0 +1,65 @@
<?php
namespace Braintree;
/**
* Braintree ApplePayGateway module
* Manages Apple Pay for Web
*
* @package Braintree
* @category Resources
*/
class ApplePayGateway
{
private $_gateway;
private $_config;
private $_http;
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function registerDomain($domain)
{
$path = $this->_config->merchantPath() . '/processing/apple_pay/validate_domains';
$response = $this->_http->post($path, ['url' => $domain]);
if (array_key_exists('response', $response) && $response['response']['success'])
{
return new Result\Successful;
}
else if (array_key_exists('apiErrorResponse', $response))
{
return new Result\Error($response['apiErrorResponse']);
}
}
public function unregisterDomain($domain)
{
$path = $this->_config->merchantPath() . '/processing/apple_pay/unregister_domain';
$this->_http->delete($path, ['url' => $domain]);
return new Result\Successful;
}
public function registeredDomains()
{
$path = $this->_config->merchantPath() . '/processing/apple_pay/registered_domains';
$response = $this->_http->get($path);
if (array_key_exists('response', $response) && array_key_exists('domains', $response['response']))
{
$options = ApplePayOptions::factory($response['response']);
return new Result\Successful($options, 'applePayOptions');
}
else if (array_key_exists('apiErrorResponse', $response))
{
return new Result\Error($response['apiErrorResponse']);
}
else
{
throw new Exception\Unexpected('expected response or apiErrorResponse');
}
}
}
class_alias('Braintree\ApplePayGateway', 'Braintree_ApplePayGateway');

View File

@@ -0,0 +1,28 @@
<?php
namespace Braintree;
/**
* Braintree ApplePayOptions module
* Manages configuration and options for Apple Pay
*
* @package Braintree
* @category Resources
*
* @property-read array $domains
*/
class ApplePayOptions extends Base
{
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
protected function _initialize($attributes)
{
$this->_attributes = $attributes;
}
}
class_alias('Braintree\ApplePayOptions', 'Braintree_ApplePayOptions');

View File

@@ -0,0 +1,35 @@
<?php
namespace Braintree;
/**
* Creates an instance of AuthorizationAdjustment as returned from a transaction
*
* @package Braintree
*
* @property-read string $amount
* @property-read boolean $success
* @property-read \DateTime $timestamp
*
*/
class AuthorizationAdjustment extends Base
{
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
protected function _initialize($authorizationAdjustmentAttribs)
{
$this->_attributes = $authorizationAdjustmentAttribs;
}
public function __toString()
{
return __CLASS__ . '[' . Util::attributesToString($this->_attributes) . ']';
}
}
class_alias('Braintree\AuthorizationAdjustment', 'Braintree_Authorization_Adjustment');

View File

@@ -0,0 +1,88 @@
<?php
namespace Braintree;
use JsonSerializable;
/**
* Braintree PHP Library.
*
* Braintree base class and initialization
* Provides methods to child classes. This class cannot be instantiated.
*
* PHP version 5
*/
abstract class Base implements JsonSerializable
{
protected $_attributes = [];
/**
* @ignore
* don't permit an explicit call of the constructor!
* (like $t = new Transaction())
*/
protected function __construct()
{
}
/**
* Disable cloning of objects
*
* @ignore
*/
protected function __clone()
{
}
/**
* Accessor for instance properties stored in the private $_attributes property
*
* @ignore
* @param string $name
* @return mixed
*/
public function __get($name)
{
if (array_key_exists($name, $this->_attributes)) {
return $this->_attributes[$name];
}
else {
trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE);
return null;
}
}
/**
* Checks for the existance of a property stored in the private $_attributes property
*
* @ignore
* @param string $name
* @return boolean
*/
public function __isset($name)
{
return array_key_exists($name, $this->_attributes);
}
/**
* Mutator for instance properties stored in the private $_attributes property
*
* @ignore
* @param string $key
* @param mixed $value
*/
public function _set($key, $value)
{
$this->_attributes[$key] = $value;
}
/**
* Implementation of JsonSerializable
*
* @ignore
* @return array
*/
public function jsonSerialize()
{
return $this->_attributes;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Braintree;
/**
* @property-read string $commercial
* @property-read string $countryOfIssuance
* @property-read string $debit
* @property-read string $durbinRegulated
* @property-read string $healthcare
* @property-read string $issuingBank
* @property-read string $payroll
* @property-read string $prepaid
* @property-read string $productId
*/
class BinData extends Base
{
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
protected function _initialize($attributes)
{
$this->_attributes = $attributes;
}
/**
* returns a string representation of the bin data
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) .']';
}
}
class_alias('Braintree\BinData', 'Braintree_BinData');

View File

@@ -0,0 +1,49 @@
<?php
namespace Braintree;
class ClientToken
{
const DEFAULT_VERSION = 2;
// static methods redirecting to gateway
/**
*
* @param array $params
* @return string
*/
public static function generate($params=[])
{
return Configuration::gateway()->clientToken()->generate($params);
}
/**
*
* @param type $params
* @throws InvalidArgumentException
*/
public static function conditionallyVerifyKeys($params)
{
return Configuration::gateway()->clientToken()->conditionallyVerifyKeys($params);
}
/**
*
* @return string client token retrieved from server
*/
public static function generateWithCustomerIdSignature()
{
return Configuration::gateway()->clientToken()->generateWithCustomerIdSignature();
}
/**
*
* @return string client token retrieved from server
*/
public static function generateWithoutCustomerIdSignature()
{
return Configuration::gateway()->clientToken()->generateWithoutCustomerIdSignature();
}
}
class_alias('Braintree\ClientToken', 'Braintree_ClientToken');

View File

@@ -0,0 +1,129 @@
<?php
namespace Braintree;
use InvalidArgumentException;
class ClientTokenGateway
{
/**
*
* @var Gateway
*/
private $_gateway;
/**
*
* @var Configuration
*/
private $_config;
/**
*
* @var Http
*/
private $_http;
/**
*
* @param Gateway $gateway
*/
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function generate($params=[])
{
if (!array_key_exists("version", $params)) {
$params["version"] = ClientToken::DEFAULT_VERSION;
}
$this->conditionallyVerifyKeys($params);
$generateParams = ["client_token" => $params];
return $this->_doGenerate('/client_token', $generateParams);
}
/**
* sends the generate request to the gateway
*
* @ignore
* @param var $url
* @param array $params
* @return string
*/
public function _doGenerate($subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->post($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
*
* @param array $params
* @throws InvalidArgumentException
*/
public function conditionallyVerifyKeys($params)
{
if (array_key_exists("customerId", $params)) {
Util::verifyKeys($this->generateWithCustomerIdSignature(), $params);
} else {
Util::verifyKeys($this->generateWithoutCustomerIdSignature(), $params);
}
}
/**
*
* @return mixed[]
*/
public function generateWithCustomerIdSignature()
{
return [
"version", "customerId", "proxyMerchantId",
["options" => ["makeDefault", "verifyCard", "failOnDuplicatePaymentMethod"]],
"merchantAccountId"];
}
/**
*
* @return string[]
*/
public function generateWithoutCustomerIdSignature()
{
return ["version", "proxyMerchantId", "merchantAccountId"];
}
/**
* generic method for validating incoming gateway responses
*
* If the request is successful, returns a client token string.
* Otherwise, throws an InvalidArgumentException with the error
* response from the Gateway or an HTTP status code exception.
*
* @ignore
* @param array $response gateway response values
* @return string client token
* @throws InvalidArgumentException | HTTP status code exception
*/
private function _verifyGatewayResponse($response)
{
if (isset($response['clientToken'])) {
return $response['clientToken']['value'];
} elseif (isset($response['apiErrorResponse'])) {
throw new InvalidArgumentException(
$response['apiErrorResponse']['message']
);
} else {
throw new Exception\Unexpected(
"Expected clientToken or apiErrorResponse"
);
}
}
}
class_alias('Braintree\ClientTokenGateway', 'Braintree_ClientTokenGateway');

View File

@@ -0,0 +1,110 @@
<?php
namespace Braintree;
/**
* Braintree CoinbaseAccount module
*
* @package Braintree
* @category Resources
*/
/**
* Manages Braintree CoinbaseAccounts
*
* <b>== More information ==</b>
*
*
* @package Braintree
* @category Resources
*
* @property-read string $customerId
* @property-read string $token
* @property-read string $userId
* @property-read string $userName
* @property-read string $userEmail
*/
class CoinbaseAccount extends Base
{
/**
* factory method: returns an instance of CoinbaseAccount
* to the requesting method, with populated properties
*
* @ignore
* @return CoinbaseAccount
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $coinbaseAccountAttribs array of coinbaseAccount data
* @return void
*/
protected function _initialize($coinbaseAccountAttribs)
{
// set the attributes
$this->_attributes = $coinbaseAccountAttribs;
$subscriptionArray = [];
if (isset($coinbaseAccountAttribs['subscriptions'])) {
foreach ($coinbaseAccountAttribs['subscriptions'] AS $subscription) {
$subscriptionArray[] = Subscription::factory($subscription);
}
}
$this->_set('subscriptions', $subscriptionArray);
}
/**
* create a printable representation of the object as:
* ClassName[property=value, property=value]
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) .']';
}
// static methods redirecting to gateway
public static function find($token)
{
return Configuration::gateway()->coinbaseAccount()->find($token);
}
public static function update($token, $attributes)
{
return Configuration::gateway()->coinbaseAccount()->update($token, $attributes);
}
public static function delete($token)
{
return Configuration::gateway()->coinbaseAccount()->delete($token);
}
public static function sale($token, $transactionAttribs)
{
return Configuration::gateway()->coinbaseAccount()->sale($token, $transactionAttribs);
}
}
class_alias('Braintree\CoinbaseAccount', 'Braintree_CoinbaseAccount');

View File

@@ -0,0 +1,161 @@
<?php
namespace Braintree;
use Countable;
use IteratorAggregate;
use ArrayAccess;
use OutOfRangeException;
use ArrayIterator;
/**
* Braintree Generic collection
*
* PHP Version 5
*
* Based on Generic Collection class from:
* {@link http://codeutopia.net/code/library/CU/Collection.php}
*
* @package Braintree
* @subpackage Utility
*/
class Collection implements Countable, IteratorAggregate, ArrayAccess
{
/**
*
* @var array collection storage
*/
protected $_collection = [];
/**
* Add a value into the collection
* @param string $value
*/
public function add($value)
{
$this->_collection[] = $value;
}
/**
* Set index's value
* @param integer $index
* @param mixed $value
* @throws OutOfRangeException
*/
public function set($index, $value)
{
if($index >= $this->count())
throw new OutOfRangeException('Index out of range');
$this->_collection[$index] = $value;
}
/**
* Remove a value from the collection
* @param integer $index index to remove
* @throws OutOfRangeException if index is out of range
*/
public function remove($index)
{
if($index >= $this->count())
throw new OutOfRangeException('Index out of range');
array_splice($this->_collection, $index, 1);
}
/**
* Return value at index
* @param integer $index
* @return mixed
* @throws OutOfRangeException
*/
public function get($index)
{
if($index >= $this->count())
throw new OutOfRangeException('Index out of range');
return $this->_collection[$index];
}
/**
* Determine if index exists
* @param integer $index
* @return boolean
*/
public function exists($index)
{
if($index >= $this->count())
return false;
return true;
}
/**
* Return count of items in collection
* Implements countable
* @return integer
*/
public function count()
{
return count($this->_collection);
}
/**
* Return an iterator
* Implements IteratorAggregate
* @return ArrayIterator
*/
public function getIterator()
{
return new ArrayIterator($this->_collection);
}
/**
* Set offset to value
* Implements ArrayAccess
* @see set
* @param integer $offset
* @param mixed $value
*/
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
/**
* Unset offset
* Implements ArrayAccess
* @see remove
* @param integer $offset
*/
public function offsetUnset($offset)
{
$this->remove($offset);
}
/**
* get an offset's value
* Implements ArrayAccess
* @see get
* @param integer $offset
* @return mixed
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Determine if offset exists
* Implements ArrayAccess
* @see exists
* @param integer $offset
* @return boolean
*/
public function offsetExists($offset)
{
return $this->exists($offset);
}
}
class_alias('Braintree\Collection', 'Braintree_Collection');

View File

@@ -0,0 +1,665 @@
<?php
namespace Braintree;
/**
*
* Configuration registry
*
* @package Braintree
* @subpackage Utility
*/
class Configuration
{
public static $global;
private $_environment = null;
private $_merchantId = null;
private $_publicKey = null;
private $_privateKey = null;
private $_clientId = null;
private $_clientSecret = null;
private $_accessToken = null;
private $_proxyHost = null;
private $_proxyPort = null;
private $_proxyType = null;
private $_proxyUser = null;
private $_proxyPassword = null;
private $_timeout = 60;
private $_sslVersion = null;
private $_acceptGzipEncoding = true;
/**
* Braintree API version to use
* @access public
*/
const API_VERSION = 5;
const GRAPHQL_API_VERSION = '2018-09-10';
public function __construct($attribs = [])
{
foreach ($attribs as $kind => $value) {
if ($kind == 'environment') {
CredentialsParser::assertValidEnvironment($value);
$this->_environment = $value;
}
if ($kind == 'merchantId') {
$this->_merchantId = $value;
}
if ($kind == 'publicKey') {
$this->_publicKey = $value;
}
if ($kind == 'privateKey') {
$this->_privateKey = $value;
}
if ($kind == 'timeout') {
$this->_timeout = $value;
}
if ($kind == 'acceptGzipEncoding') {
$this->_acceptGzipEncoding = $value;
}
}
if (isset($attribs['clientId']) || isset($attribs['accessToken'])) {
if (isset($attribs['environment']) || isset($attribs['merchantId']) || isset($attribs['publicKey']) || isset($attribs['privateKey'])) {
throw new Exception\Configuration('Cannot mix OAuth credentials (clientId, clientSecret, accessToken) with key credentials (publicKey, privateKey, environment, merchantId).');
}
$parsedCredentials = new CredentialsParser($attribs);
$this->_environment = $parsedCredentials->getEnvironment();
$this->_merchantId = $parsedCredentials->getMerchantId();
$this->_clientId = $parsedCredentials->getClientId();
$this->_clientSecret = $parsedCredentials->getClientSecret();
$this->_accessToken = $parsedCredentials->getAccessToken();
}
}
/**
* resets configuration to default
* @access public
*/
public static function reset()
{
self::$global = new Configuration();
}
public static function gateway()
{
return new Gateway(self::$global);
}
public static function environment($value=null)
{
if (empty($value)) {
return self::$global->getEnvironment();
}
CredentialsParser::assertValidEnvironment($value);
self::$global->setEnvironment($value);
}
public static function merchantId($value=null)
{
if (empty($value)) {
return self::$global->getMerchantId();
}
self::$global->setMerchantId($value);
}
public static function publicKey($value=null)
{
if (empty($value)) {
return self::$global->getPublicKey();
}
self::$global->setPublicKey($value);
}
public static function privateKey($value=null)
{
if (empty($value)) {
return self::$global->getPrivateKey();
}
self::$global->setPrivateKey($value);
}
/**
* Sets or gets the read timeout to use for making requests.
*
* @param integer $value If provided, sets the read timeout
* @return integer The read timeout used for connecting to Braintree
*/
public static function timeout($value=null)
{
if (empty($value)) {
return self::$global->getTimeout();
}
self::$global->setTimeout($value);
}
/**
* Sets or gets the SSL version to use for making requests. See
* https://php.net/manual/en/function.curl-setopt.php for possible
* CURLOPT_SSLVERSION values.
*
* @param integer $value If provided, sets the SSL version
* @return integer The SSL version used for connecting to Braintree
*/
public static function sslVersion($value=null)
{
if (empty($value)) {
return self::$global->getSslVersion();
}
self::$global->setSslVersion($value);
}
/**
* Sets or gets the proxy host to use for connecting to Braintree
*
* @param string $value If provided, sets the proxy host
* @return string The proxy host used for connecting to Braintree
*/
public static function proxyHost($value = null)
{
if (empty($value)) {
return self::$global->getProxyHost();
}
self::$global->setProxyHost($value);
}
/**
* Sets or gets the port of the proxy to use for connecting to Braintree
*
* @param string $value If provided, sets the port of the proxy
* @return string The port of the proxy used for connecting to Braintree
*/
public static function proxyPort($value = null)
{
if (empty($value)) {
return self::$global->getProxyPort();
}
self::$global->setProxyPort($value);
}
/**
* Sets or gets the proxy type to use for connecting to Braintree. This value
* can be any of the CURLOPT_PROXYTYPE options in PHP cURL.
*
* @param string $value If provided, sets the proxy type
* @return string The proxy type used for connecting to Braintree
*/
public static function proxyType($value = null)
{
if (empty($value)) {
return self::$global->getProxyType();
}
self::$global->setProxyType($value);
}
/**
* Specifies whether or not a proxy is properly configured
*
* @return bool true if a proxy is configured properly, false if not
*/
public static function isUsingProxy()
{
$proxyHost = self::$global->getProxyHost();
$proxyPort = self::$global->getProxyPort();
return !empty($proxyHost) && !empty($proxyPort);
}
public static function proxyUser($value = null)
{
if (empty($value)) {
return self::$global->getProxyUser();
}
self::$global->setProxyUser($value);
}
public static function proxyPassword($value = null)
{
if (empty($value)) {
return self::$global->getProxyPassword();
}
self::$global->setProxyPassword($value);
}
/**
* Specified whether or not a username and password have been provided for
* use with an authenticated proxy
*
* @return bool true if both proxyUser and proxyPassword are present
*/
public static function isAuthenticatedProxy()
{
$proxyUser = self::$global->getProxyUser();
$proxyPwd = self::$global->getProxyPassword();
return !empty($proxyUser) && !empty($proxyPwd);
}
/**
* Specify if the HTTP client is able to decode gzipped responses.
*
* @param bool $value If true, will send an Accept-Encoding header with a gzip value. If false, will not send an Accept-Encoding header with a gzip value.
* @return bool true if an Accept-Encoding header with a gzip value will be sent, false if not
*/
public static function acceptGzipEncoding($value = null)
{
if (is_null($value)) {
return self::$global->getAcceptGzipEncoding();
}
self::$global->setAcceptGzipEncoding($value);
}
public static function assertGlobalHasAccessTokenOrKeys()
{
self::$global->assertHasAccessTokenOrKeys();
}
public function assertHasAccessTokenOrKeys()
{
if (empty($this->_accessToken)) {
if (empty($this->_merchantId)) {
throw new Exception\Configuration('Braintree\\Configuration::merchantId needs to be set (or accessToken needs to be passed to Braintree\\Gateway).');
} else if (empty($this->_environment)) {
throw new Exception\Configuration('Braintree\\Configuration::environment needs to be set.');
} else if (empty($this->_publicKey)) {
throw new Exception\Configuration('Braintree\\Configuration::publicKey needs to be set.');
} else if (empty($this->_privateKey)) {
throw new Exception\Configuration('Braintree\\Configuration::privateKey needs to be set.');
}
}
}
public function assertHasClientCredentials()
{
$this->assertHasClientId();
$this->assertHasClientSecret();
}
public function assertHasClientId()
{
if (empty($this->_clientId)) {
throw new Exception\Configuration('clientId needs to be passed to Braintree\\Gateway.');
}
}
public function assertHasClientSecret()
{
if (empty($this->_clientSecret)) {
throw new Exception\Configuration('clientSecret needs to be passed to Braintree\\Gateway.');
}
}
public function getEnvironment()
{
return $this->_environment;
}
/**
* Do not use this method directly. Pass in the environment to the constructor.
*/
public function setEnvironment($value)
{
$this->_environment = $value;
}
public function getMerchantId()
{
return $this->_merchantId;
}
/**
* Do not use this method directly. Pass in the merchantId to the constructor.
*/
public function setMerchantId($value)
{
$this->_merchantId = $value;
}
public function getPublicKey()
{
return $this->_publicKey;
}
public function getClientId()
{
return $this->_clientId;
}
/**
* Do not use this method directly. Pass in the publicKey to the constructor.
*/
public function setPublicKey($value)
{
$this->_publicKey = $value;
}
public function getPrivateKey()
{
return $this->_privateKey;
}
public function getClientSecret()
{
return $this->_clientSecret;
}
/**
* Do not use this method directly. Pass in the privateKey to the constructor.
*/
public function setPrivateKey($value)
{
$this->_privateKey = $value;
}
private function setProxyHost($value)
{
$this->_proxyHost = $value;
}
public function getProxyHost()
{
return $this->_proxyHost;
}
private function setProxyPort($value)
{
$this->_proxyPort = $value;
}
public function getProxyPort()
{
return $this->_proxyPort;
}
private function setProxyType($value)
{
$this->_proxyType = $value;
}
public function getProxyType()
{
return $this->_proxyType;
}
private function setProxyUser($value)
{
$this->_proxyUser = $value;
}
public function getProxyUser()
{
return $this->_proxyUser;
}
private function setProxyPassword($value)
{
$this->_proxyPassword = $value;
}
public function getProxyPassword()
{
return $this->_proxyPassword;
}
private function setTimeout($value)
{
$this->_timeout = $value;
}
public function getTimeout()
{
return $this->_timeout;
}
private function setSslVersion($value)
{
$this->_sslVersion = $value;
}
private function getSslVersion()
{
return $this->_sslVersion;
}
public function getAcceptGzipEncoding()
{
return $this->_acceptGzipEncoding;
}
private function setAcceptGzipEncoding($value)
{
$this->_acceptGzipEncoding = $value;
}
public function getAccessToken()
{
return $this->_accessToken;
}
public function isAccessToken()
{
return !empty($this->_accessToken);
}
public function isClientCredentials()
{
return !empty($this->_clientId);
}
/**
* returns the base braintree gateway URL based on config values
*
* @access public
* @param none
* @return string braintree gateway URL
*/
public function baseUrl()
{
return sprintf('%s://%s:%d', $this->protocol(), $this->serverName(), $this->portNumber());
}
/**
* returns the base URL for Braintree's GraphQL endpoint based on config values
*
* @access public
* @param none
* @return string Braintree GraphQL URL
*/
public function graphQLBaseUrl()
{
return sprintf('%s://%s:%d/graphql', $this->protocol(), $this->graphQLServerName(), $this->graphQLPortNumber());
}
/**
* sets the merchant path based on merchant ID
*
* @access protected
* @param none
* @return string merchant path uri
*/
public function merchantPath()
{
return '/merchants/' . $this->_merchantId;
}
/**
* sets the physical path for the location of the CA certs
*
* @access public
* @param none
* @return string filepath
*/
public function caFile($sslPath = NULL)
{
$sslPath = $sslPath ? $sslPath : DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR .
'ssl' . DIRECTORY_SEPARATOR;
$caPath = __DIR__ . $sslPath . 'api_braintreegateway_com.ca.crt';
if (!file_exists($caPath))
{
throw new Exception\SSLCaFileNotFound();
}
return $caPath;
}
/**
* returns the port number depending on environment
*
* @access public
* @param none
* @return int portnumber
*/
public function portNumber()
{
if ($this->sslOn()) {
return 443;
}
return getenv("GATEWAY_PORT") ? getenv("GATEWAY_PORT") : 3000;
}
/**
* returns the graphql port number depending on environment
*
* @access public
* @param none
* @return int graphql portnumber
*/
public function graphQLPortNumber()
{
if ($this->sslOn()) {
return 443;
}
return getenv("GRAPHQL_PORT") ?: 8080;
}
/**
* returns http protocol depending on environment
*
* @access public
* @param none
* @return string http || https
*/
public function protocol()
{
return $this->sslOn() ? 'https' : 'http';
}
/**
* returns gateway server name depending on environment
*
* @access public
* @param none
* @return string server domain name
*/
public function serverName()
{
switch($this->_environment) {
case 'production':
$serverName = 'api.braintreegateway.com';
break;
case 'qa':
$serverName = 'gateway.qa.braintreepayments.com';
break;
case 'sandbox':
$serverName = 'api.sandbox.braintreegateway.com';
break;
case 'development':
case 'integration':
default:
$serverName = 'localhost';
break;
}
return $serverName;
}
/**
* returns Braintree GraphQL server name depending on environment
*
* @access public
* @param none
* @return string graphql domain name
*/
public function graphQLServerName()
{
switch($this->_environment) {
case 'production':
$graphQLServerName = 'payments.braintree-api.com';
break;
case 'qa':
$graphQLServerName = 'payments-qa.dev.braintree-api.com';
break;
case 'sandbox':
$graphQLServerName = 'payments.sandbox.braintree-api.com';
break;
case 'development':
case 'integration':
default:
$graphQLServerName = 'graphql.bt.local';
break;
}
return $graphQLServerName;
}
public function authUrl()
{
switch($this->_environment) {
case 'production':
$serverName = 'https://auth.venmo.com';
break;
case 'qa':
$serverName = 'https://auth.qa.venmo.com';
break;
case 'sandbox':
$serverName = 'https://auth.sandbox.venmo.com';
break;
case 'development':
case 'integration':
default:
$serverName = 'http://auth.venmo.dev:9292';
break;
}
return $serverName;
}
/**
* returns boolean indicating SSL is on or off for this session,
* depending on environment
*
* @access public
* @param none
* @return boolean
*/
public function sslOn()
{
switch($this->_environment) {
case 'integration':
case 'development':
$ssl = false;
break;
case 'production':
case 'qa':
case 'sandbox':
default:
$ssl = true;
break;
}
return $ssl;
}
/**
* log message to default logger
*
* @param string $message
*
*/
public function logMessage($message)
{
error_log('[Braintree] ' . $message);
}
}
Configuration::reset();
class_alias('Braintree\Configuration', 'Braintree_Configuration');

View File

@@ -0,0 +1,37 @@
<?php
namespace Braintree;
/**
* Connected Merchant PayPal Status Changed Payload
*
* @package Braintree
*
* @property-read string $merchantPublicId
* @property-read string $action
* @property-read string $oauthApplicationClientId
*/
class ConnectedMerchantPayPalStatusChanged extends Base
{
protected $_attributes = [];
/**
* @ignore
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
$instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId'];
return $instance;
}
/**
* @ignore
*/
protected function _initialize($attributes)
{
$this->_attributes = $attributes;
}
}
class_alias('Braintree\ConnectedMerchantPayPalStatusChanged', 'Braintree_ConnectedMerchantPayPalStatusChanged');

View File

@@ -0,0 +1,37 @@
<?php
namespace Braintree;
/**
* Connected Merchant Status Transitioned Payload
*
* @package Braintree
*
* @property-read string $merchantPublicId
* @property-read string $status
* @property-read string $oauthApplicationClientId
*/
class ConnectedMerchantStatusTransitioned extends Base
{
protected $_attributes = [];
/**
* @ignore
*/
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
$instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId'];
return $instance;
}
/**
* @ignore
*/
protected function _initialize($attributes)
{
$this->_attributes = $attributes;
}
}
class_alias('Braintree\ConnectedMerchantStatusTransitioned', 'Braintree_ConnectedMerchantStatusTransitioned');

View File

@@ -0,0 +1,147 @@
<?php
namespace Braintree;
/**
*
* CredentialsParser registry
*
* @package Braintree
* @subpackage Utility
*/
class CredentialsParser
{
private $_clientId;
private $_clientSecret;
private $_accessToken;
private $_environment;
private $_merchantId;
public function __construct($attribs)
{
foreach ($attribs as $kind => $value) {
if ($kind == 'clientId') {
$this->_clientId = $value;
}
if ($kind == 'clientSecret') {
$this->_clientSecret = $value;
}
if ($kind == 'accessToken') {
$this->_accessToken = $value;
}
}
$this->parse();
}
/**
*
* @access protected
* @static
* @var array valid environments, used for validation
*/
private static $_validEnvironments = [
'development',
'integration',
'sandbox',
'production',
'qa',
];
public function parse()
{
$environments = [];
if (!empty($this->_clientId)) {
$environments[] = ['clientId', $this->_parseClientCredential('clientId', $this->_clientId, 'client_id')];
}
if (!empty($this->_clientSecret)) {
$environments[] = ['clientSecret', $this->_parseClientCredential('clientSecret', $this->_clientSecret, 'client_secret')];
}
if (!empty($this->_accessToken)) {
$environments[] = ['accessToken', $this->_parseAccessToken()];
}
$checkEnv = $environments[0];
foreach ($environments as $env) {
if ($env[1] !== $checkEnv[1]) {
throw new Exception\Configuration(
'Mismatched credential environments: ' . $checkEnv[0] . ' environment is ' . $checkEnv[1] .
' and ' . $env[0] . ' environment is ' . $env[1]);
}
}
self::assertValidEnvironment($checkEnv[1]);
$this->_environment = $checkEnv[1];
}
public static function assertValidEnvironment($environment) {
if (!in_array($environment, self::$_validEnvironments)) {
throw new Exception\Configuration('"' .
$environment . '" is not a valid environment.');
}
}
private function _parseClientCredential($credentialType, $value, $expectedValuePrefix)
{
$explodedCredential = explode('$', $value);
if (sizeof($explodedCredential) != 3) {
throw new Exception\Configuration('Incorrect ' . $credentialType . ' format. Expected: type$environment$token');
}
$gotValuePrefix = $explodedCredential[0];
$environment = $explodedCredential[1];
$token = $explodedCredential[2];
if ($gotValuePrefix != $expectedValuePrefix) {
throw new Exception\Configuration('Value passed for ' . $credentialType . ' is not a ' . $credentialType);
}
return $environment;
}
private function _parseAccessToken()
{
$accessTokenExploded = explode('$', $this->_accessToken);
if (sizeof($accessTokenExploded) != 4) {
throw new Exception\Configuration('Incorrect accessToken syntax. Expected: type$environment$merchant_id$token');
}
$gotValuePrefix = $accessTokenExploded[0];
$environment = $accessTokenExploded[1];
$merchantId = $accessTokenExploded[2];
$token = $accessTokenExploded[3];
if ($gotValuePrefix != 'access_token') {
throw new Exception\Configuration('Value passed for accessToken is not an accessToken');
}
$this->_merchantId = $merchantId;
return $environment;
}
public function getClientId()
{
return $this->_clientId;
}
public function getClientSecret()
{
return $this->_clientSecret;
}
public function getAccessToken()
{
return $this->_accessToken;
}
public function getEnvironment()
{
return $this->_environment;
}
public function getMerchantId()
{
return $this->_merchantId;
}
}
class_alias('Braintree\CredentialsParser', 'Braintree_CredentialsParser');

View File

@@ -0,0 +1,332 @@
<?php
namespace Braintree;
/**
* Braintree CreditCard module
* Creates and manages Braintree CreditCards
*
* <b>== More information ==</b>
*
* For more detailed information on CreditCards, see {@link https://developers.braintreepayments.com/reference/response/credit-card/php https://developers.braintreepayments.com/reference/response/credit-card/php}<br />
* For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php}
*
* @package Braintree
* @category Resources
*
* @property-read \Braintree\Address $billingAddress
* @property-read string $bin
* @property-read string $cardType
* @property-read string $cardholderName
* @property-read string $commercial
* @property-read \DateTime $createdAt
* @property-read string $customerId
* @property-read string $customerLocation
* @property-read string $debit
* @property-read boolean $default
* @property-read string $durbinRegulated
* @property-read string $expirationDate
* @property-read string $expirationMonth
* @property-read string $expirationYear
* @property-read boolean $expired
* @property-read boolean $healthcare
* @property-read string $imageUrl
* @property-read string $issuingBank
* @property-read string $last4
* @property-read string $maskedNumber
* @property-read string $payroll
* @property-read string $prepaid
* @property-read string $productId
* @property-read \Braintree\Subscription[] $subscriptions
* @property-read string $token
* @property-read string $uniqueNumberIdentifier
* @property-read \DateTime $updatedAt
* @property-read \Braintree\CreditCardVerification|null $verification
*/
class CreditCard extends Base
{
// Card Type
const AMEX = 'American Express';
const CARTE_BLANCHE = 'Carte Blanche';
const CHINA_UNION_PAY = 'China UnionPay';
const DINERS_CLUB_INTERNATIONAL = 'Diners Club';
const DISCOVER = 'Discover';
const ELO = 'Elo';
const JCB = 'JCB';
const LASER = 'Laser';
const MAESTRO = 'Maestro';
const UK_MAESTRO = 'UK Maestro';
const MASTER_CARD = 'MasterCard';
const SOLO = 'Solo';
const SWITCH_TYPE = 'Switch';
const VISA = 'Visa';
const UNKNOWN = 'Unknown';
// Credit card origination location
const INTERNATIONAL = "international";
const US = "us";
const PREPAID_YES = 'Yes';
const PREPAID_NO = 'No';
const PREPAID_UNKNOWN = 'Unknown';
const PAYROLL_YES = 'Yes';
const PAYROLL_NO = 'No';
const PAYROLL_UNKNOWN = 'Unknown';
const HEALTHCARE_YES = 'Yes';
const HEALTHCARE_NO = 'No';
const HEALTHCARE_UNKNOWN = 'Unknown';
const DURBIN_REGULATED_YES = 'Yes';
const DURBIN_REGULATED_NO = 'No';
const DURBIN_REGULATED_UNKNOWN = 'Unknown';
const DEBIT_YES = 'Yes';
const DEBIT_NO = 'No';
const DEBIT_UNKNOWN = 'Unknown';
const COMMERCIAL_YES = 'Yes';
const COMMERCIAL_NO = 'No';
const COMMERCIAL_UNKNOWN = 'Unknown';
const COUNTRY_OF_ISSUANCE_UNKNOWN = "Unknown";
const ISSUING_BANK_UNKNOWN = "Unknown";
const PRODUCT_ID_UNKNOWN = "Unknown";
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* checks whether the card is expired based on the current date
*
* @return boolean
*/
public function isExpired()
{
return $this->expired;
}
/**
* checks whether the card is associated with venmo sdk
*
* @return boolean
*/
public function isVenmoSdk()
{
return $this->venmoSdk;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $creditCardAttribs array of creditcard data
* @return void
*/
protected function _initialize($creditCardAttribs)
{
// set the attributes
$this->_attributes = $creditCardAttribs;
// map each address into its own object
$billingAddress = isset($creditCardAttribs['billingAddress']) ?
Address::factory($creditCardAttribs['billingAddress']) :
null;
$subscriptionArray = [];
if (isset($creditCardAttribs['subscriptions'])) {
foreach ($creditCardAttribs['subscriptions'] AS $subscription) {
$subscriptionArray[] = Subscription::factory($subscription);
}
}
$this->_set('subscriptions', $subscriptionArray);
$this->_set('billingAddress', $billingAddress);
$this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear);
$this->_set('maskedNumber', $this->bin . '******' . $this->last4);
if(isset($creditCardAttribs['verifications']) && count($creditCardAttribs['verifications']) > 0) {
$verifications = $creditCardAttribs['verifications'];
usort($verifications, [$this, '_compareCreatedAtOnVerifications']);
$this->_set('verification', CreditCardVerification::factory($verifications[0]));
}
}
private function _compareCreatedAtOnVerifications($verificationAttrib1, $verificationAttrib2)
{
return ($verificationAttrib2['createdAt'] < $verificationAttrib1['createdAt']) ? -1 : 1;
}
/**
* returns false if comparing object is not a CreditCard,
* or is a CreditCard with a different id
*
* @param object $otherCreditCard customer to compare against
* @return boolean
*/
public function isEqual($otherCreditCard)
{
return !($otherCreditCard instanceof self) ? false : $this->token === $otherCreditCard->token;
}
/**
* create a printable representation of the object as:
* ClassName[property=value, property=value]
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) .']';
}
/**
* factory method: returns an instance of CreditCard
* to the requesting method, with populated properties
*
* @ignore
* @return CreditCard
*/
public static function factory($attributes)
{
$defaultAttributes = [
'bin' => '',
'expirationMonth' => '',
'expirationYear' => '',
'last4' => '',
];
$instance = new self();
$instance->_initialize(array_merge($defaultAttributes, $attributes));
return $instance;
}
// static methods redirecting to gateway
public static function create($attribs)
{
return Configuration::gateway()->creditCard()->create($attribs);
}
public static function createNoValidate($attribs)
{
return Configuration::gateway()->creditCard()->createNoValidate($attribs);
}
public static function createFromTransparentRedirect($queryString)
{
return Configuration::gateway()->creditCard()->createFromTransparentRedirect($queryString);
}
public static function createCreditCardUrl()
{
return Configuration::gateway()->creditCard()->createCreditCardUrl();
}
public static function expired()
{
return Configuration::gateway()->creditCard()->expired();
}
public static function fetchExpired($ids)
{
return Configuration::gateway()->creditCard()->fetchExpired($ids);
}
public static function expiringBetween($startDate, $endDate)
{
return Configuration::gateway()->creditCard()->expiringBetween($startDate, $endDate);
}
public static function fetchExpiring($startDate, $endDate, $ids)
{
return Configuration::gateway()->creditCard()->fetchExpiring($startDate, $endDate, $ids);
}
public static function find($token)
{
return Configuration::gateway()->creditCard()->find($token);
}
public static function fromNonce($nonce)
{
return Configuration::gateway()->creditCard()->fromNonce($nonce);
}
public static function credit($token, $transactionAttribs)
{
return Configuration::gateway()->creditCard()->credit($token, $transactionAttribs);
}
public static function creditNoValidate($token, $transactionAttribs)
{
return Configuration::gateway()->creditCard()->creditNoValidate($token, $transactionAttribs);
}
public static function sale($token, $transactionAttribs)
{
return Configuration::gateway()->creditCard()->sale($token, $transactionAttribs);
}
public static function saleNoValidate($token, $transactionAttribs)
{
return Configuration::gateway()->creditCard()->saleNoValidate($token, $transactionAttribs);
}
public static function update($token, $attributes)
{
return Configuration::gateway()->creditCard()->update($token, $attributes);
}
public static function updateNoValidate($token, $attributes)
{
return Configuration::gateway()->creditCard()->updateNoValidate($token, $attributes);
}
public static function updateCreditCardUrl()
{
return Configuration::gateway()->creditCard()->updateCreditCardUrl();
}
public static function updateFromTransparentRedirect($queryString)
{
return Configuration::gateway()->creditCard()->updateFromTransparentRedirect($queryString);
}
public static function delete($token)
{
return Configuration::gateway()->creditCard()->delete($token);
}
/** @return array */
public static function allCardTypes()
{
return [
CreditCard::AMEX,
CreditCard::CARTE_BLANCHE,
CreditCard::CHINA_UNION_PAY,
CreditCard::DINERS_CLUB_INTERNATIONAL,
CreditCard::DISCOVER,
CreditCard::ELO,
CreditCard::JCB,
CreditCard::LASER,
CreditCard::MAESTRO,
CreditCard::MASTER_CARD,
CreditCard::SOLO,
CreditCard::SWITCH_TYPE,
CreditCard::VISA,
CreditCard::UNKNOWN
];
}
}
class_alias('Braintree\CreditCard', 'Braintree_CreditCard');

View File

@@ -0,0 +1,486 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Braintree CreditCardGateway module
* Creates and manages Braintree CreditCards
*
* <b>== More information ==</b>
*
* For more detailed information on CreditCards, see {@link https://developers.braintreepayments.com/reference/response/credit-card/php https://developers.braintreepayments.com/reference/response/credit-card/php}<br />
* For more detailed information on CreditCard verifications, see {@link https://developers.braintreepayments.com/reference/response/credit-card-verification/php https://developers.braintreepayments.com/reference/response/credit-card-verification/php}
*
* @package Braintree
* @category Resources
*/
class CreditCardGateway
{
private $_gateway;
private $_config;
private $_http;
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function create($attribs)
{
Util::verifyKeys(self::createSignature(), $attribs);
return $this->_doCreate('/payment_methods', ['credit_card' => $attribs]);
}
/**
* attempts the create operation assuming all data will validate
* returns a CreditCard object instead of a Result
*
* @access public
* @param array $attribs
* @return CreditCard
* @throws Exception\ValidationError
*/
public function createNoValidate($attribs)
{
$result = $this->create($attribs);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
* create a customer from a TransparentRedirect operation
*
* @deprecated since version 2.3.0
* @access public
* @param array $attribs
* @return Result\Successful|Result\Error
*/
public function createFromTransparentRedirect($queryString)
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE);
$params = TransparentRedirect::parseAndValidateQueryString(
$queryString
);
return $this->_doCreate(
'/payment_methods/all/confirm_transparent_redirect_request',
['id' => $params['id']]
);
}
/**
*
* @deprecated since version 2.3.0
* @access public
* @param none
* @return string
*/
public function createCreditCardUrl()
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE);
return $this->_config->baseUrl() . $this->_config->merchantPath().
'/payment_methods/all/create_via_transparent_redirect_request';
}
/**
* returns a ResourceCollection of expired credit cards
* @return ResourceCollection
*/
public function expired()
{
$path = $this->_config->merchantPath() . '/payment_methods/all/expired_ids';
$response = $this->_http->post($path);
$pager = [
'object' => $this,
'method' => 'fetchExpired',
'methodArgs' => []
];
return new ResourceCollection($response, $pager);
}
public function fetchExpired($ids)
{
$path = $this->_config->merchantPath() . "/payment_methods/all/expired";
$response = $this->_http->post($path, ['search' => ['ids' => $ids]]);
return Util::extractattributeasarray(
$response['paymentMethods'],
'creditCard'
);
}
/**
* returns a ResourceCollection of credit cards expiring between start/end
*
* @return ResourceCollection
*/
public function expiringBetween($startDate, $endDate)
{
$queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring_ids?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate);
$response = $this->_http->post($queryPath);
$pager = [
'object' => $this,
'method' => 'fetchExpiring',
'methodArgs' => [$startDate, $endDate]
];
return new ResourceCollection($response, $pager);
}
public function fetchExpiring($startDate, $endDate, $ids)
{
$queryPath = $this->_config->merchantPath() . '/payment_methods/all/expiring?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate);
$response = $this->_http->post($queryPath, ['search' => ['ids' => $ids]]);
return Util::extractAttributeAsArray(
$response['paymentMethods'],
'creditCard'
);
}
/**
* find a creditcard by token
*
* @access public
* @param string $token credit card unique id
* @return CreditCard
* @throws Exception\NotFound
*/
public function find($token)
{
$this->_validateId($token);
try {
$path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token;
$response = $this->_http->get($path);
return CreditCard::factory($response['creditCard']);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound(
'credit card with token ' . $token . ' not found'
);
}
}
/**
* Convert a payment method nonce to a credit card
*
* @access public
* @param string $nonce payment method nonce
* @return CreditCard
* @throws Exception\NotFound
*/
public function fromNonce($nonce)
{
$this->_validateId($nonce, "nonce");
try {
$path = $this->_config->merchantPath() . '/payment_methods/from_nonce/' . $nonce;
$response = $this->_http->get($path);
return CreditCard::factory($response['creditCard']);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound(
'credit card with nonce ' . $nonce . ' locked, consumed or not found'
);
}
}
/**
* create a credit on the card for the passed transaction
*
* @access public
* @param array $attribs
* @return Result\Successful|Result\Error
*/
public function credit($token, $transactionAttribs)
{
$this->_validateId($token);
return Transaction::credit(
array_merge(
$transactionAttribs,
['paymentMethodToken' => $token]
)
);
}
/**
* create a credit on this card, assuming validations will pass
*
* returns a Transaction object on success
*
* @access public
* @param array $attribs
* @return Transaction
* @throws Exception\ValidationError
*/
public function creditNoValidate($token, $transactionAttribs)
{
$result = $this->credit($token, $transactionAttribs);
return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
}
/**
* create a new sale for the current card
*
* @param string $token
* @param array $transactionAttribs
* @return Result\Successful|Result\Error
* @see Transaction::sale()
*/
public function sale($token, $transactionAttribs)
{
$this->_validateId($token);
return Transaction::sale(
array_merge(
$transactionAttribs,
['paymentMethodToken' => $token]
)
);
}
/**
* create a new sale using this card, assuming validations will pass
*
* returns a Transaction object on success
*
* @access public
* @param array $transactionAttribs
* @param string $token
* @return Transaction
* @throws Exception\ValidationsFailed
* @see Transaction::sale()
*/
public function saleNoValidate($token, $transactionAttribs)
{
$result = $this->sale($token, $transactionAttribs);
return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
}
/**
* updates the creditcard record
*
* if calling this method in context, $token
* is the 2nd attribute. $token is not sent in object context.
*
* @access public
* @param array $attributes
* @param string $token (optional)
* @return Result\Successful|Result\Error
*/
public function update($token, $attributes)
{
Util::verifyKeys(self::updateSignature(), $attributes);
$this->_validateId($token);
return $this->_doUpdate('put', '/payment_methods/credit_card/' . $token, ['creditCard' => $attributes]);
}
/**
* update a creditcard record, assuming validations will pass
*
* if calling this method in context, $token
* is the 2nd attribute. $token is not sent in object context.
* returns a CreditCard object on success
*
* @access public
* @param array $attributes
* @param string $token
* @return CreditCard
* @throws Exception\ValidationsFailed
*/
public function updateNoValidate($token, $attributes)
{
$result = $this->update($token, $attributes);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
*
* @access public
* @param none
* @return string
*/
public function updateCreditCardUrl()
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE);
return $this->_config->baseUrl() . $this->_config->merchantPath() .
'/payment_methods/all/update_via_transparent_redirect_request';
}
/**
* update a customer from a TransparentRedirect operation
*
* @deprecated since version 2.3.0
* @access public
* @param array $attribs
* @return object
*/
public function updateFromTransparentRedirect($queryString)
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE);
$params = TransparentRedirect::parseAndValidateQueryString(
$queryString
);
return $this->_doUpdate(
'post',
'/payment_methods/all/confirm_transparent_redirect_request',
['id' => $params['id']]
);
}
public function delete($token)
{
$this->_validateId($token);
$path = $this->_config->merchantPath() . '/payment_methods/credit_card/' . $token;
$this->_http->delete($path);
return new Result\Successful();
}
private static function baseOptions()
{
return ['makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'verificationAmount', 'venmoSdkSession'];
}
private static function baseSignature($options)
{
return [
'billingAddressId', 'cardholderName', 'cvv', 'number', 'deviceSessionId',
'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode',
'deviceData', 'fraudMerchantId', 'paymentMethodNonce',
['options' => $options],
[
'billingAddress' => self::billingAddressSignature()
],
];
}
public static function billingAddressSignature()
{
return [
'firstName',
'lastName',
'company',
'countryCodeAlpha2',
'countryCodeAlpha3',
'countryCodeNumeric',
'countryName',
'extendedAddress',
'locality',
'region',
'postalCode',
'streetAddress'
];
}
public static function createSignature()
{
$options = self::baseOptions();
$options[] = "failOnDuplicatePaymentMethod";
$signature = self::baseSignature($options);
$signature[] = 'customerId';
return $signature;
}
public static function updateSignature()
{
$options = self::baseOptions();
$options[] = "failOnDuplicatePaymentMethod";
$signature = self::baseSignature($options);
$updateExistingBillingSignature = [
[
'options' => [
'updateExisting'
]
]
];
foreach($signature AS $key => $value) {
if(is_array($value) and array_key_exists('billingAddress', $value)) {
$signature[$key]['billingAddress'] = array_merge_recursive($value['billingAddress'], $updateExistingBillingSignature);
}
}
return $signature;
}
/**
* sends the create request to the gateway
*
* @ignore
* @param string $subPath
* @param array $params
* @return mixed
*/
public function _doCreate($subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->post($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
* verifies that a valid credit card identifier is being used
* @ignore
* @param string $identifier
* @param Optional $string $identifierType type of identifier supplied, default "token"
* @throws InvalidArgumentException
*/
private function _validateId($identifier = null, $identifierType = "token")
{
if (empty($identifier)) {
throw new InvalidArgumentException(
'expected credit card id to be set'
);
}
if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) {
throw new InvalidArgumentException(
$identifier . ' is an invalid credit card ' . $identifierType . '.'
);
}
}
/**
* sends the update request to the gateway
*
* @ignore
* @param string $url
* @param array $params
* @return mixed
*/
private function _doUpdate($httpVerb, $subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->$httpVerb($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
* generic method for validating incoming gateway responses
*
* creates a new CreditCard object and encapsulates
* it inside a Result\Successful object, or
* encapsulates a Errors object inside a Result\Error
* alternatively, throws an Unexpected exception if the response is invalid
*
* @ignore
* @param array $response gateway response values
* @return Result\Successful|Result\Error
* @throws Exception\Unexpected
*/
private function _verifyGatewayResponse($response)
{
if (isset($response['creditCard'])) {
// return a populated instance of Address
return new Result\Successful(
CreditCard::factory($response['creditCard'])
);
} elseif (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
} else {
throw new Exception\Unexpected(
"Expected address or apiErrorResponse"
);
}
}
}
class_alias('Braintree\CreditCardGateway', 'Braintree_CreditCardGateway');

View File

@@ -0,0 +1,67 @@
<?php
namespace Braintree;
/**
* {@inheritdoc}
*
* @property-read string $amount
* @property-read mixed $billing
* @property-read string $company
* @property-read string $countryName
* @property-read string $extendedAddress
* @property-read string $firstName
* @property-read string $lastName
* @property-read string $locality
* @property-read string $postalCode
* @property-read string $region
* @property-read string $streetAddress
* @property-read \DateTime $createdAt
* @property-read \Braintree\CreditCard $creditCard
* @property-read string|null $gatewayRejectionReason
* @property-read string $id
* @property-read string $merchantAccountId
* @property-read string $processorResponseCode
* @property-read string $processorResponseText
* @property-read string $processorResponseType
* @property-read \Braintree\RiskData|null $riskData
*/
class CreditCardVerification extends Result\CreditCardVerification
{
public static function factory($attributes)
{
$instance = new self($attributes);
return $instance;
}
// static methods redirecting to gateway
//
public static function create($attributes)
{
Util::verifyKeys(self::createSignature(), $attributes);
return Configuration::gateway()->creditCardVerification()->create($attributes);
}
public static function fetch($query, $ids)
{
return Configuration::gateway()->creditCardVerification()->fetch($query, $ids);
}
public static function search($query)
{
return Configuration::gateway()->creditCardVerification()->search($query);
}
public static function createSignature()
{
return [
['options' => ['amount', 'merchantAccountId']],
['creditCard' =>
[
'cardholderName', 'cvv', 'number',
'expirationDate', 'expirationMonth', 'expirationYear',
['billingAddress' => CreditCardGateway::billingAddressSignature()]
]
]];
}
}
class_alias('Braintree\CreditCardVerification', 'Braintree_CreditCardVerification');

View File

@@ -0,0 +1,74 @@
<?php
namespace Braintree;
class CreditCardVerificationGateway
{
private $_gateway;
private $_config;
private $_http;
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function create($attributes)
{
$response = $this->_http->post($this->_config->merchantPath() . "/verifications", ['verification' => $attributes]);
return $this->_verifyGatewayResponse($response);
}
private function _verifyGatewayResponse($response)
{
if(isset($response['verification'])){
return new Result\Successful(
CreditCardVerification::factory($response['verification'])
);
} else if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
} else {
throw new Exception\Unexpected(
"Expected transaction or apiErrorResponse"
);
}
}
public function fetch($query, $ids)
{
$criteria = [];
foreach ($query as $term) {
$criteria[$term->name] = $term->toparam();
}
$criteria["ids"] = CreditCardVerificationSearch::ids()->in($ids)->toparam();
$path = $this->_config->merchantPath() . '/verifications/advanced_search';
$response = $this->_http->post($path, ['search' => $criteria]);
return Util::extractattributeasarray(
$response['creditCardVerifications'],
'verification'
);
}
public function search($query)
{
$criteria = [];
foreach ($query as $term) {
$criteria[$term->name] = $term->toparam();
}
$path = $this->_config->merchantPath() . '/verifications/advanced_search_ids';
$response = $this->_http->post($path, ['search' => $criteria]);
$pager = [
'object' => $this,
'method' => 'fetch',
'methodArgs' => [$query]
];
return new ResourceCollection($response, $pager);
}
}
class_alias('Braintree\CreditCardVerificationGateway', 'Braintree_CreditCardVerificationGateway');

View File

@@ -0,0 +1,56 @@
<?php
namespace Braintree;
class CreditCardVerificationSearch
{
public static function id() {
return new TextNode('id');
}
public static function creditCardCardholderName() {
return new TextNode('credit_card_cardholder_name');
}
public static function billingAddressDetailsPostalCode() {
return new TextNode('billing_address_details_postal_code');
}
public static function customerEmail() {
return new TextNode('customer_email');
}
public static function customerId() {
return new TextNode('customer_id');
}
public static function paymentMethodToken(){
return new TextNode('payment_method_token');
}
public static function creditCardExpirationDate() {
return new EqualityNode('credit_card_expiration_date');
}
public static function creditCardNumber() {
return new PartialMatchNode('credit_card_number');
}
public static function ids() {
return new MultipleValueNode('ids');
}
public static function createdAt() {
return new RangeNode("created_at");
}
public static function creditCardCardType()
{
return new MultipleValueNode("credit_card_card_type", CreditCard::allCardTypes());
}
public static function status()
{
return new MultipleValueNode("status", Result\CreditCardVerification::allStatuses());
}
}
class_alias('Braintree\CreditCardVerificationSearch', 'Braintree_CreditCardVerificationSearch');

View File

@@ -0,0 +1,435 @@
<?php
namespace Braintree;
/**
* Braintree Customer module
* Creates and manages Customers
*
* <b>== More information ==</b>
*
* For more detailed information on Customers, see {@link https://developers.braintreepayments.com/reference/response/customer/php https://developers.braintreepayments.com/reference/response/customer/php}
*
* @package Braintree
* @category Resources
*
* @property-read \Braintree\Address[] $addresses
* @property-read \Braintree\AndroidPayCard[] $androidPayCards
* @property-read \Braintree\AmexExpressCheckoutCard[] $amexExpressCheckoutCards
* @property-read \Braintree\ApplePayCard[] $applePayCards
* @property-read \Braintree\CoinbaseAccount[] $coinbaseAccounts
* @property-read string $company
* @property-read \DateTime $createdAt
* @property-read \Braintree\CreditCard[] $creditCards
* @property-read array $customFields custom fields passed with the request
* @property-read string $email
* @property-read string $fax
* @property-read string $firstName
* @property-read string $id
* @property-read string $lastName
* @property-read \Braintree\MasterpassCard[] $masterpassCards
* @property-read \Braintree\PaymentMethod[] $paymentMethods
* @property-read \Braintree\PayPalAccount[] $paypalAccounts
* @property-read string $phone
* @property-read \Braintree\SamsungPayCard[] $samsungPayCards
* @property-read \DateTime $updatedAt
* @property-read \Braintree\UsBankAccount[] $usBankAccounts
* @property-read \Braintree\VenmoAccount[] $venmoAccounts
* @property-read \Braintree\VisaCheckoutCard[] $visaCheckoutCards
* @property-read string $website
*/
class Customer extends Base
{
/**
*
* @return Customer[]
*/
public static function all()
{
return Configuration::gateway()->customer()->all();
}
/**
*
* @param string $query
* @param int[] $ids
* @return Customer|Customer[]
*/
public static function fetch($query, $ids)
{
return Configuration::gateway()->customer()->fetch($query, $ids);
}
/**
*
* @param array $attribs
* @return Result\Successful|Result\Error
*/
public static function create($attribs = [])
{
return Configuration::gateway()->customer()->create($attribs);
}
/**
*
* @param array $attribs
* @return Customer
*/
public static function createNoValidate($attribs = [])
{
return Configuration::gateway()->customer()->createNoValidate($attribs);
}
/**
* @deprecated since version 2.3.0
* @param string $queryString
* @return Result\Successful
*/
public static function createFromTransparentRedirect($queryString)
{
return Configuration::gateway()->customer()->createFromTransparentRedirect($queryString);
}
/**
* @deprecated since version 2.3.0
* @return string
*/
public static function createCustomerUrl()
{
return Configuration::gateway()->customer()->createCustomerUrl();
}
/**
*
* @throws Exception\NotFound
* @param string $id customer id
* @return Customer
*/
public static function find($id, $associationFilterId = null)
{
return Configuration::gateway()->customer()->find($id, $associationFilterId);
}
/**
*
* @param int $customerId
* @param array $transactionAttribs
* @return Result\Successful|Result\Error
*/
public static function credit($customerId, $transactionAttribs)
{
return Configuration::gateway()->customer()->credit($customerId, $transactionAttribs);
}
/**
*
* @throws Exception\ValidationError
* @param type $customerId
* @param type $transactionAttribs
* @return Transaction
*/
public static function creditNoValidate($customerId, $transactionAttribs)
{
return Configuration::gateway()->customer()->creditNoValidate($customerId, $transactionAttribs);
}
/**
*
* @throws Exception on invalid id or non-200 http response code
* @param int $customerId
* @return Result\Successful
*/
public static function delete($customerId)
{
return Configuration::gateway()->customer()->delete($customerId);
}
/**
*
* @param int $customerId
* @param array $transactionAttribs
* @return Transaction
*/
public static function sale($customerId, $transactionAttribs)
{
return Configuration::gateway()->customer()->sale($customerId, $transactionAttribs);
}
/**
*
* @param int $customerId
* @param array $transactionAttribs
* @return Transaction
*/
public static function saleNoValidate($customerId, $transactionAttribs)
{
return Configuration::gateway()->customer()->saleNoValidate($customerId, $transactionAttribs);
}
/**
*
* @throws InvalidArgumentException
* @param string $query
* @return ResourceCollection
*/
public static function search($query)
{
return Configuration::gateway()->customer()->search($query);
}
/**
*
* @throws Exception\Unexpected
* @param int $customerId
* @param array $attributes
* @return Result\Successful|Result\Error
*/
public static function update($customerId, $attributes)
{
return Configuration::gateway()->customer()->update($customerId, $attributes);
}
/**
*
* @throws Exception\Unexpected
* @param int $customerId
* @param array $attributes
* @return CustomerGateway
*/
public static function updateNoValidate($customerId, $attributes)
{
return Configuration::gateway()->customer()->updateNoValidate($customerId, $attributes);
}
/**
*
* @deprecated since version 2.3.0
* @return string
*/
public static function updateCustomerUrl()
{
return Configuration::gateway()->customer()->updateCustomerUrl();
}
/**
*
* @deprecated since version 2.3.0
* @param string $queryString
* @return Result\Successful|Result\Error
*/
public static function updateFromTransparentRedirect($queryString)
{
return Configuration::gateway()->customer()->updateFromTransparentRedirect($queryString);
}
/* instance methods */
/**
* sets instance properties from an array of values
*
* @ignore
* @access protected
* @param array $customerAttribs array of customer data
*/
protected function _initialize($customerAttribs)
{
$this->_attributes = $customerAttribs;
$addressArray = [];
if (isset($customerAttribs['addresses'])) {
foreach ($customerAttribs['addresses'] AS $address) {
$addressArray[] = Address::factory($address);
}
}
$this->_set('addresses', $addressArray);
$creditCardArray = [];
if (isset($customerAttribs['creditCards'])) {
foreach ($customerAttribs['creditCards'] AS $creditCard) {
$creditCardArray[] = CreditCard::factory($creditCard);
}
}
$this->_set('creditCards', $creditCardArray);
$coinbaseAccountArray = [];
if (isset($customerAttribs['coinbaseAccounts'])) {
foreach ($customerAttribs['coinbaseAccounts'] AS $coinbaseAccount) {
$coinbaseAccountArray[] = CoinbaseAccount::factory($coinbaseAccount);
}
}
$this->_set('coinbaseAccounts', $coinbaseAccountArray);
$paypalAccountArray = [];
if (isset($customerAttribs['paypalAccounts'])) {
foreach ($customerAttribs['paypalAccounts'] AS $paypalAccount) {
$paypalAccountArray[] = PayPalAccount::factory($paypalAccount);
}
}
$this->_set('paypalAccounts', $paypalAccountArray);
$applePayCardArray = [];
if (isset($customerAttribs['applePayCards'])) {
foreach ($customerAttribs['applePayCards'] AS $applePayCard) {
$applePayCardArray[] = ApplePayCard::factory($applePayCard);
}
}
$this->_set('applePayCards', $applePayCardArray);
$androidPayCardArray = [];
if (isset($customerAttribs['androidPayCards'])) {
foreach ($customerAttribs['androidPayCards'] AS $androidPayCard) {
$androidPayCardArray[] = AndroidPayCard::factory($androidPayCard);
}
}
$this->_set('androidPayCards', $androidPayCardArray);
$amexExpressCheckoutCardArray = [];
if (isset($customerAttribs['amexExpressCheckoutCards'])) {
foreach ($customerAttribs['amexExpressCheckoutCards'] AS $amexExpressCheckoutCard) {
$amexExpressCheckoutCardArray[] = AmexExpressCheckoutCard::factory($amexExpressCheckoutCard);
}
}
$this->_set('amexExpressCheckoutCards', $amexExpressCheckoutCardArray);
$venmoAccountArray = array();
if (isset($customerAttribs['venmoAccounts'])) {
foreach ($customerAttribs['venmoAccounts'] AS $venmoAccount) {
$venmoAccountArray[] = VenmoAccount::factory($venmoAccount);
}
}
$this->_set('venmoAccounts', $venmoAccountArray);
$visaCheckoutCardArray = [];
if (isset($customerAttribs['visaCheckoutCards'])) {
foreach ($customerAttribs['visaCheckoutCards'] AS $visaCheckoutCard) {
$visaCheckoutCardArray[] = VisaCheckoutCard::factory($visaCheckoutCard);
}
}
$this->_set('visaCheckoutCards', $visaCheckoutCardArray);
$masterpassCardArray = [];
if (isset($customerAttribs['masterpassCards'])) {
foreach ($customerAttribs['masterpassCards'] AS $masterpassCard) {
$masterpassCardArray[] = MasterpassCard::factory($masterpassCard);
}
}
$this->_set('masterpassCards', $masterpassCardArray);
$samsungPayCardArray = [];
if (isset($customerAttribs['samsungPayCards'])) {
foreach ($customerAttribs['samsungPayCards'] AS $samsungPayCard) {
$samsungPayCardArray[] = SamsungPayCard::factory($samsungPayCard);
}
}
$this->_set('samsungPayCards', $samsungPayCardArray);
$usBankAccountArray = array();
if (isset($customerAttribs['usBankAccounts'])) {
foreach ($customerAttribs['usBankAccounts'] AS $usBankAccount) {
$usBankAccountArray[] = UsBankAccount::factory($usBankAccount);
}
}
$this->_set('usBankAccounts', $usBankAccountArray);
$this->_set('paymentMethods', array_merge(
$this->creditCards,
$this->paypalAccounts,
$this->applePayCards,
$this->coinbaseAccounts,
$this->androidPayCards,
$this->amexExpressCheckoutCards,
$this->venmoAccounts,
$this->visaCheckoutCards,
$this->masterpassCards,
$this->samsungPayCards,
$this->usBankAccounts
));
}
/**
* returns a string representation of the customer
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) .']';
}
/**
* returns false if comparing object is not a Customer,
* or is a Customer with a different id
*
* @param object $otherCust customer to compare against
* @return boolean
*/
public function isEqual($otherCust)
{
return !($otherCust instanceof Customer) ? false : $this->id === $otherCust->id;
}
/**
* returns an array containt all of the customer's payment methods
*
* @deprecated since version 3.1.0 - use the paymentMethods property directly
*
* @return array
*/
public function paymentMethods()
{
return $this->paymentMethods;
}
/**
* returns the customer's default payment method
*
* @return CreditCard|PayPalAccount
*/
public function defaultPaymentMethod()
{
$defaultPaymentMethods = array_filter($this->paymentMethods, 'Braintree\Customer::_defaultPaymentMethodFilter');
return current($defaultPaymentMethods);
}
public static function _defaultPaymentMethodFilter($paymentMethod)
{
return $paymentMethod->isDefault();
}
/* private class properties */
/**
* @access protected
* @var array registry of customer data
*/
protected $_attributes = [
'addresses' => '',
'company' => '',
'creditCards' => '',
'email' => '',
'fax' => '',
'firstName' => '',
'id' => '',
'lastName' => '',
'phone' => '',
'createdAt' => '',
'updatedAt' => '',
'website' => '',
];
/**
* factory method: returns an instance of Customer
* to the requesting method, with populated properties
*
* @ignore
* @param array $attributes
* @return Customer
*/
public static function factory($attributes)
{
$instance = new Customer();
$instance->_initialize($attributes);
return $instance;
}
}
class_alias('Braintree\Customer', 'Braintree_Customer');

View File

@@ -0,0 +1,668 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Braintree CustomerGateway module
* Creates and manages Customers
*
* <b>== More information ==</b>
*
* For more detailed information on Customers, see {@link https://developers.braintreepayments.com/reference/response/customer/php https://developers.braintreepayments.com/reference/response/customer/php}
*
* @package Braintree
* @category Resources
*/
class CustomerGateway
{
private $_gateway;
private $_config;
private $_http;
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function all()
{
$path = $this->_config->merchantPath() . '/customers/advanced_search_ids';
$response = $this->_http->post($path);
$pager = [
'object' => $this,
'method' => 'fetch',
'methodArgs' => [[]]
];
return new ResourceCollection($response, $pager);
}
public function fetch($query, $ids)
{
$criteria = [];
foreach ($query as $term) {
$criteria[$term->name] = $term->toparam();
}
$criteria["ids"] = CustomerSearch::ids()->in($ids)->toparam();
$path = $this->_config->merchantPath() . '/customers/advanced_search';
$response = $this->_http->post($path, ['search' => $criteria]);
return Util::extractattributeasarray(
$response['customers'],
'customer'
);
}
/**
* Creates a customer using the given +attributes+. If <tt>:id</tt> is not passed,
* the gateway will generate it.
*
* <code>
* $result = Customer::create(array(
* 'first_name' => 'John',
* 'last_name' => 'Smith',
* 'company' => 'Smith Co.',
* 'email' => 'john@smith.com',
* 'website' => 'www.smithco.com',
* 'fax' => '419-555-1234',
* 'phone' => '614-555-1234'
* ));
* if($result->success) {
* echo 'Created customer ' . $result->customer->id;
* } else {
* echo 'Could not create customer, see result->errors';
* }
* </code>
*
* @access public
* @param array $attribs
* @return Result\Successful|Result\Error
*/
public function create($attribs = [])
{
Util::verifyKeys(self::createSignature(), $attribs);
return $this->_doCreate('/customers', ['customer' => $attribs]);
}
/**
* attempts the create operation assuming all data will validate
* returns a Customer object instead of a Result
*
* @access public
* @param array $attribs
* @return Customer
* @throws Exception\ValidationError
*/
public function createNoValidate($attribs = [])
{
$result = $this->create($attribs);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
* create a customer from a TransparentRedirect operation
*
* @deprecated since version 2.3.0
* @access public
* @param array $attribs
* @return Customer
*/
public function createFromTransparentRedirect($queryString)
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE);
$params = TransparentRedirect::parseAndValidateQueryString(
$queryString
);
return $this->_doCreate(
'/customers/all/confirm_transparent_redirect_request',
['id' => $params['id']]
);
}
/**
*
* @deprecated since version 2.3.0
* @access public
* @param none
* @return string
*/
public function createCustomerUrl()
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE);
return $this->_config->baseUrl() . $this->_config->merchantPath() .
'/customers/all/create_via_transparent_redirect_request';
}
/**
* creates a full array signature of a valid create request
* @return array gateway create request format
*/
public static function createSignature()
{
$creditCardSignature = CreditCardGateway::createSignature();
unset($creditCardSignature[array_search('customerId', $creditCardSignature)]);
$signature = [
'id', 'company', 'email', 'fax', 'firstName',
'lastName', 'phone', 'website', 'deviceData',
'deviceSessionId', 'fraudMerchantId', 'paymentMethodNonce',
['riskData' =>
['customerBrowser', 'customerIp', 'customer_browser', 'customer_ip']
],
['creditCard' => $creditCardSignature],
['customFields' => ['_anyKey_']],
['options' => [
['paypal' => [
'payee_email',
'payeeEmail',
'order_id',
'orderId',
'custom_field',
'customField',
'description',
'amount',
['shipping' =>
[
'firstName', 'lastName', 'company', 'countryName',
'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric',
'extendedAddress', 'locality', 'postalCode', 'region',
'streetAddress'],
],
]]
]],
];
return $signature;
}
/**
* creates a full array signature of a valid update request
* @return array update request format
*/
public static function updateSignature()
{
$creditCardSignature = CreditCardGateway::updateSignature();
foreach($creditCardSignature AS $key => $value) {
if(is_array($value) and array_key_exists('options', $value)) {
array_push($creditCardSignature[$key]['options'], 'updateExistingToken');
}
}
$signature = [
'id', 'company', 'email', 'fax', 'firstName',
'lastName', 'phone', 'website', 'deviceData',
'deviceSessionId', 'fraudMerchantId', 'paymentMethodNonce', 'defaultPaymentMethodToken',
['creditCard' => $creditCardSignature],
['customFields' => ['_anyKey_']],
['options' => [
['paypal' => [
'payee_email',
'payeeEmail',
'order_id',
'orderId',
'custom_field',
'customField',
'description',
'amount',
['shipping' =>
[
'firstName', 'lastName', 'company', 'countryName',
'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric',
'extendedAddress', 'locality', 'postalCode', 'region',
'streetAddress'],
],
]],
]],
];
return $signature;
}
/**
* find a customer by id
*
* @access public
* @param string id customer Id
* @param string associationFilterId association filter Id
* @return Customer|boolean The customer object or false if the request fails.
* @throws Exception\NotFound
*/
public function find($id, $associationFilterId = null)
{
$this->_validateId($id);
try {
$queryParams = '';
if ($associationFilterId) {
$queryParams = '?association_filter_id=' . $associationFilterId;
}
$path = $this->_config->merchantPath() . '/customers/' . $id . $queryParams;
$response = $this->_http->get($path);
return Customer::factory($response['customer']);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound(
'customer with id ' . $id . ' not found'
);
}
}
/**
* credit a customer for the passed transaction
*
* @access public
* @param int $customerId
* @param array $transactionAttribs
* @return Result\Successful|Result\Error
*/
public function credit($customerId, $transactionAttribs)
{
$this->_validateId($customerId);
return Transaction::credit(
array_merge($transactionAttribs,
['customerId' => $customerId]
)
);
}
/**
* credit a customer, assuming validations will pass
*
* returns a Transaction object on success
*
* @access public
* @param int $customerId
* @param array $transactionAttribs
* @return Transaction
* @throws Exception\ValidationError
*/
public function creditNoValidate($customerId, $transactionAttribs)
{
$result = $this->credit($customerId, $transactionAttribs);
return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
}
/**
* delete a customer by id
*
* @param string $customerId
*/
public function delete($customerId)
{
$this->_validateId($customerId);
$path = $this->_config->merchantPath() . '/customers/' . $customerId;
$this->_http->delete($path);
return new Result\Successful();
}
/**
* create a new sale for a customer
*
* @param string $customerId
* @param array $transactionAttribs
* @return Result\Successful|Result\Error
* @see Transaction::sale()
*/
public function sale($customerId, $transactionAttribs)
{
$this->_validateId($customerId);
return Transaction::sale(
array_merge($transactionAttribs,
['customerId' => $customerId]
)
);
}
/**
* create a new sale for a customer, assuming validations will pass
*
* returns a Transaction object on success
* @access public
* @param string $customerId
* @param array $transactionAttribs
* @return Transaction
* @throws Exception\ValidationsFailed
* @see Transaction::sale()
*/
public function saleNoValidate($customerId, $transactionAttribs)
{
$result = $this->sale($customerId, $transactionAttribs);
return Util::returnObjectOrThrowException('Braintree\Transaction', $result);
}
/**
* Returns a ResourceCollection of customers matching the search query.
*
* If <b>query</b> is a string, the search will be a basic search.
* If <b>query</b> is a hash, the search will be an advanced search.
* For more detailed information and examples, see {@link https://developers.braintreepayments.com/reference/request/customer/search/php https://developers.braintreepayments.com/reference/request/customer/search/php}
*
* @param mixed $query search query
* @return ResourceCollection
* @throws InvalidArgumentException
*/
public function search($query)
{
$criteria = [];
foreach ($query as $term) {
$result = $term->toparam();
if(is_null($result) || empty($result)) {
throw new InvalidArgumentException('Operator must be provided');
}
$criteria[$term->name] = $term->toparam();
}
$path = $this->_config->merchantPath() . '/customers/advanced_search_ids';
$response = $this->_http->post($path, ['search' => $criteria]);
$pager = [
'object' => $this,
'method' => 'fetch',
'methodArgs' => [$query]
];
return new ResourceCollection($response, $pager);
}
/**
* updates the customer record
*
* if calling this method in static context, customerId
* is the 2nd attribute. customerId is not sent in object context.
*
* @access public
* @param string $customerId (optional)
* @param array $attributes
* @return Result\Successful|Result\Error
*/
public function update($customerId, $attributes)
{
Util::verifyKeys(self::updateSignature(), $attributes);
$this->_validateId($customerId);
return $this->_doUpdate(
'put',
'/customers/' . $customerId,
['customer' => $attributes]
);
}
/**
* update a customer record, assuming validations will pass
*
* if calling this method in static context, customerId
* is the 2nd attribute. customerId is not sent in object context.
* returns a Customer object on success
*
* @access public
* @param string $customerId
* @param array $attributes
* @return Customer
* @throws Exception\ValidationsFailed
*/
public function updateNoValidate($customerId, $attributes)
{
$result = $this->update($customerId, $attributes);
return Util::returnObjectOrThrowException(__CLASS__, $result);
}
/**
*
* @deprecated since version 2.3.0
* @access public
* @return string
*/
public function updateCustomerUrl()
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::url", E_USER_NOTICE);
return $this->_config->baseUrl() . $this->_config->merchantPath() .
'/customers/all/update_via_transparent_redirect_request';
}
/**
* update a customer from a TransparentRedirect operation
*
* @deprecated since version 2.3.0
* @access public
* @param string $queryString
* @return object
*/
public function updateFromTransparentRedirect($queryString)
{
trigger_error("DEPRECATED: Please use TransparentRedirectRequest::confirm", E_USER_NOTICE);
$params = TransparentRedirect::parseAndValidateQueryString(
$queryString
);
return $this->_doUpdate(
'post',
'/customers/all/confirm_transparent_redirect_request',
['id' => $params['id']]
);
}
/* instance methods */
/**
* sets instance properties from an array of values
*
* @ignore
* @access protected
* @param array $customerAttribs array of customer data
* @return void
*/
protected function _initialize($customerAttribs)
{
// set the attributes
$this->_attributes = $customerAttribs;
// map each address into its own object
$addressArray = [];
if (isset($customerAttribs['addresses'])) {
foreach ($customerAttribs['addresses'] AS $address) {
$addressArray[] = Address::factory($address);
}
}
$this->_set('addresses', $addressArray);
// map each creditCard into its own object
$creditCardArray = [];
if (isset($customerAttribs['creditCards'])) {
foreach ($customerAttribs['creditCards'] AS $creditCard) {
$creditCardArray[] = CreditCard::factory($creditCard);
}
}
$this->_set('creditCards', $creditCardArray);
// map each coinbaseAccount into its own object
$coinbaseAccountArray = [];
if (isset($customerAttribs['coinbaseAccounts'])) {
foreach ($customerAttribs['coinbaseAccounts'] AS $coinbaseAccount) {
$coinbaseAccountArray[] = CoinbaseAccount::factory($coinbaseAccount);
}
}
$this->_set('coinbaseAccounts', $coinbaseAccountArray);
// map each paypalAccount into its own object
$paypalAccountArray = [];
if (isset($customerAttribs['paypalAccounts'])) {
foreach ($customerAttribs['paypalAccounts'] AS $paypalAccount) {
$paypalAccountArray[] = PayPalAccount::factory($paypalAccount);
}
}
$this->_set('paypalAccounts', $paypalAccountArray);
// map each applePayCard into its own object
$applePayCardArray = [];
if (isset($customerAttribs['applePayCards'])) {
foreach ($customerAttribs['applePayCards'] AS $applePayCard) {
$applePayCardArray[] = ApplePayCard::factory($applePayCard);
}
}
$this->_set('applePayCards', $applePayCardArray);
// map each androidPayCard into its own object
$androidPayCardArray = [];
if (isset($customerAttribs['androidPayCards'])) {
foreach ($customerAttribs['androidPayCards'] AS $androidPayCard) {
$androidPayCardArray[] = AndroidPayCard::factory($androidPayCard);
}
}
$this->_set('androidPayCards', $androidPayCardArray);
$this->_set('paymentMethods', array_merge($this->creditCards, $this->paypalAccounts, $this->applePayCards, $this->coinbaseAccounts, $this->androidPayCards));
}
/**
* returns a string representation of the customer
* @return string
*/
public function __toString()
{
return __CLASS__ . '[' .
Util::attributesToString($this->_attributes) .']';
}
/**
* returns false if comparing object is not a Customer,
* or is a Customer with a different id
*
* @param object $otherCust customer to compare against
* @return boolean
*/
public function isEqual($otherCust)
{
return !($otherCust instanceof Customer) ? false : $this->id === $otherCust->id;
}
/**
* returns an array containt all of the customer's payment methods
*
* @return array
*/
public function paymentMethods()
{
return $this->paymentMethods;
}
/**
* returns the customer's default payment method
*
* @return CreditCard|PayPalAccount|ApplePayCard|AndroidPayCard
*/
public function defaultPaymentMethod()
{
$defaultPaymentMethods = array_filter($this->paymentMethods, 'Braintree\\Customer::_defaultPaymentMethodFilter');
return current($defaultPaymentMethods);
}
public static function _defaultPaymentMethodFilter($paymentMethod)
{
return $paymentMethod->isDefault();
}
/* private class properties */
/**
* @access protected
* @var array registry of customer data
*/
protected $_attributes = [
'addresses' => '',
'company' => '',
'creditCards' => '',
'email' => '',
'fax' => '',
'firstName' => '',
'id' => '',
'lastName' => '',
'phone' => '',
'createdAt' => '',
'updatedAt' => '',
'website' => '',
];
/**
* sends the create request to the gateway
*
* @ignore
* @param string $subPath
* @param array $params
* @return mixed
*/
public function _doCreate($subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->post($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
* verifies that a valid customer id is being used
* @ignore
* @param string customer id
* @throws InvalidArgumentException
*/
private function _validateId($id = null) {
if (is_null($id)) {
throw new InvalidArgumentException(
'expected customer id to be set'
);
}
if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) {
throw new InvalidArgumentException(
$id . ' is an invalid customer id.'
);
}
}
/* private class methods */
/**
* sends the update request to the gateway
*
* @ignore
* @param string $subPath
* @param array $params
* @return mixed
*/
private function _doUpdate($httpVerb, $subPath, $params)
{
$fullPath = $this->_config->merchantPath() . $subPath;
$response = $this->_http->$httpVerb($fullPath, $params);
return $this->_verifyGatewayResponse($response);
}
/**
* generic method for validating incoming gateway responses
*
* creates a new Customer object and encapsulates
* it inside a Result\Successful object, or
* encapsulates a Errors object inside a Result\Error
* alternatively, throws an Unexpected exception if the response is invalid.
*
* @ignore
* @param array $response gateway response values
* @return Result\Successful|Result\Error
* @throws Exception\Unexpected
*/
private function _verifyGatewayResponse($response)
{
if (isset($response['customer'])) {
// return a populated instance of Customer
return new Result\Successful(
Customer::factory($response['customer'])
);
} else if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
} else {
throw new Exception\Unexpected(
"Expected customer or apiErrorResponse"
);
}
}
}
class_alias('Braintree\CustomerGateway', 'Braintree_CustomerGateway');

View File

@@ -0,0 +1,34 @@
<?php
namespace Braintree;
class CustomerSearch
{
public static function addressCountryName() { return new TextNode('address_country_name'); }
public static function addressExtendedAddress() { return new TextNode('address_extended_address'); }
public static function addressFirstName() { return new TextNode('address_first_name'); }
public static function addressLastName() { return new TextNode('address_last_name'); }
public static function addressLocality() { return new TextNode('address_locality'); }
public static function addressPostalCode() { return new TextNode('address_postal_code'); }
public static function addressRegion() { return new TextNode('address_region'); }
public static function addressStreetAddress() { return new TextNode('address_street_address'); }
public static function cardholderName() { return new TextNode('cardholder_name'); }
public static function company() { return new TextNode('company'); }
public static function email() { return new TextNode('email'); }
public static function fax() { return new TextNode('fax'); }
public static function firstName() { return new TextNode('first_name'); }
public static function id() { return new TextNode('id'); }
public static function lastName() { return new TextNode('last_name'); }
public static function paymentMethodToken() { return new TextNode('payment_method_token'); }
public static function paymentMethodTokenWithDuplicates() { return new IsNode('payment_method_token_with_duplicates'); }
public static function paypalAccountEmail() { return new IsNode('paypal_account_email'); }
public static function phone() { return new TextNode('phone'); }
public static function website() { return new TextNode('website'); }
public static function creditCardExpirationDate() { return new EqualityNode('credit_card_expiration_date'); }
public static function creditCardNumber() { return new PartialMatchNode('credit_card_number'); }
public static function ids() { return new MultipleValueNode('ids'); }
public static function createdAt() { return new RangeNode("created_at"); }
}
class_alias('Braintree\CustomerSearch', 'Braintree_CustomerSearch');

View File

@@ -0,0 +1,12 @@
<?php
namespace Braintree;
/**
* @property-read string $name
* @property-read string $phone
* @property-read string $url
*/
class Descriptor extends Instance
{
}
class_alias('Braintree\Descriptor', 'Braintree_Descriptor');

View File

@@ -0,0 +1,60 @@
<?php
namespace Braintree;
/**
* Digest encryption module
* Digest creates an HMAC-SHA1 hash for encrypting messages
*/
class Digest
{
public static function hexDigestSha1($key, $string)
{
if(function_exists('hash_hmac')) {
return self::_builtInHmacSha1($string, $key);
} else {
return self::_hmacSha1($string, $key);
}
}
public static function hexDigestSha256($key, $string)
{
return hash_hmac('sha256', $string, hash('sha256', $key, true));
}
public static function secureCompare($left, $right)
{
if (strlen($left) != strlen($right)) {
return false;
}
$leftBytes = unpack("C*", $left);
$rightBytes = unpack("C*", $right);
$result = 0;
for ($i = 1; $i <= count($leftBytes); $i++) {
$result = $result | ($leftBytes[$i] ^ $rightBytes[$i]);
}
return $result == 0;
}
public static function _builtInHmacSha1($message, $key)
{
return hash_hmac('sha1', $message, sha1($key, true));
}
public static function _hmacSha1($message, $key)
{
$pack = 'H40';
$keyDigest = sha1($key,true);
$innerPad = str_repeat(chr(0x36), 64);
$outerPad = str_repeat(chr(0x5C), 64);
for ($i = 0; $i < 20; $i++) {
$innerPad{$i} = $keyDigest{$i} ^ $innerPad{$i};
$outerPad{$i} = $keyDigest{$i} ^ $outerPad{$i};
}
return sha1($outerPad.pack($pack, sha1($innerPad.$message)));
}
}
class_alias('Braintree\Digest', 'Braintree_Digest');

View File

@@ -0,0 +1,66 @@
<?php
namespace Braintree;
class Disbursement extends Base
{
const TYPE_CREDIT = "credit";
const TYPE_DEBIT = "debit";
private $_merchantAccount;
protected function _initialize($disbursementAttribs)
{
$this->_attributes = $disbursementAttribs;
$this->merchantAccountDetails = $disbursementAttribs['merchantAccount'];
if (isset($disbursementAttribs['merchantAccount'])) {
$this->_set('merchantAccount',
MerchantAccount::factory($disbursementAttribs['merchantAccount'])
);
}
}
public function transactions()
{
$collection = Transaction::search([
TransactionSearch::ids()->in($this->transactionIds),
]);
return $collection;
}
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
public function __toString()
{
$display = [
'id', 'merchantAccountDetails', 'exceptionMessage', 'amount',
'disbursementDate', 'followUpAction', 'retry', 'success',
'transactionIds', 'disbursementType'
];
$displayAttributes = [];
foreach ($display AS $attrib) {
$displayAttributes[$attrib] = $this->$attrib;
}
return __CLASS__ . '[' .
Util::attributesToString($displayAttributes) .']';
}
public function isDebit()
{
return $this->disbursementType == Disbursement::TYPE_DEBIT;
}
public function isCredit()
{
return $this->disbursementType == Disbursement::TYPE_CREDIT;
}
}
class_alias('Braintree\Disbursement', 'Braintree_Disbursement');

View File

@@ -0,0 +1,24 @@
<?php
namespace Braintree;
/**
* Disbursement details from a transaction
* Creates an instance of DisbursementDetails as returned from a transaction
*
*
* @package Braintree
*
* @property-read string $disbursementDate
* @property-read boolean $fundsHeld
* @property-read string $settlementAmount
* @property-read string $settlementCurrencyExchangeRate
* @property-read string $settlementCurrencyIsoCode
* @property-read string $success
*/
class DisbursementDetails extends Instance
{
public function isValid() {
return !is_null($this->disbursementDate);
}
}
class_alias('Braintree\DisbursementDetails', 'Braintree_DisbursementDetails');

View File

@@ -0,0 +1,35 @@
<?php
namespace Braintree;
/**
* @property-read string $amount
* @property-read \DateTime $createdAt
* @property-read int|null $currentBillingCycle
* @property-read string $description
* @property-read string $id
* @property-read string|null $kind
* @property-read string $merchantId
* @property-read string $name
* @property-read boolean $neverExpires
* @property-read int|null $numberOfBillingCycles
* @property-read int|null $quantity
* @property-read \DateTime $updatedAt
*/
class Discount extends Modification
{
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
// static methods redirecting to gateway
public static function all()
{
return Configuration::gateway()->discount()->all();
}
}
class_alias('Braintree\Discount', 'Braintree_Discount');

View File

@@ -0,0 +1,31 @@
<?php
namespace Braintree;
class DiscountGateway
{
private $_gateway;
private $_config;
private $_http;
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
public function all()
{
$path = $this->_config->merchantPath() . '/discounts';
$response = $this->_http->get($path);
$discounts = ["discount" => $response['discounts']];
return Util::extractAttributeAsArray(
$discounts,
'discount'
);
}
}
class_alias('Braintree\DiscountGateway', 'Braintree_DiscountGateway');

View File

@@ -0,0 +1,191 @@
<?php
namespace Braintree;
/**
* Creates an instance of Dispute as returned from a transaction
*
*
* @package Braintree
*
* @property-read string $amount
* @property-read \DateTime $createdAt
* @property-read string $currencyIsoCode
* @property-read string $disbursementDate
* @property-read \Braintree\Dispute\EvidenceDetails $evidence
* @property-read string $id
* @property-read string $kind
* @property-read string $merchantAccountId
* @property-read string $originalDisputeId
* @property-read string $processorComments
* @property-read string $reason
* @property-read string $reasonCode
* @property-read string $reasonDescription
* @property-read \DateTime $receivedDate
* @property-read string $referenceNumber
* @property-read \DateTime $replyByDate
* @property-read string $status
* @property-read \Braintree\Dispute\StatusHistoryDetails[] $statusHistory
* @property-read \Braintree\Dispute\TransactionDetails $transaction
* @property-read \Braintree\Dispute\TransactionDetails $transactionDetails
* @property-read \DateTime $updatedAt
*/
class Dispute extends Base
{
protected $_attributes = [];
/* Dispute Status */
const ACCEPTED = 'accepted';
const DISPUTED = 'disputed';
const EXPIRED = 'expired';
const OPEN = 'open';
const WON = 'won';
const LOST = 'lost';
/* deprecated; for backwards compatibilty */
const Open = 'open';
/* Dispute Reason */
const CANCELLED_RECURRING_TRANSACTION = "cancelled_recurring_transaction";
const CREDIT_NOT_PROCESSED = "credit_not_processed";
const DUPLICATE = "duplicate";
const FRAUD = "fraud";
const GENERAL = "general";
const INVALID_ACCOUNT = "invalid_account";
const NOT_RECOGNIZED = "not_recognized";
const PRODUCT_NOT_RECEIVED = "product_not_received";
const PRODUCT_UNSATISFACTORY = "product_unsatisfactory";
const TRANSACTION_AMOUNT_DIFFERS = "transaction_amount_differs";
const RETRIEVAL = "retrieval";
/* Dispute Kind */
const CHARGEBACK = 'chargeback';
const PRE_ARBITRATION = 'pre_arbitration';
// RETRIEVAL for kind already defined under Dispute Reason
protected function _initialize($disputeAttribs)
{
$this->_attributes = $disputeAttribs;
if (isset($disputeAttribs['transaction'])) {
$transactionDetails = new Dispute\TransactionDetails($disputeAttribs['transaction']);
$this->_set('transactionDetails', $transactionDetails);
$this->_set('transaction', $transactionDetails);
}
if (isset($disputeAttribs['evidence'])) {
$evidenceArray = array_map(function($evidence) {
return new Dispute\EvidenceDetails($evidence);
}, $disputeAttribs['evidence']);
$this->_set('evidence', $evidenceArray);
}
if (isset($disputeAttribs['statusHistory'])) {
$statusHistoryArray = array_map(function($statusHistory) {
return new Dispute\StatusHistoryDetails($statusHistory);
}, $disputeAttribs['statusHistory']);
$this->_set('statusHistory', $statusHistoryArray);
}
if (isset($disputeAttribs['transaction'])) {
$this->_set('transaction',
new Dispute\TransactionDetails($disputeAttribs['transaction'])
);
}
}
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
public function __toString()
{
$display = [
'amount', 'reason', 'status',
'replyByDate', 'receivedDate', 'currencyIsoCode'
];
$displayAttributes = [];
foreach ($display AS $attrib) {
$displayAttributes[$attrib] = $this->$attrib;
}
return __CLASS__ . '[' .
Util::attributesToString($displayAttributes) .']';
}
/**
* Accepts a dispute, given a dispute ID
*
* @param string $id
*/
public static function accept($id)
{
return Configuration::gateway()->dispute()->accept($id);
}
/**
* Adds file evidence to a dispute, given a dispute ID and a document ID
*
* @param string $disputeId
* @param string $documentIdOrRequest
*/
public static function addFileEvidence($disputeId, $documentIdOrRequest)
{
return Configuration::gateway()->dispute()->addFileEvidence($disputeId, $documentIdOrRequest);
}
/**
* Adds text evidence to a dispute, given a dispute ID and content
*
* @param string $id
* @param string $contentOrRequest
*/
public static function addTextEvidence($id, $contentOrRequest)
{
return Configuration::gateway()->dispute()->addTextEvidence($id, $contentOrRequest);
}
/**
* Finalize a dispute, given a dispute ID
*
* @param string $id
*/
public static function finalize($id)
{
return Configuration::gateway()->dispute()->finalize($id);
}
/**
* Find a dispute, given a dispute ID
*
* @param string $id
*/
public static function find($id)
{
return Configuration::gateway()->dispute()->find($id);
}
/**
* Remove evidence from a dispute, given a dispute ID and evidence ID
*
* @param string $disputeId
* @param string $evidenceId
*/
public static function removeEvidence($disputeId, $evidenceId)
{
return Configuration::gateway()->dispute()->removeEvidence($disputeId, $evidenceId);
}
/**
* Search for Disputes, given a DisputeSearch query
*
* @param DisputeSearch $query
*/
public static function search($query)
{
return Configuration::gateway()->dispute()->search($query);
}
}
class_alias('Braintree\Dispute', 'Braintree_Dispute');

View File

@@ -0,0 +1,31 @@
<?php
namespace Braintree\Dispute;
use Braintree\Instance;
/**
* Evidence details for a dispute
*
* @package Braintree
*
* @property-read string $category
* @property-read string $comment
* @property-read \DateTime $createdAt
* @property-read string $id
* @property-read \DateTime $sentToProcessorAt
* @property-read string $sequenceNumber
* @property-read string $tag
* @property-read string $url
*/
class EvidenceDetails extends Instance
{
public function __construct($attributes)
{
if (array_key_exists('category', $attributes)) {
$attributes['tag'] = $attributes['category'];
}
parent::__construct($attributes);
}
}
class_alias('Braintree\Dispute\EvidenceDetails', 'Braintree_Dispute_EvidenceDetails');

View File

@@ -0,0 +1,20 @@
<?php
namespace Braintree\Dispute;
use Braintree\Instance;
/**
* Status History for a dispute
*
* @package Braintree
*
* @property-read \DateTime $disbursementDate
* @property-read \DateTime $effectiveDate
* @property-read string $status
* @property-read \DateTime $timestamp
*/
class StatusHistoryDetails extends Instance
{
}
class_alias('Braintree\Dispute\StatusHistoryDetails', 'Braintree_Dispute_StatusHistoryDetails');

View File

@@ -0,0 +1,25 @@
<?php
namespace Braintree\Dispute;
use Braintree\Instance;
/**
* Transaction details for a dispute
*
* @package Braintree
*/
/**
* Creates an instance of DisbursementDetails as returned from a transaction
*
*
* @package Braintree
*
* @property-read string $amount
* @property-read string $id
*/
class TransactionDetails extends Instance
{
}
class_alias('Braintree\Dispute\TransactionDetails', 'Braintree_Dispute_TransactionDetails');

View File

@@ -0,0 +1,274 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Braintree DisputeGateway module
* PHP Version 5
* Creates and manages Braintree Disputes
*
* @package Braintree
*/
class DisputeGateway
{
/**
* @var Gateway
*/
private $_gateway;
/**
* @var Configuration
*/
private $_config;
/**
* @var Http
*/
private $_http;
/**
* @param Gateway $gateway
*/
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
/* public class methods */
/**
* Accepts a dispute, given a dispute ID
*
* @param string $id
*/
public function accept($id)
{
try {
if (trim($id) == "") {
throw new Exception\NotFound();
}
$path = $this->_config->merchantPath() . '/disputes/' . $id . '/accept';
$response = $this->_http->put($path);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
return new Result\Successful();
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('dispute with id "' . $id . '" not found');
}
}
/**
* Adds file evidence to a dispute, given a dispute ID and a document ID
*
* @param string $disputeId
* @param string $documentIdOrRequest
*/
public function addFileEvidence($disputeId, $documentIdOrRequest)
{
$request = is_array($documentIdOrRequest) ? $documentIdOrRequest : ['documentId' => $documentIdOrRequest];
if (trim($disputeId) == "") {
throw new Exception\NotFound('dispute with id "' . $disputeId . '" not found');
}
if (trim($request['documentId']) == "") {
throw new Exception\NotFound('document with id "' . $request['documentId'] . '" not found');
}
try {
if (array_key_exists('category', $request)) {
if (trim($request['category']) == "") {
throw new InvalidArgumentException('category cannot be blank');
}
}
$request['document_upload_id'] = $request['documentId'];
unset($request['documentId']);
$path = $this->_config->merchantPath() . '/disputes/' . $disputeId . '/evidence';
$response = $this->_http->post($path, ['evidence' => $request]);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
if (isset($response['evidence'])) {
$evidence = new Dispute\EvidenceDetails($response['evidence']);
return new Result\Successful($evidence);
}
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('dispute with id "' . $disputeId . '" not found');
}
}
/**
* Adds text evidence to a dispute, given a dispute ID and content
*
* @param string $id
* @param string $content
*/
public function addTextEvidence($id, $contentOrRequest)
{
$request = is_array($contentOrRequest) ? $contentOrRequest : ['content' => $contentOrRequest];
if (trim($request['content']) == "") {
throw new InvalidArgumentException('content cannot be blank');
}
try {
$evidence = [
'comments' => $request['content'],
];
if (trim($id) == "") {
throw new Exception\NotFound();
}
if (array_key_exists('tag', $request)) {
$evidence['category'] = $request['tag'];
}
if (array_key_exists('category', $request)) {
if (trim($request['category']) == "") {
throw new InvalidArgumentException('category cannot be blank');
}
$evidence['category'] = $request['category'];
}
if (array_key_exists('sequenceNumber', $request)) {
if (trim($request['sequenceNumber']) == "") {
throw new InvalidArgumentException('sequenceNumber cannot be blank');
} else if ((string)(int)($request['sequenceNumber']) != $request['sequenceNumber']) {
throw new InvalidArgumentException('sequenceNumber must be an integer');
}
$evidence['sequenceNumber'] = (int)$request['sequenceNumber'];
}
$path = $this->_config->merchantPath() . '/disputes/' . $id . '/evidence';
$response = $this->_http->post($path, [
'evidence' => $evidence
]);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
if (isset($response['evidence'])) {
$evidence = new Dispute\EvidenceDetails($response['evidence']);
return new Result\Successful($evidence);
}
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('dispute with id "' . $id . '" not found');
}
}
/**
* Finalize a dispute, given a dispute ID
*
* @param string $id
*/
public function finalize($id)
{
try {
if (trim($id) == "") {
throw new Exception\NotFound();
}
$path = $this->_config->merchantPath() . '/disputes/' . $id . '/finalize';
$response = $this->_http->put($path);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
return new Result\Successful();
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('dispute with id "' . $id . '" not found');
}
}
/**
* Find a dispute, given a dispute ID
*
* @param string $id
*/
public function find($id)
{
if (trim($id) == "") {
throw new Exception\NotFound('dispute with id "' . $id . '" not found');
}
try {
$path = $this->_config->merchantPath() . '/disputes/' . $id;
$response = $this->_http->get($path);
return Dispute::factory($response['dispute']);
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('dispute with id "' . $id . '" not found');
}
}
/**
* Remove evidence from a dispute, given a dispute ID and evidence ID
*
* @param string $disputeId
* @param string $evidenceId
*/
public function removeEvidence($disputeId, $evidenceId)
{
try {
if (trim($disputeId) == "" || trim($evidenceId) == "") {
throw new Exception\NotFound();
}
$path = $this->_config->merchantPath() . '/disputes/' . $disputeId . '/evidence/' . $evidenceId;
$response = $this->_http->delete($path);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
return new Result\Successful();
} catch (Exception\NotFound $e) {
throw new Exception\NotFound('evidence with id "' . $evidenceId . '" for dispute with id "' . $disputeId . '" not found');
}
}
/**
* Search for Disputes, given a DisputeSearch query
*
* @param DisputeSearch $query
*/
public function search($query)
{
$criteria = [];
foreach ($query as $term) {
$criteria[$term->name] = $term->toparam();
}
$pager = [
'object' => $this,
'method' => 'fetchDisputes',
'query' => $criteria
];
return new PaginatedCollection($pager);
}
public function fetchDisputes($query, $page)
{
$response = $this->_http->post($this->_config->merchantPath() . '/disputes/advanced_search?page=' . $page, [
'search' => $query
]);
$body = $response['disputes'];
$disputes = Util::extractattributeasarray($body, 'dispute');
$totalItems = $body['totalItems'][0];
$pageSize = $body['pageSize'][0];
return new PaginatedResult($totalItems, $pageSize, $disputes);
}
}
class_alias('Braintree\DisputeGateway', 'Braintree_DisputeGateway');

View File

@@ -0,0 +1,90 @@
<?php
namespace Braintree;
class DisputeSearch
{
public static function amountDisputed() {
return new RangeNode("amount_disputed");
}
public static function amountWon()
{
return new RangeNode("amount_won");
}
public static function caseNumber()
{
return new TextNode("case_number");
}
public static function id()
{
return new TextNode("id");
}
public static function customerId()
{
return new TextNode("customer_id");
}
public static function kind()
{
return new MultipleValueNode("kind");
}
public static function merchantAccountId()
{
return new MultipleValueNode("merchant_account_id");
}
public static function reason()
{
return new MultipleValueNode("reason");
}
public static function reasonCode()
{
return new MultipleValueNode("reason_code");
}
public static function receivedDate()
{
return new RangeNode("received_date");
}
public static function disbursementDate()
{
return new RangeNode("disbursement_date");
}
public static function effectiveDate()
{
return new RangeNode("effective_date");
}
public static function referenceNumber()
{
return new TextNode("reference_number");
}
public static function replyByDate()
{
return new RangeNode("reply_by_date");
}
public static function status()
{
return new MultipleValueNode("status");
}
public static function transactionId()
{
return new TextNode("transaction_id");
}
public static function transactionSource()
{
return new MultipleValueNode("transaction_source");
}
}
class_alias('Braintree\DisputeSearch', 'Braintree_DisputeSearch');

View File

@@ -0,0 +1,52 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Upload documents to Braintree in exchange for a DocumentUpload object.
*
* An example of creating a document upload with all available fields:
* $result = Braintree\DocumentUpload::create([
* "kind" => Braintree\DocumentUpload::EVIDENCE_DOCUMENT,
* "file" => $pngFile
* ]);
*
* For more information on DocumentUploads, see https://developers.braintreepayments.com/reference/request/document_upload/create
*
* @property-read string $contentType
* @property-read \DateTime $expiresAt
* @property-read string $id
* @property-read string $kind
* @property-read string $name
* @property-read int $size
*/
class DocumentUpload extends Base
{
/* DocumentUpload Kind */
const EVIDENCE_DOCUMENT = "evidence_document";
protected function _initialize($documentUploadAttribs)
{
$this->_attributes = $documentUploadAttribs;
}
/**
* Creates a DocumentUpload object
* @param kind The kind of document
* @param file The open file to upload
* @throws InvalidArgumentException if the params are not expected
*/
public static function create($params)
{
return Configuration::gateway()->documentUpload()->create($params);
}
public static function factory($attributes)
{
$instance = new self();
$instance->_initialize($attributes);
return $instance;
}
}
class_alias('Braintree\DocumentUpload', 'Braintree_DocumentUpload');

View File

@@ -0,0 +1,81 @@
<?php
namespace Braintree;
use InvalidArgumentException;
/**
* Braintree DisputeGateway module
* PHP Version 5
* Creates and manages Braintree Disputes
*
* @package Braintree
*/
class DocumentUploadGateway
{
/**
* @var Gateway
*/
private $_gateway;
/**
* @var Configuration
*/
private $_config;
/**
* @var Http
*/
private $_http;
/**
* @param Gateway $gateway
*/
public function __construct($gateway)
{
$this->_gateway = $gateway;
$this->_config = $gateway->config;
$this->_config->assertHasAccessTokenOrKeys();
$this->_http = new Http($gateway->config);
}
/* public class methods */
/**
* Accepts a dispute, given a dispute ID
*
* @param string $id
*/
public function create($params)
{
Util::verifyKeys(self::createSignature(), $params);
$file = $params['file'];
if (!is_resource($file)) {
throw new InvalidArgumentException('file must be a stream resource');
}
$payload = [
'document_upload[kind]' => $params['kind']
];
$path = $this->_config->merchantPath() . '/document_uploads/';
$response = $this->_http->postMultipart($path, $payload, $file);
if (isset($response['apiErrorResponse'])) {
return new Result\Error($response['apiErrorResponse']);
}
if (isset($response['documentUpload'])) {
$documentUpload = DocumentUpload::factory($response['documentUpload']);
return new Result\Successful($documentUpload);
}
}
public static function createSignature()
{
return [
'file', 'kind'
];
}
}
class_alias('Braintree\DocumentUploadGateway', 'Braintree_DocumentUploadGateway');

View File

@@ -0,0 +1,23 @@
<?php
namespace Braintree;
class EndsWithNode
{
public function __construct($name)
{
$this->name = $name;
$this->searchTerms = [];
}
public function endsWith($value)
{
$this->searchTerms["ends_with"] = strval($value);
return $this;
}
public function toParam()
{
return $this->searchTerms;
}
}
class_alias('Braintree\EndsWithNode', 'Braintree_EndsWithNode');

View File

@@ -0,0 +1,12 @@
<?php
namespace Braintree;
class EqualityNode extends IsNode
{
function isNot($value)
{
$this->searchTerms['is_not'] = strval($value);
return $this;
}
}
class_alias('Braintree\EqualityNode', 'Braintree_EqualityNode');

View File

@@ -0,0 +1,671 @@
<?php
namespace Braintree\Error;
/**
*
* Validation Error codes and messages
*
* ErrorCodes class provides constants for validation errors.
* The constants should be used to check for a specific validation
* error in a ValidationErrorCollection.
* The error messages returned from the server may change;
* but the codes will remain the same.
*
* @package Braintree
* @subpackage Errors
* @category Validation
*/
class Codes
{
const ADDRESS_CANNOT_BE_BLANK = '81801';
const ADDRESS_COMPANY_IS_INVALID = '91821';
const ADDRESS_COMPANY_IS_TOO_LONG = '81802';
const ADDRESS_COUNTRY_CODE_ALPHA2_IS_NOT_ACCEPTED = '91814';
const ADDRESS_COUNTRY_CODE_ALPHA3_IS_NOT_ACCEPTED = '91816';
const ADDRESS_COUNTRY_CODE_NUMERIC_IS_NOT_ACCEPTED = '91817';
const ADDRESS_COUNTRY_NAME_IS_NOT_ACCEPTED = '91803';
const ADDRESS_EXTENDED_ADDRESS_IS_INVALID = '91823';
const ADDRESS_EXTENDED_ADDRESS_IS_TOO_LONG = '81804';
const ADDRESS_FIRST_NAME_IS_INVALID = '91819';
const ADDRESS_FIRST_NAME_IS_TOO_LONG = '81805';
const ADDRESS_INCONSISTENT_COUNTRY = '91815';
const ADDRESS_LAST_NAME_IS_INVALID = '91820';
const ADDRESS_LAST_NAME_IS_TOO_LONG = '81806';
const ADDRESS_LOCALITY_IS_INVALID = '91824';
const ADDRESS_LOCALITY_IS_TOO_LONG = '81807';
const ADDRESS_POSTAL_CODE_INVALID_CHARACTERS = '81813';
const ADDRESS_POSTAL_CODE_IS_INVALID = '91826';
const ADDRESS_POSTAL_CODE_IS_REQUIRED = '81808';
const ADDRESS_POSTAL_CODE_IS_TOO_LONG = '81809';
const ADDRESS_REGION_IS_INVALID = '91825';
const ADDRESS_REGION_IS_TOO_LONG = '81810';
const ADDRESS_STATE_IS_INVALID_FOR_SELLER_PROTECTION = '81827';
const ADDRESS_STREET_ADDRESS_IS_INVALID = '91822';
const ADDRESS_STREET_ADDRESS_IS_REQUIRED = '81811';
const ADDRESS_STREET_ADDRESS_IS_TOO_LONG = '81812';
const ADDRESS_TOO_MANY_ADDRESSES_PER_CUSTOMER = '91818';
const APPLE_PAY_CARDS_ARE_NOT_ACCEPTED = '83501';
const APPLE_PAY_CUSTOMER_ID_IS_REQUIRED_FOR_VAULTING = '83502';
const APPLE_PAY_TOKEN_IS_IN_USE = '93503';
const APPLE_PAY_PAYMENT_METHOD_NONCE_CONSUMED = '93504';
const APPLE_PAY_PAYMENT_METHOD_NONCE_UNKNOWN = '93505';
const APPLE_PAY_PAYMENT_METHOD_NONCE_UNLOCKED = '93506';
const APPLE_PAY_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '83518';
const APPLE_PAY_CANNOT_UPDATE_APPLE_PAY_CARD_USING_PAYMENT_METHOD_NONCE = '93507';
const APPLE_PAY_NUMBER_IS_REQUIRED = '93508';
const APPLE_PAY_EXPIRATION_MONTH_IS_REQUIRED = '93509';
const APPLE_PAY_EXPIRATION_YEAR_IS_REQUIRED = '93510';
const APPLE_PAY_CRYPTOGRAM_IS_REQUIRED = '93511';
const APPLE_PAY_DECRYPTION_FAILED = '83512';
const APPLE_PAY_DISABLED = '93513';
const APPLE_PAY_MERCHANT_NOT_CONFIGURED = '93514';
const APPLE_PAY_MERCHANT_KEYS_ALREADY_CONFIGURED = '93515';
const APPLE_PAY_MERCHANT_KEYS_NOT_CONFIGURED = '93516';
const APPLE_PAY_CERTIFICATE_INVALID = '93517';
const APPLE_PAY_CERTIFICATE_MISMATCH = '93519';
const APPLE_PAY_INVALID_TOKEN = '83520';
const APPLE_PAY_PRIVATE_KEY_MISMATCH = '93521';
const APPLE_PAY_KEY_MISMATCH_STORING_CERTIFICATE = '93522';
const AUTHORIZATION_FINGERPRINT_INVALID_CREATED_AT = '93204';
const AUTHORIZATION_FINGERPRINT_INVALID_FORMAT = '93202';
const AUTHORIZATION_FINGERPRINT_INVALID_PUBLIC_KEY = '93205';
const AUTHORIZATION_FINGERPRINT_INVALID_SIGNATURE = '93206';
const AUTHORIZATION_FINGERPRINT_MISSING_FINGERPRINT = '93201';
const AUTHORIZATION_FINGERPRINT_OPTIONS_NOT_ALLOWED_WITHOUT_CUSTOMER = '93207';
const AUTHORIZATION_FINGERPRINT_SIGNATURE_REVOKED = '93203';
const CLIENT_TOKEN_CUSTOMER_DOES_NOT_EXIST = '92804';
const CLIENT_TOKEN_FAIL_ON_DUPLICATE_PAYMENT_METHOD_REQUIRES_CUSTOMER_ID = '92803';
const CLIENT_TOKEN_MAKE_DEFAULT_REQUIRES_CUSTOMER_ID = '92801';
const CLIENT_TOKEN_PROXY_MERCHANT_DOES_NOT_EXIST = '92805';
const CLIENT_TOKEN_UNSUPPORTED_VERSION = '92806';
const CLIENT_TOKEN_VERIFY_CARD_REQUIRES_CUSTOMER_ID = '92802';
const CLIENT_TOKEN_MERCHANT_ACCOUNT_DOES_NOT_EXIST = '92807';
const CREDIT_CARD_BILLING_ADDRESS_CONFLICT = '91701';
const CREDIT_CARD_BILLING_ADDRESS_FORMAT_IS_INVALID = '91744';
const CREDIT_CARD_BILLING_ADDRESS_ID_IS_INVALID = '91702';
const CREDIT_CARD_CANNOT_UPDATE_CARD_USING_PAYMENT_METHOD_NONCE = '91735';
const CREDIT_CARD_CARDHOLDER_NAME_IS_TOO_LONG = '81723';
const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED = '81703';
const CREDIT_CARD_CREDIT_CARD_TYPE_IS_NOT_ACCEPTED_BY_SUBSCRIPTION_MERCHANT_ACCOUNT = '81718';
const CREDIT_CARD_CUSTOMER_ID_IS_INVALID = '91705';
const CREDIT_CARD_CUSTOMER_ID_IS_REQUIRED = '91704';
const CREDIT_CARD_CVV_IS_INVALID = '81707';
const CREDIT_CARD_CVV_IS_REQUIRED = '81706';
const CREDIT_CARD_CVV_VERIFICATION_FAILED = '81736';
const CREDIT_CARD_DUPLICATE_CARD_EXISTS = '81724';
const CREDIT_CARD_EXPIRATION_DATE_CONFLICT = '91708';
const CREDIT_CARD_EXPIRATION_DATE_IS_INVALID = '81710';
const CREDIT_CARD_EXPIRATION_DATE_IS_REQUIRED = '81709';
const CREDIT_CARD_EXPIRATION_DATE_YEAR_IS_INVALID = '81711';
const CREDIT_CARD_EXPIRATION_MONTH_IS_INVALID = '81712';
const CREDIT_CARD_EXPIRATION_YEAR_IS_INVALID = '81713';
const CREDIT_CARD_INVALID_PARAMS_FOR_CREDIT_CARD_UPDATE = '91745';
const CREDIT_CARD_INVALID_VENMO_SDK_PAYMENT_METHOD_CODE = '91727';
const CREDIT_CARD_NUMBER_INVALID_LENGTH = '81716';
const CREDIT_CARD_NUMBER_IS_INVALID = '81715';
const CREDIT_CARD_NUMBER_IS_PROHIBITED = '81750';
const CREDIT_CARD_NUMBER_IS_REQUIRED = '81714';
const CREDIT_CARD_NUMBER_LENGTH_IS_INVALID = '81716';
const CREDIT_CARD_NUMBER_MUST_BE_TEST_NUMBER = '81717';
const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_IS_INVALID = '91723';
const CREDIT_CARD_OPTIONS_UPDATE_EXISTING_TOKEN_NOT_ALLOWED = '91729';
const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_CANNOT_BE_NEGATIVE = '91739';
const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_FORMAT_IS_INVALID = '91740';
const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_IS_TOO_LARGE = '91752';
const CREDIT_CARD_OPTIONS_VERIFICATION_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '91741';
const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91728';
const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_FORBIDDEN = '91743';
const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_IS_SUSPENDED = '91742';
const CREDIT_CARD_OPTIONS_VERIFICATION_MERCHANT_ACCOUNT_CANNOT_BE_SUB_MERCHANT_ACCOUNT = '91755';
const CREDIT_CARD_PAYMENT_METHOD_CONFLICT = '81725';
const CREDIT_CARD_PAYMENT_METHOD_IS_NOT_A_CREDIT_CARD = '91738';
const CREDIT_CARD_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '91734';
const CREDIT_CARD_PAYMENT_METHOD_NONCE_CONSUMED = '91731';
const CREDIT_CARD_PAYMENT_METHOD_NONCE_LOCKED = '91733';
const CREDIT_CARD_PAYMENT_METHOD_NONCE_UNKNOWN = '91732';
const CREDIT_CARD_POSTAL_CODE_VERIFICATION_FAILED = '81737';
const CREDIT_CARD_TOKEN_FORMAT_IS_INVALID = '91718';
const CREDIT_CARD_TOKEN_INVALID = '91718';
const CREDIT_CARD_TOKEN_IS_IN_USE = '91719';
const CREDIT_CARD_TOKEN_IS_NOT_ALLOWED = '91721';
const CREDIT_CARD_TOKEN_IS_REQUIRED = '91722';
const CREDIT_CARD_TOKEN_IS_TOO_LONG = '91720';
const CREDIT_CARD_VENMO_SDK_PAYMENT_METHOD_CODE_CARD_TYPE_IS_NOT_ACCEPTED = '91726';
const CREDIT_CARD_VERIFICATION_NOT_SUPPORTED_ON_THIS_MERCHANT_ACCOUNT = '91730';
const CUSTOMER_COMPANY_IS_TOO_LONG = '81601';
const CUSTOMER_CUSTOM_FIELD_IS_INVALID = '91602';
const CUSTOMER_CUSTOM_FIELD_IS_TOO_LONG = '81603';
const CUSTOMER_EMAIL_FORMAT_IS_INVALID = '81604';
const CUSTOMER_EMAIL_IS_INVALID = '81604';
const CUSTOMER_EMAIL_IS_REQUIRED = '81606';
const CUSTOMER_EMAIL_IS_TOO_LONG = '81605';
const CUSTOMER_FAX_IS_TOO_LONG = '81607';
const CUSTOMER_FIRST_NAME_IS_TOO_LONG = '81608';
const CUSTOMER_ID_IS_INVAILD = '91610'; //Deprecated
const CUSTOMER_ID_IS_INVALID = '91610';
const CUSTOMER_ID_IS_IN_USE = '91609';
const CUSTOMER_ID_IS_NOT_ALLOWED = '91611';
const CUSTOMER_ID_IS_REQUIRED = '91613';
const CUSTOMER_ID_IS_TOO_LONG = '91612';
const CUSTOMER_LAST_NAME_IS_TOO_LONG = '81613';
const CUSTOMER_PHONE_IS_TOO_LONG = '81614';
const CUSTOMER_VAULTED_PAYMENT_INSTRUMENT_NONCE_BELONGS_TO_DIFFERENT_CUSTOMER = '91617';
const CUSTOMER_WEBSITE_FORMAT_IS_INVALID = '81616';
const CUSTOMER_WEBSITE_IS_INVALID = '81616';
const CUSTOMER_WEBSITE_IS_TOO_LONG = '81615';
const DESCRIPTOR_NAME_FORMAT_IS_INVALID = '92201';
const DESCRIPTOR_PHONE_FORMAT_IS_INVALID = '92202';
const DESCRIPTOR_INTERNATIONAL_NAME_FORMAT_IS_INVALID = '92204';
const DESCRIPTOR_DYNAMIC_DESCRIPTORS_DISABLED = '92203';
const DESCRIPTOR_INTERNATIONAL_PHONE_FORMAT_IS_INVALID = '92205';
const DESCRIPTOR_URL_FORMAT_IS_INVALID = '92206';
const DISPUTE_CAN_ONLY_ADD_EVIDENCE_TO_OPEN_DISPUTE = '95701';
const DISPUTE_CAN_ONLY_REMOVE_EVIDENCE_FROM_OPEN_DISPUTE = '95702';
const DISPUTE_CAN_ONLY_ADD_EVIDENCE_TO_DISPUTE = '95703';
const DISPUTE_CAN_ONLY_ACCEPT_OPEN_DISPUTE = '95704';
const DISPUTE_CAN_ONLY_FINALIZE_OPEN_DISPUTE = '95705';
const DISPUTE_CAN_ONLY_CREATE_EVIDENCE_WITH_VALID_CATEGORY = '95706';
const DISPUTE_EVIDENCE_CONTENT_DATE_INVALID = '95707';
const DISPUTE_EVIDENCE_CONTENT_TOO_LONG = '95708';
const DISPUTE_EVIDENCE_CONTENT_ARN_TOO_LONG = '95709';
const DISPUTE_EVIDENCE_CONTENT_PHONE_TOO_LONG = '95710';
const DISPUTE_EVIDENCE_CATEGORY_TEXT_ONLY = '95711';
const DISPUTE_EVIDENCE_CATEGORY_DOCUMENT_ONLY = '95712';
const DISPUTE_EVIDENCE_CATEGORY_NOT_FOR_REASON_CODE = '95713';
const DISPUTE_EVIDENCE_CATEGORY_DUPLICATE = '95713';
const DISPUTE_EVIDENCE_CATEGORY_EMAIL_INVALID = '95713';
const DISPUTE_DIGITAL_GOODS_MISSING_EVIDENCE = '95720';
const DISPUTE_DIGITAL_GOODS_MISSING_DOWNLOAD_DATE = '95721';
const DISPUTE_PRIOR_NON_DISPUTED_TRANSACTION_MISSING_ARN = '95722';
const DISPUTE_PRIOR_NON_DISPUTED_TRANSACTION_MISSING_DATE = '95723';
const DISPUTE_RECURRING_TRANSACTION_MISSING_DATE = '95724';
const DISPUTE_RECURRING_TRANSACTION_MISSING_ARN = '95725';
const DISPUTE_VALID_EVIDENCE_REQUIRED_TO_FINALIZE = '95726';
const DOCUMENT_UPLOAD_KIND_IS_INVALID = '84901';
const DOCUMENT_UPLOAD_FILE_IS_TOO_LARGE = '84902';
const DOCUMENT_UPLOAD_FILE_TYPE_IS_INVALID = '84903';
const DOCUMENT_UPLOAD_FILE_IS_MALFORMED_OR_ENCRYPTED = '84904';
const DOCUMENT_UPLOAD_FILE_IS_TOO_LONG = '84905';
const FAILED_AUTH_ADJUSTMENT_ALLOW_RETRY = '95603';
const FAILED_AUTH_ADJUSTMENT_HARD_DECLINE = '95602';
const FINAL_AUTH_SUBMIT_FOR_SETTLEMENT_FOR_DIFFERENT_AMOUNT = '95601';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_ARRIVAL_AIRPORT_CODE_IS_TOO_LONG = '96301';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_ARRIVAL_TIME_FORMAT_IS_INVALID = '96302';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_CARRIER_CODE_IS_TOO_LONG = '96303';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_CONJUNCTION_TICKET_IS_TOO_LONG = '96304';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_COUPON_NUMBER_IS_TOO_LONG = '96305';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_DEPARTURE_AIRPORT_CODE_IS_TOO_LONG = '96306';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_DEPARTURE_TIME_FORMAT_IS_INVALID = '96307';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_EXCHANGE_TICKET_IS_TOO_LONG = '96308';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_CANNOT_BE_NEGATIVE = '96309';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_FORMAT_IS_INVALID = '96310';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_AMOUNT_IS_TOO_LARGE = '96311';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FARE_BASIS_CODE_IS_TOO_LONG = '96312';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_CANNOT_BE_NEGATIVE = '96313';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_FORMAT_IS_INVALID = '96314';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_FEE_AMOUNT_IS_TOO_LARGE = '96315';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_SERVICE_CLASS_IS_TOO_LONG = '96316';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '96317';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_FORMAT_IS_INVALID = '96318';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TAX_AMOUNT_IS_TOO_LARGE = '96319';
const INDUSTRY_DATA_LEG_TRAVEL_FLIGHT_TICKET_NUMBER_IS_TOO_LONG = '96320';
const INDUSTRY_DATA_INDUSTRY_TYPE_IS_INVALID = '93401';
const INDUSTRY_DATA_LODGING_EMPTY_DATA = '93402';
const INDUSTRY_DATA_LODGING_FOLIO_NUMBER_IS_INVALID = '93403';
const INDUSTRY_DATA_LODGING_CHECK_IN_DATE_IS_INVALID = '93404';
const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_IS_INVALID = '93405';
const INDUSTRY_DATA_LODGING_CHECK_OUT_DATE_MUST_FOLLOW_CHECK_IN_DATE = '93406';
const INDUSTRY_DATA_LODGING_UNKNOWN_DATA_FIELD = '93407';
const INDUSTRY_DATA_TRAVEL_CRUISE_EMPTY_DATA = '93408';
const INDUSTRY_DATA_TRAVEL_CRUISE_UNKNOWN_DATA_FIELD = '93409';
const INDUSTRY_DATA_TRAVEL_CRUISE_TRAVEL_PACKAGE_IS_INVALID = '93410';
const INDUSTRY_DATA_TRAVEL_CRUISE_DEPARTURE_DATE_IS_INVALID = '93411';
const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_IN_DATE_IS_INVALID = '93412';
const INDUSTRY_DATA_TRAVEL_CRUISE_LODGING_CHECK_OUT_DATE_IS_INVALID = '93413';
const INDUSTRY_DATA_TRAVEL_FLIGHT_EMPTY_DATA = '93414';
const INDUSTRY_DATA_TRAVEL_FLIGHT_UNKNOWN_DATA_FIELD = '93415';
const INDUSTRY_DATA_TRAVEL_FLIGHT_CUSTOMER_CODE_IS_TOO_LONG = '93416';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_CANNOT_BE_NEGATIVE = '93417';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_FORMAT_IS_INVALID = '93418';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FARE_AMOUNT_IS_TOO_LARGE = '93419';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_CANNOT_BE_NEGATIVE = '93420';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_FORMAT_IS_INVALID = '93421';
const INDUSTRY_DATA_TRAVEL_FLIGHT_FEE_AMOUNT_IS_TOO_LARGE = '93422';
const INDUSTRY_DATA_TRAVEL_FLIGHT_ISSUED_DATE_FORMAT_IS_INVALID = '93423';
const INDUSTRY_DATA_TRAVEL_FLIGHT_ISSUING_CARRIER_CODE_IS_TOO_LONG = '93424';
const INDUSTRY_DATA_TRAVEL_FLIGHT_PASSENGER_MIDDLE_INITIAL_IS_TOO_LONG = '93425';
const INDUSTRY_DATA_TRAVEL_FLIGHT_RESTRICTED_TICKET_IS_REQUIRED = '93426';
const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '93427';
const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_FORMAT_IS_INVALID = '93428';
const INDUSTRY_DATA_TRAVEL_FLIGHT_TAX_AMOUNT_IS_TOO_LARGE = '93429';
const INDUSTRY_DATA_TRAVEL_FLIGHT_TICKET_NUMBER_IS_TOO_LONG = '93430';
const INDUSTRY_DATA_TRAVEL_FLIGHT_LEGS_EXPECTED = '93431';
const INDUSTRY_DATA_TRAVEL_FLIGHT_TOO_MANY_LEGS = '93432';
const TRANSACTION_LINE_ITEM_COMMODITY_CODE_IS_TOO_LONG = '95801';
const TRANSACTION_LINE_ITEM_DESCRIPTION_IS_TOO_LONG = '95803';
const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_FORMAT_IS_INVALID = '95804';
const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_IS_TOO_LARGE = '95805';
const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95806'; // Deprecated as the amount may be zero. Use TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_CANNOT_BE_ZERO.
const TRANSACTION_LINE_ITEM_DISCOUNT_AMOUNT_CANNOT_BE_NEGATIVE = '95806';
const TRANSACTION_LINE_ITEM_KIND_IS_INVALID = '95807';
const TRANSACTION_LINE_ITEM_KIND_IS_REQUIRED = '95808';
const TRANSACTION_LINE_ITEM_NAME_IS_REQUIRED = '95822';
const TRANSACTION_LINE_ITEM_NAME_IS_TOO_LONG = '95823';
const TRANSACTION_LINE_ITEM_PRODUCT_CODE_IS_TOO_LONG = '95809';
const TRANSACTION_LINE_ITEM_QUANTITY_FORMAT_IS_INVALID = '95810';
const TRANSACTION_LINE_ITEM_QUANTITY_IS_REQUIRED = '95811';
const TRANSACTION_LINE_ITEM_QUANTITY_IS_TOO_LARGE = '95812';
const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_FORMAT_IS_INVALID = '95813';
const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_IS_REQUIRED = '95814';
const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_IS_TOO_LARGE = '95815';
const TRANSACTION_LINE_ITEM_TOTAL_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95816';
const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_FORMAT_IS_INVALID = '95817';
const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_IS_REQUIRED = '95818';
const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_IS_TOO_LARGE = '95819';
const TRANSACTION_LINE_ITEM_UNIT_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95820';
const TRANSACTION_LINE_ITEM_UNIT_OF_MEASURE_IS_TOO_LONG = '95821';
const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_FORMAT_IS_INVALID = '95824';
const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_IS_TOO_LARGE = '95825';
const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '95826'; // Deprecated as the amount may be zero. Use TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_CANNOT_BE_ZERO.
const TRANSACTION_LINE_ITEM_UNIT_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '95826';
const TRANSACTION_LINE_ITEM_TAX_AMOUNT_FORMAT_IS_INVALID = '95827';
const TRANSACTION_LINE_ITEM_TAX_AMOUNT_IS_TOO_LARGE = '95828';
const TRANSACTION_LINE_ITEM_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '95829';
const TRANSACTION_EXTERNAL_VAULT_STATUS_IS_INVALID = '915175';
const TRANSACTION_EXTERNAL_VAULT_STATUS_WITH_PREVIOUS_NETWORK_TRANSACTION_ID_IS_INVALID = '915177';
const TRANSACTION_EXTERNAL_VAULT_CARD_TYPE_IS_INVALID = '915178';
const TRANSACTION_EXTERNAL_VAULT_PREVIOUS_NETWORK_TRANSACTION_ID_IS_INVALID = '915179';
const MERCHANT_COUNTRY_CANNOT_BE_BLANK = '83603';
const MERCHANT_COUNTRY_CODE_ALPHA2_IS_INVALID = '93607';
const MERCHANT_COUNTRY_CODE_ALPHA2_IS_NOT_ACCEPTED = '93606';
const MERCHANT_COUNTRY_CODE_ALPHA3_IS_INVALID = '93605';
const MERCHANT_COUNTRY_CODE_ALPHA3_IS_NOT_ACCEPTED = '93604';
const MERCHANT_COUNTRY_CODE_NUMERIC_IS_INVALID = '93609';
const MERCHANT_COUNTRY_CODE_NUMERIC_IS_NOT_ACCEPTED = '93608';
const MERCHANT_COUNTRY_NAME_IS_INVALID = '93611';
const MERCHANT_COUNTRY_NAME_IS_NOT_ACCEPTED = '93610';
const MERCHANT_CURRENCIES_ARE_INVALID = '93614';
const MERCHANT_EMAIL_FORMAT_IS_INVALID = '93602';
const MERCHANT_EMAIL_IS_REQUIRED = '83601';
const MERCHANT_INCONSISTENT_COUNTRY = '93612';
const MERCHANT_ACCOUNT_PAYMENT_METHODS_ARE_INVALID = '93613';
const MERCHANT_PAYMENT_METHODS_ARE_NOT_ALLOWED = '93615';
const MERCHANT_MERCHANT_ACCOUNT_EXISTS_FOR_CURRENCY = '93616';
const MERCHANT_CURRENCY_IS_REQUIRED = '93617';
const MERCHANT_CURRENCY_IS_INVALID = '93618';
const MERCHANT_NO_MERCHANT_ACCOUNTS = '93619';
const MERCHANT_MERCHANT_ACCOUNT_EXISTS_FOR_ID = '93620';
const MERCHANT_MERCHANT_ACCOUNT_NOT_AUTH_ONBOARDED = '93621';
const MERCHANT_ACCOUNT_ID_FORMAT_IS_INVALID = '82603';
const MERCHANT_ACCOUNT_ID_IS_IN_USE = '82604';
const MERCHANT_ACCOUNT_ID_IS_NOT_ALLOWED = '82605';
const MERCHANT_ACCOUNT_ID_IS_TOO_LONG = '82602';
const MERCHANT_ACCOUNT_MASTER_MERCHANT_ACCOUNT_ID_IS_INVALID = '82607';
const MERCHANT_ACCOUNT_MASTER_MERCHANT_ACCOUNT_ID_IS_REQUIRED = '82606';
const MERCHANT_ACCOUNT_MASTER_MERCHANT_ACCOUNT_MUST_BE_ACTIVE = '82608';
const MERCHANT_ACCOUNT_TOS_ACCEPTED_IS_REQUIRED = '82610';
const MERCHANT_ACCOUNT_CANNOT_BE_UPDATED = '82674';
const MERCHANT_ACCOUNT_DECLINED = '82626';
const MERCHANT_ACCOUNT_DECLINED_MASTER_CARD_MATCH = '82622';
const MERCHANT_ACCOUNT_DECLINED_OFAC = '82621';
const MERCHANT_ACCOUNT_DECLINED_FAILED_KYC = '82623';
const MERCHANT_ACCOUNT_DECLINED_SSN_INVALID = '82624';
const MERCHANT_ACCOUNT_DECLINED_SSN_MATCHES_DECEASED = '82625';
const MERCHANT_ACCOUNT_ID_CANNOT_BE_UPDATED = '82675';
const MERCHANT_ACCOUNT_MASTER_MERCHANT_ACCOUNT_ID_CANNOT_BE_UPDATED = '82676';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ACCOUNT_NUMBER_IS_REQUIRED = '82614';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_COMPANY_NAME_IS_INVALID = '82631';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_COMPANY_NAME_IS_REQUIRED_WITH_TAX_ID = '82633';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DATE_OF_BIRTH_IS_REQUIRED = '82612';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED = '82626'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED_MASTER_CARD_MATCH = '82622'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED_OFAC = '82621'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED_FAILED_KYC = '82623'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED_SSN_INVALID = '82624'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DECLINED_SSN_MATCHES_DECEASED = '82625'; // Keep for backwards compatibility
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_EMAIL_ADDRESS_IS_INVALID = '82616';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_FIRST_NAME_IS_INVALID = '82627';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_FIRST_NAME_IS_REQUIRED = '82609';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_LAST_NAME_IS_INVALID = '82628';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_LAST_NAME_IS_REQUIRED = '82611';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_PHONE_IS_INVALID = '82636';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ROUTING_NUMBER_IS_INVALID = '82635';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ROUTING_NUMBER_IS_REQUIRED = '82613';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_SSN_IS_INVALID = '82615';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_TAX_ID_IS_INVALID = '82632';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_TAX_ID_IS_REQUIRED_WITH_COMPANY_NAME = '82634';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_DATE_OF_BIRTH_IS_INVALID = '82663';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_REGION_IS_INVALID = '82664';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_EMAIL_ADDRESS_IS_REQUIRED = '82665';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ACCOUNT_NUMBER_IS_INVALID = '82670';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_TAX_ID_MUST_BE_BLANK = '82673';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_LOCALITY_IS_REQUIRED = '82618';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_POSTAL_CODE_IS_INVALID = '82630';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_POSTAL_CODE_IS_REQUIRED = '82619';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_REGION_IS_REQUIRED = '82620';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_STREET_ADDRESS_IS_INVALID = '82629';
const MERCHANT_ACCOUNT_APPLICANT_DETAILS_ADDRESS_STREET_ADDRESS_IS_REQUIRED = '82617';
const MERCHANT_ACCOUNT_BUSINESS_DBA_NAME_IS_INVALID = '82646';
const MERCHANT_ACCOUNT_BUSINESS_TAX_ID_IS_INVALID = '82647';
const MERCHANT_ACCOUNT_BUSINESS_TAX_ID_IS_REQUIRED_WITH_LEGAL_NAME = '82648';
const MERCHANT_ACCOUNT_BUSINESS_LEGAL_NAME_IS_REQUIRED_WITH_TAX_ID = '82669';
const MERCHANT_ACCOUNT_BUSINESS_TAX_ID_MUST_BE_BLANK = '82672';
const MERCHANT_ACCOUNT_BUSINESS_LEGAL_NAME_IS_INVALID = '82677';
const MERCHANT_ACCOUNT_BUSINESS_ADDRESS_REGION_IS_INVALID = '82684';
const MERCHANT_ACCOUNT_BUSINESS_ADDRESS_STREET_ADDRESS_IS_INVALID = '82685';
const MERCHANT_ACCOUNT_BUSINESS_ADDRESS_POSTAL_CODE_IS_INVALID = '82686';
const MERCHANT_ACCOUNT_INDIVIDUAL_FIRST_NAME_IS_REQUIRED = '82637';
const MERCHANT_ACCOUNT_INDIVIDUAL_LAST_NAME_IS_REQUIRED = '82638';
const MERCHANT_ACCOUNT_INDIVIDUAL_DATE_OF_BIRTH_IS_REQUIRED = '82639';
const MERCHANT_ACCOUNT_INDIVIDUAL_SSN_IS_INVALID = '82642';
const MERCHANT_ACCOUNT_INDIVIDUAL_EMAIL_IS_INVALID = '82643';
const MERCHANT_ACCOUNT_INDIVIDUAL_FIRST_NAME_IS_INVALID = '82644';
const MERCHANT_ACCOUNT_INDIVIDUAL_LAST_NAME_IS_INVALID = '82645';
const MERCHANT_ACCOUNT_INDIVIDUAL_PHONE_IS_INVALID = '82656';
const MERCHANT_ACCOUNT_INDIVIDUAL_DATE_OF_BIRTH_IS_INVALID = '82666';
const MERCHANT_ACCOUNT_INDIVIDUAL_EMAIL_IS_REQUIRED = '82667';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_STREET_ADDRESS_IS_REQUIRED = '82657';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_LOCALITY_IS_REQUIRED = '82658';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_POSTAL_CODE_IS_REQUIRED = '82659';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_REGION_IS_REQUIRED = '82660';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_STREET_ADDRESS_IS_INVALID = '82661';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_POSTAL_CODE_IS_INVALID = '82662';
const MERCHANT_ACCOUNT_INDIVIDUAL_ADDRESS_REGION_IS_INVALID = '82668';
const MERCHANT_ACCOUNT_FUNDING_ROUTING_NUMBER_IS_REQUIRED = '82640';
const MERCHANT_ACCOUNT_FUNDING_ACCOUNT_NUMBER_IS_REQUIRED = '82641';
const MERCHANT_ACCOUNT_FUNDING_ROUTING_NUMBER_IS_INVALID = '82649';
const MERCHANT_ACCOUNT_FUNDING_ACCOUNT_NUMBER_IS_INVALID = '82671';
const MERCHANT_ACCOUNT_FUNDING_DESTINATION_IS_REQUIRED = '82678';
const MERCHANT_ACCOUNT_FUNDING_DESTINATION_IS_INVALID = '82679';
const MERCHANT_ACCOUNT_FUNDING_EMAIL_IS_REQUIRED = '82680';
const MERCHANT_ACCOUNT_FUNDING_EMAIL_IS_INVALID = '82681';
const MERCHANT_ACCOUNT_FUNDING_MOBILE_PHONE_IS_REQUIRED = '82682';
const MERCHANT_ACCOUNT_FUNDING_MOBILE_PHONE_IS_INVALID = '82683';
const OAUTH_INVALID_GRANT = '93801';
const OAUTH_INVALID_CREDENTIALS = '93802';
const OAUTH_INVALID_SCOPE = '93803';
const OAUTH_INVALID_REQUEST = '93804';
const OAUTH_UNSUPPORTED_GRANT_TYPE = '93805';
const PAYMENT_METHOD_CANNOT_FORWARD_PAYMENT_METHOD_TYPE = '93106';
const PAYMENT_METHOD_CUSTOMER_ID_IS_INVALID = '93105';
const PAYMENT_METHOD_CUSTOMER_ID_IS_REQUIRED = '93104';
const PAYMENT_METHOD_NONCE_IS_INVALID = '93102';
const PAYMENT_METHOD_NONCE_IS_REQUIRED = '93103';
const PAYMENT_METHOD_PAYMENT_METHOD_NONCE_CONSUMED = '93107';
const PAYMENT_METHOD_PAYMENT_METHOD_NONCE_UNKNOWN = '93108';
const PAYMENT_METHOD_PAYMENT_METHOD_NONCE_LOCKED = '93109';
const PAYMENT_METHOD_PAYMENT_METHOD_PARAMS_ARE_REQUIRED = '93101';
const PAYMENT_METHOD_NO_LONGER_SUPPORTED = '93117';
const PAYMENT_METHOD_OPTIONS_US_BANK_ACCOUNT_VERIFICATION_METHOD_IS_INVALID = '93121';
const PAYPAL_ACCOUNT_AUTH_EXPIRED = '92911';
const PAYPAL_ACCOUNT_CANNOT_HAVE_BOTH_ACCESS_TOKEN_AND_CONSENT_CODE = '82903';
const PAYPAL_ACCOUNT_CANNOT_HAVE_FUNDING_SOURCE_WITHOUT_ACCESS_TOKEN = '92912';
const PAYPAL_ACCOUNT_CANNOT_UPDATE_PAYPAL_ACCOUNT_USING_PAYMENT_METHOD_NONCE = '92914';
const PAYPAL_ACCOUNT_CANNOT_VAULT_ONE_TIME_USE_PAYPAL_ACCOUNT = '82902';
const PAYPAL_ACCOUNT_CONSENT_CODE_OR_ACCESS_TOKEN_IS_REQUIRED = '82901';
const PAYPAL_ACCOUNT_CUSTOMER_ID_IS_REQUIRED_FOR_VAULTING = '82905';
const PAYPAL_ACCOUNT_INVALID_FUNDING_SOURCE_SELECTION = '92913';
const PAYPAL_ACCOUNT_INVALID_PARAMS_FOR_PAYPAL_ACCOUNT_UPDATE = '92915';
const PAYPAL_ACCOUNT_PAYMENT_METHOD_NONCE_CONSUMED = '92907';
const PAYPAL_ACCOUNT_PAYMENT_METHOD_NONCE_LOCKED = '92909';
const PAYPAL_ACCOUNT_PAYMENT_METHOD_NONCE_UNKNOWN = '92908';
const PAYPAL_ACCOUNT_PAYPAL_ACCOUNTS_ARE_NOT_ACCEPTED = '82904';
const PAYPAL_ACCOUNT_PAYPAL_COMMUNICATION_ERROR = '92910';
const PAYPAL_ACCOUNT_TOKEN_IS_IN_USE = '92906';
const SEPA_BANK_ACCOUNT_ACCOUNT_HOLDER_NAME_IS_REQUIRED = '93003';
const SEPA_BANK_ACCOUNT_BIC_IS_REQUIRED = '93002';
const SEPA_BANK_ACCOUNT_IBAN_IS_REQUIRED = '93001';
const SEPA_MANDATE_ACCOUNT_HOLDER_NAME_IS_REQUIRED = '83301';
const SEPA_MANDATE_BIC_INVALID_CHARACTER = '83306';
const SEPA_MANDATE_BIC_IS_REQUIRED = '83302';
const SEPA_MANDATE_BIC_LENGTH_IS_INVALID = '83307';
const SEPA_MANDATE_BIC_UNSUPPORTED_COUNTRY = '83308';
const SEPA_MANDATE_BILLING_ADDRESS_CONFLICT = '93311';
const SEPA_MANDATE_BILLING_ADDRESS_ID_IS_INVALID = '93312';
const SEPA_MANDATE_IBAN_INVALID_CHARACTER = '83305';
const SEPA_MANDATE_IBAN_INVALID_FORMAT = '83310';
const SEPA_MANDATE_IBAN_IS_REQUIRED = '83303';
const SEPA_MANDATE_IBAN_UNSUPPORTED_COUNTRY = '83309';
const SEPA_MANDATE_TYPE_IS_REQUIRED = '93304';
const SEPA_MANDATE_TYPE_IS_INVALID = '93313';
const SETTLEMENT_BATCH_SUMMARY_SETTLEMENT_DATE_IS_INVALID = '82302';
const SETTLEMENT_BATCH_SUMMARY_SETTLEMENT_DATE_IS_REQUIRED = '82301';
const SETTLEMENT_BATCH_SUMMARY_CUSTOM_FIELD_IS_INVALID = '82303';
const SUBSCRIPTION_BILLING_DAY_OF_MONTH_CANNOT_BE_UPDATED = '91918';
const SUBSCRIPTION_BILLING_DAY_OF_MONTH_IS_INVALID = '91914';
const SUBSCRIPTION_BILLING_DAY_OF_MONTH_MUST_BE_NUMERIC = '91913';
const SUBSCRIPTION_CANNOT_ADD_DUPLICATE_ADDON_OR_DISCOUNT = '91911';
const SUBSCRIPTION_CANNOT_EDIT_CANCELED_SUBSCRIPTION = '81901';
const SUBSCRIPTION_CANNOT_EDIT_EXPIRED_SUBSCRIPTION = '81910';
const SUBSCRIPTION_CANNOT_EDIT_PRICE_CHANGING_FIELDS_ON_PAST_DUE_SUBSCRIPTION = '91920';
const SUBSCRIPTION_FIRST_BILLING_DATE_CANNOT_BE_IN_THE_PAST = '91916';
const SUBSCRIPTION_FIRST_BILLING_DATE_CANNOT_BE_UPDATED = '91919';
const SUBSCRIPTION_FIRST_BILLING_DATE_IS_INVALID = '91915';
const SUBSCRIPTION_ID_IS_IN_USE = '81902';
const SUBSCRIPTION_INCONSISTENT_NUMBER_OF_BILLING_CYCLES = '91908';
const SUBSCRIPTION_INCONSISTENT_START_DATE = '91917';
const SUBSCRIPTION_INVALID_REQUEST_FORMAT = '91921';
const SUBSCRIPTION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91901';
const SUBSCRIPTION_MISMATCH_CURRENCY_ISO_CODE = '91923';
const SUBSCRIPTION_NUMBER_OF_BILLING_CYCLES_CANNOT_BE_BLANK = '91912';
const SUBSCRIPTION_NUMBER_OF_BILLING_CYCLES_IS_TOO_SMALL = '91909';
const SUBSCRIPTION_NUMBER_OF_BILLING_CYCLES_MUST_BE_GREATER_THAN_ZERO = '91907';
const SUBSCRIPTION_NUMBER_OF_BILLING_CYCLES_MUST_BE_NUMERIC = '91906';
const SUBSCRIPTION_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '91924';
const SUBSCRIPTION_PAYMENT_METHOD_NONCE_IS_INVALID = '91925';
const SUBSCRIPTION_PAYMENT_METHOD_NONCE_NOT_ASSOCIATED_WITH_CUSTOMER = '91926';
const SUBSCRIPTION_PAYMENT_METHOD_NONCE_UNVAULTED_CARD_IS_NOT_ACCEPTED = '91927';
const SUBSCRIPTION_PAYMENT_METHOD_TOKEN_CARD_TYPE_IS_NOT_ACCEPTED = '91902';
const SUBSCRIPTION_PAYMENT_METHOD_TOKEN_IS_INVALID = '91903';
const SUBSCRIPTION_PAYMENT_METHOD_TOKEN_NOT_ASSOCIATED_WITH_CUSTOMER = '91905';
const SUBSCRIPTION_PLAN_BILLING_FREQUENCY_CANNOT_BE_UPDATED = '91922';
const SUBSCRIPTION_PLAN_ID_IS_INVALID = '91904';
const SUBSCRIPTION_PRICE_CANNOT_BE_BLANK = '81903';
const SUBSCRIPTION_PRICE_FORMAT_IS_INVALID = '81904';
const SUBSCRIPTION_PRICE_IS_TOO_LARGE = '81923';
const SUBSCRIPTION_STATUS_IS_CANCELED = '81905';
const SUBSCRIPTION_TOKEN_FORMAT_IS_INVALID = '81906';
const SUBSCRIPTION_TRIAL_DURATION_FORMAT_IS_INVALID = '81907';
const SUBSCRIPTION_TRIAL_DURATION_IS_REQUIRED = '81908';
const SUBSCRIPTION_TRIAL_DURATION_UNIT_IS_INVALID = '81909';
const SUBSCRIPTION_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_INSTRUMENT_TYPE = '91930';
const SUBSCRIPTION_PAYMENT_METHOD_NONCE_INSTRUMENT_TYPE_DOES_NOT_SUPPORT_SUBSCRIPTIONS = '91929';
const SUBSCRIPTION_PAYMENT_METHOD_TOKEN_INSTRUMENT_TYPE_DOES_NOT_SUPPORT_SUBSCRIPTIONS = '91928';
const SUBSCRIPTION_MODIFICATION_AMOUNT_CANNOT_BE_BLANK = '92003';
const SUBSCRIPTION_MODIFICATION_AMOUNT_IS_INVALID = '92002';
const SUBSCRIPTION_MODIFICATION_AMOUNT_IS_TOO_LARGE = '92023';
const SUBSCRIPTION_MODIFICATION_CANNOT_EDIT_MODIFICATIONS_ON_PAST_DUE_SUBSCRIPTION = '92022';
const SUBSCRIPTION_MODIFICATION_CANNOT_UPDATE_AND_REMOVE = '92015';
const SUBSCRIPTION_MODIFICATION_EXISTING_ID_IS_INCORRECT_KIND = '92020';
const SUBSCRIPTION_MODIFICATION_EXISTING_ID_IS_INVALID = '92011';
const SUBSCRIPTION_MODIFICATION_EXISTING_ID_IS_REQUIRED = '92012';
const SUBSCRIPTION_MODIFICATION_ID_TO_REMOVE_IS_INCORRECT_KIND = '92021';
const SUBSCRIPTION_MODIFICATION_ID_TO_REMOVE_IS_INVALID = '92025';
const SUBSCRIPTION_MODIFICATION_ID_TO_REMOVE_IS_NOT_PRESENT = '92016';
const SUBSCRIPTION_MODIFICATION_INCONSISTENT_NUMBER_OF_BILLING_CYCLES = '92018';
const SUBSCRIPTION_MODIFICATION_INHERITED_FROM_ID_IS_INVALID = '92013';
const SUBSCRIPTION_MODIFICATION_INHERITED_FROM_ID_IS_REQUIRED = '92014';
const SUBSCRIPTION_MODIFICATION_MISSING = '92024';
const SUBSCRIPTION_MODIFICATION_NUMBER_OF_BILLING_CYCLES_CANNOT_BE_BLANK = '92017';
const SUBSCRIPTION_MODIFICATION_NUMBER_OF_BILLING_CYCLES_IS_INVALID = '92005';
const SUBSCRIPTION_MODIFICATION_NUMBER_OF_BILLING_CYCLES_MUST_BE_GREATER_THAN_ZERO = '92019';
const SUBSCRIPTION_MODIFICATION_QUANTITY_CANNOT_BE_BLANK = '92004';
const SUBSCRIPTION_MODIFICATION_QUANTITY_IS_INVALID = '92001';
const SUBSCRIPTION_MODIFICATION_QUANTITY_MUST_BE_GREATER_THAN_ZERO = '92010';
const TRANSACTION_AMOUNT_CANNOT_BE_NEGATIVE = '81501';
const TRANSACTION_AMOUNT_DOES_NOT_MATCH3_D_SECURE_AMOUNT = '91585';
const TRANSACTION_AMOUNT_DOES_NOT_MATCH_IDEAL_PAYMENT_AMOUNT = '915144';
const TRANSACTION_AMOUNT_FORMAT_IS_INVALID = '81503';
const TRANSACTION_AMOUNT_IS_INVALID = '81503';
const TRANSACTION_AMOUNT_IS_REQUIRED = '81502';
const TRANSACTION_AMOUNT_IS_TOO_LARGE = '81528';
const TRANSACTION_AMOUNT_MUST_BE_GREATER_THAN_ZERO = '81531';
const TRANSACTION_BILLING_ADDRESS_CONFLICT = '91530';
const TRANSACTION_CANNOT_BE_VOIDED = '91504';
const TRANSACTION_CANNOT_CANCEL_RELEASE = '91562';
const TRANSACTION_CANNOT_CLONE_CREDIT = '91543';
const TRANSACTION_CANNOT_CLONE_MARKETPLACE_TRANSACTION = '915137';
const TRANSACTION_CANNOT_CLONE_TRANSACTION_WITH_PAYPAL_ACCOUNT = '91573';
const TRANSACTION_CANNOT_CLONE_TRANSACTION_WITH_VAULT_CREDIT_CARD = '91540';
const TRANSACTION_CANNOT_CLONE_UNSUCCESSFUL_TRANSACTION = '91542';
const TRANSACTION_CANNOT_CLONE_VOICE_AUTHORIZATIONS = '91541';
const TRANSACTION_CANNOT_HOLD_IN_ESCROW = '91560';
const TRANSACTION_CANNOT_PARTIALLY_REFUND_ESCROWED_TRANSACTION = '91563';
const TRANSACTION_CANNOT_REFUND_CREDIT = '91505';
const TRANSACTION_CANNOT_REFUND_SETTLING_TRANSACTION = '91574';
const TRANSACTION_CANNOT_REFUND_UNLESS_SETTLED = '91506';
const TRANSACTION_CANNOT_REFUND_WITH_PENDING_MERCHANT_ACCOUNT = '91559';
const TRANSACTION_CANNOT_REFUND_WITH_SUSPENDED_MERCHANT_ACCOUNT = '91538';
const TRANSACTION_CANNOT_RELEASE_FROM_ESCROW = '91561';
const TRANSACTION_CANNOT_SIMULATE_SETTLEMENT = '91575';
const TRANSACTION_CANNOT_SUBMIT_FOR_PARTIAL_SETTLEMENT = '915103';
const TRANSACTION_CANNOT_SUBMIT_FOR_SETTLEMENT = '91507';
const TRANSACTION_CANNOT_UPDATE_DETAILS_NOT_SUBMITTED_FOR_SETTLEMENT = '915129';
const TRANSACTION_CHANNEL_IS_TOO_LONG = '91550';
const TRANSACTION_CREDIT_CARD_IS_REQUIRED = '91508';
const TRANSACTION_CUSTOMER_DEFAULT_PAYMENT_METHOD_CARD_TYPE_IS_NOT_ACCEPTED = '81509';
const TRANSACTION_CUSTOMER_DOES_NOT_HAVE_CREDIT_CARD = '91511';
const TRANSACTION_CUSTOMER_ID_IS_INVALID = '91510';
const TRANSACTION_CUSTOM_FIELD_IS_INVALID = '91526';
const TRANSACTION_CUSTOM_FIELD_IS_TOO_LONG = '81527';
const TRANSACTION_PAYMENT_INSTRUMENT_WITH_EXTERNAL_VAULT_IS_INVALID = '915176';
const TRANSACTION_HAS_ALREADY_BEEN_REFUNDED = '91512';
const TRANSACTION_IDEAL_PAYMENT_NOT_COMPLETE = '815141';
const TRANSACTION_IDEAL_PAYMENTS_CANNOT_BE_VAULTED = '915150';
const TRANSACTION_LINE_ITEMS_EXPECTED = '915158';
const TRANSACTION_TOO_MANY_LINE_ITEMS = '915157';
const TRANSACTION_DISCOUNT_AMOUNT_FORMAT_IS_INVALID = '915159';
const TRANSACTION_DISCOUNT_AMOUNT_CANNOT_BE_NEGATIVE = '915160';
const TRANSACTION_DISCOUNT_AMOUNT_IS_TOO_LARGE = '915161';
const TRANSACTION_SHIPPING_AMOUNT_FORMAT_IS_INVALID = '915162';
const TRANSACTION_SHIPPING_AMOUNT_CANNOT_BE_NEGATIVE = '915163';
const TRANSACTION_SHIPPING_AMOUNT_IS_TOO_LARGE = '915164';
const TRANSACTION_SHIPS_FROM_POSTAL_CODE_IS_TOO_LONG = '915165';
const TRANSACTION_SHIPS_FROM_POSTAL_CODE_IS_INVALID = '915166';
const TRANSACTION_SHIPS_FROM_POSTAL_CODE_INVALID_CHARACTERS = '915167';
const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_MATCH3_D_SECURE_MERCHANT_ACCOUNT = '91584';
const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_MATCH_IDEAL_PAYMENT_MERCHANT_ACCOUNT = '915143';
const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_MOTO = '91558';
const TRANSACTION_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_REFUNDS = '91547';
const TRANSACTION_MERCHANT_ACCOUNT_ID_DOES_NOT_MATCH_SUBSCRIPTION = '915180';
const TRANSACTION_MERCHANT_ACCOUNT_ID_IS_INVALID = '91513';
const TRANSACTION_MERCHANT_ACCOUNT_IS_SUSPENDED = '91514';
const TRANSACTION_MERCHANT_ACCOUNT_NAME_IS_INVALID = '91513'; //Deprecated
const TRANSACTION_OPTIONS_PAY_PAL_CUSTOM_FIELD_TOO_LONG = '91580';
const TRANSACTION_OPTIONS_SUBMIT_FOR_SETTLEMENT_IS_REQUIRED_FOR_CLONING = '91544';
const TRANSACTION_OPTIONS_SUBMIT_FOR_SETTLEMENT_IS_REQUIRED_FOR_PAYPAL_UNILATERAL = '91582';
const TRANSACTION_OPTIONS_USE_BILLING_FOR_SHIPPING_DISABLED = '91572';
const TRANSACTION_OPTIONS_VAULT_IS_DISABLED = '91525';
const TRANSACTION_ORDER_ID_DOES_NOT_MATCH_IDEAL_PAYMENT_ORDER_ID = '91503';
const TRANSACTION_ORDER_ID_IS_REQUIRED_WITH_IDEAL_PAYMENT = '91502';
const TRANSACTION_ORDER_ID_IS_TOO_LONG = '91501';
const TRANSACTION_PAYMENT_INSTRUMENT_NOT_SUPPORTED_BY_MERCHANT_ACCOUNT = '91577';
const TRANSACTION_PAYMENT_INSTRUMENT_TYPE_IS_NOT_ACCEPTED = '915101';
const TRANSACTION_PAYMENT_METHOD_CONFLICT = '91515';
const TRANSACTION_PAYMENT_METHOD_CONFLICT_WITH_VENMO_SDK = '91549';
const TRANSACTION_PAYMENT_METHOD_DOES_NOT_BELONG_TO_CUSTOMER = '91516';
const TRANSACTION_PAYMENT_METHOD_DOES_NOT_BELONG_TO_SUBSCRIPTION = '91527';
const TRANSACTION_PAYMENT_METHOD_NONCE_CARD_TYPE_IS_NOT_ACCEPTED = '91567';
const TRANSACTION_PAYMENT_METHOD_NONCE_CONSUMED = '91564';
const TRANSACTION_PAYMENT_METHOD_NONCE_HAS_NO_VALID_PAYMENT_INSTRUMENT_TYPE = '91569';
const TRANSACTION_PAYMENT_METHOD_NONCE_LOCKED = '91566';
const TRANSACTION_PAYMENT_METHOD_NONCE_UNKNOWN = '91565';
const TRANSACTION_PAYMENT_METHOD_TOKEN_CARD_TYPE_IS_NOT_ACCEPTED = '91517';
const TRANSACTION_PAYMENT_METHOD_TOKEN_IS_INVALID = '91518';
const TRANSACTION_PAYPAL_NOT_ENABLED = '91576';
const TRANSACTION_PAY_PAL_AUTH_EXPIRED = '91579';
const TRANSACTION_PAY_PAL_VAULT_RECORD_MISSING_DATA = '91583';
const TRANSACTION_PROCESSOR_AUTHORIZATION_CODE_CANNOT_BE_SET = '91519';
const TRANSACTION_PROCESSOR_AUTHORIZATION_CODE_IS_INVALID = '81520';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_AUTHS = '915104';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_CREDITS = '91546';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_PARTIAL_SETTLEMENT = '915102';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_UPDATING_ORDER_ID = '915107';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_UPDATING_DESCRIPTOR = '915108';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_UPDATING_DETAILS = '915130';
const TRANSACTION_PROCESSOR_DOES_NOT_SUPPORT_VOICE_AUTHORIZATIONS = '91545';
const TRANSACTION_PURCHASE_ORDER_NUMBER_IS_INVALID = '91548';
const TRANSACTION_PURCHASE_ORDER_NUMBER_IS_TOO_LONG = '91537';
const TRANSACTION_REFUND_AMOUNT_IS_TOO_LARGE = '91521';
const TRANSACTION_SERVICE_FEE_AMOUNT_CANNOT_BE_NEGATIVE = '91554';
const TRANSACTION_SERVICE_FEE_AMOUNT_FORMAT_IS_INVALID = '91555';
const TRANSACTION_SERVICE_FEE_AMOUNT_IS_TOO_LARGE = '91556';
const TRANSACTION_SERVICE_FEE_AMOUNT_NOT_ALLOWED_ON_MASTER_MERCHANT_ACCOUNT = '91557';
const TRANSACTION_SERVICE_FEE_IS_NOT_ALLOWED_ON_CREDITS = '91552';
const TRANSACTION_SERVICE_FEE_NOT_ACCEPTED_FOR_PAYPAL = '91578';
const TRANSACTION_SETTLEMENT_AMOUNT_IS_LESS_THAN_SERVICE_FEE_AMOUNT = '91551';
const TRANSACTION_SETTLEMENT_AMOUNT_IS_TOO_LARGE = '91522';
const TRANSACTION_SHIPPING_ADDRESS_DOESNT_MATCH_CUSTOMER = '91581';
const TRANSACTION_SUBSCRIPTION_DOES_NOT_BELONG_TO_CUSTOMER = '91529';
const TRANSACTION_SUBSCRIPTION_ID_IS_INVALID = '91528';
const TRANSACTION_SUBSCRIPTION_STATUS_MUST_BE_PAST_DUE = '91531';
const TRANSACTION_SUB_MERCHANT_ACCOUNT_REQUIRES_SERVICE_FEE_AMOUNT = '91553';
const TRANSACTION_TAX_AMOUNT_CANNOT_BE_NEGATIVE = '81534';
const TRANSACTION_TAX_AMOUNT_FORMAT_IS_INVALID = '81535';
const TRANSACTION_TAX_AMOUNT_IS_TOO_LARGE = '81536';
const TRANSACTION_THREE_D_SECURE_AUTHENTICATION_FAILED = '81571';
const TRANSACTION_THREE_D_SECURE_TOKEN_IS_INVALID = '91568';
const TRANSACTION_THREE_D_SECURE_TRANSACTION_DATA_DOESNT_MATCH_VERIFY = '91570';
const TRANSACTION_THREE_D_SECURE_ECI_FLAG_IS_REQUIRED = '915113';
const TRANSACTION_THREE_D_SECURE_CAVV_IS_REQUIRED = '915116';
const TRANSACTION_THREE_D_SECURE_XID_IS_REQUIRED = '915115';
const TRANSACTION_THREE_D_SECURE_ECI_FLAG_IS_INVALID = '915114';
const TRANSACTION_THREE_D_SECURE_MERCHANT_ACCOUNT_DOES_NOT_SUPPORT_CARD_TYPE = '915131';
const TRANSACTION_TYPE_IS_INVALID = '91523';
const TRANSACTION_TYPE_IS_REQUIRED = '91524';
const TRANSACTION_UNSUPPORTED_VOICE_AUTHORIZATION = '91539';
const TRANSACTION_US_BANK_ACCOUNT_NONCE_MUST_BE_PLAID_VERIFIED = '915171';
const TRANSACTION_US_BANK_ACCOUNT_NOT_VERIFIED = '915172';
const TRANSACTION_TRANSACTION_SOURCE_IS_INVALID = '915133';
const US_BANK_ACCOUNT_VERIFICATION_NOT_CONFIRMABLE = '96101';
const US_BANK_ACCOUNT_VERIFICATION_MUST_BE_MICRO_TRANSFERS_VERIFICATION = '96102';
const US_BANK_ACCOUNT_VERIFICATION_AMOUNTS_DO_NOT_MATCH = '96103';
const US_BANK_ACCOUNT_VERIFICATION_TOO_MANY_CONFIRMATION_ATTEMPTS = '96104';
const US_BANK_ACCOUNT_VERIFICATION_UNABLE_TO_CONFIRM_DEPOSIT_AMOUNTS = '96105';
const US_BANK_ACCOUNT_VERIFICATION_INVALID_DEPOSIT_AMOUNTS = '96106';
const VERIFICATION_OPTIONS_AMOUNT_CANNOT_BE_NEGATIVE = '94201';
const VERIFICATION_OPTIONS_AMOUNT_FORMAT_IS_INVALID = '94202';
const VERIFICATION_OPTIONS_AMOUNT_IS_TOO_LARGE = '94207';
const VERIFICATION_OPTIONS_AMOUNT_NOT_SUPPORTED_BY_PROCESSOR = '94203';
const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_ID_IS_INVALID = '94204';
const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_SUSPENDED = '94205';
const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_IS_FORBIDDEN = '94206';
const VERIFICATION_OPTIONS_MERCHANT_ACCOUNT_CANNOT_BE_SUB_MERCHANT_ACCOUNT = '94208';
}
class_alias('Braintree\Error\Codes', 'Braintree_Error_Codes');

View File

@@ -0,0 +1,123 @@
<?php
namespace Braintree\Error;
use Braintree\Util;
/**
*
* Error handler
* Handles validation errors
*
* Contains a read-only property $error which is a ValidationErrorCollection
*
* @package Braintree
* @subpackage Errors
* @category Errors
*
* @property-read object $errors
*/
class ErrorCollection implements \Countable
{
private $_errors;
public function __construct($errorData)
{
$this->_errors =
new ValidationErrorCollection($errorData);
}
/**
* Return count of items in collection
* Implements countable
*
* @return integer
*/
public function count()
{
return $this->deepSize();
}
/**
* Returns all of the validation errors at all levels of nesting in a single, flat array.
*/
public function deepAll()
{
return $this->_errors->deepAll();
}
/**
* Returns the total number of validation errors at all levels of nesting. For example,
*if creating a customer with a credit card and a billing address, and each of the customer,
* credit card, and billing address has 1 error, this method will return 3.
*
* @return int size
*/
public function deepSize()
{
$size = $this->_errors->deepSize();
return $size;
}
/**
* return errors for the passed key name
*
* @param string $key
* @return mixed
*/
public function forKey($key)
{
return $this->_errors->forKey($key);
}
/**
* return errors for the passed html field.
* For example, $result->errors->onHtmlField("transaction[customer][last_name]")
*
* @param string $field
* @return array
*/
public function onHtmlField($field)
{
$pieces = preg_split("/[\[\]]+/", $field, 0, PREG_SPLIT_NO_EMPTY);
$errors = $this;
foreach(array_slice($pieces, 0, -1) as $key) {
$errors = $errors->forKey(Util::delimiterToCamelCase($key));
if (!isset($errors)) { return []; }
}
$finalKey = Util::delimiterToCamelCase(end($pieces));
return $errors->onAttribute($finalKey);
}
/**
* Returns the errors at the given nesting level (see forKey) in a single, flat array:
*
* <code>
* $result = Customer::create(...);
* $customerErrors = $result->errors->forKey('customer')->shallowAll();
* </code>
*/
public function shallowAll()
{
return $this->_errors->shallowAll();
}
/**
*
* @ignore
*/
public function __get($name)
{
$varName = "_$name";
return isset($this->$varName) ? $this->$varName : null;
}
/**
*
* @ignore
*/
public function __toString()
{
return sprintf('%s', $this->_errors);
}
}
class_alias('Braintree\Error\ErrorCollection', 'Braintree_Error_ErrorCollection');

View File

@@ -0,0 +1,60 @@
<?php
namespace Braintree\Error;
use Braintree\Util;
/**
* error object returned as part of a validation error collection
* provides read-only access to $attribute, $code, and $message
*
* <b>== More information ==</b>
*
* For more detailed information on Validation errors, see {@link https://developers.braintreepayments.com/reference/general/validation-errors/overview/php https://developers.braintreepayments.com/reference/general/validation-errors/overview/php}
*
* @package Braintree
* @subpackage Error
*
* @property-read string $attribute
* @property-read string $code
* @property-read string $message
*/
class Validation
{
private $_attribute;
private $_code;
private $_message;
/**
* @ignore
* @param array $attributes
*/
public function __construct($attributes)
{
$this->_initializeFromArray($attributes);
}
/**
* initializes instance properties from the keys/values of an array
* @ignore
* @access protected
* @param array $attributes array of properties to set - single level
* @return void
*/
private function _initializeFromArray($attributes)
{
foreach($attributes AS $name => $value) {
$varName = "_$name";
$this->$varName = Util::delimiterToCamelCase($value, '_');
}
}
/**
*
* @ignore
*/
public function __get($name)
{
$varName = "_$name";
return isset($this->$varName) ? $this->$varName : null;
}
}
class_alias('Braintree\Error\Validation', 'Braintree_Error_Validation');

View File

@@ -0,0 +1,131 @@
<?php
namespace Braintree\Error;
use Braintree\Collection;
/**
* collection of errors enumerating all validation errors for a given request
*
* <b>== More information ==</b>
*
* For more detailed information on Validation errors, see {@link https://developers.braintreepayments.com/reference/general/validation-errors/overview/php https://developers.braintreepayments.com/reference/general/validation-errors/overview/php}
*
* @package Braintree
* @subpackage Error
*
* @property-read array $errors
* @property-read array $nested
*/
class ValidationErrorCollection extends Collection
{
private $_errors = [];
private $_nested = [];
/**
* @ignore
*/
public function __construct($data)
{
foreach($data AS $key => $errorData)
// map errors to new collections recursively
if ($key == 'errors') {
foreach ($errorData AS $error) {
$this->_errors[] = new Validation($error);
}
} else {
$this->_nested[$key] = new ValidationErrorCollection($errorData);
}
}
public function deepAll()
{
$validationErrors = array_merge([], $this->_errors);
foreach($this->_nested as $nestedErrors)
{
$validationErrors = array_merge($validationErrors, $nestedErrors->deepAll());
}
return $validationErrors;
}
public function deepSize()
{
$total = sizeof($this->_errors);
foreach($this->_nested as $_nestedErrors)
{
$total = $total + $_nestedErrors->deepSize();
}
return $total;
}
public function forIndex($index)
{
return $this->forKey("index" . $index);
}
public function forKey($key)
{
return isset($this->_nested[$key]) ? $this->_nested[$key] : null;
}
public function onAttribute($attribute)
{
$matches = [];
foreach ($this->_errors AS $key => $error) {
if($error->attribute == $attribute) {
$matches[] = $error;
}
}
return $matches;
}
public function shallowAll()
{
return $this->_errors;
}
/**
*
* @ignore
*/
public function __get($name)
{
$varName = "_$name";
return isset($this->$varName) ? $this->$varName : null;
}
/**
* @ignore
*/
public function __toString()
{
$output = [];
// TODO: implement scope
if (!empty($this->_errors)) {
$output[] = $this->_inspect($this->_errors);
}
if (!empty($this->_nested)) {
foreach ($this->_nested AS $key => $values) {
$output[] = $this->_inspect($this->_nested);
}
}
return join(', ', $output);
}
/**
* @ignore
*/
private function _inspect($errors, $scope = null)
{
$eOutput = '[' . __CLASS__ . '/errors:[';
foreach($errors AS $error => $errorObj) {
$outputErrs[] = "({$errorObj->error['code']} {$errorObj->error['message']})";
}
$eOutput .= join(', ', $outputErrs) . ']]';
return $eOutput;
}
}
class_alias('Braintree\Error\ValidationErrorCollection', 'Braintree_Error_ValidationErrorCollection');

View File

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

View File

@@ -0,0 +1,67 @@
<?php
namespace Braintree;
/**
* Braintree EuropeBankAccount module
* Creates and manages Braintree Europe Bank Accounts
*
* <b>== More information ==</b>
*
* See {@link https://developers.braintreepayments.com/javascript+php}<br />
*
* @package Braintree
* @category Resources
*
* @property-read string $account-holder-name
* @property-read string $bic
* @property-read string $customerId
* @property-read string $default
* @property-read string $image-url
* @property-read string $mandate-reference-number
* @property-read string $masked-iban
* @property-read string $token
*/
class EuropeBankAccount extends Base
{
/* instance methods */
/**
* returns false if default is null or false
*
* @return boolean
*/
public function isDefault()
{
return $this->default;
}
/**
* factory method: returns an instance of EuropeBankAccount
* to the requesting method, with populated properties
*
* @ignore
* @return EuropeBankAccount
*/
public static function factory($attributes)
{
$defaultAttributes = [
];
$instance = new self();
$instance->_initialize(array_merge($defaultAttributes, $attributes));
return $instance;
}
/**
* sets instance properties from an array of values
*
* @access protected
* @param array $europeBankAccountAttribs array of EuropeBankAccount properties
* @return void
*/
protected function _initialize($europeBankAccountAttribs)
{
$this->_attributes = $europeBankAccountAttribs;
}
}
class_alias('Braintree\EuropeBankAccount', 'Braintree_EuropeBankAccount');

View File

@@ -0,0 +1,13 @@
<?php
namespace Braintree;
/**
* super class for all Braintree exceptions
*
* @package Braintree
* @subpackage Exception
*/
class Exception extends \Exception
{
}
class_alias('Braintree\Exception', 'Braintree_Exception');

View File

@@ -0,0 +1,17 @@
<?php
namespace Braintree\Exception;
use Braintree\Exception;
/**
* Raised when authentication fails.
* This may be caused by an incorrect Configuration
*
* @package Braintree
* @subpackage Exception
*/
class Authentication extends Exception
{
}
class_alias('Braintree\Exception\Authentication', 'Braintree_Exception_Authentication');

View File

@@ -0,0 +1,19 @@
<?php
namespace Braintree\Exception;
use Braintree\Exception;
/**
* Raised when authorization fails
* Raised when the API key being used is not authorized to perform
* the attempted action according to the roles assigned to the user
* who owns the API key.
*
* @package Braintree
* @subpackage Exception
*/
class Authorization extends Exception
{
}
class_alias('Braintree\Exception\Authorization', 'Braintree_Exception_Authorization');

View File

@@ -0,0 +1,17 @@
<?php
namespace Braintree\Exception;
use Braintree\Exception;
/**
* Raised when the Braintree library is not completely configured.
*
* @package Braintree
* @subpackage Exception
* @see Configuration
*/
class Configuration extends Exception
{
}
class_alias('Braintree\Exception\Configuration', 'Braintree_Exception_Configuration');

Some files were not shown because too many files have changed in this diff Show More