diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62e7820 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Created by .ignore support plugin (hsz.mobi) +.idea \ No newline at end of file diff --git a/README.md b/README.md index 991f90f..42ea532 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,28 @@ # Integrate Gravity Forms with Braintree -Braintree Payments is a payment gateway provider owned by eBAY Inc, which allows you to proces credit card payments without the need for a bank merchant account and full PCI-compliance. No sensitive data such as credit card numbers are stored on your server, Braintree takes care of everything. - -#### If you have found this plugin useful, consider taking a moment to rate it, or perhaps even a small donation. +Braintree Payments is a payment gateway provider owned by PayPal which allows you to process credit card payments without the need for a bank merchant account and full PCI-compliance. No sensitive data such as credit card numbers are stored on your server, Braintree takes care of everything. ## Installation -1. Upload the `gravity-forms-braintree` folder to the `/wp-content/plugins/` directory. -2. Activate the plugin through the 'Plugins' menu in WordPress. -3. Navigate to the Form you wish to setup with a Braintree feed. -4. Under Form Settings, choose the Braintree option. +### Automatic installation -## Features +Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't need to leave your web browser. To do an automatic install of Gravity Forms Braintree Payments, log in to your WordPress dashboard, navigate to the Plugins menu and click Add New. + +In the search field type Gravity Forms Braintree Payments and click Search Plugins. Once you've found our plugin (make sure it says "by Angell EYE") you can view details about it such as the the rating and description. Most importantly, of course, you can install it by simply clicking Install Now. + +### Manual Installation -* Seamlessly integrates your Gravity Forms credit card forms with Braintree Payments -* Supports both production and sandbox environments, enabling you to test payments before going live -* Form entries will only be created when payment is successful -* Quick and easy setup +1. Unzip the files and upload the folder into your plugins folder (/wp-content/plugins/) overwriting older versions if they exist +2. Activate the plugin in your WordPress admin area. -## Subscriptions -The plugin does not currently support Braintree Subscriptions. Keep a look out for it in a future version +### Usage + +1. Navigate to the Form you wish to setup with a Braintree feed. +2. Under Form Settings, choose the Braintree option. + +## Features -## Upgrade Notice -If you are updating from a version previous to 1.0, your existing feeds will not work. Please make sure you check all your feeds and ensure they function correctly. +* Seamlessly integrates your Gravity Forms credit card forms with Braintree Payments. +* Supports both production and sandbox environments, enabling you to test payments before going live. +* Form entries will only be created when payment is successful. +* Quick and easy setup. \ No newline at end of file diff --git a/angelleye-gravity-forms-braintree.php b/angelleye-gravity-forms-braintree.php new file mode 100644 index 0000000..544b6e3 --- /dev/null +++ b/angelleye-gravity-forms-braintree.php @@ -0,0 +1,138 @@ +setPHP('7.2'); + $checker->setRequiredClasses(['GFForms' => 'The Gravity Forms plugin is required in order to run Gravity Forms Braintree Payments.']); + $checker->setRequiredExtensions(['xmlwriter', 'openssl', 'dom', 'hash', 'curl']); + $checker->setRequiredPlugins(['gravityforms/gravityforms.php'=>['min_version'=>'2.4', 'install_link'=>'https://rocketgenius.pxf.io/c/1331556/445235/7938', 'name'=>'Gravity Forms']]); + //$checker->setDeactivatePlugins([self::$plugin_base_file]); + if($checker->check()===true) { + $this->init(); + } + } + + public function init() + { + $path = trailingslashit( dirname( __FILE__ ) ); + + // Ensure Gravity Forms (payment addon framework) is installed and good to go + if( is_callable( array( 'GFForms', 'include_payment_addon_framework' ) ) ) { + + // Bootstrap payment addon framework + GFForms::include_payment_addon_framework(); + GFForms::include_addon_framework(); + + // Require Braintree Payments core + if(!class_exists('Braintree')) { + require_once $path . 'lib/Braintree.php'; + } + + // Require plugin entry point + require_once $path . 'includes/angelleye-gravity-braintree-helper.php'; + require_once $path . 'lib/class.plugify-gform-braintree.php'; + require_once $path . 'includes/class-angelleye-gravity-braintree-ach-field.php'; + require_once $path . 'includes/class-angelleye-gravity-braintree-ach-toggle-field.php'; + require_once $path . 'lib/angelleye-gravity-forms-payment-logger.php'; + require_once $path . 'includes/angelleye-gravity-braintree-field-mapping.php'; + require_once $path . 'includes/class-angelleye-gravity-braintree-creditcard.php'; + + /** + * Required functions + */ + if (!function_exists('angelleye_queue_update')) { + require_once( 'includes/angelleye-functions.php' ); + } + + // Fire off entry point + new Plugify_GForm_Braintree(); + new AngelleyeGravityBraintreeFieldMapping(); + + /** + * Register the ACH form field and Payment Method toggle field + */ + GF_Fields::register( new Angelleye_Gravity_Braintree_ACH_Field() ); + GF_Fields::register( new Angelleye_Gravity_Braintree_ACH_Toggle_Field() ); + GF_Fields::register( new Angelleye_Gravity_Braintree_CreditCard_Field() ); + AngellEYE_GForm_Braintree_Payment_Logger::instance(); + + } + } + + public static function isBraintreeFeedActive() + { + global $wpdb; + $addon_feed_table_name = $wpdb->prefix . 'gf_addon_feed'; + $is_active = $wpdb->get_var("select is_active from ".$addon_feed_table_name." where addon_slug='gravity-forms-braintree' and is_active=1"); + + return $is_active=='1'; + } +} + +AngelleyeGravityFormsBraintree::getInstance(); \ No newline at end of file diff --git a/assets/css/gravity-forms-braintree-admin.css b/assets/css/gravity-forms-braintree-admin.css new file mode 100644 index 0000000..ff5e739 --- /dev/null +++ b/assets/css/gravity-forms-braintree-admin.css @@ -0,0 +1,88 @@ +.notice.notice-success.angelleye-notice { + padding: 0 !important; + margin: 5px 0 10px; + background: #FFF; + overflow: hidden; + -webkit-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); + position: relative; + z-index: 1; + min-height: 80px; + display: table; /* The magic ingredient! */ + font: 13px "Open Sans", sans-serif; +} +.angelleye-notice > div { + display: table-cell; + vertical-align: middle; + cursor: default; + line-height: 1.5; +} +.angelleye-notice-logo-push { + background-repeat: no-repeat; + background-position: 50% 50%; +} +.angelleye-notice-logo-push span { + display: block; + margin: 0px 10px 0px 10px; +} +.notice.notice-success.angelleye-notice { + font: 13px "Open Sans", sans-serif; + line-height: normal; +} +.angelleye-notice-message { + color: #23282D; + font-size: 13px; + font-weight: normal; + line-height: 20px; + padding: 3px 5px; + -webkit-font-smoothing: antialiased; + width: 100%; +} +.angelleye-notice-message h3 { + margin: 4px 0px !important; +} +.angelleye-notice-message-inner { + margin-bottom: 10px; +} +.angelleye-notice-message p { + margin: 3px 0px !important; + padding: 0px; +} +.angelleye-notice-action { + padding-top: 5px; +} +.angelleye-notice-cta { + border-left: 1px solid #E5E5E5; + background: #F8F8F8; + padding: 0 30px; + position: relative; + white-space: nowrap; +} +.wp-core-ui .angelleye-notice-cta button, +.wp-core-ui .angelleye-notice-cta .button-primary:active { + vertical-align: middle; +} +.angelleye-notice-dismiss { + background: transparent; + border: 0; + cursor: pointer; + color: #C5C5C5; + -webkit-font-smoothing: antialiased; +} +.updater-dismissible { + padding-right: 38px; + position: relative; +} + +.alert-notification-custom-fields:after,.alert-notification-custom-fields:before { + content:"";display:table;clear:both; +} +.alert-notification-custom-fields{ + padding:0.01em 16px;color:#000!important;background-color:#ffffcc!important;margin-top:16px; +} + +#gform_braintree_mapping table{ + width: 800px; +} \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css index 73216a6..0efd233 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -14,3 +14,77 @@ table.feeds th#id { body { display: none; } + +.notice.notice-success.angelleye-notice { + padding: 0 !important; + margin: 5px 0 10px; + background: #FFF; + overflow: hidden; + -webkit-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.05); + position: relative; + z-index: 1; + min-height: 80px; + display: table; /* The magic ingredient! */ + font: 13px "Open Sans", sans-serif; +} +.angelleye-notice > div { + display: table-cell; + vertical-align: middle; + cursor: default; + line-height: 1.5; +} +.angelleye-notice-logo-push { + background-repeat: no-repeat; + background-position: 50% 50%; +} +.angelleye-notice-logo-push span { + display: block; + margin: 0px 10px 0px 10px; +} +.notice.notice-success.angelleye-notice { + font: 13px "Open Sans", sans-serif; + line-height: normal; +} +.angelleye-notice-message { + color: #23282D; + font-size: 13px; + font-weight: normal; + line-height: 20px; + padding: 3px 5px; + -webkit-font-smoothing: antialiased; + width: 100%; +} +.angelleye-notice-message h3 { + margin: 4px 0px !important; +} +.angelleye-notice-message-inner { + margin-bottom: 10px; +} +.angelleye-notice-message p { + margin: 3px 0px !important; + padding: 0px; +} +.angelleye-notice-action { + padding-top: 5px; +} +.angelleye-notice-cta { + border-left: 1px solid #E5E5E5; + background: #F8F8F8; + padding: 0 30px; + position: relative; + white-space: nowrap; +} +.wp-core-ui .angelleye-notice-cta button, +.wp-core-ui .angelleye-notice-cta .button-primary:active { + vertical-align: middle; +} +.angelleye-notice-dismiss { + background: transparent; + border: 0; + cursor: pointer; + color: #C5C5C5; + -webkit-font-smoothing: antialiased; +} \ No newline at end of file diff --git a/assets/js/angelleye-braintree-ach-cc.js b/assets/js/angelleye-braintree-ach-cc.js new file mode 100644 index 0000000..29efbe3 --- /dev/null +++ b/assets/js/angelleye-braintree-ach-cc.js @@ -0,0 +1,271 @@ +jQuery(document).ready(function ($) { + if ($('.gform_payment_method_options').length) { + var payment_methods = {}; + $('.gform_payment_method_options input[type=radio]').each(function () { + var targetdiv = $(this).attr('targetdiv'); + var value = $(this).val(); + payment_methods[value] = targetdiv; + }); + + $('.gform_payment_method_options').on('click', 'input[type=radio]', function () { + var selectedradio = $(this).val(); + for (var i in payment_methods) + if (i !== selectedradio) + $(this).closest('form').find('#' + payment_methods[i]).slideUp() + + var targetdiv = $(this).attr('targetdiv'); + $(this).closest('form').find('#' + targetdiv).slideDown(); + }); + + var selectedradio = $('.gform_payment_method_options input[type=radio]:checked').val(); + + switch (selectedradio) { + case 'braintree_ach': + $('.gform_payment_method_options input[value=braintree_ach]').trigger('click'); + break; + default: + case 'creditcard': + $('.gform_payment_method_options input[value=creditcard]').trigger('click'); + break; + } + } + + + + $('.custom_ach_form_submit_btn').click(function (e) { + window[ 'gf_submitting_' + $("input[name='gform_submit']").val() ] = true; + $('#gform_ajax_spinner_' + $("input[name='gform_submit']").val()).remove(); + e.preventDefault(); + var curlabel = $(this).html(); + var form = $(this).closest('form'); + + var selectedradio = form.find('.gform_payment_method_options input[type=radio]:checked').val(); + + var check_if_ach_form = form.find('.ginput_ach_form_container'); + if (check_if_ach_form.length && (selectedradio === 'braintree_ach' || check_if_ach_form.closest('.gfield').css('display') !== 'none')) { + if (form.find('.ginput_container_address').length == 0) { + alert('ACH payment requires billing address fields, so please include Billing Address field in your Gravity form.'); + return; + } + + var account_number = form.find('.ginput_account_number').val(); + var account_number_verification = form.find('.ginput_account_number_verification').val(); + var account_type = form.find('.ginput_account_type').val(); + var routing_number = form.find('.ginput_routing_number').val(); + var account_holdername = form.find('.ginput_account_holdername').val(); + + var streetAddress = form.find('.ginput_container_address .address_line_1 input[type=text]').val(); + var extendedAddress = form.find('.ginput_container_address .address_line_2 input[type=text]').val(); + var locality = form.find('.ginput_container_address .address_city input[type=text]').val(); + var region = form.find('.ginput_container_address .address_state input[type=text], .ginput_container_address .address_state select').val(); + var postalCode = form.find('.ginput_container_address .address_zip input[type=text]').val(); + + if (region.length > 2) { + region = stateNameToAbbreviation(region); + } + + var address_validation_errors = []; + if (streetAddress == '') { + address_validation_errors.push('Please enter a street address.'); + } + + if (locality == '') { + address_validation_errors.push('Please enter your city.'); + } + + if (region == '') { + address_validation_errors.push('Please enter your state.'); + } + + if (postalCode == '') { + address_validation_errors.push('Please enter your postal code.'); + } + + if (address_validation_errors.length) { + alert(address_validation_errors.join('\n')); + return; + } + + var achform_validation_errors = []; + if (routing_number == '' || isNaN(routing_number) || account_number == '' || isNaN(account_number)) { + achform_validation_errors.push('Please enter a valid routing and account number.') + } + + if (account_type == '') { + achform_validation_errors.push('Please select your account type.') + } + + if (account_holdername == '') { + achform_validation_errors.push('Please enter the account holder name.'); + } else { + var account_holder_namebreak = account_holdername.split(' '); + if (account_type == 'S' && account_holder_namebreak.length < 2) { + achform_validation_errors.push('Please enter the account holder first and last name.'); + } + } + + if (account_number !== account_number_verification) { + achform_validation_errors.push('Account Number and Account Number Verification field should be same.'); + } + + if (achform_validation_errors.length) { + alert(achform_validation_errors.join('\n')); + return; + } + + var submitbtn = $(this); + // submitbtn.attr('disabled', true).html('Please wait...').css('opacity', '0.4'); + + braintree.client.create({ + authorization: angelleye_gravity_form_braintree_ach_handler_strings.ach_bt_token + }, function (clientErr, clientInstance) { + if (clientErr) { + alert('There was an error creating the Client, Please check your Braintree Settings.'); + console.error('clientErr', clientErr); + return; + } + + braintree.dataCollector.create({ + client: clientInstance, + paypal: true + }, function (err, dataCollectorInstance) { + if (err) { + alert('We are unable to validate your system, please try again.'); + resetButtonLoading(submitbtn, curlabel); + console.error('dataCollectorError', err); + return; + } + + var deviceData = dataCollectorInstance.deviceData; + + braintree.usBankAccount.create({ + client: clientInstance + }, function (usBankAccountErr, usBankAccountInstance) { + if (usBankAccountErr) { + alert('There was an error initiating the bank request. Please try again.'); + resetButtonLoading(submitbtn, curlabel); + console.error('usBankAccountErr', usBankAccountErr); + return; + } + + var bankDetails = { + accountNumber: account_number, //'1000000000', + routingNumber: routing_number, //'011000015', + accountType: account_type == 'S' ? 'savings' : 'checking', + ownershipType: account_type == 'S' ? 'personal' : 'business', + billingAddress: { + streetAddress: streetAddress, //'1111 Thistle Ave', + extendedAddress: extendedAddress, + locality: locality, //'Fountain Valley', + region: region, //'CA', + postalCode: postalCode //'92708' + } + }; + + if (bankDetails.ownershipType === 'personal') { + bankDetails.firstName = account_holder_namebreak[0]; + bankDetails.lastName = account_holder_namebreak[1]; + } else { + bankDetails.businessName = account_holdername; + } + + usBankAccountInstance.tokenize({ + bankDetails: bankDetails, + mandateText: 'By clicking ["Submit"], I authorize Braintree, a service of PayPal, on behalf of ' + angelleye_gravity_form_braintree_ach_handler_strings.ach_business_name + ' (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account.' + }, function (tokenizeErr, tokenizedPayload) { + if (tokenizeErr) { + var errormsg = tokenizeErr['details']['originalError']['details']['originalError'][0]['message']; + if (errormsg.indexOf("Variable 'zipCode' has an invalid value") != -1) + alert('Please enter valid postal code.'); + else if (errormsg.indexOf("Variable 'state' has an invalid value") != -1) + alert('Please enter valid state code. (e.g.: CA)'); + else + alert(errormsg); + + resetButtonLoading(submitbtn, curlabel); + console.error('tokenizeErr', tokenizeErr); + return; + } + + form.append(""); + form.append(''); + + + form.submit(); + }); + }); + }); + }); + + } else { + form.submit(); + } + }); +}); + +function stateNameToAbbreviation(name) { + let states = { + "Alabama": "AL", + "Alaska": "AK", + "Arizona": "AZ", + "Arkansas": "AR", + "California": "CA", + "Colorado": "CO", + "Connecticut": "CT", + "Delaware": "DE", + "District of Columbia": "DC", + "Florida": "FL", + "Georgia": "GA", + "Hawaii": "HI", + "Idaho": "ID", + "Illinois": "IL", + "Indiana": "IN", + "Iowa": "IA", + "Kansas": "KS", + "Kentucky": "KY", + "Louisiana": "LA", + "Maine": "ME", + "Maryland": "MD", + "Massachusetts": "MA", + "Michigan": "MI", + "Minnesota": "MN", + "Mississippi": "MS", + "Missouri": "MO", + "Montana": "MT", + "Nebraska": "NE", + "Nevada": "NV", + "New Hampshire": "NH", + "New Jersey": "NJ", + "New Mexico": "NM", + "New York": "NY", + "North Carolina": "NC", + "North Dakota": "ND", + "Ohio": "OH", + "Oklahoma": "OK", + "Oregon": "OR", + "Pennsylvania": "PA", + "Rhode Island": "RI", + "South Carolina": "SC", + "South Dakota": "SD", + "Tennessee": "TN", + "Texas": "TX", + "Utah": "UT", + "Vermont": "VT", + "Virginia": "VA", + "Washington": "WA", + "West Virginia": "WV", + "Wisconsin": "WI", + "Wyoming": "WY", + "Armed Forces Americas": "AA", + "Armed Forces Europe": "AE", + "Armed Forces Pacific": "AP" + } + if (states[name] !== null) { + return states[name]; + } + return name; +} + +function resetButtonLoading(submitbtn, curlabel) { + // submitbtn.attr('disabled', false).html(curlabel).css('opacity', '1'); +} diff --git a/assets/js/gravity-forms-braintree-admin.js b/assets/js/gravity-forms-braintree-admin.js new file mode 100644 index 0000000..5eff35f --- /dev/null +++ b/assets/js/gravity-forms-braintree-admin.js @@ -0,0 +1,76 @@ +jQuery(function () { + jQuery('[id^=angelleye_notification]').each(function (i) { + jQuery('[id="' + this.id + '"]').slice(1).remove(); + }); + var el_notice = jQuery(".angelleye-notice"); + el_notice.fadeIn(750); + jQuery(".angelleye-notice-dismiss").click(function(e){ + e.preventDefault(); + jQuery( this ).parent().parent(".angelleye-notice").fadeOut(600, function () { + jQuery( this ).parent().parent(".angelleye-notice").remove(); + }); + notify_wordpress(jQuery( this ).data("msg")); + }); + function notify_wordpress(message) { + var param = { + action: 'angelleye_gform_braintree_adismiss_notice', + data: message + }; + jQuery.post(ajaxurl, param); + } + jQuery(document).off('click', '#angelleye-updater-notice .notice-dismiss').on('click', '#angelleye-updater-notice .notice-dismiss',function(event) { + var r = confirm("If you do not install the Updater plugin you will not receive automated updates for Angell EYE products going forward!"); + if (r == true) { + var data = { + action : 'angelleye_updater_dismissible_admin_notice' + }; + jQuery.post(ajaxurl, data, function (response) { + var $el = jQuery( '#angelleye-updater-notice' ); + event.preventDefault(); + $el.fadeTo( 100, 0, function() { + $el.slideUp( 100, function() { + $el.remove(); + }); + }); + }); + } + }); +}); + +jQuery(document).ready(function ($) { + $('.addmorecustomfield').click(function () { + $('.custom_field_row:last').after(''+$('.custom_fields_template').html()+' Remove '); + if($('.custom_field_row').length>1){ + $('.alert-notification-custom-fields').removeClass('hide'); + } + }); + + $('body').on('click','.remove_custom_field', function () { + $(this).closest('tr.custom_field_row') .remove(); + }); + + $('#gform_braintree_mapping').submit(function (e) { + e.preventDefault(); + + var data = $(this).serialize(); + var url = $(this).attr('action'); + $('.successful_message').html(''); + $('.updatemappingbtn').html('Saving...').attr('disabled','disabled'); + $.ajax({ + url:url, + method: 'post', + 'data': data, + 'dataType': 'json' + }).done(function (response) { + if(response.status){ + $('.successful_message').html('

'+response.message+'

') + }else { + console.log('error', response); + } + + }).complete(function () { + $('.updatemappingbtn').html('Update Mapping').removeAttr('disabled'); + }); + + }) +}); \ No newline at end of file diff --git a/assets/js/scripts.js b/assets/js/scripts.js index e1fcb85..6145dea 100644 --- a/assets/js/scripts.js +++ b/assets/js/scripts.js @@ -148,4 +148,4 @@ jQuery( function($) { }); -}); +}); \ No newline at end of file diff --git a/gravity-forms-braintree.php b/gravity-forms-braintree.php deleted file mode 100644 index 45534e9..0000000 --- a/gravity-forms-braintree.php +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/includes/angelleye-functions.php b/includes/angelleye-functions.php new file mode 100644 index 0000000..1d33b50 --- /dev/null +++ b/includes/angelleye-functions.php @@ -0,0 +1,91 @@ +file = $file; + $plugin->file_id = $file_id; + $plugin->product_id = $product_id; + + $angelleye_queued_updates[] = $plugin; + } + +} + + +/** + * Load installer for the AngellEYE Updater. + * @return $api Object + */ +if (!class_exists('AngellEYE_Updater') && !function_exists('angell_updater_install')) { + + function angell_updater_install($api, $action, $args) { + $download_url = AEU_ZIP_URL; + + if ('plugin_information' != $action || + false !== $api || + !isset($args->slug) || + 'angelleye-updater' != $args->slug + ) + return $api; + + $api = new stdClass(); + $api->name = 'AngellEYE Updater'; + $api->version = ''; + $api->download_link = esc_url($download_url); + return $api; + } + + add_filter('plugins_api', 'angell_updater_install', 10, 3); +} + +/** + * AngellEYE Installation Prompts + */ +if (!class_exists('AngellEYE_Updater') && !function_exists('angell_updater_notice')) { + + /** + * Display a notice if the "AngellEYE Updater" plugin hasn't been installed. + * @return void + */ + function angell_updater_notice() { + $active_plugins = apply_filters('active_plugins', get_option('active_plugins')); + if (in_array('angelleye-updater/angelleye-updater.php', $active_plugins)) + return; + + $slug = 'angelleye-updater'; + $install_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=' . $slug), 'install-plugin_' . $slug); + $activate_url = 'plugins.php?action=activate&plugin=' . urlencode('angelleye-updater/angelleye-updater.php') . '&plugin_status=all&paged=1&s&_wpnonce=' . urlencode(wp_create_nonce('activate-plugin_angelleye-updater/angelleye-updater.php')); + + $message = 'Install the Angell EYE Updater plugin to get updates for your Angell EYE plugins.'; + $is_downloaded = false; + $plugins = array_keys(get_plugins()); + foreach ($plugins as $plugin) { + if (strpos($plugin, 'angelleye-updater.php') !== false) { + $is_downloaded = true; + $message = ' Activate the Angell EYE Updater plugin to get updates for your Angell EYE plugins.'; + } + } + echo '

' . $message . '

' . "\n"; + } + + function angelleye_updater_dismissible_admin_notice() { + set_transient( 'angelleye_updater_notice_hide', 'yes', MONTH_IN_SECONDS ); + } + if ( false === ( $angelleye_updater_notice_hide = get_transient( 'angelleye_updater_notice_hide' ) ) ) { + add_action('admin_notices', 'angell_updater_notice'); + } + add_action( 'wp_ajax_angelleye_updater_dismissible_admin_notice', 'angelleye_updater_dismissible_admin_notice' ); +} \ No newline at end of file diff --git a/includes/angelleye-gravity-braintree-activator.php b/includes/angelleye-gravity-braintree-activator.php new file mode 100755 index 0000000..d7524f2 --- /dev/null +++ b/includes/angelleye-gravity-braintree-activator.php @@ -0,0 +1,24 @@ +isCreditCardFieldExist($form_id)) { + $menu_items[] = array( + 'name' => 'braintree_mapping_settings_page', + 'label' => __('Braintree Field Mapping') + ); + } + } + return $menu_items; + } + + public function braintreeFieldMapping() + { + GFFormSettings::page_header(); + require dirname(__FILE__).'/pages/angelleye-braintree-field-map-form.php'; + GFFormSettings::page_footer(); + } + + public function isCreditCardFieldExist($id) + { + $get_form = GFAPI::get_form($id); + if(isset($get_form['fields'])) { + foreach ($get_form['fields'] as $single_field) { + if ($single_field->type == 'creditcard' || $single_field->type=='braintree_ach' || $single_field->type=='braintree_credit_card' ) { + return true; + } + } + } + return false; + } + + public function saveMapping() + { + $form_id = $_POST['gform_id']; + //sanitize input values + $final_mapping = []; + foreach ($_POST['gravity_form_field'] as $key=>$field_id){ + if(empty($field_id)) continue; + $final_mapping[$key] = $field_id; + } + + $custom_fields=[]; + if(isset($_POST['gravity_form_custom_field_name'])){ + $mapped_field_ids = $_POST['gravity_form_custom_field']; + foreach ($_POST['gravity_form_custom_field_name'] as $key => $single_custom_field_name){ + if(!isset($mapped_field_ids[$key]) || empty($mapped_field_ids[$key])) + continue; + + $custom_fields[$single_custom_field_name] = $mapped_field_ids[$key]; + } + } + if(count($custom_fields)) + $final_mapping['custom_fields'] = $custom_fields; + + $get_form = GFAPI::get_form($form_id); + $get_form['braintree_fields_mapping'] = $final_mapping; + GFAPI::update_form($get_form, $form_id); + + die(json_encode(['status'=>true,'message'=>'Mapping has been updated successfully.'])); + } + + function assignArrayByPath(&$arr, $path, $value, $separator='.') { + $keys = explode($separator, $path); + + foreach ($keys as $key) { + $arr = &$arr[$key]; + } + + $arr = $value; + } + + public function mapGravityBraintreeFields($args, $submission_data, $form, $entry) + { + + $braintree_mapping = isset($form['braintree_fields_mapping'])?$form['braintree_fields_mapping']:[]; + $final_array = []; + + if(count($braintree_mapping)){ + foreach ($braintree_mapping as $key_name => $single_mapid) + { + if(is_array($single_mapid)){ + if($key_name=='custom_fields') { + foreach ($single_mapid as $subkey_name => $sub_mapid) { + if (isset($entry[$sub_mapid])) { + $this->assignArrayByPath($final_array, 'customFields.' . $subkey_name, $entry[$sub_mapid]); + } + } + } + }else { + if (isset($entry[$single_mapid])) { + $this->assignArrayByPath($final_array, $key_name, $entry[$single_mapid]); + } + } + } + } + if(count($final_array)){ + $args = array_merge($args, $final_array); + } + //var_dump($args); die; + return $args; + } + + function addNoticeToCreditCardForm( $field_content, $field, $value, $lead_id, $form_id ) { + if(is_admin()) { + if ($field->type == 'creditcard' || $field->type == 'braintree_credit_card') { + //echo ($field_content); die; + $first_label_position = strpos($field_content, ' 'settings', + 'subview' => 'braintree_mapping_settings_page', + 'id' => $form_id + ], menu_page_url('gf_edit_forms', false)); + + if(!AngelleyeGravityFormsBraintree::isBraintreeFeedActive()) { + $feed_page_link = add_query_arg([ + 'view' => 'settings', + 'subview' => 'gravity-forms-braintree', + 'id' => $form_id + ], menu_page_url('gf_edit_forms', false)); + $add_text[] = "To process payments, please configure a Braintree feed."; + } + + $add_text[] = "You can use Braintree Field Mapping to pass specific data values into the Braintree transaction details."; + $final_text = ''; + foreach ($add_text as $key=>$single_text){ + $final_text.=($final_text!=''?'
':'').($key+1).") ".$single_text; + } + $replacement_text = "

$final_text

"; + + $field_content = substr_replace($field_content,$replacement_text, $first_label_position, 0); + } + } + } + return $field_content; + } + +} diff --git a/includes/angelleye-gravity-braintree-helper.php b/includes/angelleye-gravity-braintree-helper.php new file mode 100644 index 0000000..7b973a0 --- /dev/null +++ b/includes/angelleye-gravity-braintree-helper.php @@ -0,0 +1,116 @@ + false, + 'braintree_ach' => false, + 'braintree_ach_cc_toggle' => false, + 'braintree_credit_card' => false + ]; + + if(isset($form['fields'])) { + foreach ($form['fields'] as $single_field) { + if ($single_field->type == 'creditcard' || $single_field->type=='braintree_ach' || $single_field->type == 'braintree_ach_cc_toggle' || $single_field->type=='braintree_credit_card') { + $response[$single_field->type] = $single_field; + } + } + } + + return $response; +} + +function getAngelleyeBraintreePaymentMethod($form){ + $selected_method = ''; + $response = getAngelleyeBraintreePaymentFields($form); + + //This means customer is using our toggle button + if($response['braintree_ach_cc_toggle'] !== false){ + $selected_method = rgpost( 'input_' . $response['braintree_ach_cc_toggle']->id . '_1' ); + } else { + if($response['creditcard']!==false){ + if(isset($response['creditcard']['conditionalLogic']) && is_array($response['creditcard']['conditionalLogic']) && count($response['creditcard']['conditionalLogic'])){ + $conditionalLogic = $response['creditcard']['conditionalLogic']; + if($conditionalLogic['actionType'] == 'show'){ + foreach ( $conditionalLogic['rules'] as $rule ) { + if($rule['operator'] == 'is') { + $fieldId = $rule['fieldId']; + $isValue = $rule['value']; + $selected_radio_value = rgpost( 'input_' . $fieldId ); + + if($selected_radio_value == $isValue){ + $selected_method = 'creditcard'; + break; + } + } + } + } + + } + } + + if($selected_method=='' && $response['braintree_ach'] !== false){ + if(isset($response['braintree_ach']['conditionalLogic']) && is_array($response['braintree_ach']['conditionalLogic']) && count($response['braintree_ach']['conditionalLogic'])){ + $conditionalLogic = $response['braintree_ach']['conditionalLogic']; + if($conditionalLogic['actionType'] == 'show'){ + foreach ( $conditionalLogic['rules'] as $rule ) { + if($rule['operator'] == 'is') { + $fieldId = $rule['fieldId']; + $isValue = $rule['value']; + $selected_radio_value = rgpost( 'input_' . $fieldId ); + if($selected_radio_value == $isValue){ + $selected_method = 'braintree_ach'; + break; + } + } + } + } + } + } + + if($selected_method == '' && $response['creditcard']!==false){ + $selected_method = 'creditcard'; + } else if($selected_method == '' && $response['braintree_ach']!==false){ + $selected_method = 'braintree_ach'; + } else if($selected_method == '' && $response['braintree_credit_card']!==false){ + $selected_method = 'braintree_credit_card'; + } + + } + + return $selected_method; +} + + +/** + * This is to setup default values for Custom Toggle and ACH form fields in admin panel + */ + +add_action( 'gform_editor_js_set_default_values', 'gravityFormSetDefaultValueOnDropin' ); +function gravityFormSetDefaultValueOnDropin() { + ?> + case "braintree_ach" : + if (!field.label) + field.label = ; + var accNumber, accType, routingNumber, accName; + + accNumber = new Input(field.id + ".1", ); + accType = new Input(field.id + ".2", ); + routingNumber = new Input(field.id + ".3", ); + accName = new Input(field.id + ".4", ); + field.inputs = [accNumber, accType, routingNumber, accName]; + break; + case "braintree_ach_cc_toggle": + if (!field.label) + field.label = ; + var paymentMethodToggle; + paymentMethodToggle = new Input(field.id + ".1", ); + field.inputs = [paymentMethodToggle]; + break; + case "braintree_credit_card": + if (!field.label) + field.label = ; + var braintreeCC = new Input(field.id + ".1", ); + field.inputs = [braintreeCC]; + break; + plugin_name = $plugin_name; + $this->plugin_version = $plugin_version; + $this->base_plugin_file = $base_plugin_file; + $this->plugin_check_option_name = sanitize_title($this->plugin_name.'-'.$this->plugin_version); + + //add_action( 'update_option_active_sitewide_plugins', [$this, 'checkPluginDeactivation'], 10, 2 ); + //add_action( 'update_option_active_plugins', [$this, 'checkPluginDeactivation'], 10, 2 ); + } + + public function setDeactivatePlugins( $plugin_basenames = [] ) { + $this->deactivate_plugins = array_merge($this->deactivate_plugins, $plugin_basenames); + } + + /** + * Accepts a key value array, where key defines the plugin name and value defines the min version required + * @param array $classes + */ + public function setRequiredPlugins( $plugin_names = [] ) { + $this->required_plugins = array_merge($this->required_plugins, $plugin_names); + } + + /** + * Accepts a key value array, where key defines the class name and value defines the error message + * @param array $classes + */ + public function setRequiredClasses( $classes = [] ) { + $this->required_classes = array_merge($this->required_classes, $classes); + } + + /** + * Accepts a version string to check + * @param $version_required + */ + public function setPHP( $version_required ) { + if (version_compare(PHP_VERSION, $version_required, '<')) { + $this->errors_list['VERSION_PHP'] = __('PHP version >= '.$version_required.' is required to run '. $this->plugin_name); + } + } + + public function setRequiredExtensions( $extensions ) { + $this->required_extensions = array_merge($this->required_extensions, $extensions); + } + + public function check( $force_check = false ) { + if(count($this->required_classes)){ + foreach ($this->required_classes as $class_name => $class_msg){ + if(!class_exists($class_name)) + $this->errors_list['CLASS_'.$class_name] = $class_msg; + } + } + + if(count($this->required_plugins)){ + require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + $all_plugins = get_plugins(); + $active_plugins = get_option('active_plugins'); + $install_needed_plugins = []; + foreach ($this->required_plugins as $single_plugin => $plugin_options){ + if(in_array($single_plugin, $active_plugins)){ + if(isset($plugin_options['min_version'])) { + $min_version_required = $plugin_options['min_version']; + if ( version_compare( $all_plugins[ $single_plugin ]['Version'], $min_version_required, '<' ) ) { + $this->errors_list[] = 'You have ' . $all_plugins[ $single_plugin ]['Name'] . ' - Version: ' . $all_plugins[ $single_plugin ]['Version'] . ' installed, '.$this->plugin_name.' requires min ' . $all_plugins[ $single_plugin ]['Name'] . ' - Version: ' . $min_version_required . ' to function properly'; + } + } + } else if(isset($all_plugins[$single_plugin])) { + $this->errors_list[] = 'Please activate the '.$all_plugins[$single_plugin]['Name'].' plugin to run the '.$this->plugin_name; + } else{ + if(isset($plugin_options['install_link'])) + $install_needed_plugins[] = ''.$plugin_options['name'].''; + else + $install_needed_plugins[] = $plugin_options['name']; + } + } + if(count($install_needed_plugins)){ + foreach ( $install_needed_plugins as $install_needed_plugin ) { + $this->errors_list[] = sprintf(__('Please install %s to continue.'), $install_needed_plugin); + } + } + } + + if(count($this->required_extensions)) { + $ext_error = []; + foreach ( $this->required_extensions AS $ext ) { + if ( ! extension_loaded( $ext ) ) { + $ext_error[] = $ext; + } + } + if(count($ext_error)){ + $this->errors_list[] = implode(', ', $ext_error).' extensions are required for the '.$this->plugin_name; + } + } + + if(count($this->errors_list)){ + add_action('admin_notices', [$this, 'showAdminNotice']); + delete_option($this->plugin_check_option_name); + return $this->errors_list; + }else { + update_option($this->plugin_check_option_name, 'passed'); + return true; + } + } + + public function showAdminNotice() { + + echo '
+

'.(in_array($this->base_plugin_file, $this->deactivate_plugins)? + 'The '.$this->plugin_name.' plugin will not function due to the following:': + $this->plugin_name.' plugin is deactivated due to following errors.').'

+

'.implode('
', $this->errors_list).'

+
'; + if(count($this->deactivate_plugins)){ + require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + foreach ($this->deactivate_plugins as $single_plugin_basename){ + if ( is_plugin_active( $single_plugin_basename ) ) { + deactivate_plugins( $single_plugin_basename ); + } + } + if ( isset( $_GET['activate'] ) ) { + unset( $_GET['activate'] ); + } + } + } + + /** + * Run custom action when another plugin is deactivated - Deprecated + * @param $new_value + * @param $old_value + */ + public function checkPluginDeactivation( $new_value, $old_value ) { + if($this->check(true)!== true){ + require_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + foreach ($this->deactivate_plugins as $single_plugin_basename) + deactivate_plugins($single_plugin_basename, true); + } + } + + /** + * Get activation or deactivation link of a plugin + * @param string $plugin plugin file name + * @param string $action action to perform. activate or deactivate + * @return string $url action url + */ + function pluginActionLink( $plugin, $action = 'activate' ) { + if ( strpos( $plugin, '/' ) ) { + $plugin = str_replace( '\/', '%2F', $plugin ); + } + $url = sprintf( admin_url( 'plugins.php?action=' . $action . '&plugin=%s&plugin_status=all&paged=1&s' ), $plugin ); + $_REQUEST['plugin'] = $plugin; + $url = wp_nonce_url( $url, $action . '-plugin_' . $plugin ); + return $url; + } + } +} \ No newline at end of file diff --git a/includes/class-angelleye-gravity-braintree-ach-field.php b/includes/class-angelleye-gravity-braintree-ach-field.php new file mode 100644 index 0000000..6cbc3f1 --- /dev/null +++ b/includes/class-angelleye-gravity-braintree-ach-field.php @@ -0,0 +1,482 @@ + 'pricing_fields', 'text' => 'ACH Form']; + } + + /** + * The settings which should be available on the field in the form editor. + * + * @return array + */ + function get_form_editor_field_settings() { + return ['label_setting', 'label_placement_setting', 'admin_label_setting', 'description_setting', 'sub_labels_setting', + 'sub_label_placement_setting', 'error_message_setting', 'css_class_setting', 'conditional_logic_field_setting', + 'force_ssl_field_setting', 'rules_setting', 'input_placeholders_setting', + ]; + } + + /** + * Enable this field for use with conditional logic. + * + * @return bool + */ + public function is_conditional_logic_supported() { + return true; + } + + /** + * The scripts to be included in the form editor. + * + * @return string + */ + public function get_form_editor_inline_script_on_page_render() { + + } + + /** + * Override default button for the ACH Payments + * @param $button + * @param $form + * + * @return string + */ + function overrideSubmitButton($button, $form) { + $dom = new DOMDocument(); + $dom->loadHTML('' . $button); + $input = $dom->getElementsByTagName('input')->item(0); + if( empty( $input ) ){ + $input = $dom->getElementsByTagName('button')->item(0); + } + + //$input->removeAttribute('onkeypress'); + //$input->removeAttribute('onclick'); + $classes = $input->getAttribute('class'); + $classes .= " custom_ach_form_submit_btn"; + $input->setAttribute('class', $classes); + return $dom->saveHtml($input); + } + + /** + * Returns the fields input for backend and frontend + * @param array $form + * @param string $value + * @param null $entry + * + * @return string + */ + public function get_field_input($form, $value = '', $entry = null) { + add_filter('gform_submit_button', [$this, 'overrideSubmitButton'], 10, 2); + $is_entry_detail = $this->is_entry_detail(); + $is_form_editor = $this->is_form_editor(); + + $form_id = $form['id']; + $id = intval($this->id); + $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; + $form_id = ( $is_entry_detail || $is_form_editor ) && empty($form_id) ? rgget('id') : $form_id; + + $disabled_text = $is_form_editor ? "disabled='disabled'" : ''; + $class_suffix = $is_entry_detail ? '_admin' : ''; + + $sub_label_placement = rgar($form, 'subLabelPlacement'); + $field_sub_label_placement = $this->subLabelPlacement; + $is_sub_label_above = $field_sub_label_placement == 'above' || ( empty($field_sub_label_placement) && $sub_label_placement == 'above' ); + $sub_label_class_attribute = $field_sub_label_placement == 'hidden_label' ? "class='hidden_sub_label screen-reader-text'" : ''; + + $account_number = ''; + $routing_number = ''; + $account_type = ''; + $account_holder_name = ''; + $autocomplete = RGFormsModel::is_html5_enabled() ? "autocomplete='off'" : ''; + + if (is_array($value)) { + $account_number = esc_attr(rgget($this->id . '.1', $value)); + $account_type = esc_attr(rgget($this->id . '.2', $value)); + $routing_number = esc_attr(rgget($this->id . '.3', $value)); + $account_holder_name = esc_attr(rgget($this->id . '.4', $value)); + } + + + $tabindex = $this->get_tabindex(); + $account_type_field_input = GFFormsModel::get_input($this, $this->id . '.2'); + $account_type_options = $this->getAccountTypeSelectOptions($account_type); + + $account_types = $this->getAccountTypeList(); + //$radio_buttons = []; + //foreach ($account_types as $ac_key => $ac_label) + // $radio_buttons[]= ""; + + $account_type_label = rgar($account_type_field_input, 'customLabel') != '' ? $account_type_field_input['customLabel'] : __('Account Type', 'gravity-forms-braintree'); + $account_type_label = gf_apply_filters(array('gform_accounttype', $form_id), $account_type_label, $form_id); + if ($is_sub_label_above) { + $account_type_field = " + + + "; + } else { + $account_type_field = " + + + "; + } + + $tabindex = $this->get_tabindex(); + $routing_number_field_input = GFFormsModel::get_input($this, $this->id . '.3'); + $html5_output = !is_admin() && GFFormsModel::is_html5_enabled() ? "pattern='[0-9]*' title='" . __('Only digits are allowed', 'gravity-forms-braintree') . "'" : ''; + $routing_number_label = rgar($routing_number_field_input, 'customLabel') != '' ? $routing_number_field_input['customLabel'] : __('Routing Number', 'gravity-forms-braintree'); + $routing_number_label = gf_apply_filters(array('gform_routingnumber', $form_id), $routing_number_label, $form_id); + + $routing_number_placeholder = $this->get_input_placeholder_attribute($routing_number_field_input); + if ($is_sub_label_above) { + $routing_field = " + + + "; + } else { + $routing_field = " + + + "; + } + + $tabindex = $this->get_tabindex(); + $account_number_field_input = GFFormsModel::get_input($this, $this->id . '.1'); + $html5_output = !is_admin() && GFFormsModel::is_html5_enabled() ? "pattern='[0-9]*' title='" . __('Only digits are allowed', 'gravity-forms-braintree') . "'" : ''; + $account_number_label = rgar($account_number_field_input, 'customLabel') != '' ? $account_number_field_input['customLabel'] : __('Account Number', 'gravity-forms-braintree'); + $account_number_label = gf_apply_filters(array('gform_1', $form_id), $account_number_label, $form_id); + + $account_number_placeholder = $this->get_input_placeholder_attribute($account_number_field_input); + if ($is_sub_label_above) { + $account_field = " + + + "; + } else { + $account_field = " + + + "; + } + + $account_number_verification_field = ''; + if ($is_sub_label_above) { + $account_number_verification_field = " + + + "; + } else { + $account_number_verification_field = " + + + "; + } + + $tabindex = $this->get_tabindex(); + $account_holder_name_field_input = GFFormsModel::get_input($this, $this->id . '.4'); + $account_holder_name_label = rgar($account_holder_name_field_input, 'customLabel') != '' ? $account_holder_name_field_input['customLabel'] : __('Account Holder Name', 'gravity-forms-braintree'); + $account_holder_name_label = gf_apply_filters(array('gform_accountholdername', $form_id), $account_holder_name_label, $form_id); + + $account_holder_name_placeholder = $this->get_input_placeholder_attribute($account_holder_name_field_input); + if ($is_sub_label_above) { + $account_holder_name_field = " + + + "; + } else { + $account_holder_name_field = " + + + "; + } + + return "
" . $account_type_field . $account_holder_name_field . $routing_field . $account_field . $account_number_verification_field . '
'; + } + + /** + * Returns Braintree ACH Supported account types + * @return mixed|void + */ + public function getAccountTypeList() { + $account_types = apply_filters('angelleye_gravity_braintree_account_types', [ + 'S' => __('Savings', 'gravity-forms-braintree'), + 'C' => __('Checking', 'gravity-forms-braintree'), + ]); + return $account_types; + } + + /** + * Returns the account type options for select field + * @param $selected_value + * @param string $placeholder + * + * @return string + */ + private function getAccountTypeSelectOptions($selected_value, $placeholder = '') { + if (empty($placeholder)) { + $placeholder = __('Select', 'gravity-forms-braintree'); + } + $options = $this->getAccountTypeList(); + $str = ""; + foreach ($options as $value => $label) { + $selected = $selected_value == $value ? "selected='selected'" : ''; + $str .= ""; + } + + return $str; + } + + public function get_field_label_class() { + return 'gfield_label gfield_label_before_complex'; + } + + public function is_value_submission_empty($form_id) { + return false; + } + + /** + * When customer chooses ACH form method then avoid credit card field validation errors + * @param $validation_result + * + * @return mixed + */ + function customCCValidation($validation_result) { + $form = $validation_result['form']; + $this->selected_payment_method = getAngelleyeBraintreePaymentMethod($form); + + if ($this->selected_payment_method !== 'braintree_ach') + return $validation_result; + + $failed_validation = 0; + if (isset($form['fields'])) { + + foreach ($form['fields'] as $key => $single_field) { + if ($single_field->type == 'creditcard') { + $form['fields'][$key]['failed_validation'] = false; + $form['fields'][$key]['validation_message'] = ''; + } else if ($single_field['failed_validation']) + $failed_validation++; + } + } + + $validation_result['form'] = $form; + if ($failed_validation == 0) { + $validation_result['failed_validation_page'] = "1"; + $validation_result['is_valid'] = true; + } + + return $validation_result; + } + + /** + * Constructor to add custom validation filter to exclude CC validation when ACH payment method is selected + * Angelleye_Gravity_Braintree_ACH_Field constructor. + * + * @param array $data + */ + public function __construct($data = array()) { + parent::__construct($data); + add_filter('gform_validation', [$this, 'customCCValidation'], 50); + } + + /** + * Validate the user input + * @param array|string $value + * @param array $form + */ + public function validate($value, $form) { + + //check if toggle field exist + $this->selected_payment_method = getAngelleyeBraintreePaymentMethod($form); + + if ($this->selected_payment_method !== 'braintree_ach') { + return; + } + + $account_number = rgpost('input_' . $this->id . '_1'); + $routing_number = rgpost('input_' . $this->id . '_2'); + $account_type = rgpost('input_' . $this->id . '_3'); + $account_holder_name = rgpost('input_' . $this->id . '_4'); + + //$this->isRequired && + if (( empty($account_number) || empty($routing_number) || empty($account_type) || empty($account_holder_name))) { + $this->failed_validation = true; + $this->validation_message = empty($this->errorMessage) ? __('Please enter your bank account information.', 'gravity-forms-braintree') : $this->errorMessage; + } elseif (!empty($account_number)) { + + if (empty($routing_number)) { + $this->failed_validation = true; + $this->validation_message = __("Please enter your bank account's routing number.", 'gravity-forms-braintree'); + } + if (empty($account_type)) { + $this->failed_validation = true; + $this->validation_message = __("Please select the account type.", 'gravity-forms-braintree'); + } + + if (empty($account_holder_name)) { + $this->failed_validation = true; + $this->validation_message = __("Please enter account holder name.", 'gravity-forms-braintree'); + } + } + } + + /** + * Shows the filled information by the users on frontend form + * @param array $field_values + * @param bool $get_from_post_global_var + * + * @return array|string + */ + public function get_value_submission($field_values, $get_from_post_global_var = true) { + + if ($get_from_post_global_var) { + $value[$this->id . '.1'] = $this->get_input_value_submission('input_' . $this->id . '_1', rgar(@$this->inputs[0], 'name'), $field_values, true); + $value[$this->id . '.2'] = $this->get_input_value_submission('input_' . $this->id . '_2', rgar(@$this->inputs[1], 'name'), $field_values, true); + $value[$this->id . '.3'] = $this->get_input_value_submission('input_' . $this->id . '_3', rgar(@$this->inputs[2], 'name'), $field_values, true); + $value[$this->id . '.4'] = $this->get_input_value_submission('input_' . $this->id . '_4', rgar(@$this->inputs[3], 'name'), $field_values, true); + } else { + $value = $this->get_input_value_submission('input_' . $this->id, $this->inputName, $field_values, $get_from_post_global_var); + } + return $value; + } + + /** + * Returns the ACH form entry input values + * @return array|null + */ + public function get_entry_inputs() { + $inputs = array(); + if (is_array($this->inputs)) { + foreach ($this->inputs as $input) { + if (in_array($input['id'], array( + $this->id . '.1', + $this->id . '.2', + $this->id . '.3', + $this->id . '.4' + ))) { + $inputs[] = $input; + } + } + } + + return $inputs; + } + + /** + * Format the entry value for display on the entries list page. + * + * Return a value that's safe to display on the page. + * + * @param string|array $value The field value. + * @param array $entry The Entry Object currently being processed. + * @param string $field_id The field or input ID currently being processed. + * @param array $columns The properties for the columns being displayed on the entry list page. + * @param array $form The Form Object currently being processed. + * + * @return string + */ + public function get_value_entry_list($value, $entry, $field_id, $columns, $form) { + + $allowable_tags = $this->get_allowable_tags($form['id']); + + list( $input_id, $field_id ) = rgexplode('.', $field_id, 2); + switch ($field_id) { + case 2: + $return = $value == 'C' ? 'Checking' : 'Savings'; + break; + case 1: + case 3: + case 4: + default: + if ($allowable_tags === false) { + // The value is unsafe so encode the value. + $return = esc_html($value); + } else { + // The value contains HTML but the value was sanitized before saving. + $return = $value; + } + } + + return $return; + } + + /** + * Returns the ACH form value on Entry detail page + * @param array|string $value + * @param string $currency + * @param bool $use_text + * @param string $format + * @param string $media + * + * @return string + */ + public function get_value_entry_detail($value, $currency = '', $use_text = false, $format = 'html', $media = 'screen') { + + if (is_array($value)) { + $account_number = trim(rgget($this->id . '.1', $value)); + $account_type = trim(rgget($this->id . '.2', $value)); + $routing_number = trim(rgget($this->id . '.3', $value)); + $account_holder_name = trim(rgget($this->id . '.4', $value)); + if ($account_number == '') + return 'N/A'; + if ($format == 'html') + return "Account Number: $account_number
+Account Type: " . ($account_type == 's' ? 'Savings' : 'Checking') . "
+Routing Number: $routing_number
+Account Holder: $account_holder_name
"; + } else { + return ''; + } + } + + /** + * Mask the values before saving + * @param string $value + * @param array $form + * @param string $input_name + * @param int $lead_id + * @param array $lead + * + * @return array|string + */ + public function get_value_save_entry($value, $form, $input_name, $lead_id, $lead) { + list( $input_token, $field_id_token, $input_id ) = rgexplode('_', $input_name, 3); + if ($input_id == '1' || $input_id == '3') { + $value = str_replace(' ', '', $value); + $card_number_length = strlen($value); + $value = substr($value, - 4, 4); + $value = str_pad($value, $card_number_length, 'X', STR_PAD_LEFT); + } + + return $this->sanitize_entry_value($value, $form['id']); + } + +} diff --git a/includes/class-angelleye-gravity-braintree-ach-toggle-field.php b/includes/class-angelleye-gravity-braintree-ach-toggle-field.php new file mode 100644 index 0000000..47c8d7d --- /dev/null +++ b/includes/class-angelleye-gravity-braintree-ach-toggle-field.php @@ -0,0 +1,177 @@ + 'pricing_fields', 'text' => 'Payment Types' ]; + } + + /** + * The settings which should be available on the field in the form editor. + * + * @return array + */ + function get_form_editor_field_settings() { + return ['label_setting', 'admin_label_setting', 'description_setting', 'error_message_setting', 'css_class_setting', 'conditional_logic_field_setting', + 'rules_setting', 'input_placeholders_setting', + ]; + } + + /** + * Return the Payment method toggle radio buttons + * @param array $form + * @param string $value + * @param null $entry + * + * @return string + */ + public function get_field_input( $form, $value = '', $entry = null ) { + + $is_entry_detail = $this->is_entry_detail(); + $is_form_editor = $this->is_form_editor(); + + $form_id = $form['id']; + $id = intval( $this->id ); + $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; + $form_id = ( $is_entry_detail || $is_form_editor ) && empty( $form_id ) ? rgget( 'id' ) : $form_id; + + $cc_field_id = ''; + $ach_field_id = ''; + if(!$is_form_editor && !$is_entry_detail){ + foreach ($form['fields'] as $single_field) { + + if ($single_field->type == 'creditcard') + $cc_field_id = $single_field->id; + else if($single_field->type=='braintree_ach') { + $ach_field_id = $single_field->id; + } + } + } + + $payment_options = ''; + $payment_methods = [['key' => 'creditcard', 'label'=>'Credit Card', 'field_id' => $cc_field_id], + ['key' => 'braintree_ach', 'label' => 'ACH', 'field_id' => $ach_field_id]]; + foreach ( $payment_methods as $payment_method ) { + $checked = rgpost( "input_{$id}_1" ) == $payment_method['key'] ? "checked='checked'" : ''; + $payment_options .= "
{$payment_method['label']}
"; + } + + return "
" .$payment_options + . '
'; + } + + public function is_value_submission_empty( $form_id ) { + return false; + } + + public function get_entry_inputs() { + $inputs = array(); + if(is_array($this->inputs)) { + foreach ( $this->inputs as $input ) { + if ( in_array( $input['id'], array( $this->id . '.1' ) ) ) { + $inputs[] = $input; + } + } + } + + return $inputs; + } + + /** + * Validate the user input + * @param array|string $value + * @param array $form + */ + public function validate( $value, $form ) { + $toggle_selected = rgpost( 'input_' . $this->id.'_1' ); + + if (empty( $toggle_selected ) || !in_array($toggle_selected, ['creditcard','braintree_ach']) ) { + $this->failed_validation = true; + $this->validation_message = empty( $this->errorMessage ) ? __( 'Please select a payment method.', 'gravity-forms-braintree' ) : $this->errorMessage; + } + } + + /** + * Shows the filled information by the users + * @param array $field_values + * @param bool $get_from_post_global_var + * + * @return array|string + */ + public function get_value_submission( $field_values, $get_from_post_global_var = true ) { + + if ( $get_from_post_global_var ) { + $value[ $this->id . '.1' ] = $this->get_input_value_submission( 'input_' . $this->id . '_1', rgar( @$this->inputs[0], 'name' ), $field_values, true ); + } else { + $value = $this->get_input_value_submission( 'input_' . $this->id, $this->inputName, $field_values, $get_from_post_global_var ); + } + return $value; + } + + /** + * Format the value on entry detail page + * @param array|string $value + * @param string $currency + * @param bool $use_text + * @param string $format + * @param string $media + * + * @return string + */ + public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) { + if ( is_array( $value ) ) { + $selected_method = trim( rgget( $this->id . '.1', $value ) ); + return $selected_method=='braintree_ach' ? 'Braintree ACH Direct Debit' : 'Credit Card'; + } else { + return ''; + } + } + + /** + * Format the entry value for display on the entries list page. + * + * Return a value that's safe to display on the page. + * + * @param string|array $value The field value. + * @param array $entry The Entry Object currently being processed. + * @param string $field_id The field or input ID currently being processed. + * @param array $columns The properties for the columns being displayed on the entry list page. + * @param array $form The Form Object currently being processed. + * + * @return string + */ + public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) { + + $allowable_tags = $this->get_allowable_tags( $form['id'] ); + + list( $input_id, $field_id ) = rgexplode( '.', $field_id, 2 ); + switch($field_id){ + case 1: + if($value=='braintree_ach') + $return = 'Braintree ACH Direct Debit'; + else if($value=='creditcard') + $return = 'Credit Card'; + break; + } + + return $return; + } +} \ No newline at end of file diff --git a/includes/class-angelleye-gravity-braintree-creditcard.php b/includes/class-angelleye-gravity-braintree-creditcard.php new file mode 100644 index 0000000..79b0dab --- /dev/null +++ b/includes/class-angelleye-gravity-braintree-creditcard.php @@ -0,0 +1,144 @@ + 'pricing_fields', 'text' => 'Braintree CC' ]; + } + + /** + * The settings which should be available on the field in the form editor. + * + * @return array + */ + function get_form_editor_field_settings() { + return [ + 'label_setting', + 'admin_label_setting', + 'description_setting', + 'error_message_setting', + 'css_class_setting', + 'conditional_logic_field_setting', + 'rules_setting', + 'input_placeholders_setting', + 'label_placement_setting' + ]; + } + + /** + * Returns the field inner markup. + * + * @param array $form The Form Object currently being processed. + * @param string $value The field value. From default/dynamic population, $_POST, or a resumed incomplete submission. + * @param null $entry Null or the Entry Object currently being edited. + * + * @return false|string + * @throws \Braintree\Exception\Configuration + */ + public function get_field_input( $form, $value = '', $entry = null ) { + + $is_entry_detail = $this->is_entry_detail(); + $is_form_editor = $this->is_form_editor(); + + $form_id = $form['id']; + $id = intval( $this->id ); + $field_id = $is_entry_detail || $is_form_editor || $form_id == 0 ? "input_$id" : 'input_' . $form_id . "_$id"; + $form_id = ( $is_entry_detail || $is_form_editor ) && empty( $form_id ) ? rgget( 'id' ) : $form_id; + + $Plugify_GForm_Braintree = new Plugify_GForm_Braintree(); + $gateway = $Plugify_GForm_Braintree->getBraintreeGateway(); + $clientToken = $gateway->clientToken()->generate(); + + ob_start(); + + ?> +
+
+ +
+ + [ + 'firstName' => 'First Name', + 'lastName' => 'Last Name', + 'company' => 'Company Name', + /*'countryCodeAlpha2' => 'Country Code (e.g US)', + 'countryCodeAlpha3' => 'Country Code (e.g. USA)', + 'countryCodeNumeric' => 'Country Code (e.g. +1)',*/ + 'streetAddress' => 'Street Address', + 'extendedAddress' => 'Street Address 2 (Apartment or Suite Number)', + 'locality' => 'Locality/City', + 'region' => 'State/Province', + 'postalCode' => 'Postal Code', + 'countryName' => 'Country Name', + ], + 'shipping' => [ + 'firstName' => 'First Name', + 'lastName' => 'Last Name', + 'company' => 'Company Name', + /*'countryCodeAlpha2' => 'Country Code (e.g US)', + 'countryCodeAlpha3' => 'Country Code (e.g. USA)', + 'countryCodeNumeric' => 'Country Code (e.g. +1)',*/ + 'streetAddress' => 'Street Address', + 'extendedAddress' => 'Street Address 2 (Apartment or Suite Number)', + 'locality' => 'Locality/City', + 'region' => 'State/Province', + 'postalCode' => 'Postal Code', + 'countryName' => 'Country Name', + ], + //'customFields' => 'Custom Fields (Defined in your account)', + 'customer' => [ + 'firstName' => 'First Name', + 'lastName' => 'Last Name', + 'company' => 'Company Name', + 'email' => 'Email', + 'phone' => 'Phone', + 'website' => 'Website URL' + ] +]; + +$form_id = $_GET['id']; +$get_form = GFAPI::get_form($form_id); +//echo '
';print_r($get_form); die;
+
+$braintree_mapping = isset($get_form['braintree_fields_mapping'])?$get_form['braintree_fields_mapping']:[];
+
+function agreegateAllFields($main_arr, $parent_key = ''){
+    $final_fields = [];
+    foreach ($main_arr as $main_key => $single_field){
+        if(is_array($single_field))
+            $final_fields[$main_key] = agreegateAllFields($single_field, $main_key);
+        else {
+            $final_fields[($parent_key != '' ? $parent_key . '.' : '') . $main_key] = $single_field;
+        }
+    }
+
+    return $final_fields;
+}
+
+$total_braintree_fields = agreegateAllFields($braintree_fields);
+//print_r($total_braintree_fields);
+
+$gravity_fields = $get_form['fields'];
+$ignore_type_fields = ['creditcard'];
+
+$final_gravity_fields = [];
+foreach ($gravity_fields as $gravity_field) {
+    if(in_array($gravity_field->type, $ignore_type_fields))
+        continue;
+    if(is_array($gravity_field['inputs']) && count($gravity_field['inputs'])){
+        foreach ($gravity_field['inputs'] as $single_input)
+            $final_gravity_fields[$single_input['id']] = $single_input['label'].' ('.$gravity_field['label'].')';
+    }else
+        $final_gravity_fields[$gravity_field['id']] = $gravity_field['label'];
+}
+//print_r($gravity_fields);
+?>
+

Braintree Field Mapping

+ + 'settings', + 'subview' => 'gravity-forms-braintree', + 'id' => $form_id + ], menu_page_url('gf_edit_forms', false)); + + echo "

Please make sure to configure the Braintree feed to process the payments.

"; +}?> + +

+ Here you can map individual Gravity form fields to Braintree fields so that they will show up in the Braintree transaction details. +

+

The field names on the left are currently available in Braintree. Simply select the Gravity form field from the drop-down that you would like to pass to the matching field in Braintree transaction details.

+

If you do not see a Braintree field available for your Gravity form field, you may create custom fields within your Braintree account, and then add these custom fields at the bottom of this field mapping section.

+

For more information, see our documentation.

+
+ + + + $single_field_set){ + if(is_array($single_field_set)){ + ?> + + + + $label){ + $selected_option = isset($braintree_mapping[$key])?$braintree_mapping[$key]:''; + ?> + + + + + + + + + + + + + + + + $mapped_id){ + ?> + + + + + + +

+ +
+ +
+

Custom Fields + + + + + Add Custom Field + + + + +

+
+

Please make sure all of the defined custom field names match the Braintree Custom Field API names. Otherwise, the payment processor will return an error.

+
+
+ + Remove
+ +
+
+
+
+ +
+ \ No newline at end of file diff --git a/lib/Braintree.php b/lib/Braintree.php index e940d65..f24f892 100644 --- a/lib/Braintree.php +++ b/lib/Braintree.php @@ -1,181 +1,28 @@ -_attributes)) { - return $this->_attributes[$name]; - } - else { - trigger_error('Undefined property on ' . get_class($this) . ': ' . $name, E_USER_NOTICE); - return null; - } - } - /** - * used by isset() and empty() - * @access public - * @param string $name property name - * @return boolean - */ - public function __isset($name) - { - return array_key_exists($name, $this->_attributes); - } - - public function _set($key, $value) - { - $this->_attributes[$key] = $value; - } - - /** - * - * @param string $className - * @param object $resultObj - * @return object returns the passed object if successful - * @throws Braintree_Exception_ValidationsFailed - */ - public static function returnObjectOrThrowException($className, $resultObj) - { - $resultObjName = Braintree_Util::cleanClassName($className); - if ($resultObj->success) { - return $resultObj->$resultObjName; - } else { - throw new Braintree_Exception_ValidationsFailed(); - } - } -} -require_once('Braintree/Modification.php'); -require_once('Braintree/Instance.php'); - -require_once('Braintree/Address.php'); -require_once('Braintree/AddOn.php'); -require_once('Braintree/Collection.php'); -require_once('Braintree/Configuration.php'); -require_once('Braintree/CreditCard.php'); -require_once('Braintree/Customer.php'); -require_once('Braintree/CustomerSearch.php'); -require_once('Braintree/DisbursementDetails.php'); -require_once('Braintree/Descriptor.php'); -require_once('Braintree/Digest.php'); -require_once('Braintree/Discount.php'); -require_once('Braintree/IsNode.php'); -require_once('Braintree/EqualityNode.php'); -require_once('Braintree/Exception.php'); -require_once('Braintree/Http.php'); -require_once('Braintree/KeyValueNode.php'); -require_once('Braintree/MerchantAccount.php'); -require_once('Braintree/MerchantAccount/BusinessDetails.php'); -require_once('Braintree/MerchantAccount/FundingDetails.php'); -require_once('Braintree/MerchantAccount/IndividualDetails.php'); -require_once('Braintree/MerchantAccount/AddressDetails.php'); -require_once('Braintree/MultipleValueNode.php'); -require_once('Braintree/MultipleValueOrTextNode.php'); -require_once('Braintree/PartialMatchNode.php'); -require_once('Braintree/Plan.php'); -require_once('Braintree/RangeNode.php'); -require_once('Braintree/ResourceCollection.php'); -require_once('Braintree/SettlementBatchSummary.php'); -require_once('Braintree/Subscription.php'); -require_once('Braintree/SubscriptionSearch.php'); -require_once('Braintree/TextNode.php'); -require_once('Braintree/Transaction.php'); -require_once('Braintree/Disbursement.php'); -require_once('Braintree/TransactionSearch.php'); -require_once('Braintree/TransparentRedirect.php'); -require_once('Braintree/Util.php'); -require_once('Braintree/Version.php'); -require_once('Braintree/Xml.php'); -require_once('Braintree/Error/Codes.php'); -require_once('Braintree/Error/ErrorCollection.php'); -require_once('Braintree/Error/Validation.php'); -require_once('Braintree/Error/ValidationErrorCollection.php'); -require_once('Braintree/Exception/Authentication.php'); -require_once('Braintree/Exception/Authorization.php'); -require_once('Braintree/Exception/Configuration.php'); -require_once('Braintree/Exception/DownForMaintenance.php'); -require_once('Braintree/Exception/ForgedQueryString.php'); -require_once('Braintree/Exception/InvalidSignature.php'); -require_once('Braintree/Exception/NotFound.php'); -require_once('Braintree/Exception/ServerError.php'); -require_once('Braintree/Exception/SSLCertificate.php'); -require_once('Braintree/Exception/SSLCaFileNotFound.php'); -require_once('Braintree/Exception/Unexpected.php'); -require_once('Braintree/Exception/UpgradeRequired.php'); -require_once('Braintree/Exception/ValidationsFailed.php'); -require_once('Braintree/Result/CreditCardVerification.php'); -require_once('Braintree/Result/Error.php'); -require_once('Braintree/Result/Successful.php'); -require_once('Braintree/Test/CreditCardNumbers.php'); -require_once('Braintree/Test/MerchantAccount.php'); -require_once('Braintree/Test/TransactionAmounts.php'); -require_once('Braintree/Test/VenmoSdk.php'); -require_once('Braintree/Transaction/AddressDetails.php'); -require_once('Braintree/Transaction/CreditCardDetails.php'); -require_once('Braintree/Transaction/CustomerDetails.php'); -require_once('Braintree/Transaction/StatusDetails.php'); -require_once('Braintree/Transaction/SubscriptionDetails.php'); -require_once('Braintree/WebhookNotification.php'); -require_once('Braintree/WebhookTesting.php'); -require_once('Braintree/Xml/Generator.php'); -require_once('Braintree/Xml/Parser.php'); -require_once('Braintree/CreditCardVerification.php'); -require_once('Braintree/CreditCardVerificationSearch.php'); -require_once('Braintree/PartnerMerchant.php'); +require_once(__DIR__ . DIRECTORY_SEPARATOR . 'autoload.php'); -if (version_compare(PHP_VERSION, '5.2.1', '<')) { - throw new Braintree_Exception('PHP version >= 5.2.1 required'); +if (version_compare(PHP_VERSION, '7.3.0', '<')) { + throw new Braintree\Exception('PHP version >= 7.3.0 required'); } - -function requireDependencies() { - $requiredExtensions = array('xmlwriter', 'SimpleXML', 'openssl', 'dom', 'hash', 'curl'); - foreach ($requiredExtensions AS $ext) { - if (!extension_loaded($ext)) { - throw new Braintree_Exception('The Braintree library requires the ' . $ext . ' extension.'); +// phpcs:ignore +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.'); + } } } } -requireDependencies(); +Braintree::requireDependencies(); diff --git a/lib/Braintree/AccountUpdaterDailyReport.php b/lib/Braintree/AccountUpdaterDailyReport.php new file mode 100644 index 0000000..39cd3fd --- /dev/null +++ b/lib/Braintree/AccountUpdaterDailyReport.php @@ -0,0 +1,43 @@ +_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) . ']'; + } +} diff --git a/lib/Braintree/AchMandate.php b/lib/Braintree/AchMandate.php new file mode 100644 index 0000000..3b5b3e6 --- /dev/null +++ b/lib/Braintree/AchMandate.php @@ -0,0 +1,53 @@ +_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; + } +} diff --git a/lib/Braintree/AddOn.php b/lib/Braintree/AddOn.php index 98a4c47..bd2d143 100644 --- a/lib/Braintree/AddOn.php +++ b/lib/Braintree/AddOn.php @@ -1,22 +1,43 @@ $response['addOns']); - - return Braintree_Util::extractAttributeAsArray( - $addOns, - 'addOn' - ); - } +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(); + } } diff --git a/lib/Braintree/AddOnGateway.php b/lib/Braintree/AddOnGateway.php new file mode 100644 index 0000000..565ddc2 --- /dev/null +++ b/lib/Braintree/AddOnGateway.php @@ -0,0 +1,53 @@ +_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' + ); + } +} diff --git a/lib/Braintree/Address.php b/lib/Braintree/Address.php index 821959d..79fcbfa 100644 --- a/lib/Braintree/Address.php +++ b/lib/Braintree/Address.php @@ -1,13 +1,9 @@ $attribs) - ); - } - - /** - * attempts the create operation assuming all data will validate - * returns a Braintree_Address object instead of a Result - * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError - */ - public static function createNoValidate($attribs) - { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - - } - - /** - * delete an address by id - * - * @param mixed $customerOrId - * @param string $addressId - */ - public static function delete($customerOrId = null, $addressId = null) - { - self::_validateId($addressId); - $customerId = self::_determineCustomerId($customerOrId); - Braintree_Http::delete( - '/customers/' . $customerId . '/addresses/' . $addressId - ); - return new Braintree_Result_Successful(); - } - - /** - * find an address by id - * - * Finds the address with the given addressId that is associated - * to the given customerOrId. - * If the address cannot be found, a NotFound exception will be thrown. - * - * - * @access public - * @param mixed $customerOrId - * @param string $addressId - * @return object Braintree_Address - * @throws Braintree_Exception_NotFound - */ - public static function find($customerOrId, $addressId) - { - - $customerId = self::_determineCustomerId($customerOrId); - self::_validateId($addressId); - - try { - $response = Braintree_Http::get( - '/customers/' . $customerId . '/addresses/' . $addressId - ); - return self::factory($response['address']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'address for customer ' . $customerId . - ' with id ' . $addressId . ' not found.' - ); - } - - } - - /** - * returns false if comparing object is not a Braintree_Address, - * or is a Braintree_Address with a different id + * 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 Braintree_Address) ? + return !($other instanceof self) ? false : ($this->id === $other->id && $this->customerId === $other->customerId); } - /** - * updates the address record - * - * if calling this method in static 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 static call) - * @param string $addressId (only used in static call) - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function update($customerOrId, $addressId, $attributes) - { - self::_validateId($addressId); - $customerId = self::_determineCustomerId($customerOrId); - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - - $response = Braintree_Http::put( - '/customers/' . $customerId . '/addresses/' . $addressId, - array('address' => $attributes) - ); - - return self::_verifyGatewayResponse($response); - - } - - /** - * update an address record, assuming validations will pass - * - * if calling this method in static 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 object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Address::update() - */ - public static function updateNoValidate($customerOrId, $addressId, $attributes) - { - $result = self::update($customerOrId, $addressId, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - /** - * creates a full array signature of a valid create request - * @return array gateway create request format - */ - public static function createSignature() - { - return array( - '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(); - - } - /** * create a printable representation of the object as: * ClassName[property=value, property=value] * @ignore - * @return var + * @return string */ - public function __toString() + public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) . ']'; } /** @@ -227,7 +61,7 @@ public function __toString() * @ignore * @access protected * @param array $addressAttribs array of address data - * @return none + * @return void */ protected function _initialize($addressAttribs) { @@ -236,117 +70,80 @@ protected function _initialize($addressAttribs) } /** - * verifies that a valid address id is being used + * factory method: returns an instance of Address + * to the requesting method, with populated properties * @ignore - * @param string $id address id - * @throws InvalidArgumentException + * @return Address */ - private static function _validateId($id = null) + public static function factory($attributes) { - 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.' - ); - } + $instance = new self(); + $instance->_initialize($attributes); + return $instance; } + + // static methods redirecting to gateway + /** - * verifies that a valid customer id is being used - * @ignore - * @param string $id customer id - * @throws InvalidArgumentException + * + * @param array $attribs + * @return Address */ - private static function _validateCustomerId($id = null) + public static function create($attribs) { - 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.' - ); - } - + return Configuration::gateway()->address()->create($attribs); } /** - * determines if a string id or Customer object was passed - * @ignore - * @param mixed $customerOrId - * @return string customerId + * + * @param array $attribs + * @return Address */ - private static function _determineCustomerId($customerOrId) + public static function createNoValidate($attribs) { - $customerId = ($customerOrId instanceof Braintree_Customer) ? $customerOrId->id : $customerOrId; - self::_validateCustomerId($customerId); - return $customerId; - + return Configuration::gateway()->address()->createNoValidate($attribs); } - /* private class methods */ /** - * sends the create request to the gateway - * @ignore - * @param string $url - * @param array $params - * @return mixed + * + * @param Customer|int $customerOrId + * @param int $addressId + * @throws InvalidArgumentException + * @return Result\Successful */ - private static function _doCreate($url, $params) + public static function delete($customerOrId = null, $addressId = null) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - + return Configuration::gateway()->address()->delete($customerOrId, $addressId); } /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Address object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected + * @param Customer|int $customerOrId + * @param int $addressId + * @throws Exception\NotFound + * @return Address */ - private static function _verifyGatewayResponse($response) + public static function find($customerOrId, $addressId) { - if (isset($response['address'])) { - // return a populated instance of Braintree_Address - return new Braintree_Result_Successful( - self::factory($response['address']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected address or apiErrorResponse" - ); - } - + return Configuration::gateway()->address()->find($customerOrId, $addressId); } /** - * factory method: returns an instance of Braintree_Address - * to the requesting method, with populated properties - * @ignore - * @return object instance of Braintree_Address + * + * @param Customer|int $customerOrId + * @param int $addressId + * @param array $attributes + * @throws Exception\Unexpected + * @return Result\Successful|Result\Error */ - public static function factory($attributes) + public static function update($customerOrId, $addressId, $attributes) { - $instance = new self(); - $instance->_initialize($attributes); - return $instance; + return Configuration::gateway()->address()->update($customerOrId, $addressId, $attributes); + } + public static function updateNoValidate($customerOrId, $addressId, $attributes) + { + return Configuration::gateway()->address()->updateNoValidate($customerOrId, $addressId, $attributes); } } diff --git a/lib/Braintree/AddressGateway.php b/lib/Braintree/AddressGateway.php new file mode 100644 index 0000000..fc5fed0 --- /dev/null +++ b/lib/Braintree/AddressGateway.php @@ -0,0 +1,305 @@ +_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 addressId that is associated + * to the given customerOrId. + * 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() + { + 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']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected address or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/ApplePayCard.php b/lib/Braintree/ApplePayCard.php new file mode 100644 index 0000000..d8f5128 --- /dev/null +++ b/lib/Braintree/ApplePayCard.php @@ -0,0 +1,103 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @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); + } +} diff --git a/lib/Braintree/ApplePayGateway.php b/lib/Braintree/ApplePayGateway.php new file mode 100644 index 0000000..9b8e390 --- /dev/null +++ b/lib/Braintree/ApplePayGateway.php @@ -0,0 +1,57 @@ +_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(); + } elseif (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'); + } elseif (array_key_exists('apiErrorResponse', $response)) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected('expected response or apiErrorResponse'); + } + } +} diff --git a/lib/Braintree/ApplePayOptions.php b/lib/Braintree/ApplePayOptions.php new file mode 100644 index 0000000..38301d8 --- /dev/null +++ b/lib/Braintree/ApplePayOptions.php @@ -0,0 +1,28 @@ +_initialize($attributes); + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} diff --git a/lib/Braintree/AuthorizationAdjustment.php b/lib/Braintree/AuthorizationAdjustment.php new file mode 100644 index 0000000..49f1ce3 --- /dev/null +++ b/lib/Braintree/AuthorizationAdjustment.php @@ -0,0 +1,35 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($authorizationAdjustmentAttribs) + { + $this->_attributes = $authorizationAdjustmentAttribs; + } + + public function __toString() + { + return __CLASS__ . '[' . Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/Base.php b/lib/Braintree/Base.php new file mode 100644 index 0000000..d1e08ad --- /dev/null +++ b/lib/Braintree/Base.php @@ -0,0 +1,107 @@ +_attributes['globalId'])) { + $this->_attributes['graphQLId'] = $this->_attributes['globalId']; + } + 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 existence of a property stored in the private $_attributes property + * + * @ignore + * @param string $name + * @return boolean + */ + public function __isset($name) + { + return isset($this->_attributes[$name]); + } + + /** + * 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; + } + + /** + * Implementation of to an Array + * + * @ignore + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + if (!is_array($value)) { + return method_exists($value, 'toArray') ? $value->toArray() : $value; + } else { + return $value; + } + }, $this->_attributes); + } +} diff --git a/lib/Braintree/BinData.php b/lib/Braintree/BinData.php new file mode 100644 index 0000000..50802ee --- /dev/null +++ b/lib/Braintree/BinData.php @@ -0,0 +1,40 @@ +_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) . ']'; + } +} diff --git a/lib/Braintree/ClientToken.php b/lib/Braintree/ClientToken.php new file mode 100644 index 0000000..d7dc174 --- /dev/null +++ b/lib/Braintree/ClientToken.php @@ -0,0 +1,49 @@ +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(); + } +} diff --git a/lib/Braintree/ClientTokenGateway.php b/lib/Braintree/ClientTokenGateway.php new file mode 100644 index 0000000..99aa747 --- /dev/null +++ b/lib/Braintree/ClientTokenGateway.php @@ -0,0 +1,128 @@ +_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" + ); + } + } +} diff --git a/lib/Braintree/Collection.php b/lib/Braintree/Collection.php index 58293c3..bc0a4cc 100644 --- a/lib/Braintree/Collection.php +++ b/lib/Braintree/Collection.php @@ -1,16 +1,15 @@ = $this->count()) + if ($index >= $this->count()) { throw new OutOfRangeException('Index out of range'); + } $this->_collection[$index] = $value; } @@ -56,8 +57,9 @@ public function set($index, $value) */ public function remove($index) { - if($index >= $this->count()) + if ($index >= $this->count()) { throw new OutOfRangeException('Index out of range'); + } array_splice($this->_collection, $index, 1); } @@ -70,8 +72,9 @@ public function remove($index) */ public function get($index) { - if($index >= $this->count()) + if ($index >= $this->count()) { throw new OutOfRangeException('Index out of range'); + } return $this->_collection[$index]; } @@ -83,8 +86,9 @@ public function get($index) */ public function exists($index) { - if($index >= $this->count()) + if ($index >= $this->count()) { return false; + } return true; } @@ -155,5 +159,4 @@ public function offsetExists($offset) { return $this->exists($offset); } - } diff --git a/lib/Braintree/Configuration.php b/lib/Braintree/Configuration.php index 2ba03ce..351e18d 100644 --- a/lib/Braintree/Configuration.php +++ b/lib/Braintree/Configuration.php @@ -1,236 +1,499 @@ - $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 == 'proxyHost') { + $this->_proxyHost = $value; + } + if ($kind == 'proxyPort') { + $this->_proxyPort = $value; + } + if ($kind == 'proxyType') { + $this->_proxyType = $value; + } + if ($kind == 'proxyUser') { + $this->_proxyUser = $value; + } + if ($kind == 'proxyPassword') { + $this->_proxyPassword = $value; + } + if ($kind == 'timeout') { + $this->_timeout = $value; + } + if ($kind == 'sslVersion') { + $this->_sslVersion = $value; + } + if ($kind == 'acceptGzipEncoding') { + $this->_acceptGzipEncoding = $value; + } + } + + if (isset($attribs['clientId']) || isset($attribs['accessToken'])) { + // phpcs:ignore Generic.Files.LineLength + if (isset($attribs['environment']) || isset($attribs['merchantId']) || isset($attribs['publicKey']) || isset($attribs['privateKey'])) { + // phpcs:ignore Generic.Files.LineLength + 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(); + } + } /** - * @var array array of config properties - * @access protected - * @static + * resets configuration to default + * @access public */ - private static $_cache = array( - 'environment' => '', - 'merchantId' => '', - 'publicKey' => '', - 'privateKey' => '', - ); + 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. * - * @access protected - * @static - * @var array valid environments, used for validation + * @param integer $value If provided, sets the read timeout + * @return integer The read timeout used for connecting to Braintree */ - private static $_validEnvironments = array( - 'development', - 'sandbox', - 'production', - 'qa', - ); + public static function timeout($value = null) + { + if (empty($value)) { + return self::$global->getTimeout(); + } + self::$global->setTimeout($value); + } /** - * resets configuration to default - * @access public - * @static + * 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 reset() + public static function sslVersion($value = null) { - self::$_cache = array ( - 'environment' => '', - 'merchantId' => '', - 'publicKey' => '', - 'privateKey' => '', - ); + if (empty($value)) { + return self::$global->getSslVersion(); + } + self::$global->setSslVersion($value); } /** - * performs sanity checks when config settings are being set + * Sets or gets the proxy host to use for connecting to Braintree * - * @ignore - * @access protected - * @param string $key name of config setting - * @param string $value value to set - * @throws InvalidArgumentException - * @throws Braintree_Exception_Configuration - * @static - * @return boolean + * @param string $value If provided, sets the proxy host + * @return string The proxy host used for connecting to Braintree */ - private static function validate($key=null, $value=null) + public static function proxyHost($value = null) { - if (empty($key) && empty($value)) { - throw new InvalidArgumentException('nothing to validate'); + if (empty($value)) { + return self::$global->getProxyHost(); } + self::$global->setProxyHost($value); + } - if ($key === 'environment' && - !in_array($value, self::$_validEnvironments) ) { - throw new Braintree_Exception_Configuration('"' . - $value . '" is not a valid environment.'); + /** + * 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); + } - if (!isset(self::$_cache[$key])) { - throw new Braintree_Exception_Configuration($key . - ' is not a valid configuration setting.'); + /** + * 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); + } + public static function proxyUser($value = null) + { if (empty($value)) { - throw new InvalidArgumentException($key . ' cannot be empty.'); + return self::$global->getProxyUser(); } + self::$global->setProxyUser($value); + } - return true; + public static function proxyPassword($value = null) + { + if (empty($value)) { + return self::$global->getProxyPassword(); + } + self::$global->setProxyPassword($value); } - private static function set($key, $value) + /** + * Specify if the HTTP client is able to decode gzipped responses. + * + * // phpcs:ignore Generic.Files.LineLength + * @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) { - // this method will raise an exception on invalid data - self::validate($key, $value); - // set the value in the cache - self::$_cache[$key] = $value; + if (is_null($value)) { + return self::$global->getAcceptGzipEncoding(); + } + self::$global->setAcceptGzipEncoding($value); + } + public static function assertGlobalHasAccessTokenOrKeys() + { + self::$global->assertHasAccessTokenOrKeys(); } - private static function get($key) + public function assertHasAccessTokenOrKeys() { - // throw an exception if the value hasn't been set - if (isset(self::$_cache[$key]) && - (empty(self::$_cache[$key]))) { - throw new Braintree_Exception_Configuration( - $key.' needs to be set' - ); + if (empty($this->_accessToken)) { + if (empty($this->_merchantId)) { + // phpcs:ignore Generic.Files.LineLength + throw new Exception\Configuration('Braintree\\Configuration::merchantId needs to be set (or accessToken needs to be passed to Braintree\\Gateway).'); + } elseif (empty($this->_environment)) { + throw new Exception\Configuration('Braintree\\Configuration::environment needs to be set.'); + } elseif (empty($this->_publicKey)) { + throw new Exception\Configuration('Braintree\\Configuration::publicKey needs to be set.'); + } elseif (empty($this->_privateKey)) { + throw new Exception\Configuration('Braintree\\Configuration::privateKey needs to be set.'); + } } + } + + public function assertHasClientCredentials() + { + $this->assertHasClientId(); + $this->assertHasClientSecret(); + } - if (array_key_exists($key, self::$_cache)) { - return self::$_cache[$key]; + public function assertHasClientId() + { + if (empty($this->_clientId)) { + throw new Exception\Configuration('clientId needs to be passed to Braintree\\Gateway.'); } + } - // return null by default to prevent __set from overloading - return null; + 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; + } - private static function setOrGet($name, $value = null) + /** + * Do not use this method directly. Pass in the environment to the constructor. + */ + public function setEnvironment($value) { - if (!empty($value) && is_array($value)) { - $value = $value[0]; - } - if (!empty($value)) { - self::set($name, $value); - } else { - return self::get($name); - } - return true; + $this->_environment = $value; } - /**#@+ - * sets or returns the property after validation - * @access public - * @static - * @param string $value pass a string to set, empty to get - * @return mixed returns true on set + + public function getMerchantId() + { + return $this->_merchantId; + } + + /** + * Do not use this method directly. Pass in the merchantId to the constructor. */ - public static function environment($value = null) + public function setMerchantId($value) { - return self::setOrGet(__FUNCTION__, $value); + $this->_merchantId = $value; } - public static function merchantId($value = null) + public function getPublicKey() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_publicKey; } - public static function publicKey($value = null) + public function getClientId() { - return self::setOrGet(__FUNCTION__, $value); + return $this->_clientId; } - public static function privateKey($value = null) + /** + * 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; + } + + public function getSslVersion() + { + return $this->_sslVersion; + } + + public function getAcceptGzipEncoding() + { + return $this->_acceptGzipEncoding; + } + + private function setAcceptGzipEncoding($value) { - return self::setOrGet(__FUNCTION__, $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 full merchant URL based on config values + * returns the base braintree gateway URL based on config values * * @access public - * @static * @param none - * @return string merchant URL + * @return string braintree gateway URL */ - public static function merchantUrl() + public function baseUrl() { - return self::baseUrl() . - self::merchantPath(); + return sprintf('%s://%s:%d', $this->protocol(), $this->serverName(), $this->portNumber()); } /** - * returns the base braintree gateway URL based on config values + * returns the base URL for Braintree's GraphQL endpoint based on config values * * @access public - * @static * @param none - * @return string braintree gateway URL + * @return string Braintree GraphQL URL */ - public static function baseUrl() + public function graphQLBaseUrl() { - return self::protocol() . '://' . - self::serverName() . ':' . - self::portNumber(); + return sprintf('%s://%s:%d/graphql', $this->protocol(), $this->graphQLServerName(), $this->graphQLPortNumber()); } /** * sets the merchant path based on merchant ID * * @access protected - * @static * @param none * @return string merchant path uri */ - public static function merchantPath() + public function merchantPath() { - return '/merchants/'.self::merchantId(); + return '/merchants/' . $this->_merchantId; } /** * sets the physical path for the location of the CA certs * * @access public - * @static * @param none * @return string filepath */ - public static function caFile($sslPath = NULL) + public function caFile($sslPath = null) { $sslPath = $sslPath ? $sslPath : DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'ssl' . DIRECTORY_SEPARATOR; + $caPath = __DIR__ . $sslPath . 'api_braintreegateway_com.ca.crt'; - $caPath = realpath( - dirname(__FILE__) . - $sslPath . 'api_braintreegateway_com.ca.crt' - ); - - if (!file_exists($caPath)) - { - throw new Braintree_Exception_SSLCaFileNotFound(); + if (!file_exists($caPath)) { + throw new Exception\SSLCaFileNotFound(); } return $caPath; @@ -240,84 +503,151 @@ public static function caFile($sslPath = NULL) * returns the port number depending on environment * * @access public - * @static * @param none * @return int portnumber */ - public static function portNumber() + public function portNumber() { - if (self::sslOn()) { + 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; + } + + /** + * Specifies whether or not a proxy is properly configured + * + * @return bool true if a proxy is configured properly, false if not + */ + public function isUsingProxy() + { + $proxyHost = $this->getProxyHost(); + $proxyPort = $this->getProxyPort(); + return !empty($proxyHost) && !empty($proxyPort); + } + + /** + * 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 function isAuthenticatedProxy() + { + $proxyUser = $this->getProxyUser(); + $proxyPwd = $this->getProxyPassword(); + return !empty($proxyUser) && !empty($proxyPwd); + } + /** * returns http protocol depending on environment * * @access public - * @static * @param none * @return string http || https */ - public static function protocol() + public function protocol() { - return self::sslOn() ? 'https' : 'http'; + return $this->sslOn() ? 'https' : 'http'; } /** * returns gateway server name depending on environment * * @access public - * @static * @param none * @return string server domain name */ - public static function serverName() - { - switch(self::environment()) { - case 'production': - $serverName = 'api.braintreegateway.com'; - break; - case 'qa': - $serverName = 'qa.braintreegateway.com'; - break; - case 'sandbox': - $serverName = 'api.sandbox.braintreegateway.com'; - break; - case 'development': - default: - $serverName = 'localhost'; - break; + 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; + } + /** * returns boolean indicating SSL is on or off for this session, * depending on environment * * @access public - * @static * @param none * @return boolean */ - public static function sslOn() - { - switch(self::environment()) { - case 'development': - $ssl = false; - break; - case 'production': - case 'qa': - case 'sandbox': - default: - $ssl = true; - break; + 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; + return $ssl; } /** @@ -326,9 +656,9 @@ public static function sslOn() * @param string $message * */ - public static function logMessage($message) + public function logMessage($message) { error_log('[Braintree] ' . $message); } - } +Configuration::reset(); diff --git a/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php b/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php new file mode 100644 index 0000000..6175049 --- /dev/null +++ b/lib/Braintree/ConnectedMerchantPayPalStatusChanged.php @@ -0,0 +1,37 @@ +_initialize($attributes); + $instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId']; + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} diff --git a/lib/Braintree/ConnectedMerchantStatusTransitioned.php b/lib/Braintree/ConnectedMerchantStatusTransitioned.php new file mode 100644 index 0000000..7a90d97 --- /dev/null +++ b/lib/Braintree/ConnectedMerchantStatusTransitioned.php @@ -0,0 +1,37 @@ +_initialize($attributes); + $instance->_attributes['merchantId'] = $instance->_attributes['merchantPublicId']; + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} diff --git a/lib/Braintree/CredentialsParser.php b/lib/Braintree/CredentialsParser.php new file mode 100644 index 0000000..394fe8a --- /dev/null +++ b/lib/Braintree/CredentialsParser.php @@ -0,0 +1,152 @@ + $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)) { + // phpcs:ignore Generic.Files.LineLength + $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) { + $message = 'Incorrect ' . $credentialType . ' format. Expected: type$environment$token'; + throw new Exception\Configuration($message); + } + + $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) { + $message = 'Incorrect accessToken syntax. Expected: type$environment$merchant_id$token'; + throw new Exception\Configuration($message); + } + + $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; + } +} diff --git a/lib/Braintree/CreditCard.php b/lib/Braintree/CreditCard.php index 74ecff3..54f7d34 100644 --- a/lib/Braintree/CreditCard.php +++ b/lib/Braintree/CreditCard.php @@ -1,40 +1,54 @@ == More information == * - * For more detailed information on CreditCards, see {@link http://www.braintreepayments.com/gateway/credit-card-api http://www.braintreepaymentsolutions.com/gateway/credit-card-api}
- * For more detailed information on CreditCard verifications, see {@link http://www.braintreepayments.com/gateway/credit-card-verification-api http://www.braintreepaymentsolutions.com/gateway/credit-card-verification-api} + * 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}
+ * 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 - * @copyright 2010 Braintree Payment Solutions * - * @property-read string $billingAddress + * @property-read \Braintree\Address $billingAddress * @property-read string $bin * @property-read string $cardType * @property-read string $cardholderName - * @property-read string $createdAt + * @property-read string $commercial + * @property-read string $countryOfIssuance + * @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 $isNetworkTokenized * @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 $updatedAt + * @property-read string $uniqueNumberIdentifier + * @property-read \DateTime $updatedAt + * @property-read \Braintree\CreditCardVerification|null $verification */ -class Braintree_CreditCard extends Braintree +// phpcs:enable Generic.Files.LineLength + +class CreditCard extends Base { // Card Type const AMEX = 'American Express'; @@ -42,18 +56,20 @@ class Braintree_CreditCard extends Braintree 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"; + // Credit card origination location + const INTERNATIONAL = "international"; + const US = "us"; const PREPAID_YES = 'Yes'; const PREPAID_NO = 'No'; @@ -81,523 +97,231 @@ class Braintree_CreditCard extends Braintree const COUNTRY_OF_ISSUANCE_UNKNOWN = "Unknown"; const ISSUING_BANK_UNKNOWN = "Unknown"; + const PRODUCT_ID_UNKNOWN = "Unknown"; - public static function create($attribs) - { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/payment_methods', array('credit_card' => $attribs)); - } - + /* instance methods */ /** - * attempts the create operation assuming all data will validate - * returns a Braintree_CreditCard object instead of a Result + * returns false if default is null or false * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @return boolean */ - public static function createNoValidate($attribs) + public function isDefault() { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); + return $this->default; } + /** - * create a customer from a TransparentRedirect operation + * checks whether the card is expired based on the current date * - * @access public - * @param array $attribs - * @return object + * @return boolean */ - public static function createFromTransparentRedirect($queryString) + public function isExpired() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/payment_methods/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return $this->expired; } /** + * checks whether the card is associated with venmo sdk * - * @access public - * @param none - * @return string + * @return boolean */ - public static function createCreditCardUrl() + public function isVenmoSdk() { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/payment_methods/all/create_via_transparent_redirect_request'; + return $this->venmoSdk; } /** - * returns a ResourceCollection of expired credit cards - * @return object ResourceCollection + * sets instance properties from an array of values + * + * @access protected + * @param array $creditCardAttribs array of creditcard data + * @return void */ - public static function expired() + protected function _initialize($creditCardAttribs) { - $response = Braintree_Http::post("/payment_methods/all/expired_ids"); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetchExpired', - 'methodArgs' => array() - ); - - return new Braintree_ResourceCollection($response, $pager); - } + // set the attributes + $this->_attributes = $creditCardAttribs; - public static function fetchExpired($ids) - { - $response = Braintree_Http::post("/payment_methods/all/expired", array('search' => array('ids' => $ids))); + // map each address into its own object + $billingAddress = isset($creditCardAttribs['billingAddress']) ? + Address::factory($creditCardAttribs['billingAddress']) : + null; - return Braintree_Util::extractattributeasarray( - $response['paymentMethods'], - 'creditCard' - ); - } - /** - * returns a ResourceCollection of credit cards expiring between start/end - * - * @return object ResourceCollection - */ - public static function expiringBetween($startDate, $endDate) - { - $queryPath = '/payment_methods/all/expiring_ids?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); - $response = Braintree_Http::post($queryPath); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetchExpiring', - 'methodArgs' => array($startDate, $endDate) - ); - - return new Braintree_ResourceCollection($response, $pager); - } + $subscriptionArray = []; + if (isset($creditCardAttribs['subscriptions'])) { + foreach ($creditCardAttribs['subscriptions'] as $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } - public static function fetchExpiring($startDate, $endDate, $ids) - { - $queryPath = '/payment_methods/all/expiring?start=' . date('mY', $startDate) . '&end=' . date('mY', $endDate); - $response = Braintree_Http::post($queryPath, array('search' => array('ids' => $ids))); + $this->_set('subscriptions', $subscriptionArray); + $this->_set('billingAddress', $billingAddress); + $this->_set('expirationDate', $this->expirationMonth . '/' . $this->expirationYear); + $this->_set('maskedNumber', $this->bin . '******' . $this->last4); - return Braintree_Util::extractAttributeAsArray( - $response['paymentMethods'], - 'creditCard' - ); - } + if (isset($creditCardAttribs['verifications']) && count($creditCardAttribs['verifications']) > 0) { + $verifications = $creditCardAttribs['verifications']; + usort($verifications, [$this, '_compareCreatedAtOnVerifications']); - /** - * find a creditcard by token - * - * @access public - * @param string $token credit card unique id - * @return object Braintree_CreditCard - * @throws Braintree_Exception_NotFound - */ - public static function find($token) - { - self::_validateId($token); - try { - $response = Braintree_Http::get('/payment_methods/'.$token); - return self::factory($response['creditCard']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'credit card with token ' . $token . ' not found' - ); + $this->_set('verification', CreditCardVerification::factory($verifications[0])); } - } - /** - * create a credit on the card for the passed transaction - * - * @access public - * @param array $attribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function credit($token, $transactionAttribs) + private function _compareCreatedAtOnVerifications($verificationAttrib1, $verificationAttrib2) { - self::_validateId($token); - return Braintree_Transaction::credit( - array_merge( - $transactionAttribs, - array('paymentMethodToken' => $token) - ) - ); + return ($verificationAttrib2['createdAt'] < $verificationAttrib1['createdAt']) ? -1 : 1; } /** - * create a credit on this card, assuming validations will pass - * - * returns a Braintree_Transaction object on success + * returns false if comparing object is not a CreditCard, + * or is a CreditCard with a different id * - * @access public - * @param array $attribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationError + * @param object $otherCreditCard customer to compare against + * @return boolean */ - public static function creditNoValidate($token, $transactionAttribs) + public function isEqual($otherCreditCard) { - $result = self::credit($token, $transactionAttribs); - return self::returnObjectOrThrowException('Transaction', $result); + return !($otherCreditCard instanceof self) ? false : $this->token === $otherCreditCard->token; } /** - * create a new sale for the current card - * - * @param string $token - * @param array $transactionAttribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - * @see Braintree_Transaction::sale() + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string */ - public static function sale($token, $transactionAttribs) + public function __toString() { - self::_validateId($token); - return Braintree_Transaction::sale( - array_merge( - $transactionAttribs, - array('paymentMethodToken' => $token) - ) - ); + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; } /** - * create a new sale using this card, assuming validations will pass - * - * returns a Braintree_Transaction object on success + * factory method: returns an instance of CreditCard + * to the requesting method, with populated properties * - * @access public - * @param array $transactionAttribs - * @param string $token - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Transaction::sale() + * @ignore + * @return CreditCard */ - public static function saleNoValidate($token, $transactionAttribs) + public static function factory($attributes) { - $result = self::sale($token, $transactionAttribs); - return self::returnObjectOrThrowException('Transaction', $result); - } + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; - /** - * updates the creditcard record - * - * if calling this method in static context, $token - * is the 2nd attribute. $token is not sent in object context. - * - * @access public - * @param array $attributes - * @param string $token (optional) - * @return object Braintree_Result_Successful or Braintree_Result_Error - */ - public static function update($token, $attributes) - { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - self::_validateId($token); - return self::_doUpdate('put', '/payment_methods/' . $token, array('creditCard' => $attributes)); + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; } - /** - * update a creditcard record, assuming validations will pass - * - * if calling this method in static context, $token - * is the 2nd attribute. $token is not sent in object context. - * returns a Braintree_CreditCard object on success - * - * @access public - * @param array $attributes - * @param string $token - * @return object Braintree_CreditCard - * @throws Braintree_Exception_ValidationsFailed - */ - public static function updateNoValidate($token, $attributes) - { - $result = self::update($token, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - /** - * - * @access public - * @param none - * @return string - */ - public static function updateCreditCardUrl() - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/payment_methods/all/update_via_transparent_redirect_request'; - } - /** - * update a customer from a TransparentRedirect operation - * - * @access public - * @param array $attribs - * @return object - */ - public static function updateFromTransparentRedirect($queryString) + // static methods redirecting to gateway + + public static function create($attribs) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doUpdate( - 'post', - '/payment_methods/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->creditCard()->create($attribs); } - /* instance methods */ - /** - * returns false if default is null or false - * - * @return boolean - */ - public function isDefault() + public static function createNoValidate($attribs) { - return $this->default; + return Configuration::gateway()->creditCard()->createNoValidate($attribs); } - /** - * checks whether the card is expired based on the current date - * - * @return boolean - */ - public function isExpired() + public static function createCreditCardUrl() { - return $this->expired; + return Configuration::gateway()->creditCard()->createCreditCardUrl(); } - /** - * checks whether the card is associated with venmo sdk - * - * @return boolean - */ - public function isVenmoSdk() + public static function expired() { - return $this->venmoSdk; + return Configuration::gateway()->creditCard()->expired(); } - public static function delete($token) + public static function fetchExpired($ids) { - self::_validateId($token); - Braintree_Http::delete('/payment_methods/' . $token); - return new Braintree_Result_Successful(); + return Configuration::gateway()->creditCard()->fetchExpired($ids); } - /** - * sets instance properties from an array of values - * - * @access protected - * @param array $creditCardAttribs array of creditcard data - * @return none - */ - protected function _initialize($creditCardAttribs) + public static function expiringBetween($startDate, $endDate) { - // set the attributes - $this->_attributes = $creditCardAttribs; - - // map each address into its own object - $billingAddress = isset($creditCardAttribs['billingAddress']) ? - Braintree_Address::factory($creditCardAttribs['billingAddress']) : - null; - - $subscriptionArray = array(); - if (isset($creditCardAttribs['subscriptions'])) { - foreach ($creditCardAttribs['subscriptions'] AS $subscription) { - $subscriptionArray[] = Braintree_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); + return Configuration::gateway()->creditCard()->expiringBetween($startDate, $endDate); } - /** - * returns false if comparing object is not a Braintree_CreditCard, - * or is a Braintree_CreditCard with a different id - * - * @param object $otherCreditCard customer to compare against - * @return boolean - */ - public function isEqual($otherCreditCard) + public static function fetchExpiring($startDate, $endDate, $ids) { - return !($otherCreditCard instanceof Braintree_CreditCard) ? false : $this->token === $otherCreditCard->token; + return Configuration::gateway()->creditCard()->fetchExpiring($startDate, $endDate, $ids); } - private static function baseOptions() + public static function find($token) { - return array('makeDefault', 'verificationMerchantAccountId', 'verifyCard', 'venmoSdkSession'); + return Configuration::gateway()->creditCard()->find($token); } - private static function baseSignature($options) + public static function fromNonce($nonce) { - return array( - 'billingAddressId', 'cardholderName', 'cvv', 'number', 'deviceSessionId', - 'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode', - 'deviceData', 'fraudMerchantId', - array('options' => $options), - array( - 'billingAddress' => array( - 'firstName', - 'lastName', - 'company', - 'countryCodeAlpha2', - 'countryCodeAlpha3', - 'countryCodeNumeric', - 'countryName', - 'extendedAddress', - 'locality', - 'region', - 'postalCode', - 'streetAddress' - ), - ), - ); + return Configuration::gateway()->creditCard()->fromNonce($nonce); } - public static function createSignature() + public static function credit($token, $transactionAttribs) { - $options = self::baseOptions(); - $options[] = "failOnDuplicatePaymentMethod"; - $signature = self::baseSignature($options); - $signature[] = 'customerId'; - return $signature; + return Configuration::gateway()->creditCard()->credit($token, $transactionAttribs); } - public static function updateSignature() + public static function creditNoValidate($token, $transactionAttribs) { - $signature = self::baseSignature(self::baseOptions()); - - $updateExistingBillingSignature = array( - array( - 'options' => array( - '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; + return Configuration::gateway()->creditCard()->creditNoValidate($token, $transactionAttribs); } - /** - * sends the create request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - public static function _doCreate($url, $params) + public static function sale($token, $transactionAttribs) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->sale($token, $transactionAttribs); } - /** - * create a printable representation of the object as: - * ClassName[property=value, property=value] - * @return string - */ - public function __toString() + public static function saleNoValidate($token, $transactionAttribs) { - return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + return Configuration::gateway()->creditCard()->saleNoValidate($token, $transactionAttribs); } - /** - * verifies that a valid credit card token is being used - * @ignore - * @param string $token - * @throws InvalidArgumentException - */ - private static function _validateId($token = null) + public static function update($token, $attributes) { - if (empty($token)) { - throw new InvalidArgumentException( - 'expected credit card id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $token)) { - throw new InvalidArgumentException( - $token . ' is an invalid credit card id.' - ); - } + return Configuration::gateway()->creditCard()->update($token, $attributes); } - /** - * sends the update request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - private static function _doUpdate($httpVerb, $url, $params) + public static function updateNoValidate($token, $attributes) { - $response = Braintree_Http::$httpVerb($url, $params); - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->creditCard()->updateNoValidate($token, $attributes); } - /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_CreditCard object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected - */ - private static function _verifyGatewayResponse($response) + public static function updateCreditCardUrl() { - if (isset($response['creditCard'])) { - // return a populated instance of Braintree_Address - return new Braintree_Result_Successful( - self::factory($response['creditCard']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected address or apiErrorResponse" - ); - } + return Configuration::gateway()->creditCard()->updateCreditCardUrl(); } - /** - * factory method: returns an instance of Braintree_CreditCard - * to the requesting method, with populated properties - * - * @ignore - * @return object instance of Braintree_CreditCard - */ - public static function factory($attributes) + public static function delete($token) { - $defaultAttributes = array( - 'bin' => '', - 'expirationMonth' => '', - 'expirationYear' => '', - 'last4' => '', - ); - - $instance = new self(); - $instance->_initialize(array_merge($defaultAttributes, $attributes)); - return $instance; + 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 + ]; } } diff --git a/lib/Braintree/CreditCardGateway.php b/lib/Braintree/CreditCardGateway.php new file mode 100644 index 0000000..7f662b1 --- /dev/null +++ b/lib/Braintree/CreditCardGateway.php @@ -0,0 +1,453 @@ +== More information == + * + * 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}
+ * 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 + */ +// phpcs:enable + +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); + } + /** + * 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) + { + $start = date('mY', $startDate); + $end = date('mY', $endDate); + $query = '/payment_methods/all/expiring_ids?start=' . $start . '&end=' . $end; + $queryPath = $this->_config->merchantPath() . $query; + $response = $this->_http->post($queryPath); + $pager = [ + 'object' => $this, + 'method' => 'fetchExpiring', + 'methodArgs' => [$startDate, $endDate] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetchExpiring($startDate, $endDate, $ids) + { + $start = date('mY', $startDate); + $end = date('mY', $endDate); + $query = '/payment_methods/all/expiring?start=' . $start . '&end=' . $end; + $queryPath = $this->_config->merchantPath() . $query; + $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); + } + + 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', + 'skipAdvancedFraudChecking', + 'venmoSdkSession', + 'verificationAccountType', + 'verificationAmount', + 'verificationMerchantAccountId', + 'verifyCard', + ]; + } + + private static function baseSignature($options) + { + return [ + 'billingAddressId', 'cardholderName', 'cvv', 'number', + 'expirationDate', 'expirationMonth', 'expirationYear', 'token', 'venmoSdkPaymentMethodCode', + 'deviceData', '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'; + $signature[] = self::threeDSecurePassThruSignature(); + return $signature; + } + + public static function threeDSecurePassThruSignature() + { + return [ + 'threeDSecurePassThru' => [ + 'eciFlag', + 'cavv', + 'xid', + 'threeDSecureVersion', + 'authenticationResponse', + 'directoryResponse', + 'cavvAlgorithm', + 'dsTransactionId', + ] + ]; + } + + public static function updateSignature() + { + $options = self::baseOptions(); + $options[] = "failOnDuplicatePaymentMethod"; + $signature = self::baseSignature($options); + $signature[] = self::threeDSecurePassThruSignature(); + + $updateExistingBillingSignature = [ + [ + 'options' => [ + 'updateExisting' + ] + ] + ]; + + foreach ($signature as $key => $value) { + if (is_array($value) and array_key_exists('billingAddress', $value)) { + // phpcs:ignore + $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" + ); + } + } +} diff --git a/lib/Braintree/CreditCardVerification.php b/lib/Braintree/CreditCardVerification.php index c20409d..c632c9c 100644 --- a/lib/Braintree/CreditCardVerification.php +++ b/lib/Braintree/CreditCardVerification.php @@ -1,5 +1,35 @@ name] = $term->toparam(); - } - $criteria["ids"] = Braintree_CreditCardVerificationSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/verifications/advanced_search', array('search' => $criteria)); + Util::verifyKeys(self::createSignature(), $attributes); + return Configuration::gateway()->creditCardVerification()->create($attributes); + } - return Braintree_Util::extractattributeasarray( - $response['creditCardVerifications'], - 'verification' - ); + public static function fetch($query, $ids) + { + return Configuration::gateway()->creditCardVerification()->fetch($query, $ids); } public static function search($query) { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - - $response = Braintree_Http::post('/verifications/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); + return Configuration::gateway()->creditCardVerification()->search($query); + } - return new Braintree_ResourceCollection($response, $pager); + public static function createSignature() + { + return [ + ['options' => ['amount', 'merchantAccountId', 'accountType']], + ['creditCard' => + [ + 'cardholderName', 'cvv', 'number', + 'expirationDate', 'expirationMonth', 'expirationYear', + ['billingAddress' => CreditCardGateway::billingAddressSignature()] + ] + ]]; } } diff --git a/lib/Braintree/CreditCardVerificationGateway.php b/lib/Braintree/CreditCardVerificationGateway.php new file mode 100644 index 0000000..fd62e9f --- /dev/null +++ b/lib/Braintree/CreditCardVerificationGateway.php @@ -0,0 +1,75 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function create($attributes) + { + $queryPath = $this->_config->merchantPath() . "/verifications"; + $response = $this->_http->post($queryPath, ['verification' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + + if (isset($response['verification'])) { + return new Result\Successful( + CreditCardVerification::factory($response['verification']) + ); + } elseif (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); + } +} diff --git a/lib/Braintree/CreditCardVerificationSearch.php b/lib/Braintree/CreditCardVerificationSearch.php index e799200..7f2ad91 100644 --- a/lib/Braintree/CreditCardVerificationSearch.php +++ b/lib/Braintree/CreditCardVerificationSearch.php @@ -1,33 +1,66 @@ == More information == * - * For more detailed information on Customers, see {@link http://www.braintreepayments.com/gateway/customer-api http://www.braintreepaymentsolutions.com/gateway/customer-api} + * // phpcs:ignore Generic.Files.LineLength + * 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 - * @copyright 2010 Braintree Payment Solutions * - * @property-read array $addresses + * @property-read \Braintree\Address[] $addresses + * @property-read \Braintree\GooglePayCard[] $googlePayCards + * @property-read \Braintree\ApplePayCard[] $applePayCards * @property-read string $company - * @property-read string $createdAt - * @property-read array $creditCards + * @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 $graphQLId * @property-read string $id * @property-read string $lastName + * @property-read \Braintree\PaymentMethod[] $paymentMethods + * @property-read \Braintree\PayPalAccount[] $paypalAccounts * @property-read string $phone - * @property-read string $updatedAt + * @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 Braintree_Customer extends Braintree +class Customer extends Base { - public static function all() - { - $response = Braintree_Http::post('/customers/advanced_search_ids'); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array(array()) - ); - - return new Braintree_ResourceCollection($response, $pager); - } - - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - $criteria["ids"] = Braintree_CustomerSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/customers/advanced_search', array('search' => $criteria)); - - return Braintree_Util::extractattributeasarray( - $response['customers'], - 'customer' - ); - } - /** - * Creates a customer using the given +attributes+. If :id is not passed, - * the gateway will generate it. * - * - * $result = Braintree_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'; - * } - * - * - * @access public - * @param array $attribs - * @return object Result, either Successful or Error + * @return Customer[] */ - public static function create($attribs = array()) + public static function all() { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/customers', array('customer' => $attribs)); + return Configuration::gateway()->customer()->all(); } /** - * attempts the create operation assuming all data will validate - * returns a Braintree_Customer object instead of a Result * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @param array $query + * @param int[] $ids + * @return Customer|Customer[] */ - public static function createNoValidate($attribs = array()) + public static function fetch($query, $ids) { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); + return Configuration::gateway()->customer()->fetch($query, $ids); } + /** - * create a customer from a TransparentRedirect operation * - * @access public * @param array $attribs - * @return object + * @return Result\Successful|Result\Error */ - public static function createFromTransparentRedirect($queryString) + public static function create($attribs = []) { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/customers/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->customer()->create($attribs); } /** * - * @access public - * @param none - * @return string - */ - public static function createCustomerUrl() - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/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 = Braintree_CreditCard::createSignature(); - unset($creditCardSignature['customerId']); - $signature = array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website', 'deviceData', - 'deviceSessionId', 'fraudMerchantId', - array('creditCard' => $creditCardSignature), - array('customFields' => array('_anyKey_')), - ); - return $signature; - } - - /** - * creates a full array signature of a valid update request - * @return array update request format + * @param array $attribs + * @return Customer */ - public static function updateSignature() + public static function createNoValidate($attribs = []) { - $creditCardSignature = Braintree_CreditCard::updateSignature(); - - foreach($creditCardSignature AS $key => $value) { - if(is_array($value) and array_key_exists('options', $value)) { - array_push($creditCardSignature[$key]['options'], 'updateExistingToken'); - } - } - - $signature = array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website', 'deviceData', - 'deviceSessionId', 'fraudMerchantId', - array('creditCard' => $creditCardSignature), - array('customFields' => array('_anyKey_')), - ); - return $signature; + return Configuration::gateway()->customer()->createNoValidate($attribs); } - /** - * find a customer by id * - * @access public - * @param string id customer Id - * @return object Braintree_Customer - * @throws Braintree_Exception_NotFound + * @throws Exception\NotFound + * @param string $id customer id + * @return Customer */ - public static function find($id) + public static function find($id, $associationFilterId = null) { - self::_validateId($id); - try { - $response = Braintree_Http::get('/customers/'.$id); - return self::factory($response['customer']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'customer with id ' . $id . ' not found' - ); - } - + return Configuration::gateway()->customer()->find($id, $associationFilterId); } /** - * credit a customer for the passed transaction * - * @access public - * @param array $attribs - * @return object Braintree_Result_Successful or Braintree_Result_Error + * @param int $customerId + * @param array $transactionAttribs + * @return Result\Successful|Result\Error */ public static function credit($customerId, $transactionAttribs) { - self::_validateId($customerId); - return Braintree_Transaction::credit( - array_merge($transactionAttribs, - array('customerId' => $customerId) - ) - ); + return Configuration::gateway()->customer()->credit($customerId, $transactionAttribs); } /** - * credit a customer, assuming validations will pass - * - * returns a Braintree_Transaction object on success * - * @access public - * @param array $attribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationError + * @throws Exception\ValidationError + * @param type $customerId + * @param type $transactionAttribs + * @return Transaction */ public static function creditNoValidate($customerId, $transactionAttribs) { - $result = self::credit($customerId, $transactionAttribs); - return self::returnObjectOrThrowException('Braintree_Transaction', $result); + return Configuration::gateway()->customer()->creditNoValidate($customerId, $transactionAttribs); } /** - * delete a customer by id * - * @param string $customerId + * @throws Exception on invalid id or non-200 http response code + * @param int $customerId + * @return Result\Successful */ public static function delete($customerId) { - self::_validateId($customerId); - Braintree_Http::delete('/customers/' . $customerId); - return new Braintree_Result_Successful(); + return Configuration::gateway()->customer()->delete($customerId); } /** - * create a new sale for a customer * - * @param string $customerId + * @param int $customerId * @param array $transactionAttribs - * @return object Braintree_Result_Successful or Braintree_Result_Error - * @see Braintree_Transaction::sale() + * @return Transaction */ public static function sale($customerId, $transactionAttribs) { - self::_validateId($customerId); - return Braintree_Transaction::sale( - array_merge($transactionAttribs, - array('customerId' => $customerId) - ) - ); + return Configuration::gateway()->customer()->sale($customerId, $transactionAttribs); } /** - * create a new sale for a customer, assuming validations will pass * - * returns a Braintree_Transaction object on success - * @access public - * @param string $customerId + * @param int $customerId * @param array $transactionAttribs - * @return object Braintree_Transaction - * @throws Braintree_Exception_ValidationsFailed - * @see Braintree_Transaction::sale() + * @return Transaction */ public static function saleNoValidate($customerId, $transactionAttribs) { - $result = self::sale($customerId, $transactionAttribs); - return self::returnObjectOrThrowException('Braintree_Transaction', $result); + return Configuration::gateway()->customer()->saleNoValidate($customerId, $transactionAttribs); } /** - * Returns a ResourceCollection of customers matching the search query. - * - * If query is a string, the search will be a basic search. - * If query is a hash, the search will be an advanced search. - * For more detailed information and examples, see {@link http://www.braintreepayments.com/gateway/customer-api#searching http://www.braintreepaymentsolutions.com/gateway/customer-api} * - * @param mixed $query search query - * @param array $options options such as page number - * @return object Braintree_ResourceCollection * @throws InvalidArgumentException + * @param array $query + * @return ResourceCollection */ public static function search($query) { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); - } - - $response = Braintree_Http::post('/customers/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); + return Configuration::gateway()->customer()->search($query); } /** - * 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 + * @throws Exception\Unexpected + * @param int $customerId * @param array $attributes - * @param string $customerId (optional) - * @return object Braintree_Result_Successful or Braintree_Result_Error + * @return Result\Successful|Result\Error */ public static function update($customerId, $attributes) { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - self::_validateId($customerId); - return self::_doUpdate( - 'put', - '/customers/' . $customerId, - array('customer' => $attributes) - ); + return Configuration::gateway()->customer()->update($customerId, $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 Braintree_Customer object on success - * - * @access public + * @throws Exception\Unexpected + * @param int $customerId * @param array $attributes - * @param string $customerId - * @return object Braintree_Customer - * @throws Braintree_Exception_ValidationsFailed + * @return CustomerGateway */ public static function updateNoValidate($customerId, $attributes) { - $result = self::update($customerId, $attributes); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - /** - * - * @access public - * @param none - * @return string - */ - public static function updateCustomerUrl() - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/customers/all/update_via_transparent_redirect_request'; - } - - /** - * update a customer from a TransparentRedirect operation - * - * @access public - * @param array $attribs - * @return object - */ - public static function updateFromTransparentRedirect($queryString) - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doUpdate( - 'post', - '/customers/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); + return Configuration::gateway()->customer()->updateNoValidate($customerId, $attributes); } /* instance methods */ @@ -395,170 +189,173 @@ public static function updateFromTransparentRedirect($queryString) * @ignore * @access protected * @param array $customerAttribs array of customer data - * @return none */ protected function _initialize($customerAttribs) { - // set the attributes $this->_attributes = $customerAttribs; - // map each address into its own object - $addressArray = array(); + $addressArray = []; if (isset($customerAttribs['addresses'])) { - - foreach ($customerAttribs['addresses'] AS $address) { - $addressArray[] = Braintree_Address::factory($address); + foreach ($customerAttribs['addresses'] as $address) { + $addressArray[] = Address::factory($address); } } $this->_set('addresses', $addressArray); - // map each creditcard into its own object - $ccArray = array(); + $creditCardArray = []; if (isset($customerAttribs['creditCards'])) { - foreach ($customerAttribs['creditCards'] AS $creditCard) { - $ccArray[] = Braintree_CreditCard::factory($creditCard); + foreach ($customerAttribs['creditCards'] as $creditCard) { + $creditCardArray[] = CreditCard::factory($creditCard); + } + } + $this->_set('creditCards', $creditCardArray); + + $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); + + $googlePayCardArray = []; + if (isset($customerAttribs['androidPayCards'])) { + foreach ($customerAttribs['androidPayCards'] as $googlePayCard) { + $googlePayCardArray[] = GooglePayCard::factory($googlePayCard); + } + } + $this->_set('googlePayCards', $googlePayCardArray); + + $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); + + $samsungPayCardArray = []; + if (isset($customerAttribs['samsungPayCards'])) { + foreach ($customerAttribs['samsungPayCards'] as $samsungPayCard) { + $samsungPayCardArray[] = SamsungPayCard::factory($samsungPayCard); } } - $this->_set('creditCards', $ccArray); + $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->googlePayCards, + $this->venmoAccounts, + $this->visaCheckoutCards, + $this->samsungPayCards, + $this->usBankAccounts + )); + + $customFields = []; + if (isset($customerAttribs['customFields'])) { + $customFields = $customerAttribs['customFields']; + } + $this->_set('customFields', $customFields); } /** * returns a string representation of the customer * @return string */ - public function __toString() + public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) . ']'; } /** - * returns false if comparing object is not a Braintree_Customer, - * or is a Braintree_Customer with a different id + * 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 Braintree_Customer) ? false : $this->id === $otherCust->id; + return !($otherCust instanceof Customer) ? false : $this->id === $otherCust->id; } - /* private class properties */ - /** - * @access protected - * @var array registry of customer data - */ - protected $_attributes = array( - 'addresses' => '', - 'company' => '', - 'creditCards' => '', - 'email' => '', - 'fax' => '', - 'firstName' => '', - 'id' => '', - 'lastName' => '', - 'phone' => '', - 'createdAt' => '', - 'updatedAt' => '', - 'website' => '', - ); - - /** - * sends the create request to the gateway + * returns the customer's default payment method * - * @ignore - * @param string $url - * @param array $params - * @return mixed + * @return CreditCard|PayPalAccount */ - public static function _doCreate($url, $params) + public function defaultPaymentMethod() { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - } - - /** - * verifies that a valid customer id is being used - * @ignore - * @param string customer id - * @throws InvalidArgumentException - */ - private static function _validateId($id = null) { - if (empty($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.' - ); - } + $defaultPaymentMethods = array_filter($this->paymentMethods, 'Braintree\Customer::_defaultPaymentMethodFilter'); + return current($defaultPaymentMethods); } - - /* private class methods */ - - /** - * sends the update request to the gateway - * - * @ignore - * @param string $url - * @param array $params - * @return mixed - */ - private static function _doUpdate($httpVerb, $url, $params) + public static function _defaultPaymentMethodFilter($paymentMethod) { - $response = Braintree_Http::$httpVerb($url, $params); - - return self::_verifyGatewayResponse($response); + return $paymentMethod->isDefault(); } + /* private class properties */ + /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Customer object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected + * @access protected + * @var array registry of customer data */ - private static function _verifyGatewayResponse($response) - { - if (isset($response['customer'])) { - // return a populated instance of Braintree_Customer - return new Braintree_Result_Successful( - self::factory($response['customer']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected customer or apiErrorResponse" - ); - } - } + protected $_attributes = [ + 'addresses' => '', + 'company' => '', + 'creditCards' => '', + 'email' => '', + 'fax' => '', + 'firstName' => '', + 'id' => '', + 'lastName' => '', + 'phone' => '', + 'taxIdentifiers' => '', + 'createdAt' => '', + 'updatedAt' => '', + 'website' => '', + ]; /** - * factory method: returns an instance of Braintree_Customer + * factory method: returns an instance of Customer * to the requesting method, with populated properties * * @ignore - * @return object instance of Braintree_Customer + * @param array $attributes + * @return Customer */ public static function factory($attributes) { - $instance = new self(); + $instance = new Customer(); $instance->_initialize($attributes); return $instance; } - } diff --git a/lib/Braintree/CustomerGateway.php b/lib/Braintree/CustomerGateway.php new file mode 100644 index 0000000..ac30430 --- /dev/null +++ b/lib/Braintree/CustomerGateway.php @@ -0,0 +1,607 @@ +== More information == + * + // phpcs:ignore Generic.Files.LineLength + * 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 :id is not passed, + * the gateway will generate it. + * + * + * $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'; + * } + * + * + * @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); + } + + /** + * 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', 'paymentMethodNonce', + ['riskData' => + ['customerBrowser', 'customerIp'] + ], + ['creditCard' => $creditCardSignature], + ['customFields' => ['_anyKey_']], + ['taxIdentifiers' => + ['countryCode', 'identifier'] + ], + ['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', + 'paymentMethodNonce', 'defaultPaymentMethodToken', + ['creditCard' => $creditCardSignature], + ['customFields' => ['_anyKey_']], + ['taxIdentifiers' => + ['countryCode', 'identifier'] + ], + ['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 query is a string, the search will be a basic search. + * If query is a hash, the search will be an advanced search. + // phpcs:ignore Generic.Files.LineLength + * 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); + } + + /* 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 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 from gateway response to googlePayCard objects + $googlePayCardArray = []; + if (isset($customerAttribs['androidPayCards'])) { + foreach ($customerAttribs['androidPayCards'] as $googlePayCard) { + $googlePayCardArray[] = GooglePayCard::factory($googlePayCard); + } + } + $this->_set('googlePayCards', $googlePayCardArray); + + $paymentMethodsArray = array_merge( + $this->creditCards, + $this->paypalAccounts, + $this->applePayCards, + $this->googlePayCards + ); + $this->_set('paymentMethods', $paymentMethodsArray); + } + + /** + * 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|GooglePayCard + */ + public function defaultPaymentMethod() + { + // phpcs:ignore Generic.Files.LineLength + $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']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected customer or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/CustomerSearch.php b/lib/Braintree/CustomerSearch.php index 5de6cee..08bdf27 100644 --- a/lib/Braintree/CustomerSearch.php +++ b/lib/Braintree/CustomerSearch.php @@ -1,30 +1,106 @@ merchantAccountDetails = $disbursementAttribs['merchantAccount']; if (isset($disbursementAttribs['merchantAccount'])) { - $this->_set('merchantAccount', - Braintree_MerchantAccount::factory($disbursementAttribs['merchantAccount']) + $this->_set( + 'merchantAccount', + MerchantAccount::factory($disbursementAttribs['merchantAccount']) ); } } public function transactions() { - $collection = Braintree_Transaction::search(array( - Braintree_TransactionSearch::ids()->in($this->transactionIds) - )); + $collection = Transaction::search([ + TransactionSearch::ids()->in($this->transactionIds), + ]); return $collection; } @@ -31,19 +39,29 @@ public static function factory($attributes) return $instance; } - public function __toString() + public function __toString() { - $display = array( + $display = [ 'id', 'merchantAccountDetails', 'exceptionMessage', 'amount', 'disbursementDate', 'followUpAction', 'retry', 'success', - 'transactionIds' - ); + 'transactionIds', 'disbursementType' + ]; - $displayAttributes = array(); - foreach ($display AS $attrib) { + $displayAttributes = []; + foreach ($display as $attrib) { $displayAttributes[$attrib] = $this->$attrib; } return __CLASS__ . '[' . - Braintree_Util::attributesToString($displayAttributes) .']'; + Util::attributesToString($displayAttributes) . ']'; + } + + public function isDebit() + { + return $this->disbursementType == Disbursement::TYPE_DEBIT; + } + + public function isCredit() + { + return $this->disbursementType == Disbursement::TYPE_CREDIT; } } diff --git a/lib/Braintree/DisbursementDetails.php b/lib/Braintree/DisbursementDetails.php index 471a163..4befb00 100644 --- a/lib/Braintree/DisbursementDetails.php +++ b/lib/Braintree/DisbursementDetails.php @@ -1,31 +1,25 @@ disbursementDate); } } diff --git a/lib/Braintree/Discount.php b/lib/Braintree/Discount.php index 0c8e12e..8c0d53a 100644 --- a/lib/Braintree/Discount.php +++ b/lib/Braintree/Discount.php @@ -1,22 +1,35 @@ $response['discounts']); - - return Braintree_Util::extractAttributeAsArray( - $discounts, - 'discount' - ); - } +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(); + } } diff --git a/lib/Braintree/DiscountGateway.php b/lib/Braintree/DiscountGateway.php new file mode 100644 index 0000000..5382495 --- /dev/null +++ b/lib/Braintree/DiscountGateway.php @@ -0,0 +1,31 @@ +_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' + ); + } +} diff --git a/lib/Braintree/Dispute.php b/lib/Braintree/Dispute.php new file mode 100644 index 0000000..a8aaf13 --- /dev/null +++ b/lib/Braintree/Dispute.php @@ -0,0 +1,221 @@ +_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['paypalMessages'])) { + $paypalMessagesArray = array_map(function ($paypalMessages) { + return new Dispute\PayPalMessageDetails($paypalMessages); + }, $disputeAttribs['paypalMessages']); + $this->_set('paypalMessages', $paypalMessagesArray); + } + + 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 + * // phpcs:ignore Generic.Files.LineLength + * @param string|mixed $contentOrRequest If a string, $contentOrRequest is the text-based content for the dispute evidence. + * Alternatively, the second argument can also be an array containing: + * string $content The text-based content for the dispute evidence, and + * string $category The category for this piece of evidence + * Note: (optional) string $tag parameter is deprecated, use $category instead. + * + * Example: https://developers.braintreepayments.com/reference/request/dispute/add-text-evidence/php#submitting-categorized-evidence + */ + 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); + } + + /** @return array */ + public static function allChargebackProtectionLevelTypes() + { + return [ + Dispute::EFFORTLESS, + Dispute::STANDARD, + Dispute::NOT_PROTECTED + ]; + } +} diff --git a/lib/Braintree/Dispute/EvidenceDetails.php b/lib/Braintree/Dispute/EvidenceDetails.php new file mode 100644 index 0000000..096925e --- /dev/null +++ b/lib/Braintree/Dispute/EvidenceDetails.php @@ -0,0 +1,30 @@ +_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)) { + trigger_error('$tag is deprecated, use $category instead', E_USER_DEPRECATED); + $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'); + } elseif ((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) { + $message = 'evidence with id "' . $evidenceId . '" for dispute with id "' . $disputeId . '" not found'; + throw new Exception\NotFound($message); + } + } + + /** + * Search for Disputes, given a DisputeSearch query + * + * @param array $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); + } +} diff --git a/lib/Braintree/DisputeSearch.php b/lib/Braintree/DisputeSearch.php new file mode 100644 index 0000000..7ac3c34 --- /dev/null +++ b/lib/Braintree/DisputeSearch.php @@ -0,0 +1,96 @@ + 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; + } +} diff --git a/lib/Braintree/DocumentUploadGateway.php b/lib/Braintree/DocumentUploadGateway.php new file mode 100644 index 0000000..1bff87f --- /dev/null +++ b/lib/Braintree/DocumentUploadGateway.php @@ -0,0 +1,80 @@ +_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' + ]; + } +} diff --git a/lib/Braintree/EndsWithNode.php b/lib/Braintree/EndsWithNode.php new file mode 100644 index 0000000..de962d9 --- /dev/null +++ b/lib/Braintree/EndsWithNode.php @@ -0,0 +1,23 @@ +name = $name; + $this->searchTerms = []; + } + + public function endsWith($value) + { + $this->searchTerms["ends_with"] = strval($value); + return $this; + } + + public function toParam() + { + return $this->searchTerms; + } +} diff --git a/lib/Braintree/EqualityNode.php b/lib/Braintree/EqualityNode.php index 68c10b5..b14675e 100644 --- a/lib/Braintree/EqualityNode.php +++ b/lib/Braintree/EqualityNode.php @@ -1,8 +1,10 @@ searchTerms['is_not'] = strval($value); return $this; diff --git a/lib/Braintree/Error/Codes.php b/lib/Braintree/Error/Codes.php index 6f18bc8..81706e1 100644 --- a/lib/Braintree/Error/Codes.php +++ b/lib/Braintree/Error/Codes.php @@ -1,12 +1,6 @@ _errors = - new Braintree_Error_ValidationErrorCollection($errorData); + 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. @@ -76,11 +83,13 @@ 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(Braintree_Util::delimiterToCamelCase($key)); - if (!isset($errors)) { return array(); } + foreach (array_slice($pieces, 0, -1) as $key) { + $errors = $errors->forKey(Util::delimiterToCamelCase($key)); + if (!isset($errors)) { + return []; + } } - $finalKey = Braintree_Util::delimiterToCamelCase(end($pieces)); + $finalKey = Util::delimiterToCamelCase(end($pieces)); return $errors->onAttribute($finalKey); } @@ -88,7 +97,7 @@ public function onHtmlField($field) * Returns the errors at the given nesting level (see forKey) in a single, flat array: * * - * $result = Braintree_Customer::create(...); + * $result = Customer::create(...); * $customerErrors = $result->errors->forKey('customer')->shallowAll(); * */ @@ -101,7 +110,7 @@ public function shallowAll() * * @ignore */ - public function __get($name) + public function __get($name) { $varName = "_$name"; return isset($this->$varName) ? $this->$varName : null; @@ -111,8 +120,19 @@ public function __get($name) * * @ignore */ - public function __toString() + public function __toString() { return sprintf('%s', $this->_errors); } + + /** + * Implementation of JsonSerializable + * + * @ignore + * @return array + */ + public function jsonSerialize() + { + return $this->_errors->deepAll(); + } } diff --git a/lib/Braintree/Error/Validation.php b/lib/Braintree/Error/Validation.php index 8253ae6..6dee0b7 100644 --- a/lib/Braintree/Error/Validation.php +++ b/lib/Braintree/Error/Validation.php @@ -1,11 +1,8 @@ == More information == * - * For more detailed information on Validation errors, see {@link http://www.braintreepayments.com/gateway/validation-errors http://www.braintreepaymentsolutions.com/gateway/validation-errors} + * // phpcs:ignore Generic.Files.LineLength + * 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 - * @copyright 2010 Braintree Payment Solutions * * @property-read string $attribute * @property-read string $code * @property-read string $message */ -class Braintree_Error_Validation +class Validation { - private $_attribute; - private $_code; - private $_message; + private $_attribute; + private $_code; + private $_message; /** * @ignore * @param array $attributes */ - public function __construct($attributes) + public function __construct($attributes) { $this->_initializeFromArray($attributes); } @@ -42,13 +39,13 @@ public function __construct($attributes) * @ignore * @access protected * @param array $attributes array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { - foreach($attributes AS $name => $value) { + foreach ($attributes as $name => $value) { $varName = "_$name"; - $this->$varName = Braintree_Util::delimiterToCamelCase($value, '_'); + $this->$varName = Util::delimiterToCamelCase($value, '_'); } } @@ -56,7 +53,7 @@ private function _initializeFromArray($attributes) * * @ignore */ - public function __get($name) + public function __get($name) { $varName = "_$name"; return isset($this->$varName) ? $this->$varName : null; diff --git a/lib/Braintree/Error/ValidationErrorCollection.php b/lib/Braintree/Error/ValidationErrorCollection.php index 5125c90..d4c8bf3 100644 --- a/lib/Braintree/Error/ValidationErrorCollection.php +++ b/lib/Braintree/Error/ValidationErrorCollection.php @@ -1,53 +1,49 @@ == More information == * - * For more detailed information on Validation errors, see {@link http://www.braintreepayments.com/gateway/validation-errors http://www.braintreepaymentsolutions.com/gateway/validation-errors} + * // phpcs:ignore Generic.Files.LineLength + * 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 - * @copyright 2010 Braintree Payment Solutions * * @property-read array $errors * @property-read array $nested */ -class Braintree_Error_ValidationErrorCollection extends Braintree_Collection +class ValidationErrorCollection extends Collection { - private $_errors = array(); - private $_nested = array(); + private $_errors = []; + private $_nested = []; /** * @ignore */ - public function __construct($data) + public function __construct($data) { - foreach($data AS $key => $errorData) + foreach ($data as $key => $errorData) { // map errors to new collections recursively if ($key == 'errors') { - foreach ($errorData AS $error) { - $this->_errors[] = new Braintree_Error_Validation($error); + foreach ($errorData as $error) { + $this->_errors[] = new Validation($error); } } else { - $this->_nested[$key] = new Braintree_Error_ValidationErrorCollection($errorData); + $this->_nested[$key] = new ValidationErrorCollection($errorData); } - + } } public function deepAll() { - $validationErrors = array_merge(array(), $this->_errors); - foreach($this->_nested as $nestedErrors) - { + $validationErrors = array_merge([], $this->_errors); + foreach ($this->_nested as $nestedErrors) { $validationErrors = array_merge($validationErrors, $nestedErrors->deepAll()); } return $validationErrors; @@ -56,8 +52,7 @@ public function deepAll() public function deepSize() { $total = sizeof($this->_errors); - foreach($this->_nested as $_nestedErrors) - { + foreach ($this->_nested as $_nestedErrors) { $total = $total + $_nestedErrors->deepSize(); } return $total; @@ -75,11 +70,11 @@ public function forKey($key) public function onAttribute($attribute) { - $matches = array(); - foreach ($this->_errors AS $key => $error) { - if($error->attribute == $attribute) { - $matches[] = $error; - } + $matches = []; + foreach ($this->_errors as $key => $error) { + if ($error->attribute == $attribute) { + $matches[] = $error; + } } return $matches; } @@ -94,7 +89,7 @@ public function shallowAll() * * @ignore */ - public function __get($name) + public function __get($name) { $varName = "_$name"; return isset($this->$varName) ? $this->$varName : null; @@ -105,14 +100,13 @@ public function __get($name) */ public function __toString() { - $output = array(); + $output = []; - // TODO: implement scope if (!empty($this->_errors)) { $output[] = $this->_inspect($this->_errors); } if (!empty($this->_nested)) { - foreach ($this->_nested AS $key => $values) { + foreach ($this->_nested as $key => $values) { $output[] = $this->_inspect($this->_nested); } } @@ -125,8 +119,11 @@ public function __toString() private function _inspect($errors, $scope = null) { $eOutput = '[' . __CLASS__ . '/errors:['; - foreach($errors AS $error => $errorObj) { - $outputErrs[] = "({$errorObj->error['code']} {$errorObj->error['message']})"; + $outputErrs = []; + foreach ($errors as $error => $errorObj) { + if (is_array($errorObj->error)) { + $outputErrs[] = "({$errorObj->error['code']} {$errorObj->error['message']})"; + } } $eOutput .= join(', ', $outputErrs) . ']]'; diff --git a/lib/Braintree/Exception.php b/lib/Braintree/Exception.php index 40ec119..d844eaf 100644 --- a/lib/Braintree/Exception.php +++ b/lib/Braintree/Exception.php @@ -1,20 +1,13 @@ _initialize($attributes); + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the facilitated details + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/FacilitatorDetails.php b/lib/Braintree/FacilitatorDetails.php new file mode 100644 index 0000000..86c1b60 --- /dev/null +++ b/lib/Braintree/FacilitatorDetails.php @@ -0,0 +1,34 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the facilitator details + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/Gateway.php b/lib/Braintree/Gateway.php new file mode 100644 index 0000000..d11176c --- /dev/null +++ b/lib/Braintree/Gateway.php @@ -0,0 +1,267 @@ +config = $config; + $this->graphQLClient = new GraphQLClient($config); + } + + /** + * + * @return AddOnGateway + */ + public function addOn() + { + return new AddOnGateway($this); + } + + /** + * + * @return AddressGateway + */ + public function address() + { + return new AddressGateway($this); + } + + /** + * + * @return ApplePayGateway + */ + public function applePay() + { + return new ApplePayGateway($this); + } + + /** + * + * @return ClientTokenGateway + */ + public function clientToken() + { + return new ClientTokenGateway($this); + } + + /** + * + * @return CreditCardGateway + */ + public function creditCard() + { + return new CreditCardGateway($this); + } + + /** + * + * @return CreditCardVerificationGateway + */ + public function creditCardVerification() + { + return new CreditCardVerificationGateway($this); + } + + /** + * + * @return CustomerGateway + */ + public function customer() + { + return new CustomerGateway($this); + } + + /** + * + * @return DiscountGateway + */ + public function discount() + { + return new DiscountGateway($this); + } + + /** + * + * @return DisputeGateway + */ + public function dispute() + { + return new DisputeGateway($this); + } + + /** + * + * @return DocumentUploadGateway + */ + public function documentUpload() + { + return new DocumentUploadGateway($this); + } + + /** + * + * @return MerchantGateway + */ + public function merchant() + { + return new MerchantGateway($this); + } + + /** + * + * @return MerchantAccountGateway + */ + public function merchantAccount() + { + return new MerchantAccountGateway($this); + } + + /** + * + * @return OAuthGateway + */ + public function oauth() + { + return new OAuthGateway($this); + } + + /** + * + * @return PaymentMethodGateway + */ + public function paymentMethod() + { + return new PaymentMethodGateway($this); + } + + /** + * + * @return PaymentMethodNonceGateway + */ + public function paymentMethodNonce() + { + return new PaymentMethodNonceGateway($this); + } + + /** + * + * @return PayPalAccountGateway + */ + public function payPalAccount() + { + return new PayPalAccountGateway($this); + } + + /** + * + * @return PlanGateway + */ + public function plan() + { + return new PlanGateway($this); + } + + /** + * + * @return SettlementBatchSummaryGateway + */ + public function settlementBatchSummary() + { + return new SettlementBatchSummaryGateway($this); + } + + /** + * + * @return SubscriptionGateway + */ + public function subscription() + { + return new SubscriptionGateway($this); + } + + /** + * + * @return TestingGateway + */ + public function testing() + { + return new TestingGateway($this); + } + + /** + * + * @return TransactionGateway + */ + public function transaction() + { + return new TransactionGateway($this); + } + + /** + * + * @return TransactionLineItemGateway + */ + public function transactionLineItem() + { + return new TransactionLineItemGateway($this); + } + + /** + * + * @return UsBankAccountGateway + */ + public function usBankAccount() + { + return new UsBankAccountGateway($this); + } + + /** + * + * @return UsBankAccountVerificationGateway + */ + public function usBankAccountVerification() + { + return new UsBankAccountVerificationGateway($this); + } + + /** + * + * @return WebhookNotificationGateway + */ + public function webhookNotification() + { + return new WebhookNotificationGateway($this); + } + + /** + * + * @return WebhookTestingGateway + */ + public function webhookTesting() + { + return new WebhookTestingGateway($this); + } +} diff --git a/lib/Braintree/GooglePayCard.php b/lib/Braintree/GooglePayCard.php new file mode 100644 index 0000000..0a3621b --- /dev/null +++ b/lib/Braintree/GooglePayCard.php @@ -0,0 +1,91 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @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 boolean $isNetworkTokenized + * @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 GooglePayCard 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 GooglePayCard + * to the requesting method, with populated properties + * + * @ignore + * @return GooglePayCard + */ + 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 $googlePayCardAttribs array of Google Pay card properties + * @return void + */ + protected function _initialize($googlePayCardAttribs) + { + // set the attributes + $this->_attributes = $googlePayCardAttribs; + + $subscriptionArray = []; + if (isset($googlePayCardAttribs['subscriptions'])) { + foreach ($googlePayCardAttribs['subscriptions'] as $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } +} diff --git a/lib/Braintree/GrantedPaymentInstrumentUpdate.php b/lib/Braintree/GrantedPaymentInstrumentUpdate.php new file mode 100644 index 0000000..9679b6b --- /dev/null +++ b/lib/Braintree/GrantedPaymentInstrumentUpdate.php @@ -0,0 +1,73 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $grantOwnerMerchantId + * @property-read string $grantRecipientMerchantId + * @property-read string $paymentMethodNonce + * @property-read string $token + * @property-read string $updatedFields + */ +class GrantedPaymentInstrumentUpdate extends Base +{ + /** + * factory method: returns an instance of GrantedPaymentInstrumentUpdate + * to the requesting method, with populated properties + * + * @ignore + * @return GrantedPaymentInstrumentUpdate + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $GrantedPaymentInstrumentAttribs array of grantedPaymentInstrumentUpdate data + * @return void + */ + protected function _initialize($grantedPaymentInstrumentUpdateAttribs) + { + // set the attributes + $this->_attributes = $grantedPaymentInstrumentUpdateAttribs; + + $paymentMethodNonce = isset($grantedPaymentInstrumentUpdateAttribs['paymentMethodNonce']) ? + GrantedPaymentInstrumentUpdate::factory($grantedPaymentInstrumentUpdateAttribs['paymentMethodNonce']) : + null; + $this->_set('paymentMethodNonce', $paymentMethodNonce); + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/GraphQL.php b/lib/Braintree/GraphQL.php new file mode 100644 index 0000000..68c0535 --- /dev/null +++ b/lib/Braintree/GraphQL.php @@ -0,0 +1,44 @@ + $definition]; + if ($variables) { + $graphQLRequest["variables"] = $variables; + } + + // phpcs:ignore Generic.Files.LineLength + $response = $this->_doUrlRequest('POST', $this->_config->graphQLBaseUrl(), json_encode($graphQLRequest), null, $this->graphQLHeaders()); + + $result = json_decode($response["body"], true); + Util::throwGraphQLResponseException($result); + + return $result; + } +} diff --git a/lib/Braintree/GraphQLClient.php b/lib/Braintree/GraphQLClient.php new file mode 100644 index 0000000..5227a39 --- /dev/null +++ b/lib/Braintree/GraphQLClient.php @@ -0,0 +1,20 @@ +_service = new GraphQL($config); + } + + public function query($definition, $variables = null) + { + return $this->_service->request($definition, $variables); + } +} diff --git a/lib/Braintree/Http.php b/lib/Braintree/Http.php index fb05985..967aa0e 100644 --- a/lib/Braintree/Http.php +++ b/lib/Braintree/Http.php @@ -1,104 +1,103 @@ _config = $config; + } + + public function delete($path, $params = null) + { + $response = $this->_doRequest('DELETE', $path, $this->_buildXml($params)); + $responseCode = $response['status']; + if ($responseCode === 200 || $responseCode === 204) { return true; + } elseif ($responseCode === 422) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($response['status']); + Util::throwStatusCodeException($response['status']); } } - public static function get($path) + public function get($path) { - $response = self::_doRequest('GET', $path); - if($response['status'] === 200) { - return Braintree_Xml::buildArrayFromXml($response['body']); + $response = $this->_doRequest('GET', $path); + if ($response['status'] === 200) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($response['status']); + Util::throwStatusCodeException($response['status']); } } - public static function post($path, $params = null) + public function post($path, $params = null) { - $response = self::_doRequest('POST', $path, self::_buildXml($params)); + $response = $this->_doRequest('POST', $path, $this->_buildXml($params)); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422) { - return Braintree_Xml::buildArrayFromXml($response['body']); + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($responseCode); + Util::throwStatusCodeException($responseCode); } } - public static function put($path, $params = null) + public function postMultipart($path, $params, $file) { - $response = self::_doRequest('PUT', $path, self::_buildXml($params)); + $headers = [ + 'User-Agent: Braintree PHP Library ' . Version::get(), + 'X-ApiVersion: ' . Configuration::API_VERSION + ]; + $response = $this->_doRequest('POST', $path, $params, $file, $headers); $responseCode = $response['status']; - if($responseCode === 200 || $responseCode === 201 || $responseCode === 422) { - return Braintree_Xml::buildArrayFromXml($response['body']); + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); } else { - Braintree_Util::throwStatusCodeException($responseCode); + Util::throwStatusCodeException($responseCode); } } - private static function _buildXml($params) + public function put($path, $params = null) { - return empty($params) ? null : Braintree_Xml::buildXmlFromArray($params); + $response = $this->_doRequest('PUT', $path, $this->_buildXml($params)); + $responseCode = $response['status']; + if ($responseCode === 200 || $responseCode === 201 || $responseCode === 422 || $responseCode == 400) { + return Xml::buildArrayFromXml($response['body']); + } else { + Util::throwStatusCodeException($responseCode); + } } - private static function _doRequest($httpVerb, $path, $requestBody = null) + private function _buildXml($params) { - return self::_doUrlRequest($httpVerb, Braintree_Configuration::merchantUrl() . $path, $requestBody); + return empty($params) ? null : Xml::buildXmlFromArray($params); } - public static function _doUrlRequest($httpVerb, $url, $requestBody = null) + public function useClientCredentials() { - $curl = curl_init(); - curl_setopt($curl, CURLOPT_TIMEOUT, 60); - curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $httpVerb); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_ENCODING, 'gzip'); - curl_setopt($curl, CURLOPT_HTTPHEADER, array( - 'Accept: application/xml', - 'Content-Type: application/xml', - 'User-Agent: Braintree PHP Library ' . Braintree_Version::get(), - 'X-ApiVersion: ' . Braintree_Configuration::API_VERSION - )); - curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); - curl_setopt($curl, CURLOPT_USERPWD, Braintree_Configuration::publicKey() . ':' . Braintree_Configuration::privateKey()); - // curl_setopt($curl, CURLOPT_VERBOSE, true); - if (Braintree_Configuration::sslOn()) { - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($curl, CURLOPT_CAINFO, Braintree_Configuration::caFile()); - } + $this->_useClientCredentials = true; + } - if(!empty($requestBody)) { - curl_setopt($curl, CURLOPT_POSTFIELDS, $requestBody); - } + private function _doRequest($httpVerb, $path, $requestBody = null, $file = null, $headers = null) + { + return $this->_doUrlRequest($httpVerb, $this->_config->baseUrl() . $path, $requestBody, $file, $headers); + } - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - $response = curl_exec($curl); - $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - if (Braintree_Configuration::sslOn()) { - if ($httpStatus == 0) { - throw new Braintree_Exception_SSLCertificate(); - } - } - return array('status' => $httpStatus, 'body' => $response); + public function _doUrlRequest($httpVerb, $url, $requestBody = null, $file = null, $customHeaders = null) + { + $curlRequest = new CurlRequest($url); + // phpcs:ignore Generic.Files.LineLength + return Curl::makeRequest($httpVerb, $url, $this->_config, $curlRequest, $requestBody, $file, $customHeaders, $this->_useClientCredentials); } } diff --git a/lib/Braintree/HttpHelpers/Curl.php b/lib/Braintree/HttpHelpers/Curl.php new file mode 100644 index 0000000..a69aca6 --- /dev/null +++ b/lib/Braintree/HttpHelpers/Curl.php @@ -0,0 +1,182 @@ +setOption(CURLOPT_TIMEOUT, $config->getTimeout()); + $httpRequest->setOption(CURLOPT_CUSTOMREQUEST, $httpVerb); + $httpRequest->setOption(CURLOPT_URL, $url); + + if ($config->getAcceptGzipEncoding()) { + $httpRequest->setOption(CURLOPT_ENCODING, 'gzip'); + } + + if ($config->getSslVersion()) { + $httpRequest->setOption(CURLOPT_SSLVERSION, $config->getSslVersion()); + } + + $headers = []; + if ($customHeaders) { + $headers = $customHeaders; + } else { + $headers[] = 'Accept: application/xml'; + $headers[] = 'Content-Type: application/xml'; + $headers[] = 'User-Agent: Braintree PHP Library ' . Version::get(); + $headers[] = 'X-ApiVersion: ' . Configuration::API_VERSION; + } + + $authorization = self::_getAuthorization($config, $useClientCredentials); + if (isset($authorization['user'])) { + $httpRequest->setOption(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + $httpRequest->setOption(CURLOPT_USERPWD, $authorization['user'] . ':' . $authorization['password']); + } elseif (isset($authorization['token'])) { + $headers[] = 'Authorization: Bearer ' . $authorization['token']; + } + + if ($config->sslOn()) { + $httpRequest->setOption(CURLOPT_SSL_VERIFYPEER, true); + $httpRequest->setOption(CURLOPT_SSL_VERIFYHOST, 2); + $httpRequest->setOption(CURLOPT_CAINFO, self::_getCaFile($config)); + } + + if (!empty($file)) { + $boundary = "---------------------" . md5(mt_rand() . microtime()); + $headers[] = "Content-Type: multipart/form-data; boundary={$boundary}"; + self::_prepareMultipart($httpRequest, $requestBody, $file, $boundary); + } elseif (!empty($requestBody)) { + $httpRequest->setOption(CURLOPT_POSTFIELDS, $requestBody); + } + + if ($config->isUsingProxy()) { + $proxyHost = $config->getProxyHost(); + $proxyPort = $config->getProxyPort(); + $proxyType = $config->getProxyType(); + $proxyUser = $config->getProxyUser(); + $proxyPwd = $config->getProxyPassword(); + $httpRequest->setOption(CURLOPT_PROXY, $proxyHost . ':' . $proxyPort); + if (!empty($proxyType)) { + $httpRequest->setOption(CURLOPT_PROXYTYPE, $proxyType); + } + if ($config->isAuthenticatedProxy()) { + $httpRequest->setOption(CURLOPT_PROXYUSERPWD, $proxyUser . ':' . $proxyPwd); + } + } + + $httpRequest->setOption(CURLOPT_HTTPHEADER, $headers); + $httpRequest->setOption(CURLOPT_RETURNTRANSFER, true); + $response = $httpRequest->execute(); + $httpStatus = $httpRequest->getInfo(CURLINFO_HTTP_CODE); + $errorCode = $httpRequest->getErrorCode(); + $error = $httpRequest->getError(); + + if ($errorCode == 28 && $httpStatus == 0) { + throw new Exception\Timeout(); + } + + $httpRequest->close(); + if ($config->sslOn() && $errorCode == 35) { + throw new Exception\SSLCertificate($error, $errorCode); + } + + if ($errorCode) { + throw new Exception\Connection($error, $errorCode); + } + + return ['status' => $httpStatus, 'body' => $response]; + } + + private static function _getAuthorization($config, $useClientCredentials) + { + if ($useClientCredentials) { + return [ + 'user' => $config->getClientId(), + 'password' => $config->getClientSecret(), + ]; + } elseif ($config->isAccessToken()) { + return [ + 'token' => $config->getAccessToken(), + ]; + } else { + return [ + 'user' => $config->getPublicKey(), + 'password' => $config->getPrivateKey(), + ]; + } + } + + private static function _getCaFile($config) + { + static $memo; + + if ($memo === null) { + $caFile = $config->caFile(); + + if (substr($caFile, 0, 7) !== 'phar://') { + return $caFile; + } + + $extractedCaFile = sys_get_temp_dir() . '/api_braintreegateway_com.ca.crt'; + + if (!file_exists($extractedCaFile) || sha1_file($extractedCaFile) != sha1_file($caFile)) { + if (!copy($caFile, $extractedCaFile)) { + throw new Exception\SSLCaFileNotFound(); + } + } + $memo = $extractedCaFile; + } + + return $memo; + } + + private static function _prepareMultipart($httpRequest, $requestBody, $file, $boundary) + { + $disallow = ["\0", "\"", "\r", "\n"]; + $fileInfo = new finfo(FILEINFO_MIME_TYPE); + $filePath = stream_get_meta_data($file)['uri']; + $data = file_get_contents($filePath); + $mimeType = $fileInfo->buffer($data); + + // build normal parameters + foreach ($requestBody as $k => $v) { + $k = str_replace($disallow, "_", $k); + $body[] = implode("\r\n", [ + "Content-Disposition: form-data; name=\"{$k}\"", + "", + filter_var($v), + ]); + } + + // build file parameter + $splitFilePath = explode(DIRECTORY_SEPARATOR, $filePath); + $filePath = end($splitFilePath); + $filePath = str_replace($disallow, "_", $filePath); + $body[] = implode("\r\n", [ + "Content-Disposition: form-data; name=\"file\"; filename=\"{$filePath}\"", + "Content-Type: {$mimeType}", + "", + $data, + ]); + + // add boundary for each parameters + array_walk($body, function (&$part) use ($boundary) { + $part = "--{$boundary}\r\n{$part}"; + }); + + // add final boundary + $body[] = "--{$boundary}--"; + $body[] = ""; + + // set options + $httpRequest->setOption(CURLOPT_POST, true); + $httpRequest->setOption(CURLOPT_POSTFIELDS, implode("\r\n", $body)); + } +} diff --git a/lib/Braintree/HttpHelpers/CurlRequest.php b/lib/Braintree/HttpHelpers/CurlRequest.php new file mode 100644 index 0000000..11d6b6c --- /dev/null +++ b/lib/Braintree/HttpHelpers/CurlRequest.php @@ -0,0 +1,43 @@ +_handle = curl_init($url); + } + + public function setOption($name, $value) + { + curl_setopt($this->_handle, $name, $value); + } + + public function execute() + { + return curl_exec($this->_handle); + } + + public function getInfo($name) + { + return curl_getinfo($this->_handle, $name); + } + + public function getErrorCode() + { + return curl_errno($this->_handle); + } + + public function getError() + { + return curl_error($this->_handle); + } + + public function close() + { + curl_close($this->_handle); + } +} diff --git a/lib/Braintree/HttpHelpers/HttpRequest.php b/lib/Braintree/HttpHelpers/HttpRequest.php new file mode 100644 index 0000000..6329eef --- /dev/null +++ b/lib/Braintree/HttpHelpers/HttpRequest.php @@ -0,0 +1,13 @@ +_initializeFromArray($attributes); } } - /** * returns private/nonexistent instance properties * @access public - * @param var $name property name + * @param string $name property name * @return mixed contents of instance properties */ public function __get($name) @@ -53,29 +46,56 @@ public function __get($name) */ public function __isset($name) { - return array_key_exists($name, $this->_attributes); + return isset($this->_attributes[$name]); } /** * create a printable representation of the object as: * ClassName[property=value, property=value] - * @return var + * @return string */ - public function __toString() + public function __toString() { - $objOutput = Braintree_Util::implodeAssociativeArray($this->_attributes); - return get_class($this) .'['.$objOutput.']'; + $objOutput = Util::implodeAssociativeArray($this->_attributes); + return get_class($this) . '[' . $objOutput . ']'; } /** * initializes instance properties from the keys/values of an array * @ignore * @access protected * @param $aAttribs array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { $this->_attributes = $attributes; } + /** + * Implementation of JsonSerializable + * + * @ignore + * @return array + */ + public function jsonSerialize() + { + return $this->_attributes; + } + + /** + * Implementation of to an Array + * + * @ignore + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + if (!is_array($value)) { + return method_exists($value, 'toArray') ? $value->toArray() : $value; + } else { + return $value; + } + }, $this->_attributes); + } } diff --git a/lib/Braintree/IsNode.php b/lib/Braintree/IsNode.php index 321fbb3..90d34a5 100644 --- a/lib/Braintree/IsNode.php +++ b/lib/Braintree/IsNode.php @@ -1,20 +1,23 @@ name = $name; - $this->searchTerms = array(); + $this->searchTerms = []; } - function is($value) + public function is($value) { $this->searchTerms['is'] = strval($value); + return $this; } - function toParam() + public function toParam() { return $this->searchTerms; } diff --git a/lib/Braintree/KeyValueNode.php b/lib/Braintree/KeyValueNode.php index ec0cb94..02ce02a 100644 --- a/lib/Braintree/KeyValueNode.php +++ b/lib/Braintree/KeyValueNode.php @@ -1,21 +1,22 @@ name = $name; - $this->searchTerm = True; - + $this->searchTerm = true; } - function is($value) + public function is($value) { $this->searchTerm = $value; return $this; } - function toParam() + public function toParam() { return $this->searchTerm; } diff --git a/lib/Braintree/LocalPaymentCompleted.php b/lib/Braintree/LocalPaymentCompleted.php new file mode 100644 index 0000000..80eb252 --- /dev/null +++ b/lib/Braintree/LocalPaymentCompleted.php @@ -0,0 +1,76 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $paymentId + * @property-read string $payerId + * @property-read string $paymentMethodNonce + * @property-read \Braintree\Transaction $transaction + */ +class LocalPaymentCompleted extends Base +{ + /** + * factory method: returns an instance of LocalPaymentCompleted + * to the requesting method, with populated properties + * + * @ignore + * @return LocalPaymentCompleted + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $LocalPaymentCompletedAttribs array of localPaymentCompleted data + * @return void + */ + protected function _initialize($localPaymentCompletedAttribs) + { + // set the attributes + $this->_attributes = $localPaymentCompletedAttribs; + + if (isset($transactionAttribs['transaction'])) { + $this->_set( + 'transaction', + new Transaction( + $transactionAttribs['transaction'] + ) + ); + } + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/LocalPaymentReversed.php b/lib/Braintree/LocalPaymentReversed.php new file mode 100644 index 0000000..46c2b3b --- /dev/null +++ b/lib/Braintree/LocalPaymentReversed.php @@ -0,0 +1,64 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $paymentId + */ +class LocalPaymentReversed extends Base +{ + /** + * factory method: returns an instance of LocalPaymentReversed + * to the requesting method, with populated properties + * + * @ignore + * @return LocalPaymentReversed + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $LocalPaymentReversedAttribs array of localPaymentReversed data + * @return void + */ + protected function _initialize($localPaymentReversedAttribs) + { + // set the attributes + $this->_attributes = $localPaymentReversedAttribs; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/Merchant.php b/lib/Braintree/Merchant.php new file mode 100644 index 0000000..eef60f6 --- /dev/null +++ b/lib/Braintree/Merchant.php @@ -0,0 +1,36 @@ +_attributes = $attribs; + + $merchantAccountArray = []; + if (isset($attribs['merchantAccounts'])) { + foreach ($attribs['merchantAccounts'] as $merchantAccount) { + $merchantAccountArray[] = MerchantAccount::factory($merchantAccount); + } + } + $this->_set('merchantAccounts', $merchantAccountArray); + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the merchant + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/MerchantAccount.php b/lib/Braintree/MerchantAccount.php index 7c9aaf9..2c5e81b 100644 --- a/lib/Braintree/MerchantAccount.php +++ b/lib/Braintree/MerchantAccount.php @@ -1,6 +1,18 @@ $attribs)); - } - - public static function find($merchant_account_id) - { - try { - $response = Braintree_Http::get('/merchant_accounts/' . $merchant_account_id); - return self::factory($response['merchantAccount']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound('merchant account with id ' . $merchant_account_id . ' not found'); - } - } - - public static function update($merchant_account_id, $attributes) - { - Braintree_Util::verifyKeys(self::updateSignature(), $attributes); - return self::_doUpdate('/merchant_accounts/' . $merchant_account_id . '/update_via_api', array('merchant_account' => $attributes)); - } - - public static function detectSignature($attribs) - { - if (isset($attribs['applicantDetails'])) { - trigger_error("DEPRECATED: Passing applicantDetails to create is deprecated. Please use individual, business, and funding", E_USER_NOTICE); - return self::createDeprecatedSignature(); - } else { - return self::createSignature(); - } - } - - public static function updateSignature() - { - $signature = self::createSignature(); - unset($signature['tosAccepted']); - return $signature; - } - - public static function createSignature() - { - $addressSignature = array('streetAddress', 'postalCode', 'locality', 'region'); - $individualSignature = array( - 'firstName', - 'lastName', - 'email', - 'phone', - 'dateOfBirth', - 'ssn', - array('address' => $addressSignature) - ); - - $businessSignature = array( - 'dbaName', - 'legalName', - 'taxId', - array('address' => $addressSignature) - ); - - $fundingSignature = array( - 'routingNumber', - 'accountNumber', - 'destination', - 'email', - 'mobilePhone' - ); - - return array( - 'id', - 'tosAccepted', - 'masterMerchantAccountId', - array('individual' => $individualSignature), - array('funding' => $fundingSignature), - array('business' => $businessSignature) - ); - } - - public static function createDeprecatedSignature() - { - $applicantDetailsAddressSignature = array('streetAddress', 'postalCode', 'locality', 'region'); - $applicantDetailsSignature = array( - 'companyName', - 'firstName', - 'lastName', - 'email', - 'phone', - 'dateOfBirth', - 'ssn', - 'taxId', - 'routingNumber', - 'accountNumber', - array('address' => $applicantDetailsAddressSignature) - ); - - return array( - array('applicantDetails' => $applicantDetailsSignature), - 'id', - 'tosAccepted', - 'masterMerchantAccountId' - ); - } - - public static function _doCreate($url, $params) - { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); - } - - private static function _doUpdate($url, $params) - { - $response = Braintree_Http::put($url, $params); - - return self::_verifyGatewayResponse($response); - } - - private static function _verifyGatewayResponse($response) - { - if (isset($response['merchantAccount'])) { - // return a populated instance of Braintree_merchantAccount - return new Braintree_Result_Successful( - self::factory($response['merchantAccount']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected merchant account or apiErrorResponse" - ); - } - } - public static function factory($attributes) { $instance = new self(); @@ -155,22 +35,40 @@ protected function _initialize($merchantAccountAttribs) if (isset($merchantAccountAttribs['individual'])) { $individual = $merchantAccountAttribs['individual']; - $this->_set('individualDetails', Braintree_MerchantAccount_IndividualDetails::Factory($individual)); + $this->_set('individualDetails', MerchantAccount\IndividualDetails::Factory($individual)); } if (isset($merchantAccountAttribs['business'])) { $business = $merchantAccountAttribs['business']; - $this->_set('businessDetails', Braintree_MerchantAccount_BusinessDetails::Factory($business)); + $this->_set('businessDetails', MerchantAccount\BusinessDetails::Factory($business)); } if (isset($merchantAccountAttribs['funding'])) { $funding = $merchantAccountAttribs['funding']; - $this->_set('fundingDetails', new Braintree_MerchantAccount_FundingDetails($funding)); + $this->_set('fundingDetails', new MerchantAccount\FundingDetails($funding)); } if (isset($merchantAccountAttribs['masterMerchantAccount'])) { $masterMerchantAccount = $merchantAccountAttribs['masterMerchantAccount']; - $this->_set('masterMerchantAccount', Braintree_MerchantAccount::Factory($masterMerchantAccount)); + $this->_set('masterMerchantAccount', self::Factory($masterMerchantAccount)); } } + + + // static methods redirecting to gateway + + public static function create($attribs) + { + return Configuration::gateway()->merchantAccount()->create($attribs); + } + + public static function find($merchant_account_id) + { + return Configuration::gateway()->merchantAccount()->find($merchant_account_id); + } + + public static function update($merchant_account_id, $attributes) + { + return Configuration::gateway()->merchantAccount()->update($merchant_account_id, $attributes); + } } diff --git a/lib/Braintree/MerchantAccount/AddressDetails.php b/lib/Braintree/MerchantAccount/AddressDetails.php index 15349f6..18b1f04 100644 --- a/lib/Braintree/MerchantAccount/AddressDetails.php +++ b/lib/Braintree/MerchantAccount/AddressDetails.php @@ -1,5 +1,10 @@ _attributes = $businessAttribs; if (isset($businessAttribs['address'])) { - $this->_set('addressDetails', new Braintree_MerchantAccount_AddressDetails($businessAttribs['address'])); + $this->_set('addressDetails', new AddressDetails($businessAttribs['address'])); } } diff --git a/lib/Braintree/MerchantAccount/FundingDetails.php b/lib/Braintree/MerchantAccount/FundingDetails.php index f33e595..8c94eb7 100644 --- a/lib/Braintree/MerchantAccount/FundingDetails.php +++ b/lib/Braintree/MerchantAccount/FundingDetails.php @@ -1,6 +1,10 @@ _attributes = $individualAttribs; if (isset($individualAttribs['address'])) { - $this->_set('addressDetails', new Braintree_MerchantAccount_AddressDetails($individualAttribs['address'])); + $this->_set('addressDetails', new AddressDetails($individualAttribs['address'])); } } diff --git a/lib/Braintree/MerchantAccountGateway.php b/lib/Braintree/MerchantAccountGateway.php new file mode 100644 index 0000000..62a6cd5 --- /dev/null +++ b/lib/Braintree/MerchantAccountGateway.php @@ -0,0 +1,149 @@ +_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('/merchant_accounts/create_via_api', ['merchant_account' => $attribs]); + } + + public function find($merchant_account_id) + { + try { + $path = $this->_config->merchantPath() . '/merchant_accounts/' . $merchant_account_id; + $response = $this->_http->get($path); + return MerchantAccount::factory($response['merchantAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('merchant account with id ' . $merchant_account_id . ' not found'); + } + } + + public function update($merchant_account_id, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + $queryPath = '/merchant_accounts/' . $merchant_account_id . '/update_via_api'; + return $this->_doUpdate($queryPath, ['merchant_account' => $attributes]); + } + + public static function updateSignature() + { + $signature = self::createSignature(); + unset($signature['tosAccepted']); + return $signature; + } + + public function createForCurrency($attribs) + { + $queryPath = $this->_config->merchantPath() . '/merchant_accounts/create_for_currency'; + $response = $this->_http->post($queryPath, ['merchant_account' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function all() + { + $pager = [ + 'object' => $this, + 'method' => 'fetchMerchantAccounts', + ]; + return new PaginatedCollection($pager); + } + + public function fetchMerchantAccounts($page) + { + $response = $this->_http->get($this->_config->merchantPath() . '/merchant_accounts?page=' . $page); + $body = $response['merchantAccounts']; + $merchantAccounts = Util::extractattributeasarray($body, 'merchantAccount'); + $totalItems = $body['totalItems'][0]; + $pageSize = $body['pageSize'][0]; + return new PaginatedResult($totalItems, $pageSize, $merchantAccounts); + } + + public static function createSignature() + { + $addressSignature = ['streetAddress', 'postalCode', 'locality', 'region']; + $individualSignature = [ + 'firstName', + 'lastName', + 'email', + 'phone', + 'dateOfBirth', + 'ssn', + ['address' => $addressSignature] + ]; + + $businessSignature = [ + 'dbaName', + 'legalName', + 'taxId', + ['address' => $addressSignature] + ]; + + $fundingSignature = [ + 'routingNumber', + 'accountNumber', + 'destination', + 'email', + 'mobilePhone', + 'descriptor', + ]; + + return [ + 'id', + 'tosAccepted', + 'masterMerchantAccountId', + ['individual' => $individualSignature], + ['funding' => $fundingSignature], + ['business' => $businessSignature] + ]; + } + + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + private function _doUpdate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->put($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['response'])) { + $response = $response['response']; + } + if (isset($response['merchantAccount'])) { + // return a populated instance of merchantAccount + return new Result\Successful( + MerchantAccount::factory($response['merchantAccount']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected merchant account or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/MerchantGateway.php b/lib/Braintree/MerchantGateway.php new file mode 100644 index 0000000..4898bfa --- /dev/null +++ b/lib/Braintree/MerchantGateway.php @@ -0,0 +1,42 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasClientCredentials(); + $this->_http = new Http($gateway->config); + $this->_http->useClientCredentials(); + } + + public function create($attribs) + { + $response = $this->_http->post('/merchants/create_via_api', ['merchant' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['response']['merchant'])) { + // return a populated instance of merchant + return new Result\Successful([ + Merchant::factory($response['response']['merchant']), + OAuthCredentials::factory($response['response']['credentials']), + ]); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected merchant or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/Modification.php b/lib/Braintree/Modification.php index 7563b57..b462608 100644 --- a/lib/Braintree/Modification.php +++ b/lib/Braintree/Modification.php @@ -1,17 +1,12 @@ _attributes = $attributes; - - $addOnArray = array(); - if (isset($attributes['addOns'])) { - foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_addOn::factory($addOn); - } - } - $this->_attributes['addOns'] = $addOnArray; } public static function factory($attributes) @@ -20,4 +15,9 @@ public static function factory($attributes) $instance->_initialize($attributes); return $instance; } + + public function __toString() + { + return get_called_class() . '[' . Util::attributesToString($this->_attributes) . ']'; + } } diff --git a/lib/Braintree/MultipleValueNode.php b/lib/Braintree/MultipleValueNode.php index 3100e4d..e5e889b 100644 --- a/lib/Braintree/MultipleValueNode.php +++ b/lib/Braintree/MultipleValueNode.php @@ -1,36 +1,40 @@ name = $name; - $this->items = array(); - $this->allowedValues = $allowedValues; + $this->items = []; + $this->allowedValues = $allowedValues; } - function in($values) + public function in($values) { - $bad_values = array_diff($values, $this->allowedValues); - if (count($this->allowedValues) > 0 && count($bad_values) > 0) { - $message = 'Invalid argument(s) for ' . $this->name . ':'; - foreach ($bad_values AS $bad_value) { - $message .= ' ' . $bad_value; - } + $bad_values = array_diff($values, $this->allowedValues); + if (count($this->allowedValues) > 0 && count($bad_values) > 0) { + $message = 'Invalid argument(s) for ' . $this->name . ':'; + foreach ($bad_values as $bad_value) { + $message .= ' ' . $bad_value; + } - throw new InvalidArgumentException($message); - } + throw new InvalidArgumentException($message); + } $this->items = $values; return $this; } - function is($value) + public function is($value) { - return $this->in(array($value)); + return $this->in([$value]); } - function toParam() + public function toParam() { return $this->items; } diff --git a/lib/Braintree/MultipleValueOrTextNode.php b/lib/Braintree/MultipleValueOrTextNode.php index e0faca4..b8b6a21 100644 --- a/lib/Braintree/MultipleValueOrTextNode.php +++ b/lib/Braintree/MultipleValueOrTextNode.php @@ -1,44 +1,46 @@ textNode = new Braintree_TextNode($name); + $this->textNode = new TextNode($name); } - function contains($value) + public function contains($value) { $this->textNode->contains($value); return $this; } - function endsWith($value) + public function endsWith($value) { $this->textNode->endsWith($value); return $this; } - function is($value) + public function is($value) { $this->textNode->is($value); return $this; } - function isNot($value) + public function isNot($value) { $this->textNode->isNot($value); return $this; } - function startsWith($value) + public function startsWith($value) { $this->textNode->startsWith($value); return $this; } - function toParam() + public function toParam() { return array_merge(parent::toParam(), $this->textNode->toParam()); } diff --git a/lib/Braintree/OAuthAccessRevocation.php b/lib/Braintree/OAuthAccessRevocation.php new file mode 100644 index 0000000..2c94674 --- /dev/null +++ b/lib/Braintree/OAuthAccessRevocation.php @@ -0,0 +1,30 @@ +_initialize($attributes); + + return $instance; + } + + /** + * @ignore + */ + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } +} diff --git a/lib/Braintree/OAuthCredentials.php b/lib/Braintree/OAuthCredentials.php new file mode 100644 index 0000000..9a4968c --- /dev/null +++ b/lib/Braintree/OAuthCredentials.php @@ -0,0 +1,34 @@ +_attributes = $attribs; + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the access token + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/OAuthGateway.php b/lib/Braintree/OAuthGateway.php new file mode 100644 index 0000000..caa7b15 --- /dev/null +++ b/lib/Braintree/OAuthGateway.php @@ -0,0 +1,114 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($gateway->config); + $this->_http->useClientCredentials(); + + $this->_config->assertHasClientCredentials(); + } + + public function createTokenFromCode($params) + { + $params['grantType'] = "authorization_code"; + return $this->_createToken($params); + } + + public function createTokenFromRefreshToken($params) + { + $params['grantType'] = "refresh_token"; + return $this->_createToken($params); + } + + public function revokeAccessToken($accessToken) + { + $params = ['token' => $accessToken]; + $response = $this->_http->post('/oauth/revoke_access_token', $params); + return $this->_verifyGatewayResponse($response); + } + + private function _createToken($params) + { + $params = ['credentials' => $params]; + $response = $this->_http->post('/oauth/access_tokens', $params); + return $this->_verifyGatewayResponse($response); + } + + private function _verifyGatewayResponse($response) + { + if (isset($response['credentials'])) { + $result = new Result\Successful( + OAuthCredentials::factory($response['credentials']) + ); + return $this->_mapSuccess($result); + } elseif (isset($response['result'])) { + $result = new Result\Successful( + OAuthResult::factory($response['result']) + ); + return $this->_mapAccessTokenRevokeSuccess($result); + } elseif (isset($response['apiErrorResponse'])) { + $result = new Result\Error($response['apiErrorResponse']); + return $this->_mapError($result); + } else { + throw new Exception\Unexpected( + "Expected credentials or apiErrorResponse" + ); + } + } + + public function _mapError($result) + { + $error = $result->errors->deepAll()[0]; + + if ($error->code == Error\Codes::OAUTH_INVALID_GRANT) { + $result->error = 'invalid_grant'; + } elseif ($error->code == Error\Codes::OAUTH_INVALID_CREDENTIALS) { + $result->error = 'invalid_credentials'; + } elseif ($error->code == Error\Codes::OAUTH_INVALID_SCOPE) { + $result->error = 'invalid_scope'; + } + $result->errorDescription = explode(': ', $error->message)[1]; + return $result; + } + + public function _mapAccessTokenRevokeSuccess($result) + { + $result->revocationResult = $result->success; + return $result; + } + + public function _mapSuccess($result) + { + $credentials = $result->credentials; + $result->accessToken = $credentials->accessToken; + $result->refreshToken = $credentials->refreshToken; + $result->tokenType = $credentials->tokenType; + $result->expiresAt = $credentials->expiresAt; + return $result; + } + + public function connectUrl($params = []) + { + $query = Util::camelCaseToDelimiterArray($params, '_'); + $query['client_id'] = $this->_config->getClientId(); + $queryString = preg_replace('/\%5B\d+\%5D/', '%5B%5D', http_build_query($query)); + + return $this->_config->baseUrl() . '/oauth/connect?' . $queryString; + } +} diff --git a/lib/Braintree/OAuthResult.php b/lib/Braintree/OAuthResult.php new file mode 100644 index 0000000..6ecdd91 --- /dev/null +++ b/lib/Braintree/OAuthResult.php @@ -0,0 +1,34 @@ +_attributes = $attribs; + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /** + * returns a string representation of the result + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/PaginatedCollection.php b/lib/Braintree/PaginatedCollection.php new file mode 100644 index 0000000..4654778 --- /dev/null +++ b/lib/Braintree/PaginatedCollection.php @@ -0,0 +1,119 @@ + + * $result = MerchantAccount::all(); + * + * foreach($result as $merchantAccount) { + * print_r($merchantAccount->status); + * } + * + * + * @package Braintree + * @subpackage Utility + */ +class PaginatedCollection implements Iterator +{ + private $_pager; + private $_pageSize; + private $_currentPage; + private $_index; + private $_totalItems; + private $_items; + + /** + * set up the paginated collection + * + * expects an array of an object and method to call on it + * + * @param array $pager + */ + public function __construct($pager) + { + $this->_pager = $pager; + $this->_pageSize = 0; + $this->_currentPage = 0; + $this->_totalItems = 0; + $this->_index = 0; + } + + /** + * returns the current item when iterating with foreach + */ + public function current() + { + return $this->_items[($this->_index % $this->_pageSize)]; + } + + public function key() + { + return null; + } + + /** + * advances to the next item in the collection when iterating with foreach + */ + public function next() + { + ++$this->_index; + } + + /** + * rewinds the collection to the first item when iterating with foreach + */ + public function rewind() + { + $this->_index = 0; + $this->_currentPage = 0; + $this->_pageSize = 0; + $this->_totalItems = 0; + $this->_items = []; + } + + /** + * returns whether the current item is valid when iterating with foreach + */ + public function valid() + { + if ($this->_currentPage == 0 || $this->_index % $this->_pageSize == 0 && $this->_index < $this->_totalItems) { + $this->_getNextPage(); + } + + return $this->_index < $this->_totalItems; + } + + private function _getNextPage() + { + $this->_currentPage++; + $object = $this->_pager['object']; + $method = $this->_pager['method']; + + if (isset($this->_pager['query'])) { + $query = $this->_pager['query']; + $result = call_user_func( + [$object, $method], + $query, + $this->_currentPage + ); + } else { + $result = call_user_func( + [$object, $method], + $this->_currentPage + ); + } + + $this->_totalItems = $result->getTotalItems(); + $this->_pageSize = $result->getPageSize(); + $this->_items = $result->getCurrentPage(); + } +} diff --git a/lib/Braintree/PaginatedResult.php b/lib/Braintree/PaginatedResult.php new file mode 100644 index 0000000..d8f2bd1 --- /dev/null +++ b/lib/Braintree/PaginatedResult.php @@ -0,0 +1,32 @@ +_totalItems = $totalItems; + $this->_pageSize = $pageSize; + $this->_currentPage = $currentPage; + } + + public function getTotalItems() + { + return $this->_totalItems; + } + + public function getPageSize() + { + return $this->_pageSize; + } + + public function getCurrentPage() + { + return $this->_currentPage; + } +} diff --git a/lib/Braintree/PartialMatchNode.php b/lib/Braintree/PartialMatchNode.php index 076204a..4e4128c 100644 --- a/lib/Braintree/PartialMatchNode.php +++ b/lib/Braintree/PartialMatchNode.php @@ -1,14 +1,16 @@ searchTerms["starts_with"] = strval($value); return $this; } - function endsWith($value) + public function endsWith($value) { $this->searchTerms["ends_with"] = strval($value); return $this; diff --git a/lib/Braintree/PartnerMerchant.php b/lib/Braintree/PartnerMerchant.php index 48673e7..4446223 100644 --- a/lib/Braintree/PartnerMerchant.php +++ b/lib/Braintree/PartnerMerchant.php @@ -1,29 +1,24 @@ == More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $billingAgreementId + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $email + * @property-read string $imageUrl + * @property-read string $payerId + * @property-read \DateTime $revokedAt + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read \DateTime $updatedAt + */ +class PayPalAccount extends Base +{ + /** + * factory method: returns an instance of PayPalAccount + * to the requesting method, with populated properties + * + * @ignore + * @return PayPalAccount + */ + 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 $paypalAccountAttribs array of paypalAccount data + * @return void + */ + protected function _initialize($paypalAccountAttribs) + { + // set the attributes + $this->_attributes = $paypalAccountAttribs; + + $subscriptionArray = []; + if (isset($paypalAccountAttribs['subscriptions'])) { + foreach ($paypalAccountAttribs['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()->payPalAccount()->find($token); + } + + public static function update($token, $attributes) + { + return Configuration::gateway()->payPalAccount()->update($token, $attributes); + } + + public static function delete($token) + { + return Configuration::gateway()->payPalAccount()->delete($token); + } + + public static function sale($token, $transactionAttribs) + { + return Configuration::gateway()->payPalAccount()->sale($token, $transactionAttribs); + } +} diff --git a/lib/Braintree/PayPalAccountGateway.php b/lib/Braintree/PayPalAccountGateway.php new file mode 100644 index 0000000..231f6e3 --- /dev/null +++ b/lib/Braintree/PayPalAccountGateway.php @@ -0,0 +1,177 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PayPalAccountGateway +{ + 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); + } + + + /** + * find a paypalAccount by token + * + * @access public + * @param string $token paypal accountunique id + * @return PayPalAccount + * @throws Exception\NotFound + */ + public function find($token) + { + $this->_validateId($token); + try { + $path = $this->_config->merchantPath() . '/payment_methods/paypal_account/' . $token; + $response = $this->_http->get($path); + return PayPalAccount::factory($response['paypalAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'paypal account with token ' . $token . ' not found' + ); + } + } + + /** + * updates the paypalAccount 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 or Result\Error + */ + public function update($token, $attributes) + { + Util::verifyKeys(self::updateSignature(), $attributes); + $this->_validateId($token); + return $this->_doUpdate('put', '/payment_methods/paypal_account/' . $token, ['paypalAccount' => $attributes]); + } + + public function delete($token) + { + $this->_validateId($token); + $path = $this->_config->merchantPath() . '/payment_methods/paypal_account/' . $token; + $this->_http->delete($path); + return new Result\Successful(); + } + + /** + * create a new sale for the current PayPal account + * + * @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] + ) + ); + } + + public static function updateSignature() + { + return [ + 'token', + ['options' => ['makeDefault']] + ]; + } + + /** + * 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 PayPalAccount 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['paypalAccount'])) { + // return a populated instance of PayPalAccount + return new Result\Successful( + PayPalAccount::factory($response['paypalAccount']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + 'Expected paypal account or apiErrorResponse' + ); + } + } + + /** + * verifies that a valid paypal account 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 paypal account id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) { + throw new InvalidArgumentException( + $identifier . ' is an invalid paypal account ' . $identifierType . '.' + ); + } + } +} diff --git a/lib/Braintree/PaymentInstrumentType.php b/lib/Braintree/PaymentInstrumentType.php new file mode 100644 index 0000000..817e30f --- /dev/null +++ b/lib/Braintree/PaymentInstrumentType.php @@ -0,0 +1,17 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethod extends Base +{ + // static methods redirecting to gateway + + public static function create($attribs) + { + return Configuration::gateway()->paymentMethod()->create($attribs); + } + + public static function find($token) + { + return Configuration::gateway()->paymentMethod()->find($token); + } + + public static function update($token, $attribs) + { + return Configuration::gateway()->paymentMethod()->update($token, $attribs); + } + + public static function delete($token, $options = []) + { + return Configuration::gateway()->paymentMethod()->delete($token, $options); + } +} diff --git a/lib/Braintree/PaymentMethodGateway.php b/lib/Braintree/PaymentMethodGateway.php new file mode 100644 index 0000000..07f1331 --- /dev/null +++ b/lib/Braintree/PaymentMethodGateway.php @@ -0,0 +1,337 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethodGateway +{ + 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', ['payment_method' => $attribs]); + } + + /** + * find a PaymentMethod by token + * + * @param string $token payment method unique id + * @return CreditCard|PayPalAccount + * @throws Exception\NotFound + */ + public function find($token) + { + $this->_validateId($token); + try { + $path = $this->_config->merchantPath() . '/payment_methods/any/' . $token; + $response = $this->_http->get($path); + return PaymentMethodParser::parsePaymentMethod($response); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'payment method with token ' . $token . ' not found' + ); + } + } + + public function update($token, $attribs) + { + Util::verifyKeys(self::updateSignature(), $attribs); + return $this->_doUpdate('/payment_methods/any/' . $token, ['payment_method' => $attribs]); + } + + public function delete($token, $options = []) + { + Util::verifyKeys(self::deleteSignature(), $options); + $this->_validateId($token); + $queryString = ""; + if (!empty($options)) { + $queryString = "?" . http_build_query(Util::camelCaseToDelimiterArray($options, '_')); + } + return $this->_doDelete('/payment_methods/any/' . $token . $queryString); + } + + public function grant($sharedPaymentMethodToken, $attribs = []) + { + if (is_bool($attribs) === true) { + $attribs = ['allow_vaulting' => $attribs]; + } + $options = [ 'shared_payment_method_token' => $sharedPaymentMethodToken ]; + + return $this->_doGrant( + '/payment_methods/grant', + [ + 'payment_method' => array_merge($attribs, $options) + ] + ); + } + + public function revoke($sharedPaymentMethodToken) + { + return $this->_doRevoke( + '/payment_methods/revoke', + [ + 'payment_method' => [ + 'shared_payment_method_token' => $sharedPaymentMethodToken + ] + ] + ); + } + + private static function baseSignature() + { + $billingAddressSignature = AddressGateway::createSignature(); + $optionsSignature = [ + 'failOnDuplicatePaymentMethod', + 'makeDefault', + 'skipAdvancedFraudChecking', + 'usBankAccountVerificationMethod', + 'verificationAccountType', + 'verificationAmount', + 'verificationMerchantAccountId', + 'verifyCard', + ['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 [ + 'billingAddressId', + 'cardholderName', + 'cvv', + 'deviceData', + 'expirationDate', + 'expirationMonth', + 'expirationYear', + 'number', + 'paymentMethodNonce', + 'token', + ['options' => $optionsSignature], + ['billingAddress' => $billingAddressSignature] + ]; + } + + public static function createSignature() + { + $signature = array_merge(self::baseSignature(), [ + 'customerId', + 'paypalRefreshToken', + CreditCardGateway::threeDSecurePassThruSignature() + ]); + return $signature; + } + + public static function updateSignature() + { + $billingAddressSignature = AddressGateway::updateSignature(); + array_push($billingAddressSignature, [ + 'options' => [ + 'updateExisting' + ] + ]); + $threeDSPassThruSignature = [ + 'authenticationResponse', + 'cavv', + 'cavvAlgorithm', + 'directoryResponse', + 'dsTransactionId', + 'eciFlag', + 'threeDSecureVersion', + 'xid' + ]; + $signature = array_merge(self::baseSignature(), [ + 'venmoSdkPaymentMethodCode', + ['billingAddress' => $billingAddressSignature], + ['threeDSecurePassThru' => $threeDSPassThruSignature] + ]); + return $signature; + } + + private static function deleteSignature() + { + return ['revokeAllGrants']; + } + + /** + * 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); + } + + public function _doGrant($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGrantResponse($response); + } + + public function _doRevoke($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyRevokeResponse($response); + } + + /** + * sends the update request to the gateway + * + * @ignore + * @param string $subPath + * @param array $params + * @return mixed + */ + public function _doUpdate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->put($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + + /** + * sends the delete request to the gateway + * + * @ignore + * @param string $subPath + * @return mixed + */ + public function _doDelete($subPath) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $this->_http->delete($fullPath); + return new Result\Successful(); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new CreditCard or PayPalAccount 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['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } elseif (($response)) { + return new Result\Successful( + PaymentMethodParser::parsePaymentMethod($response), + 'paymentMethod' + ); + } else { + throw new Exception\Unexpected( + 'Expected payment method or apiErrorResponse' + ); + } + } + + private function _verifyGrantResponse($response) + { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } elseif (isset($response['paymentMethodNonce'])) { + return new Result\Successful( + PaymentMethodNonce::factory($response['paymentMethodNonce']), + 'paymentMethodNonce' + ); + } else { + throw new Exception\Unexpected( + 'Expected paymentMethodNonce or apiErrorResponse' + ); + } + } + + private function _verifyRevokeResponse($response) + { + if (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } elseif (isset($response['success'])) { + return new Result\Successful(); + } else { + throw new Exception\Unexpected( + 'Expected success or apiErrorResponse' + ); + } + } + + /** + * verifies that a valid payment method 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 payment method id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $identifier)) { + throw new InvalidArgumentException( + $identifier . ' is an invalid payment method ' . $identifierType . '.' + ); + } + } +} diff --git a/lib/Braintree/PaymentMethodNonce.php b/lib/Braintree/PaymentMethodNonce.php new file mode 100644 index 0000000..a7e4200 --- /dev/null +++ b/lib/Braintree/PaymentMethodNonce.php @@ -0,0 +1,66 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read \Braintree\BinData $binData + * @property-read boolean $default + * @property-read string $nonce + * @property-read \Braintree\ThreeDSecureInfo $threeDSecureInfo + * @property-read string $type + */ +class PaymentMethodNonce extends Base +{ + // static methods redirecting to gateway + + public static function create($token, $params = []) + { + return Configuration::gateway()->paymentMethodNonce()->create($token, $params); + } + + public static function find($nonce) + { + return Configuration::gateway()->paymentMethodNonce()->find($nonce); + } + + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + protected function _initialize($nonceAttributes) + { + $this->_attributes = $nonceAttributes; + $this->_set('nonce', $nonceAttributes['nonce']); + $this->_set('type', $nonceAttributes['type']); + + if (isset($nonceAttributes['authenticationInsight'])) { + $this->_set('authenticationInsight', $nonceAttributes['authenticationInsight']); + } + + if (isset($nonceAttributes['binData'])) { + $this->_set('binData', BinData::factory($nonceAttributes['binData'])); + } + + if (isset($nonceAttributes['threeDSecureInfo'])) { + $this->_set('threeDSecureInfo', ThreeDSecureInfo::factory($nonceAttributes['threeDSecureInfo'])); + } + } +} diff --git a/lib/Braintree/PaymentMethodNonceGateway.php b/lib/Braintree/PaymentMethodNonceGateway.php new file mode 100644 index 0000000..77482dd --- /dev/null +++ b/lib/Braintree/PaymentMethodNonceGateway.php @@ -0,0 +1,75 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class PaymentMethodNonceGateway +{ + private $_gateway; + private $_config; + private $_http; + + public function __construct($gateway) + { + $this->_gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($gateway->config); + } + + + public function create($token, $params = []) + { + $subPath = '/payment_methods/' . $token . '/nonces'; + $fullPath = $this->_config->merchantPath() . $subPath; + $schema = [[ + 'paymentMethodNonce' => [ + 'merchantAccountId', + 'authenticationInsight', + ['authenticationInsightOptions' => [ + 'amount', + 'recurringCustomerConsent', + 'recurringMaxAmount' + ] + ]] + ]]; + Util::verifyKeys($schema, $params); + $response = $this->_http->post($fullPath, $params); + + return new Result\Successful( + PaymentMethodNonce::factory($response['paymentMethodNonce']), + "paymentMethodNonce" + ); + } + + /** + * @access public + * + */ + public function find($nonce) + { + try { + $path = $this->_config->merchantPath() . '/payment_method_nonces/' . $nonce; + $response = $this->_http->get($path); + return PaymentMethodNonce::factory($response['paymentMethodNonce']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'payment method nonce with id ' . $nonce . ' not found' + ); + } + } +} diff --git a/lib/Braintree/PaymentMethodParser.php b/lib/Braintree/PaymentMethodParser.php new file mode 100644 index 0000000..bb409a5 --- /dev/null +++ b/lib/Braintree/PaymentMethodParser.php @@ -0,0 +1,50 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + */ +class PaymentMethodParser +{ + public static function parsePaymentMethod($response) + { + if (isset($response['creditCard'])) { + return CreditCard::factory($response['creditCard']); + } elseif (isset($response['paypalAccount'])) { + return PayPalAccount::factory($response['paypalAccount']); + } elseif (isset($response['applePayCard'])) { + return ApplePayCard::factory($response['applePayCard']); + } elseif (isset($response['androidPayCard'])) { + return GooglePayCard::factory($response['androidPayCard']); + } elseif (isset($response['usBankAccount'])) { + return UsBankAccount::factory($response['usBankAccount']); + } elseif (isset($response['venmoAccount'])) { + return VenmoAccount::factory($response['venmoAccount']); + } elseif (isset($response['visaCheckoutCard'])) { + return VisaCheckoutCard::factory($response['visaCheckoutCard']); + } elseif (isset($response['samsungPayCard'])) { + return SamsungPayCard::factory($response['samsungPayCard']); + } elseif (is_array($response)) { + return UnknownPaymentMethod::factory($response); + } else { + throw new Exception\Unexpected( + 'Expected payment method' + ); + } + } +} diff --git a/lib/Braintree/Plan.php b/lib/Braintree/Plan.php index b0fb3d0..d11102a 100644 --- a/lib/Braintree/Plan.php +++ b/lib/Braintree/Plan.php @@ -1,21 +1,26 @@ $response['plans']); - } else { - $plans = array("plan" => array()); - } - return Braintree_Util::extractAttributeAsArray( - $plans, - 'plan' - ); - } +namespace Braintree; +/** + * @property-read \Braintree\Addon[] $addOns + * @property-read string $id + * @property-read int|null $billingDayOfMonth + * @property-read int $billingFrequency + * @property-read \DateTime $createdAt + * @property-read string $currencyIsoCode + * @property-read string|null $description + * @property-read \Braintree\Discount[] $discounts + * @property-read string $name + * @property-read int|null $numberOfBillingCycles + * @property-read string $price + * @property-read int|null $trialDuration + * @property-read string|null $trialDurationUnit + * @property-read boolean $trialPeriod + * @property-read \DateTime $updatedAt + */ +class Plan extends Base +{ public static function factory($attributes) { $instance = new self(); @@ -28,28 +33,36 @@ protected function _initialize($attributes) { $this->_attributes = $attributes; - $addOnArray = array(); + $addOnArray = []; if (isset($attributes['addOns'])) { - foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); + foreach ($attributes['addOns'] as $addOn) { + $addOnArray[] = AddOn::factory($addOn); } } $this->_attributes['addOns'] = $addOnArray; - $discountArray = array(); + $discountArray = []; if (isset($attributes['discounts'])) { - foreach ($attributes['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); + foreach ($attributes['discounts'] as $discount) { + $discountArray[] = Discount::factory($discount); } } $this->_attributes['discounts'] = $discountArray; - $planArray = array(); + $planArray = []; if (isset($attributes['plans'])) { - foreach ($attributes['plans'] AS $plan) { - $planArray[] = Braintree_Plan::factory($plan); + foreach ($attributes['plans'] as $plan) { + $planArray[] = self::factory($plan); } } $this->_attributes['plans'] = $planArray; } + + + // static methods redirecting to gateway + + public static function all() + { + return Configuration::gateway()->plan()->all(); + } } diff --git a/lib/Braintree/PlanGateway.php b/lib/Braintree/PlanGateway.php new file mode 100644 index 0000000..84883e7 --- /dev/null +++ b/lib/Braintree/PlanGateway.php @@ -0,0 +1,34 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + public function all() + { + $path = $this->_config->merchantPath() . '/plans'; + $response = $this->_http->get($path); + if (key_exists('plans', $response)) { + $plans = ["plan" => $response['plans']]; + } else { + $plans = ["plan" => []]; + } + + return Util::extractAttributeAsArray( + $plans, + 'plan' + ); + } +} diff --git a/lib/Braintree/ProcessorResponseTypes.php b/lib/Braintree/ProcessorResponseTypes.php new file mode 100644 index 0000000..143b2d7 --- /dev/null +++ b/lib/Braintree/ProcessorResponseTypes.php @@ -0,0 +1,15 @@ +name = $name; - $this->searchTerms = array(); + $this->searchTerms = []; } - function greaterThanOrEqualTo($value) + public function greaterThanOrEqualTo($value) { $this->searchTerms['min'] = $value; return $this; } - function lessThanOrEqualTo($value) + public function lessThanOrEqualTo($value) { $this->searchTerms['max'] = $value; return $this; } - function is($value) + public function is($value) { $this->searchTerms['is'] = $value; return $this; } - function between($min, $max) + public function between($min, $max) { - return $this->greaterThanOrEqualTo($min)->lessThanOrEqualTo($max); + return $this->greaterThanOrEqualTo($min)->lessThanOrEqualTo($max); } - function toParam() + public function toParam() { return $this->searchTerms; } diff --git a/lib/Braintree/ResourceCollection.php b/lib/Braintree/ResourceCollection.php index dc09cf1..e1a83ff 100644 --- a/lib/Braintree/ResourceCollection.php +++ b/lib/Braintree/ResourceCollection.php @@ -1,20 +1,18 @@ - * $result = Braintree_Customer::all(); + * $result = Customer::all(); * * foreach($result as $transaction) { * print_r($transaction->id); @@ -23,12 +21,12 @@ * * @package Braintree * @subpackage Utility - * @copyright 2010 Braintree Payment Solutions */ -class Braintree_ResourceCollection implements Iterator +class ResourceCollection implements Iterator { - private $_index; private $_batchIndex; + private $_ids; + private $_index; private $_items; private $_pageSize; private $_pager; @@ -38,10 +36,10 @@ class Braintree_ResourceCollection implements Iterator * * expects an array of attributes with literal keys * - * @param array $attributes - * @param array $pagerAttribs + * @param array $response + * @param array $pager */ - public function __construct($response, $pager) + public function __construct($response, $pager) { $this->_pageSize = $response["searchResults"]["pageSize"]; $this->_ids = $response["searchResults"]["ids"]; @@ -64,7 +62,7 @@ public function current() public function firstItem() { $ids = $this->_ids; - $page = $this->_getPage(array($ids[0])); + $page = $this->_getPage([$ids[0]]); return $page[0]; } @@ -82,7 +80,7 @@ public function next() } /** - * rewinds thtestIterateOverResultse collection to the first item when iterating with foreach + * rewinds the testIterateOverResults collection to the first item when iterating with foreach */ public function rewind() { @@ -113,12 +111,9 @@ public function maximumCount() private function _getNextPage() { - if (empty($this->_ids)) - { - $this->_items = array(); - } - else - { + if (empty($this->_ids)) { + $this->_items = []; + } else { $this->_items = $this->_getPage(array_slice($this->_ids, $this->_batchIndex, $this->_pageSize)); $this->_batchIndex += $this->_pageSize; $this->_index = 0; @@ -128,21 +123,31 @@ private function _getNextPage() /** * requests the next page of results for the collection * - * @return none + * @return void */ private function _getPage($ids) { - $className = $this->_pager['className']; - $classMethod = $this->_pager['classMethod']; - $methodArgs = array(); + $object = $this->_pager['object']; + $method = $this->_pager['method']; + $methodArgs = []; foreach ($this->_pager['methodArgs'] as $arg) { array_push($methodArgs, $arg); } array_push($methodArgs, $ids); return call_user_func_array( - array($className, $classMethod), + [$object, $method], $methodArgs ); } + + /** + * returns all IDs in the collection + * + * @return array + */ + public function getIds() + { + return $this->_ids; + } } diff --git a/lib/Braintree/Result/CreditCardVerification.php b/lib/Braintree/Result/CreditCardVerification.php index 68bc75a..cd08f32 100644 --- a/lib/Braintree/Result/CreditCardVerification.php +++ b/lib/Braintree/Result/CreditCardVerification.php @@ -1,11 +1,11 @@ _initializeFromArray($attributes); } + /** * initializes instance properties from the keys/values of an array * @ignore * @access protected * @param $aAttribs array of properties to set - single level - * @return none + * @return void */ private function _initializeFromArray($attributes) { + if (isset($attributes['riskData'])) { + $attributes['riskData'] = RiskData::factory($attributes['riskData']); + } + + if (isset($attributes['globalId'])) { + $attributes['graphQLId'] = $attributes['globalId']; + } + + if (isset($attributes['threeDSecureInfo'])) { + $attributes['threeDSecureInfo'] = ThreeDSecureInfo::factory($attributes['threeDSecureInfo']); + } $this->_attributes = $attributes; - foreach($attributes AS $name => $value) { + foreach ($attributes as $name => $value) { $varName = "_$name"; $this->$varName = $value; - // $this->$varName = Braintree_Util::delimiterToCamelCase($value, '_'); } } + /** - * * @ignore */ - public function __get($name) + public function __get($name) { $varName = "_$name"; return isset($this->$varName) ? $this->$varName : null; @@ -78,9 +89,19 @@ public function __get($name) * returns a string representation of the customer * @return string */ - public function __toString() + public function __toString() { return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + Util::attributesToString($this->_attributes) . ']'; + } + + public static function allStatuses() + { + return [ + CreditCardVerification::FAILED, + CreditCardVerification::GATEWAY_REJECTED, + CreditCardVerification::PROCESSOR_DECLINED, + CreditCardVerification::VERIFIED + ]; } } diff --git a/lib/Braintree/Result/Error.php b/lib/Braintree/Result/Error.php index 94f6bce..2808f07 100644 --- a/lib/Braintree/Result/Error.php +++ b/lib/Braintree/Result/Error.php @@ -1,11 +1,13 @@ - * $result = Braintree_Transaction::void('abc123'); + * $result = Transaction::void('abc123'); * if ($result->success) { * // Successful Result * } else { - * // Braintree_Result_Error + * // Result\Error * } * * * @package Braintree * @subpackage Result - * @copyright 2010 Braintree Payment Solutions * * @property-read array $params original passed params - * @property-read object $errors Braintree_Error_ErrorCollection - * @property-read object $creditCardVerification credit card verification data + * @property-read \Braintree\Error\ErrorCollection $errors + * @property-read \Braintree\Result\CreditCardVerification $creditCardVerification credit card verification data */ -class Braintree_Result_Error extends Braintree +class Error extends Base { - /** - * - * @var boolean always false + /** + * @var bool always false */ - public $success = false; + public $success = false; /** * return original value for a field @@ -49,69 +49,75 @@ class Braintree_Result_Error extends Braintree * @param string $field * @return string */ - public function valueForHtmlField($field) - { - $pieces = preg_split("/[\[\]]+/", $field, 0, PREG_SPLIT_NO_EMPTY); - $params = $this->params; - foreach(array_slice($pieces, 0, -1) as $key) { - $params = $params[Braintree_Util::delimiterToCamelCase($key)]; - } - if ($key != 'custom_fields') { - $finalKey = Braintree_Util::delimiterToCamelCase(end($pieces)); - } else { - $finalKey = end($pieces); - } - $fieldValue = isset($params[$finalKey]) ? $params[$finalKey] : null; - return $fieldValue; - } + public function valueForHtmlField($field) + { + $pieces = preg_split("/[\[\]]+/", $field, 0, PREG_SPLIT_NO_EMPTY); + $params = $this->params; + foreach (array_slice($pieces, 0, -1) as $key) { + $params = $params[Util::delimiterToCamelCase($key)]; + } + if ($key != 'custom_fields') { + $finalKey = Util::delimiterToCamelCase(end($pieces)); + } else { + $finalKey = end($pieces); + } + $fieldValue = isset($params[$finalKey]) ? $params[$finalKey] : null; + return $fieldValue; + } /** * overrides default constructor * @ignore * @param array $response gateway response array */ - public function __construct($response) - { - $this->_attributes = $response; - $this->_set('errors', new Braintree_Error_ErrorCollection($response['errors'])); + public function __construct($response) + { + $this->_attributes = $response; + $this->_set('errors', new ErrorCollection($response['errors'])); - if(isset($response['verification'])) { - $this->_set('creditCardVerification', new Braintree_Result_CreditCardVerification($response['verification'])); - } else { - $this->_set('creditCardVerification', null); - } + if (isset($response['verification'])) { + $this->_set('creditCardVerification', new CreditCardVerification($response['verification'])); + } else { + $this->_set('creditCardVerification', null); + } - if(isset($response['transaction'])) { - $this->_set('transaction', Braintree_Transaction::factory($response['transaction'])); - } else { - $this->_set('transaction', null); - } + if (isset($response['transaction'])) { + $this->_set('transaction', Transaction::factory($response['transaction'])); + } else { + $this->_set('transaction', null); + } - if(isset($response['subscription'])) { - $this->_set('subscription', Braintree_Subscription::factory($response['subscription'])); - } else { - $this->_set('subscription', null); - } + if (isset($response['subscription'])) { + $this->_set('subscription', Subscription::factory($response['subscription'])); + } else { + $this->_set('subscription', null); + } - if(isset($response['merchantAccount'])) { - $this->_set('merchantAccount', Braintree_MerchantAccount::factory($response['merchantAccount'])); - } else { - $this->_set('merchantAccount', null); - } - } + if (isset($response['merchantAccount'])) { + $this->_set('merchantAccount', MerchantAccount::factory($response['merchantAccount'])); + } else { + $this->_set('merchantAccount', null); + } + + if (isset($response['verification'])) { + $this->_set('verification', new CreditCardVerification($response['verification'])); + } else { + $this->_set('verification', null); + } + } /** * create a printable representation of the object as: * ClassName[property=value, property=value] * @ignore - * @return var + * @return string */ - public function __toString() + public function __toString() { - $output = Braintree_Util::attributesToString($this->_attributes); + $output = Util::attributesToString($this->_attributes); if (isset($this->_creditCardVerification)) { $output .= sprintf('%s', $this->_creditCardVerification); } - return __CLASS__ .'['.$output.']'; + return __CLASS__ . '[' . $output . ']'; } } diff --git a/lib/Braintree/Result/Successful.php b/lib/Braintree/Result/Successful.php index 8b7d16c..ed09102 100644 --- a/lib/Braintree/Result/Successful.php +++ b/lib/Braintree/Result/Successful.php @@ -1,11 +1,9 @@ customer like so: * * - * $result = Braintree_Customer::create(array('first_name' => "John")); + * $result = Customer::create(array('first_name' => "John")); * if ($result->success) { - * // Braintree_Result_Successful + * // Successful * echo "Created customer {$result->customer->id}"; * } else { - * // Braintree_Result_Error + * // Error * } * * * * @package Braintree * @subpackage Result - * @copyright 2010 Braintree Payment Solutions */ -class Braintree_Result_Successful extends Braintree_Instance +class Successful extends Instance { /** * @@ -42,37 +39,58 @@ class Braintree_Result_Successful extends Braintree_Instance * * @var string stores the internal name of the object providing access to */ - private $_returnObjectName; + private $_returnObjectNames; /** * @ignore - * @param string $classToReturn name of class to instantiate + * @param array|null $objsToReturn + * @param array|null $propertyNames */ - public function __construct($objToReturn = null) + public function __construct($objsToReturn = [], $propertyNames = []) { - if(!empty($objToReturn)) { - // get a lowercase direct name for the property - $property = Braintree_Util::cleanClassName( - get_class($objToReturn) - ); + // Sanitize arguments (preserves backwards compatibility) + if (!is_array($objsToReturn)) { + $objsToReturn = [$objsToReturn]; + } + if (!is_array($propertyNames)) { + $propertyNames = [$propertyNames]; + } + + $objects = $this->_mapPropertyNamesToObjsToReturn($propertyNames, $objsToReturn); + $this->_attributes = []; + $this->_returnObjectNames = []; + + foreach ($objects as $propertyName => $objToReturn) { // save the name for indirect access - $this->_returnObjectName = $property; + array_push($this->_returnObjectNames, $propertyName); // create the property! - $this->$property = $objToReturn; + $this->$propertyName = $objToReturn; } } - /** * * @ignore * @return string string representation of the object's structure */ - public function __toString() - { - $returnObject = $this->_returnObjectName; - return __CLASS__ . '['.$this->$returnObject->__toString().']'; - } + public function __toString() + { + $objects = []; + foreach ($this->_returnObjectNames as $returnObjectName) { + array_push($objects, $returnObjectName); + } + return __CLASS__ . '[' . implode(', ', $objects) . ']'; + } + private function _mapPropertyNamesToObjsToReturn($propertyNames, $objsToReturn) + { + if (count($objsToReturn) != count($propertyNames)) { + $propertyNames = []; + foreach ($objsToReturn as $obj) { + array_push($propertyNames, Util::cleanClassName(get_class($obj))); + } + } + return array_combine($propertyNames, $objsToReturn); + } } diff --git a/lib/Braintree/Result/UsBankAccountVerification.php b/lib/Braintree/Result/UsBankAccountVerification.php new file mode 100644 index 0000000..73bc9d6 --- /dev/null +++ b/lib/Braintree/Result/UsBankAccountVerification.php @@ -0,0 +1,112 @@ +_initializeFromArray($attributes); + + $usBankAccount = isset($attributes['usBankAccount']) ? + UsBankAccount::factory($attributes['usBankAccount']) : + null; + $this->usBankAccount = $usBankAccount; + } + + /** + * initializes instance properties from the keys/values of an array + * @ignore + * @access protected + * @param $aAttribs array of properties to set - single level + * @return void + */ + private function _initializeFromArray($attributes) + { + $this->_attributes = $attributes; + foreach ($attributes as $name => $value) { + $varName = "_$name"; + $this->$varName = $value; + } + } + + /** + * @ignore + */ + public function __get($name) + { + $varName = "_$name"; + return isset($this->$varName) ? $this->$varName : null; + } + + /** + * returns a string representation of the customer + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + public static function allStatuses() + { + return [ + UsBankAccountVerification::FAILED, + UsBankAccountVerification::GATEWAY_REJECTED, + UsBankAccountVerification::PROCESSOR_DECLINED, + UsBankAccountVerification::VERIFIED, + UsBankAccountVerification::PENDING, + ]; + } + + public static function allVerificationMethods() + { + return [ + UsBankAccountVerification::TOKENIZED_CHECK, + UsBankAccountVerification::NETWORK_CHECK, + UsBankAccountVerification::INDEPENDENT_CHECK, + UsBankAccountVerification::MICRO_TRANSFERS, + ]; + } +} diff --git a/lib/Braintree/RevokedPaymentMethodMetadata.php b/lib/Braintree/RevokedPaymentMethodMetadata.php new file mode 100644 index 0000000..93d67fd --- /dev/null +++ b/lib/Braintree/RevokedPaymentMethodMetadata.php @@ -0,0 +1,53 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $customerId + * @property-read string $token + * @property-read string $revokedPaymentMethod + */ +class RevokedPaymentMethodMetadata extends Base +{ + /** + * factory method: returns an instance of RevokedPaymentMethodMetadata + * to the requesting method, with populated properties + * + * @ignore + * @return RevokedPaymentMethodMetadata + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->revokedPaymentMethod = PaymentMethodParser::parsePaymentMethod($attributes); + $instance->customerId = $instance->revokedPaymentMethod->customerId; + $instance->token = $instance->revokedPaymentMethod->token; + return $instance; + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/RiskData.php b/lib/Braintree/RiskData.php new file mode 100644 index 0000000..4133443 --- /dev/null +++ b/lib/Braintree/RiskData.php @@ -0,0 +1,45 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + public function decisionReasons() + { + return $this->_attributes['decisionReasons']; + } + + + /** + * returns a string representation of the risk data + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/SamsungPayCard.php b/lib/Braintree/SamsungPayCard.php new file mode 100644 index 0000000..e9f1185 --- /dev/null +++ b/lib/Braintree/SamsungPayCard.php @@ -0,0 +1,138 @@ +default; + } + + /** + * checks whether the card is expired based on the current date + * + * @return boolean + */ + public function isExpired() + { + return $this->expired; + } + + /** + * 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); + } + + /** + * returns false if comparing object is not a SamsungPayCard, + * or is a SamsungPayCard with a different id + * + * @param object $otherSamsungPayCard customer to compare against + * @return boolean + */ + public function isEqual($otherSamsungPayCard) + { + return !($otherSamsungPayCard instanceof self) ? false : $this->token === $otherSamsungPayCard->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 SamsungPayCard + * to the requesting method, with populated properties + * + * @ignore + * @return SamsungPayCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } +} diff --git a/lib/Braintree/SettlementBatchSummary.php b/lib/Braintree/SettlementBatchSummary.php index 98591a7..697c0ef 100644 --- a/lib/Braintree/SettlementBatchSummary.php +++ b/lib/Braintree/SettlementBatchSummary.php @@ -1,57 +1,17 @@ $settlement_date); - if (isset($groupByCustomField)) - { - $criteria['group_by_custom_field'] = $groupByCustomField; - } - $params = array('settlement_batch_summary' => $criteria); - $response = Braintree_Http::post('/settlement_batch_summary', $params); - - if (isset($groupByCustomField)) - { - $response['settlementBatchSummary']['records'] = self::_underscoreCustomField( - $groupByCustomField, - $response['settlementBatchSummary']['records'] - ); - } - return self::_verifyGatewayResponse($response); - } - - private static function _underscoreCustomField($groupByCustomField, $records) - { - $updatedRecords = array(); - - foreach ($records as $record) - { - $camelized = Braintree_Util::delimiterToCamelCase($groupByCustomField); - $record[$groupByCustomField] = $record[$camelized]; - unset($record[$camelized]); - $updatedRecords[] = $record; - } - - return $updatedRecords; - } - - private static function _verifyGatewayResponse($response) - { - if (isset($response['settlementBatchSummary'])) { - return new Braintree_Result_Successful( - self::factory($response['settlementBatchSummary']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected settlementBatchSummary or apiErrorResponse" - ); - } - } +namespace Braintree; +/** + * @property-read array $records + */ +class SettlementBatchSummary extends Base +{ + /** + * + * @param array $attributes + * @return SettlementBatchSummary + */ public static function factory($attributes) { $instance = new self(); @@ -61,6 +21,7 @@ public static function factory($attributes) /** * @ignore + * @param array $attributes */ protected function _initialize($attributes) { @@ -71,4 +32,17 @@ public function records() { return $this->_attributes['records']; } + + + /** + * static method redirecting to gateway + * + * @param string $settlement_date Date YYYY-MM-DD + * @param string $groupByCustomField + * @return Result\Successful|Result\Error + */ + public static function generate($settlement_date, $groupByCustomField = null) + { + return Configuration::gateway()->settlementBatchSummary()->generate($settlement_date, $groupByCustomField); + } } diff --git a/lib/Braintree/SettlementBatchSummaryGateway.php b/lib/Braintree/SettlementBatchSummaryGateway.php new file mode 100644 index 0000000..85d0ede --- /dev/null +++ b/lib/Braintree/SettlementBatchSummaryGateway.php @@ -0,0 +1,103 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * + * @param string $settlement_date + * @param string $groupByCustomField + * @return SettlementBatchSummary|Result\Error + */ + public function generate($settlement_date, $groupByCustomField = null) + { + $criteria = ['settlement_date' => $settlement_date]; + if (isset($groupByCustomField)) { + $criteria['group_by_custom_field'] = $groupByCustomField; + } + $params = ['settlement_batch_summary' => $criteria]; + $path = $this->_config->merchantPath() . '/settlement_batch_summary'; + $response = $this->_http->post($path, $params); + + if (isset($groupByCustomField)) { + $response['settlementBatchSummary']['records'] = $this->_underscoreCustomField( + $groupByCustomField, + $response['settlementBatchSummary']['records'] + ); + } + + return $this->_verifyGatewayResponse($response); + } + + /** + * + * @param string $groupByCustomField + * @param array $records + * @return array + */ + private function _underscoreCustomField($groupByCustomField, $records) + { + $updatedRecords = []; + + foreach ($records as $record) { + $camelized = Util::delimiterToCamelCase($groupByCustomField); + $record[$groupByCustomField] = $record[$camelized]; + unset($record[$camelized]); + $updatedRecords[] = $record; + } + + return $updatedRecords; + } + + /** + * + * @param array $response + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['settlementBatchSummary'])) { + return new Result\Successful( + SettlementBatchSummary::factory($response['settlementBatchSummary']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected settlementBatchSummary or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/ShippingMethod.php b/lib/Braintree/ShippingMethod.php new file mode 100644 index 0000000..682521e --- /dev/null +++ b/lib/Braintree/ShippingMethod.php @@ -0,0 +1,18 @@ +key = $key; + $this->digest = $digest; + } + + public function sign($payload) + { + return $this->hash($payload) . "|" . $payload; + } + + public function hash($data) + { + return call_user_func($this->digest, $this->key, $data); + } +} diff --git a/lib/Braintree/Subscription.php b/lib/Braintree/Subscription.php index 12bbb54..34331e9 100644 --- a/lib/Braintree/Subscription.php +++ b/lib/Braintree/Subscription.php @@ -1,17 +1,49 @@ == More information == * - * For more detailed information on Subscriptions, see {@link http://www.braintreepayments.com/gateway/subscription-api http://www.braintreepaymentsolutions.com/gateway/subscription-api} - * - * PHP Version 5 + * // phpcs:ignore + * For more detailed information on Subscriptions, see {@link https://developers.braintreepayments.com/reference/response/subscription/php https://developers.braintreepayments.com/reference/response/subscription/php} * * @package Braintree - * @copyright 2010 Braintree Payment Solutions + * + * @property-read \Braintree\Addon[] $addOns + * @property-read string $balance + * @property-read int $billingDayOfMonth + * @property-read \DateTime $billingPeriodEndDate + * @property-read \DateTime $billingPeriodStartDate + * @property-read \DateTime $createdAt + * @property-read int $currentBillingCycle + * @property-read int|null $daysPastDue + * @property-read string|null $description + * @property-read \Braintree\Descriptor|null $descriptor + * @property-read \Braintree\Discount[] $discounts + * @property-read int $failureCount + * @property-read \DateTime $firstBillingDate + * @property-read string $id + * @property-read string $merchantAccountId + * @property-read boolean $neverExpires + * @property-read string $nextBillingPeriodAmount + * @property-read \DateTime $nextBillingDate + * @property-read int|null $numberOfBillingCycles + * @property-read \DateTime|null $paidThroughDate + * @property-read string $paymentMethodToken + * @property-read string $planId + * @property-read string $price + * @property-read string $status + * @property-read \Braintree\Subscription\StatusDetails[] $statusHistory + * @property-read \Braintree\Transaction[] $transactions + * @property-read int $trialDuration + * @property-read string $trialDurationUnit + * @property-read boolean $trialPeriod + * @property-read \DateTime $updatedAt */ -class Braintree_Subscription extends Braintree +class Subscription extends Base { const ACTIVE = 'Active'; const CANCELED = 'Canceled'; @@ -19,12 +51,10 @@ class Braintree_Subscription extends Braintree const PAST_DUE = 'Past Due'; const PENDING = 'Pending'; - public static function create($attributes) - { - Braintree_Util::verifyKeys(self::_createSignature(), $attributes); - $response = Braintree_Http::post('/subscriptions', array('subscription' => $attributes)); - return self::_verifyGatewayResponse($response); - } + // Subscription Sources + const API = 'api'; + const CONTROL_PANEL = 'control_panel'; + const RECURRING = 'recurring'; /** * @ignore @@ -37,220 +67,108 @@ public static function factory($attributes) return $instance; } - public static function find($id) + /** + * @ignore + */ + protected function _initialize($attributes) { - self::_validateId($id); + $this->_attributes = $attributes; - try { - $response = Braintree_Http::get('/subscriptions/' . $id); - return self::factory($response['subscription']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound('subscription with id ' . $id . ' not found'); + $addOnArray = []; + if (isset($attributes['addOns'])) { + foreach ($attributes['addOns'] as $addOn) { + $addOnArray[] = AddOn::factory($addOn); + } } + $this->_attributes['addOns'] = $addOnArray; - } - - public static function search($query) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + $discountArray = []; + if (isset($attributes['discounts'])) { + foreach ($attributes['discounts'] as $discount) { + $discountArray[] = Discount::factory($discount); + } } + $this->_attributes['discounts'] = $discountArray; + if (isset($attributes['descriptor'])) { + $this->_set('descriptor', new Descriptor($attributes['descriptor'])); + } - $response = Braintree_Http::post('/subscriptions/advanced_search_ids', array('search' => $criteria)); - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); - } + if (isset($attributes['description'])) { + $this->_set('description', $attributes['description']); + } - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + $statusHistory = []; + if (isset($attributes['statusHistory'])) { + foreach ($attributes['statusHistory'] as $history) { + $statusHistory[] = new Subscription\StatusDetails($history); + } } - $criteria["ids"] = Braintree_SubscriptionSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/subscriptions/advanced_search', array('search' => $criteria)); + $this->_attributes['statusHistory'] = $statusHistory; - return Braintree_Util::extractAttributeAsArray( - $response['subscriptions'], - 'subscription' - ); + $transactionArray = []; + if (isset($attributes['transactions'])) { + foreach ($attributes['transactions'] as $transaction) { + $transactionArray[] = Transaction::factory($transaction); + } + } + $this->_attributes['transactions'] = $transactionArray; } - public static function update($subscriptionId, $attributes) + /** + * returns a string representation of the customer + * @return string + */ + public function __toString() { - Braintree_Util::verifyKeys(self::_updateSignature(), $attributes); - $response = Braintree_Http::put( - '/subscriptions/' . $subscriptionId, - array('subscription' => $attributes) - ); - return self::_verifyGatewayResponse($response); - } + $excludedAttributes = ['statusHistory']; - public static function retryCharge($subscriptionId, $amount = null) - { - $transaction_params = array('type' => Braintree_Transaction::SALE, - 'subscriptionId' => $subscriptionId); - if (isset($amount)) { - $transaction_params['amount'] = $amount; + $displayAttributes = []; + foreach ($this->_attributes as $key => $val) { + if (!in_array($key, $excludedAttributes)) { + $displayAttributes[$key] = $val; + } } - $response = Braintree_Http::post( - '/transactions', - array('transaction' => $transaction_params)); - return self::_verifyGatewayResponse($response); + return __CLASS__ . '[' . + Util::attributesToString($displayAttributes) . ']'; } - public static function cancel($subscriptionId) + + // static methods redirecting to gateway + + public static function create($attributes) { - $response = Braintree_Http::put('/subscriptions/' . $subscriptionId . '/cancel'); - return self::_verifyGatewayResponse($response); + return Configuration::gateway()->subscription()->create($attributes); } - private static function _createSignature() + public static function find($id) { - return array_merge( - array( - 'billingDayOfMonth', - 'firstBillingDate', - 'id', - 'merchantAccountId', - 'neverExpires', - 'numberOfBillingCycles', - 'paymentMethodToken', - 'planId', - 'price', - 'trialDuration', - 'trialDurationUnit', - 'trialPeriod', - array('descriptor' => array('name', 'phone')), - array('options' => array('doNotInheritAddOnsOrDiscounts', 'startImmediately')), - ), - self::_addOnDiscountSignature() - ); + return Configuration::gateway()->subscription()->find($id); } - private static function _updateSignature() + public static function search($query) { - return array_merge( - array( - 'merchantAccountId', 'numberOfBillingCycles', 'paymentMethodToken', 'planId', - 'id', 'neverExpires', 'price', - array('descriptor' => array('name', 'phone')), - array('options' => array('prorateCharges', 'replaceAllAddOnsAndDiscounts', 'revertSubscriptionOnProrationFailure')), - ), - self::_addOnDiscountSignature() - ); + return Configuration::gateway()->subscription()->search($query); } - private static function _addOnDiscountSignature() + public static function fetch($query, $ids) { - return array( - array( - 'addOns' => array( - array('add' => array('amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('update' => array('amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('remove' => array('_anyKey_')), - ) - ), - array( - 'discounts' => array( - array('add' => array('amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('update' => array('amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity')), - array('remove' => array('_anyKey_')), - ) - ) - ); + return Configuration::gateway()->subscription()->fetch($query, $ids); } - /** - * @ignore - */ - protected function _initialize($attributes) + public static function update($subscriptionId, $attributes) { - $this->_attributes = $attributes; - - $addOnArray = array(); - if (isset($attributes['addOns'])) { - foreach ($attributes['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); - } - } - $this->_attributes['addOns'] = $addOnArray; - - $discountArray = array(); - if (isset($attributes['discounts'])) { - foreach ($attributes['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); - } - } - $this->_attributes['discounts'] = $discountArray; - - if (isset($attributes['descriptor'])) { - $this->_set('descriptor', new Braintree_Descriptor($attributes['descriptor'])); - } - - $transactionArray = array(); - if (isset($attributes['transactions'])) { - foreach ($attributes['transactions'] AS $transaction) { - $transactionArray[] = Braintree_Transaction::factory($transaction); - } - } - $this->_attributes['transactions'] = $transactionArray; + return Configuration::gateway()->subscription()->update($subscriptionId, $attributes); } - /** - * @ignore - */ - private static function _validateId($id = null) { - if (empty($id)) { - throw new InvalidArgumentException( - 'expected subscription id to be set' - ); - } - if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid subscription id.' - ); - } - } - /** - * @ignore - */ - private static function _verifyGatewayResponse($response) + public static function retryCharge($subscriptionId, $amount = null, $submitForSettlement = false) { - if (isset($response['subscription'])) { - return new Braintree_Result_Successful( - self::factory($response['subscription']) - ); - } else if (isset($response['transaction'])) { - // return a populated instance of Braintree_Transaction, for subscription retryCharge - return new Braintree_Result_Successful( - Braintree_Transaction::factory($response['transaction']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected subscription, transaction, or apiErrorResponse" - ); - } + return Configuration::gateway()->subscription()->retryCharge($subscriptionId, $amount, $submitForSettlement); } - /** - * returns a string representation of the customer - * @return string - */ - public function __toString() + public static function cancel($subscriptionId) { - return __CLASS__ . '[' . - Braintree_Util::attributesToString($this->_attributes) .']'; + return Configuration::gateway()->subscription()->cancel($subscriptionId); } - } diff --git a/lib/Braintree/Subscription/StatusDetails.php b/lib/Braintree/Subscription/StatusDetails.php new file mode 100644 index 0000000..7fd430e --- /dev/null +++ b/lib/Braintree/Subscription/StatusDetails.php @@ -0,0 +1,24 @@ +== More information == + * + * // phpcs:ignore Generic.Files.LineLength + * For more detailed information on Subscriptions, see {@link https://developers.braintreepayments.com/reference/response/subscription/php https://developers.braintreepayments.com/reference/response/subscription/php} + * + * @package Braintree + */ +class SubscriptionGateway +{ + 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) + { + Util::verifyKeys(self::_createSignature(), $attributes); + $path = $this->_config->merchantPath() . '/subscriptions'; + $response = $this->_http->post($path, ['subscription' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + public function find($id) + { + $this->_validateId($id); + + try { + $path = $this->_config->merchantPath() . '/subscriptions/' . $id; + $response = $this->_http->get($path); + return Subscription::factory($response['subscription']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('subscription with id ' . $id . ' not found'); + } + } + + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + + $path = $this->_config->merchantPath() . '/subscriptions/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = SubscriptionSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/subscriptions/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + return Util::extractAttributeAsArray( + $response['subscriptions'], + 'subscription' + ); + } + + public function update($subscriptionId, $attributes) + { + Util::verifyKeys(self::_updateSignature(), $attributes); + $path = $this->_config->merchantPath() . '/subscriptions/' . $subscriptionId; + $response = $this->_http->put($path, ['subscription' => $attributes]); + return $this->_verifyGatewayResponse($response); + } + + public function retryCharge($subscriptionId, $amount = null, $submitForSettlement = false) + { + $transaction_params = ['type' => Transaction::SALE, + 'subscriptionId' => $subscriptionId]; + if (isset($amount)) { + $transaction_params['amount'] = $amount; + } + if ($submitForSettlement) { + $transaction_params['options'] = ['submitForSettlement' => $submitForSettlement]; + } + + $path = $this->_config->merchantPath() . '/transactions'; + $response = $this->_http->post($path, ['transaction' => $transaction_params]); + return $this->_verifyGatewayResponse($response); + } + + public function cancel($subscriptionId) + { + $path = $this->_config->merchantPath() . '/subscriptions/' . $subscriptionId . '/cancel'; + $response = $this->_http->put($path); + return $this->_verifyGatewayResponse($response); + } + + private static function _createSignature() + { + return array_merge( + [ + 'billingDayOfMonth', + 'firstBillingDate', + 'createdAt', + 'updatedAt', + 'id', + 'merchantAccountId', + 'neverExpires', + 'numberOfBillingCycles', + 'paymentMethodToken', + 'paymentMethodNonce', + 'planId', + 'price', + 'trialDuration', + 'trialDurationUnit', + 'trialPeriod', + ['descriptor' => ['name', 'phone', 'url']], + ['options' => [ + 'doNotInheritAddOnsOrDiscounts', + 'startImmediately', + ['paypal' => ['description']] + ]], + ], + self::_addOnDiscountSignature() + ); + } + + private static function _updateSignature() + { + return array_merge( + [ + 'merchantAccountId', 'numberOfBillingCycles', 'paymentMethodToken', 'planId', + 'paymentMethodNonce', 'id', 'neverExpires', 'price', + ['descriptor' => ['name', 'phone', 'url']], + ['options' => [ + 'prorateCharges', + 'replaceAllAddOnsAndDiscounts', + 'revertSubscriptionOnProrationFailure', + ['paypal' => ['description']] + ]], + ], + self::_addOnDiscountSignature() + ); + } + + private static function _addOnDiscountSignature() + { + return [ + [ + 'addOns' => [ + ['add' => ['amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['update' => ['amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['remove' => ['_anyKey_']], + ] + ], + [ + 'discounts' => [ + ['add' => ['amount', 'inheritedFromId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['update' => ['amount', 'existingId', 'neverExpires', 'numberOfBillingCycles', 'quantity']], + ['remove' => ['_anyKey_']], + ] + ] + ]; + } + + /** + * @ignore + */ + private function _validateId($id = null) + { + if (empty($id)) { + throw new InvalidArgumentException( + 'expected subscription id to be set' + ); + } + if (!preg_match('/^[0-9A-Za-z_-]+$/', $id)) { + throw new InvalidArgumentException( + $id . ' is an invalid subscription id.' + ); + } + } + + /** + * @ignore + */ + private function _verifyGatewayResponse($response) + { + if (isset($response['subscription'])) { + return new Result\Successful( + Subscription::factory($response['subscription']) + ); + } elseif (isset($response['transaction'])) { + // return a populated instance of Transaction, for subscription retryCharge + return new Result\Successful( + Transaction::factory($response['transaction']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected subscription, transaction, or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/SubscriptionSearch.php b/lib/Braintree/SubscriptionSearch.php index 01cba1a..98b642d 100644 --- a/lib/Braintree/SubscriptionSearch.php +++ b/lib/Braintree/SubscriptionSearch.php @@ -1,64 +1,72 @@ '378734493671000', 'Discover' => '6011000990139424', 'MasterCard' => '5105105105105100', 'Visa' => '4000111111111115', - ); + ]; + public static $amexPayWithPoints = [ + 'Success' => "371260714673002", + 'IneligibleCard' => "378267515471109", + 'InsufficientPoints' => "371544868764018", + ]; + + public static $disputes = [ + 'Chargeback' => '4023898493988028', + ]; public static function getAll() { return array_merge( - self::$amExes, - self::$discoverCards, - self::$masterCards, - self::$visas - ); + self::$amExes, + self::$discoverCards, + self::$eloCards, + self::$masterCards, + self::$visas + ); } } diff --git a/lib/Braintree/Test/MerchantAccount.php b/lib/Braintree/Test/MerchantAccount.php index 85a7774..62b1cce 100644 --- a/lib/Braintree/Test/MerchantAccount.php +++ b/lib/Braintree/Test/MerchantAccount.php @@ -1,12 +1,14 @@ testing()->settle($transactionId); + } + + /** + * settlement confirm a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementConfirm($transactionId) + { + return Configuration::gateway()->testing()->settlementConfirm($transactionId); + } + + /** + * settlement decline a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementDecline($transactionId) + { + return Configuration::gateway()->testing()->settlementDecline($transactionId); + } + + /** + * settlement pending a transaction by id in sandbox + * + * @param string $id transaction id + * @param Configuration $config gateway config + * @return Transaction + */ + public static function settlementPending($transactionId) + { + return Configuration::gateway()->testing()->settlementPending($transactionId); + } +} diff --git a/lib/Braintree/Test/TransactionAmounts.php b/lib/Braintree/Test/TransactionAmounts.php index 7dc1a5b..4098acb 100644 --- a/lib/Braintree/Test/TransactionAmounts.php +++ b/lib/Braintree/Test/TransactionAmounts.php @@ -1,11 +1,6 @@ _gateway = $gateway; + $this->_config = $gateway->config; + $this->_http = new Http($this->_config); + } + + public function settle($transactionId) + { + return self::_doTestRequest('/settle', $transactionId); + } + + public function settlementPending($transactionId) + { + return self::_doTestRequest('/settlement_pending', $transactionId); + } + + public function settlementConfirm($transactionId) + { + return self::_doTestRequest('/settlement_confirm', $transactionId); + } + + public function settlementDecline($transactionId) + { + return self::_doTestRequest('/settlement_decline', $transactionId); + } + + private function _doTestRequest($testPath, $transactionId) + { + self::_checkEnvironment(); + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . $testPath; + $response = $this->_http->put($path); + return Transaction::factory($response['transaction']); + } + + private function _checkEnvironment() + { + if (Configuration::$global->getEnvironment() === 'production') { + throw new Exception\TestOperationPerformedInProduction(); + } + } +} diff --git a/lib/Braintree/TextNode.php b/lib/Braintree/TextNode.php index f193d1d..51081e0 100644 --- a/lib/Braintree/TextNode.php +++ b/lib/Braintree/TextNode.php @@ -1,8 +1,10 @@ searchTerms["contains"] = strval($value); return $this; diff --git a/lib/Braintree/ThreeDSecureInfo.php b/lib/Braintree/ThreeDSecureInfo.php new file mode 100644 index 0000000..8912111 --- /dev/null +++ b/lib/Braintree/ThreeDSecureInfo.php @@ -0,0 +1,46 @@ +_initialize($attributes); + + return $instance; + } + + protected function _initialize($attributes) + { + $this->_attributes = $attributes; + } + + /** + * returns a string representation of the three d secure info + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } +} diff --git a/lib/Braintree/Transaction.php b/lib/Braintree/Transaction.php index eb9babf..cda59ea 100644 --- a/lib/Braintree/Transaction.php +++ b/lib/Braintree/Transaction.php @@ -1,13 +1,10 @@ Minimalistic example: * - * Braintree_Transaction::saleNoValidate(array( + * Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'creditCard' => array( * 'number' => '5105105105105100', @@ -26,7 +23,7 @@ * * Full example: * - * Braintree_Transaction::saleNoValidate(array( + * Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'orderId' => '123', * 'channel' => 'MyShoppingCardProvider', @@ -42,7 +39,7 @@ * 'id' => 'customer_123', * 'firstName' => 'Dan', * 'lastName' => 'Smith', - * 'company' => 'Braintree Payment Solutions', + * 'company' => 'Braintree', * 'email' => 'dan@example.com', * 'phone' => '419-555-1234', * 'fax' => '419-555-1235', @@ -82,7 +79,7 @@ * a transaction can be stored in the vault by setting * transaction[options][storeInVault] to true. * - * $transaction = Braintree_Transaction::saleNoValidate(array( + * $transaction = Transaction::saleNoValidate(array( * 'customer' => array( * 'firstName' => 'Adam', * 'lastName' => 'Williams' @@ -105,7 +102,7 @@ * To also store the billing address in the vault, pass the * addBillingAddressToPaymentMethod option. * - * Braintree_Transaction.saleNoValidate(array( + * Transaction.saleNoValidate(array( * ... * 'options' => array( * 'storeInVault' => true @@ -126,7 +123,7 @@ * $transaction[options][submitForSettlement] to true. * * - * $transaction = Braintree_Transaction::saleNoValidate(array( + * $transaction = Transaction::saleNoValidate(array( * 'amount' => '100.00', * 'creditCard' => array( * 'number' => '5105105105105100', @@ -140,35 +137,88 @@ * * == More information == * - * For more detailed information on Transactions, see {@link http://www.braintreepayments.com/gateway/transaction-api http://www.braintreepaymentsolutions.com/gateway/transaction-api} + * For more detailed information on Transactions, see {@link https://developers.braintreepayments.com/reference/response/transaction/php https://developers.braintreepayments.com/reference/response/transaction/php} * * @package Braintree * @category Resources - * @copyright 2010 Braintree Payment Solutions * * + * @property-read \Braintree\AddOn[] $addOns + * @property-read string $additionalProcessorResponse raw response from processor + * @property-read string $amount transaction amount + * @property-read \Braintree\Transaction\GooglePayCardDetails $googlePayCardDetails transaction Google Pay card info + * @property-read \Braintree\Transaction\ApplePayCardDetails $applePayCardDetails transaction Apple Pay card info + * @property-read \Braintree\AuthorizationAdjustment[] $authorizationAdjustments populated when a transaction has authorization adjustments created when submitted for settlement + * @property-read \DateTime $authorizationExpiresAt DateTime authorization will expire * @property-read string $avsErrorResponseCode * @property-read string $avsPostalCodeResponseCode * @property-read string $avsStreetAddressResponseCode + * @property-read \Braintree\Transaction\AddressDetails $billingDetails transaction billing address + * @property-read string $channel + * @property-read \DateTime $createdAt transaction created DateTime + * @property-read \Braintree\Transaction\CreditCardDetails $creditCardDetails transaction credit card info + * @property-read string $currencyIsoCode + * @property-read array $customFields custom fields passed with the request + * @property-read \Braintree\Transaction\CustomerDetails $customerDetails transaction customer info * @property-read string $cvvResponseCode + * @property-read \Braintree\Descriptor $descriptor + * @property-read Braintree\DisbursementDetails $disbursementDetails populated when transaction is disbursed + * @property-read string $discountAmount + * @property-read \Braintree\Discount[] $discounts + * @property-read \Braintree\Dispute[] $disputes populated when transaction is disputed + * @property-read string $escrowStatus + * @property-read \Braintree\FacilitatedDetails $facilitatedDetails + * @property-read \Braintree\FacilitatorDetails $facilitatorDetails + * @property-read string $gatewayRejectionReason + * @property-read string $graphQLId transaction graphQLId * @property-read string $id transaction id - * @property-read string $amount transaction amount - * @property-read object $billingDetails transaction billing address - * @property-read string $createdAt transaction created timestamp - * @property-read object $creditCardDetails transaction credit card info - * @property-read object $customerDetails transaction customer info - * @property-read array $customFields custom fields passed with the request + * @property-read \Braintree\TransactionLineItem[] $lineItems + * @property-read string $merchantAccountId + * @property-read string $networkTransactionId + * @property-read string $orderId + * @property-read string $acquirerReferenceNumber + * @property-read string $paymentInstrumentType + * @property-read \Braintree\Transaction\PayPalDetails $paypalDetails transaction paypal account info + * @property-read \Braintree\Transaction\PayPalHereDetails $paypalHereDetails + * @property-read \Braintree\Transaction\LocalPaymentDetails $localPaymentDetails transaction local payment info + * @property-read string $planId + * @property-read string $processedWithNetworkToken + * @property-read string $processorAuthorizationCode * @property-read string $processorResponseCode gateway response code - * @property-read object $shippingDetails transaction shipping address + * @property-read string $processorResponseText + * @property-read string $processorResponseType + * @property-read string $processorSettlementResponseCode + * @property-read string $processorSettlementResponseText + * @property-read string $productSku + * @property-read string $purchaseOrderNumber + * @property-read mixed $reccuring + * @property-read mixed $refundIds + * @property-read string $refundedTransactionId + * @property-read string $retrievalReferenceNumber + * @property-read \Braintree\RiskData $riskData + * @property-read \Braintree\Transaction\SamsungPayCardDetails $samsungPayCardDetails transaction Samsung Pay card info + * @property-read string $scaExemptionRequested + * @property-read string $serviceFeeAmount + * @property-read string $settlementBatchId + * @property-read string $shippingAmount + * @property-read \Braintree\Transaction\AddressDetails $shippingDetails transaction shipping address * @property-read string $status transaction status - * @property-read array $statusHistory array of StatusDetails objects + * @property-read \Braintree\Transaction\StatusDetails[] $statusHistory array of StatusDetails objects + * @property-read \Braintree\Transaction\SubscriptionDetails $subscriptionDetails + * @property-read string $subscriptionId + * @property-read string $taxAmount + * @property-read string $taxExcempt + * @property-read \Braintree\ThreeDSecureInfo $threeDSecureInfo * @property-read string $type transaction type - * @property-read string $updatedAt transaction updated timestamp - * @property-read object $disbursementDetails populated when transaction is disbursed + * @property-read \DateTime $updatedAt transaction updated DateTime + * @property-read \Braintree\VenmoAccount $venmoAccountDetails transaction Venmo Account info + * @property-read \Braintree\Transaction\VisaCheckoutCardDetails $visaCheckoutCardDetails transaction Visa Checkout card info + * @property-read string $voiceReferralName * */ +// phpcs:enable Generic.Files.LineLength -final class Braintree_Transaction extends Braintree +class Transaction extends Base { // Transaction Status const AUTHORIZATION_EXPIRED = 'authorization_expired'; @@ -182,6 +232,9 @@ final class Braintree_Transaction extends Braintree const SUBMITTED_FOR_SETTLEMENT = 'submitted_for_settlement'; const VOIDED = 'voided'; const UNRECOGNIZED = 'unrecognized'; + const SETTLEMENT_DECLINED = 'settlement_declined'; + const SETTLEMENT_PENDING = 'settlement_pending'; + const SETTLEMENT_CONFIRMED = 'settlement_confirmed'; // Transaction Escrow Status const ESCROW_HOLD_PENDING = 'hold_pending'; @@ -204,429 +257,259 @@ final class Braintree_Transaction extends Braintree const RECURRING = 'recurring'; // Gateway Rejection Reason - const AVS = 'avs'; - const AVS_AND_CVV = 'avs_and_cvv'; - const CVV = 'cvv'; - const DUPLICATE = 'duplicate'; - const FRAUD = 'fraud'; - - public static function cloneTransaction($transactionId, $attribs) - { - Braintree_Util::verifyKeys(self::cloneSignature(), $attribs); - return self::_doCreate('/transactions/' . $transactionId . '/clone', array('transactionClone' => $attribs)); - } - - /** - * @ignore - * @access public - * @param array $attribs - * @return object - */ - private static function create($attribs) - { - Braintree_Util::verifyKeys(self::createSignature(), $attribs); - return self::_doCreate('/transactions', array('transaction' => $attribs)); - } + const AVS = 'avs'; + const AVS_AND_CVV = 'avs_and_cvv'; + const CVV = 'cvv'; + const DUPLICATE = 'duplicate'; + const FRAUD = 'fraud'; + const RISK_THRESHOLD = 'risk_threshold'; + const THREE_D_SECURE = 'three_d_secure'; + const TOKEN_ISSUANCE = 'token_issuance'; + const APPLICATION_INCOMPLETE = 'application_incomplete'; + + // Industry Types + const LODGING_INDUSTRY = 'lodging'; + const TRAVEL_AND_CRUISE_INDUSTRY = 'travel_cruise'; + const TRAVEL_AND_FLIGHT_INDUSTRY = 'travel_flight'; + + // Additional Charge Types + const RESTAURANT = 'lodging'; + const GIFT_SHOP = 'gift_shop'; + const MINI_BAR = 'mini_bar'; + const TELEPHONE = 'telephone'; + const LAUNDRY = 'laundry'; + const OTHER = 'other'; /** + * sets instance properties from an array of values * * @ignore - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError - */ - private static function createNoValidate($attribs) - { - $result = self::create($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - /** - * - * @access public - * @param array $attribs - * @return object - */ - public static function createFromTransparentRedirect($queryString) - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::confirm", E_USER_NOTICE); - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - return self::_doCreate( - '/transactions/all/confirm_transparent_redirect_request', - array('id' => $params['id']) - ); - } - /** - * - * @access public - * @param none - * @return string - */ - public static function createTransactionUrl() - { - trigger_error("DEPRECATED: Please use Braintree_TransparentRedirectRequest::url", E_USER_NOTICE); - return Braintree_Configuration::merchantUrl() . - '/transactions/all/create_via_transparent_redirect_request'; - } - - public static function cloneSignature() - { - return array('amount', 'channel', array('options' => array('submitForSettlement'))); - } - - /** - * creates a full array signature of a valid gateway request - * @return array gateway request signature format - */ - public static function createSignature() - { - return array( - 'amount', 'customerId', 'merchantAccountId', 'orderId', 'channel', 'paymentMethodToken', 'deviceSessionId', - 'purchaseOrderNumber', 'recurring', 'shippingAddressId', 'taxAmount', 'taxExempt', 'type', 'venmoSdkPaymentMethodCode', - 'serviceFeeAmount', 'deviceData', 'fraudMerchantId', 'billingAddressId', - array('creditCard' => - array('token', 'cardholderName', 'cvv', 'expirationDate', 'expirationMonth', 'expirationYear', 'number'), - ), - array('customer' => - array( - 'id', 'company', 'email', 'fax', 'firstName', - 'lastName', 'phone', 'website'), - ), - array('billing' => - array( - 'firstName', 'lastName', 'company', 'countryName', - 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', - 'extendedAddress', 'locality', 'postalCode', 'region', - 'streetAddress'), - ), - array('shipping' => - array( - 'firstName', 'lastName', 'company', 'countryName', - 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', - 'extendedAddress', 'locality', 'postalCode', 'region', - 'streetAddress'), - ), - array('options' => - array( - 'holdInEscrow', - 'storeInVault', - 'storeInVaultOnSuccess', - 'submitForSettlement', - 'addBillingAddressToPaymentMethod', - 'venmoSdkSession', - 'storeShippingAddressInVault'), - ), - array('customFields' => array('_anyKey_') - ), - array('descriptor' => array('name', 'phone')) - ); - } - - /** - * - * @access public - * @param array $attribs - * @return object - */ - public static function credit($attribs) - { - return self::create(array_merge($attribs, array('type' => Braintree_Transaction::CREDIT))); - } - - /** - * - * @access public - * @param array $attribs - * @return object - * @throws Braintree_Exception_ValidationError + * @access protected + * @param array $transactionAttribs array of transaction data + * @return void */ - public static function creditNoValidate($attribs) + protected function _initialize($transactionAttribs) { - $result = self::credit($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - + $this->_attributes = $transactionAttribs; - /** - * @access public - * - */ - public static function find($id) - { - self::_validateId($id); - try { - $response = Braintree_Http::get('/transactions/'.$id); - return self::factory($response['transaction']); - } catch (Braintree_Exception_NotFound $e) { - throw new Braintree_Exception_NotFound( - 'transaction with id ' . $id . ' not found' + if (isset($transactionAttribs['applePay'])) { + $this->_set( + 'applePayCardDetails', + new Transaction\ApplePayCardDetails( + $transactionAttribs['applePay'] + ) ); } - } - /** - * new sale - * @param array $attribs - * @return array - */ - public static function sale($attribs) - { - return self::create(array_merge(array('type' => Braintree_Transaction::SALE), $attribs)); - } - - /** - * roughly equivalent to the ruby bang method - * @access public - * @param array $attribs - * @return array - * @throws Braintree_Exception_ValidationsFailed - */ - public static function saleNoValidate($attribs) - { - $result = self::sale($attribs); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - /** - * Returns a ResourceCollection of transactions matching the search query. - * - * If query is a string, the search will be a basic search. - * If query is a hash, the search will be an advanced search. - * For more detailed information and examples, see {@link http://www.braintreepayments.com/gateway/transaction-api#searching http://www.braintreepaymentsolutions.com/gateway/transaction-api} - * - * @param mixed $query search query - * @param array $options options such as page number - * @return object Braintree_ResourceCollection - * @throws InvalidArgumentException - */ - public static function search($query) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + // Rename androidPayCard from API responses to GooglePayCard + if (isset($transactionAttribs['androidPayCard'])) { + $this->_set( + 'googlePayCardDetails', + new Transaction\GooglePayCardDetails( + $transactionAttribs['androidPayCard'] + ) + ); } - $response = Braintree_Http::post('/transactions/advanced_search_ids', array('search' => $criteria)); - if (array_key_exists('searchResults', $response)) { - $pager = array( - 'className' => __CLASS__, - 'classMethod' => 'fetch', - 'methodArgs' => array($query) - ); - - return new Braintree_ResourceCollection($response, $pager); - } else { - throw new Braintree_Exception_DownForMaintenance(); + if (isset($transactionAttribs['visaCheckoutCard'])) { + $this->_set( + 'visaCheckoutCardDetails', + new Transaction\VisaCheckoutCardDetails( + $transactionAttribs['visaCheckoutCard'] + ) + ); } - } - public static function fetch($query, $ids) - { - $criteria = array(); - foreach ($query as $term) { - $criteria[$term->name] = $term->toparam(); + if (isset($transactionAttribs['samsungPayCard'])) { + $this->_set( + 'samsungPayCardDetails', + new Transaction\SamsungPayCardDetails( + $transactionAttribs['samsungPayCard'] + ) + ); } - $criteria["ids"] = Braintree_TransactionSearch::ids()->in($ids)->toparam(); - $response = Braintree_Http::post('/transactions/advanced_search', array('search' => $criteria)); - - return Braintree_Util::extractattributeasarray( - $response['creditCardTransactions'], - 'transaction' - ); - } - - /** - * void a transaction by id - * - * @param string $id transaction id - * @return object Braintree_Result_Successful|Braintree_Result_Error - */ - public static function void($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put('/transactions/'. $transactionId . '/void'); - return self::_verifyGatewayResponse($response); - } - /** - * - */ - public static function voidNoValidate($transactionId) - { - $result = self::void($transactionId); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - public static function submitForSettlement($transactionId, $amount = null) - { - self::_validateId($transactionId); - $response = Braintree_Http::put( - '/transactions/'. $transactionId . '/submit_for_settlement', - array( 'transaction' => array( 'amount' => $amount)) - ); - return self::_verifyGatewayResponse($response); - } - - public static function submitForSettlementNoValidate($transactionId, $amount = null) - { - $result = self::submitForSettlement($transactionId, $amount); - return self::returnObjectOrThrowException(__CLASS__, $result); - } - - public static function holdInEscrow($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/hold_in_escrow', - array() - ); - return self::_verifyGatewayResponse($response); - } - - public static function releaseFromEscrow($transactionId) - { - self::_validateId($transactionId); - - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/release_from_escrow', - array() - ); - return self::_verifyGatewayResponse($response); - } + if (isset($transactionAttribs['venmoAccount'])) { + $this->_set( + 'venmoAccountDetails', + new Transaction\VenmoAccountDetails( + $transactionAttribs['venmoAccount'] + ) + ); + } - public static function cancelRelease($transactionId) - { - self::_validateId($transactionId); + if (isset($transactionAttribs['creditCard'])) { + $this->_set( + 'creditCardDetails', + new Transaction\CreditCardDetails( + $transactionAttribs['creditCard'] + ) + ); + } - $response = Braintree_Http::put( - '/transactions/' . $transactionId . '/cancel_release', - array() - ); - return self::_verifyGatewayResponse($response); - } + if (isset($transactionAttribs['usBankAccount'])) { + $this->_set( + 'usBankAccount', + new Transaction\UsBankAccountDetails( + $transactionAttribs['usBankAccount'] + ) + ); + } + if (isset($transactionAttribs['paypal'])) { + $this->_set( + 'paypalDetails', + new Transaction\PayPalDetails( + $transactionAttribs['paypal'] + ) + ); + } - /** - * sets instance properties from an array of values - * - * @ignore - * @access protected - * @param array $transactionAttribs array of transaction data - * @return none - */ - protected function _initialize($transactionAttribs) - { - $this->_attributes = $transactionAttribs; + if (isset($transactionAttribs['paypalHere'])) { + $this->_set( + 'paypalHereDetails', + new Transaction\PayPalHereDetails( + $transactionAttribs['paypalHere'] + ) + ); + } - if (isset($transactionAttribs['creditCard'])) { - $this->_set('creditCardDetails', - new Braintree_Transaction_CreditCardDetails( - $transactionAttribs['creditCard'] + if (isset($transactionAttribs['localPayment'])) { + $this->_set( + 'localPaymentDetails', + new Transaction\LocalPaymentDetails( + $transactionAttribs['localPayment'] ) ); } if (isset($transactionAttribs['customer'])) { - $this->_set('customerDetails', - new Braintree_Transaction_CustomerDetails( + $this->_set( + 'customerDetails', + new Transaction\CustomerDetails( $transactionAttribs['customer'] ) ); } if (isset($transactionAttribs['billing'])) { - $this->_set('billingDetails', - new Braintree_Transaction_AddressDetails( + $this->_set( + 'billingDetails', + new Transaction\AddressDetails( $transactionAttribs['billing'] ) ); } if (isset($transactionAttribs['shipping'])) { - $this->_set('shippingDetails', - new Braintree_Transaction_AddressDetails( + $this->_set( + 'shippingDetails', + new Transaction\AddressDetails( $transactionAttribs['shipping'] ) ); } if (isset($transactionAttribs['subscription'])) { - $this->_set('subscriptionDetails', - new Braintree_Transaction_SubscriptionDetails( + $this->_set( + 'subscriptionDetails', + new Transaction\SubscriptionDetails( $transactionAttribs['subscription'] ) ); } if (isset($transactionAttribs['descriptor'])) { - $this->_set('descriptor', - new Braintree_Descriptor( + $this->_set( + 'descriptor', + new Descriptor( $transactionAttribs['descriptor'] ) ); } if (isset($transactionAttribs['disbursementDetails'])) { - $this->_set('disbursementDetails', - new Braintree_DisbursementDetails($transactionAttribs['disbursementDetails']) + $this->_set( + 'disbursementDetails', + new DisbursementDetails($transactionAttribs['disbursementDetails']) ); } - $statusHistory = array(); + $disputes = []; + if (isset($transactionAttribs['disputes'])) { + foreach ($transactionAttribs['disputes'] as $dispute) { + $disputes[] = Dispute::factory($dispute); + } + } + + $this->_set('disputes', $disputes); + + $statusHistory = []; if (isset($transactionAttribs['statusHistory'])) { - foreach ($transactionAttribs['statusHistory'] AS $history) { - $statusHistory[] = new Braintree_Transaction_StatusDetails($history); + foreach ($transactionAttribs['statusHistory'] as $history) { + $statusHistory[] = new Transaction\StatusDetails($history); } } $this->_set('statusHistory', $statusHistory); - $addOnArray = array(); + $addOnArray = []; if (isset($transactionAttribs['addOns'])) { - foreach ($transactionAttribs['addOns'] AS $addOn) { - $addOnArray[] = Braintree_AddOn::factory($addOn); + foreach ($transactionAttribs['addOns'] as $addOn) { + $addOnArray[] = AddOn::factory($addOn); } } $this->_set('addOns', $addOnArray); - $discountArray = array(); + $discountArray = []; if (isset($transactionAttribs['discounts'])) { - foreach ($transactionAttribs['discounts'] AS $discount) { - $discountArray[] = Braintree_Discount::factory($discount); + foreach ($transactionAttribs['discounts'] as $discount) { + $discountArray[] = Discount::factory($discount); } } $this->_set('discounts', $discountArray); + + $authorizationAdjustments = []; + if (isset($transactionAttribs['authorizationAdjustments'])) { + foreach ($transactionAttribs['authorizationAdjustments'] as $authorizationAdjustment) { + $authorizationAdjustments[] = AuthorizationAdjustment::factory($authorizationAdjustment); + } + } + + $this->_set('authorizationAdjustments', $authorizationAdjustments); + + if (isset($transactionAttribs['riskData'])) { + $this->_set('riskData', RiskData::factory($transactionAttribs['riskData'])); + } + if (isset($transactionAttribs['threeDSecureInfo'])) { + $this->_set('threeDSecureInfo', ThreeDSecureInfo::factory($transactionAttribs['threeDSecureInfo'])); + } + if (isset($transactionAttribs['facilitatedDetails'])) { + $this->_set('facilitatedDetails', FacilitatedDetails::factory($transactionAttribs['facilitatedDetails'])); + } + if (isset($transactionAttribs['facilitatorDetails'])) { + $this->_set('facilitatorDetails', FacilitatorDetails::factory($transactionAttribs['facilitatorDetails'])); + } } /** * returns a string representation of the transaction * @return string */ - public function __toString() + public function __toString() { // array of attributes to print - $display = array( + $display = [ 'id', 'type', 'amount', 'status', 'createdAt', 'creditCardDetails', 'customerDetails' - ); + ]; - $displayAttributes = array(); - foreach ($display AS $attrib) { + $displayAttributes = []; + foreach ($display as $attrib) { $displayAttributes[$attrib] = $this->$attrib; } return __CLASS__ . '[' . - Braintree_Util::attributesToString($displayAttributes) .']'; - } - - public static function refund($transactionId, $amount = null) - { - $params = array('transaction' => array('amount' => $amount)); - $response = Braintree_Http::post('/transactions/' . $transactionId . '/refund', $params); - return self::_verifyGatewayResponse($response); + Util::attributesToString($displayAttributes) . ']'; } public function isEqual($otherTx) @@ -639,104 +522,155 @@ public function vaultCreditCard() $token = $this->creditCardDetails->token; if (empty($token)) { return null; - } - else { - return Braintree_CreditCard::find($token); + } else { + return CreditCard::find($token); } } + /** @return null|\Braintree\Customer */ public function vaultCustomer() { $customerId = $this->customerDetails->id; if (empty($customerId)) { return null; - } - else { - return Braintree_Customer::find($customerId); + } else { + return Customer::find($customerId); } } - public function isDisbursed() { + /** @return boolean */ + public function isDisbursed() + { return $this->disbursementDetails->isValid(); } + /** @return line items */ + public function lineItems() + { + return Configuration::gateway()->transactionLineItem()->findAll($this->id); + } + /** - * sends the create request to the gateway + * factory method: returns an instance of Transaction + * to the requesting method, with populated properties * * @ignore - * @param var $url - * @param array $params - * @return mixed + * @return Transaction */ - public static function _doCreate($url, $params) + public static function factory($attributes) { - $response = Braintree_Http::post($url, $params); - - return self::_verifyGatewayResponse($response); + $instance = new self(); + $instance->_initialize($attributes); + return $instance; } /** - * verifies that a valid transaction id is being used - * @ignore - * @param string transaction id - * @throws InvalidArgumentException + * adjustAuthorization: It is a static method to invoke + * method in the TransactionGateway + * + * @param string transactionId + * @param string amount */ - private static function _validateId($id = null) { - if (empty($id)) { - throw new InvalidArgumentException( - 'expected transaction id to be set' - ); - } - if (!preg_match('/^[0-9a-z]+$/', $id)) { - throw new InvalidArgumentException( - $id . ' is an invalid transaction id.' - ); - } + public static function adjustAuthorization($transactionId, $amount) + { + return Configuration::gateway()->transaction()->adjustAuthorization($transactionId, $amount); } + // static methods redirecting to gateway - /* private class methods */ + public static function cloneTransaction($transactionId, $attribs) + { + return Configuration::gateway()->transaction()->cloneTransaction($transactionId, $attribs); + } - /** - * generic method for validating incoming gateway responses - * - * creates a new Braintree_Transaction object and encapsulates - * it inside a Braintree_Result_Successful object, or - * encapsulates a Braintree_Errors object inside a Result_Error - * alternatively, throws an Unexpected exception if the response is invalid. - * - * @ignore - * @param array $response gateway response values - * @return object Result_Successful or Result_Error - * @throws Braintree_Exception_Unexpected - */ - private static function _verifyGatewayResponse($response) + public static function createTransactionUrl() { - if (isset($response['transaction'])) { - // return a populated instance of Braintree_Transaction - return new Braintree_Result_Successful( - self::factory($response['transaction']) - ); - } else if (isset($response['apiErrorResponse'])) { - return new Braintree_Result_Error($response['apiErrorResponse']); - } else { - throw new Braintree_Exception_Unexpected( - "Expected transaction or apiErrorResponse" - ); - } + return Configuration::gateway()->transaction()->createTransactionUrl(); } - /** - * factory method: returns an instance of Braintree_Transaction - * to the requesting method, with populated properties - * - * @ignore - * @return object instance of Braintree_Transaction - */ - public static function factory($attributes) + public static function credit($attribs) { - $instance = new self(); - $instance->_initialize($attributes); - return $instance; + return Configuration::gateway()->transaction()->credit($attribs); + } + + public static function creditNoValidate($attribs) + { + return Configuration::gateway()->transaction()->creditNoValidate($attribs); + } + + public static function find($id) + { + return Configuration::gateway()->transaction()->find($id); + } + + public static function sale($attribs) + { + return Configuration::gateway()->transaction()->sale($attribs); + } + + public static function saleNoValidate($attribs) + { + return Configuration::gateway()->transaction()->saleNoValidate($attribs); + } + + public static function search($query) + { + return Configuration::gateway()->transaction()->search($query); + } + + public static function fetch($query, $ids) + { + return Configuration::gateway()->transaction()->fetch($query, $ids); + } + + public static function void($transactionId) + { + return Configuration::gateway()->transaction()->void($transactionId); + } + + public static function voidNoValidate($transactionId) + { + return Configuration::gateway()->transaction()->voidNoValidate($transactionId); + } + + public static function submitForSettlement($transactionId, $amount = null, $attribs = []) + { + return Configuration::gateway()->transaction()->submitForSettlement($transactionId, $amount, $attribs); + } + + public static function submitForSettlementNoValidate($transactionId, $amount = null, $attribs = []) + { + // phpcs:ignore Generic.Files.LineLength + return Configuration::gateway()->transaction()->submitForSettlementNoValidate($transactionId, $amount, $attribs); + } + + public static function updateDetails($transactionId, $attribs = []) + { + return Configuration::gateway()->transaction()->updateDetails($transactionId, $attribs); + } + + public static function submitForPartialSettlement($transactionId, $amount, $attribs = []) + { + return Configuration::gateway()->transaction()->submitForPartialSettlement($transactionId, $amount, $attribs); + } + + public static function holdInEscrow($transactionId) + { + return Configuration::gateway()->transaction()->holdInEscrow($transactionId); + } + + public static function releaseFromEscrow($transactionId) + { + return Configuration::gateway()->transaction()->releaseFromEscrow($transactionId); + } + + public static function cancelRelease($transactionId) + { + return Configuration::gateway()->transaction()->cancelRelease($transactionId); + } + + public static function refund($transactionId, $amount = null) + { + return Configuration::gateway()->transaction()->refund($transactionId, $amount); } } diff --git a/lib/Braintree/Transaction/AddressDetails.php b/lib/Braintree/Transaction/AddressDetails.php index dc89573..e59c296 100644 --- a/lib/Braintree/Transaction/AddressDetails.php +++ b/lib/Braintree/Transaction/AddressDetails.php @@ -1,11 +1,8 @@ _attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; - } } diff --git a/lib/Braintree/Transaction/CustomerDetails.php b/lib/Braintree/Transaction/CustomerDetails.php index b5151b3..8841fca 100644 --- a/lib/Braintree/Transaction/CustomerDetails.php +++ b/lib/Braintree/Transaction/CustomerDetails.php @@ -1,18 +1,15 @@ _attributes['cardType'] = $this->virtualCardType; + $this->_attributes['last4'] = $this->virtualCardLast4; + } +} diff --git a/lib/Braintree/Transaction/LocalPaymentDetails.php b/lib/Braintree/Transaction/LocalPaymentDetails.php new file mode 100644 index 0000000..acd343a --- /dev/null +++ b/lib/Braintree/Transaction/LocalPaymentDetails.php @@ -0,0 +1,45 @@ +_attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; + $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; + } +} diff --git a/lib/Braintree/Transaction/StatusDetails.php b/lib/Braintree/Transaction/StatusDetails.php index c105ced..c6df5b4 100644 --- a/lib/Braintree/Transaction/StatusDetails.php +++ b/lib/Braintree/Transaction/StatusDetails.php @@ -1,25 +1,21 @@ achMandate = $achMandate; + } +} diff --git a/lib/Braintree/Transaction/VenmoAccountDetails.php b/lib/Braintree/Transaction/VenmoAccountDetails.php new file mode 100644 index 0000000..bd00e39 --- /dev/null +++ b/lib/Braintree/Transaction/VenmoAccountDetails.php @@ -0,0 +1,39 @@ +_attributes['expirationDate'] = $this->expirationMonth . '/' . $this->expirationYear; + $this->_attributes['maskedNumber'] = $this->bin . '******' . $this->last4; + } +} diff --git a/lib/Braintree/TransactionGateway.php b/lib/Braintree/TransactionGateway.php new file mode 100644 index 0000000..2ba16a2 --- /dev/null +++ b/lib/Braintree/TransactionGateway.php @@ -0,0 +1,663 @@ +== More information == + * + * // phpcs:ignore Generic.Files.LineLength + * For more detailed information on Transactions, see {@link https://developers.braintreepayments.com/reference/response/transaction/php https://developers.braintreepayments.com/reference/response/transaction/php} + * + * @package Braintree + * @category Resources + */ + +class TransactionGateway +{ + 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 cloneTransaction($transactionId, $attribs) + { + Util::verifyKeys(self::cloneSignature(), $attribs); + return $this->_doCreate('/transactions/' . $transactionId . '/clone', ['transactionClone' => $attribs]); + } + + /** + * @ignore + * @access private + * @param array $attribs + * @return Result\Successful|Result\Error + */ + private function create($attribs) + { + Util::verifyKeys(self::createSignature(), $attribs); + $attribs = Util::replaceKey($attribs, 'googlePayCard', 'androidPayCard'); + return $this->_doCreate('/transactions', ['transaction' => $attribs]); + } + + /** + * @ignore + * @access private + * @param array $attribs + * @return object + * @throws Exception\ValidationError + */ + private function createNoValidate($attribs) + { + $result = $this->create($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + public static function cloneSignature() + { + return ['amount', 'channel', ['options' => ['submitForSettlement']]]; + } + + /** + * creates a full array signature of a valid gateway request + * @return array gateway request signature format + */ + public static function createSignature() + { + return [ + 'amount', + 'billingAddressId', + 'channel', + 'customerId', + 'deviceData', + 'merchantAccountId', + 'orderId', + 'paymentMethodNonce', + 'paymentMethodToken', + 'productSku', + 'purchaseOrderNumber', + 'recurring', + 'serviceFeeAmount', + 'sharedPaymentMethodToken', + 'sharedPaymentMethodNonce', + 'sharedCustomerId', + 'sharedShippingAddressId', + 'sharedBillingAddressId', + 'shippingAddressId', + 'taxAmount', + 'taxExempt', + 'threeDSecureToken', + 'threeDSecureAuthenticationId', + 'transactionSource', + 'type', + 'venmoSdkPaymentMethodCode', + 'scaExemption', + 'shippingAmount', + 'discountAmount', + 'shipsFromPostalCode', + ['riskData' => + [ + 'customerBrowser', 'customerIp', 'customerDeviceId', + 'customerLocationZip', 'customerTenure'], + ], + ['creditCard' => + [ + 'token', + 'cardholderName', + 'cvv', + 'expirationDate', + 'expirationMonth', + 'expirationYear', + 'number', + ['paymentReaderCardDetails' => ['encryptedCardData', 'keySerialNumber']], + ], + ], + ['customer' => + [ + 'id', 'company', 'email', 'fax', 'firstName', + 'lastName', 'phone', 'website'], + ], + ['billing' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'phoneNumber', 'postalCode', 'region', + 'streetAddress'], + ], + ['shipping' => + [ + 'firstName', 'lastName', 'company', 'countryName', + 'countryCodeAlpha2', 'countryCodeAlpha3', 'countryCodeNumeric', + 'extendedAddress', 'locality', 'phoneNumber', 'postalCode', 'region', + 'shippingMethod', 'streetAddress'], + ], + ['threeDSecurePassThru' => + [ + 'eciFlag', + 'cavv', + 'xid', + 'threeDSecureVersion', + 'authenticationResponse', + 'directoryResponse', + 'cavvAlgorithm', + 'dsTransactionId'], + ], + ['options' => + [ + 'holdInEscrow', + 'storeInVault', + 'storeInVaultOnSuccess', + 'submitForSettlement', + 'addBillingAddressToPaymentMethod', + 'venmoSdkSession', + 'storeShippingAddressInVault', + 'payeeId', + 'payeeEmail', + 'skipAdvancedFraudChecking', + 'skipAvs', + 'skipCvv', + ['creditCard' => + ['accountType'] + ], + ['threeDSecure' => + ['required'] + ], + ['paypal' => + [ + 'payeeId', + 'payeeEmail', + 'customField', + 'description', + ['supplementaryData' => ['_anyKey_']], + ] + ], + ['amexRewards' => + [ + 'requestId', + 'points', + 'currencyAmount', + 'currencyIsoCode' + ] + ], + ['venmo' => + [ + 'profileId' + ] + ] + ], + ], + ['customFields' => ['_anyKey_']], + ['descriptor' => ['name', 'phone', 'url']], + ['paypalAccount' => ['payeeId', 'payeeEmail', 'payerId', 'paymentId']], + ['applePayCard' => + [ + 'cardholderName', + 'cryptogram', + 'eciIndicator', + 'expirationMonth', + 'expirationYear', + 'number' + ] + ], + ['industry' => + ['industryType', + ['data' => + [ + 'folioNumber', + 'checkInDate', + 'checkOutDate', + 'travelPackage', + 'departureDate', + 'lodgingCheckInDate', + 'lodgingCheckOutDate', + 'lodgingName', + 'roomRate', + 'roomTax', + 'passengerFirstName', + 'passengerLastName', + 'passengerMiddleInitial', + 'passengerTitle', + 'issuedDate', + 'travelAgencyName', + 'travelAgencyCode', + 'ticketNumber', + 'issuingCarrierCode', + 'customerCode', + 'fareAmount', + 'feeAmount', + 'taxAmount', + 'restrictedTicket', + 'noShow', + 'advancedDeposit', + 'fireSafe', + 'propertyPhone', + ['legs' => + [ + 'conjunctionTicket', + 'exchangeTicket', + 'couponNumber', + 'serviceClass', + 'carrierCode', + 'fareBasisCode', + 'flightNumber', + 'departureDate', + 'departureAirportCode', + 'departureTime', + 'arrivalAirportCode', + 'arrivalTime', + 'stopoverPermitted', + 'fareAmount', + 'feeAmount', + 'taxAmount', + 'endorsementOrRestrictions' + ] + ], + ['additionalCharges' => + [ + 'kind', + 'amount' + ] + ] + ] + ] + ] + ], + ['lineItems' => + [ + 'commodityCode', + 'description', + 'discountAmount', + 'kind', + 'name', + 'productCode', + 'quantity', + 'taxAmount', + 'totalAmount', + 'unitAmount', + 'unitOfMeasure', + 'unitTaxAmount', + 'url' + ] + ], + ['externalVault' => + ['status' , 'previousNetworkTransactionId'], + ], + ['googlePayCard' => + [ + 'cryptogram', + 'eciIndicator', + 'expirationMonth', + 'expirationYear', + 'googleTransactionId', + 'number', + 'sourceCardLastFour', + 'sourceCardType' + ] + ], + ['installments' => ['count']] + ]; + } + + public static function submitForSettlementSignature() + { + return ['orderId', ['descriptor' => ['name', 'phone', 'url']], + 'purchaseOrderNumber', + 'taxAmount', + 'taxExempt', + 'shippingAmount', + 'discountAmount', + 'shipsFromPostalCode', + ['lineItems' => + [ + 'commodityCode', + 'description', + 'discountAmount', + 'kind', + 'name', + 'productCode', + 'quantity', + 'taxAmount', + 'totalAmount', + 'unitAmount', + 'unitOfMeasure', + 'unitTaxAmount', + 'url' + ] + ], + ]; + } + + public static function updateDetailsSignature() + { + return ['amount', 'orderId', ['descriptor' => ['name', 'phone', 'url']]]; + } + + public static function refundSignature() + { + return [ + 'amount', + 'merchantAccountId', + 'orderId' + ]; + } + + /** + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + */ + public function credit($attribs) + { + return $this->create(array_merge($attribs, ['type' => Transaction::CREDIT])); + } + + /** + * + * @access public + * @param array $attribs + * @return Result\Successful|Result\Error + * @throws Exception\ValidationError + */ + public function creditNoValidate($attribs) + { + $result = $this->credit($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + /** + * @access public + * @param string id + * @return Transaction + */ + public function find($id) + { + $this->_validateId($id); + try { + $path = $this->_config->merchantPath() . '/transactions/' . $id; + $response = $this->_http->get($path); + return Transaction::factory($response['transaction']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'transaction with id ' . $id . ' not found' + ); + } + } + /** + * new sale + * @param array $attribs (Note: $recurring param is deprecated. Use $transactionSource instead) + * @return Result\Successful|Result\Error + */ + public function sale($attribs) + { + if (array_key_exists('recurring', $attribs)) { + trigger_error('$recurring is deprecated, use $transactionSource instead', E_USER_DEPRECATED); + } + return $this->create(array_merge(['type' => Transaction::SALE], $attribs)); + } + + /** + * roughly equivalent to the ruby bang method + * @access public + * @param array $attribs + * @return array + * @throws Exception\ValidationsFailed + */ + public function saleNoValidate($attribs) + { + $result = $this->sale($attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + /** + * Returns a ResourceCollection of transactions matching the search query. + * + * If query is a string, the search will be a basic search. + * If query is a hash, the search will be an advanced search. + * // phpcs:ignore Generic.Files.LineLength + * For more detailed information and examples, see {@link https://developers.braintreepayments.com/reference/request/transaction/search/php https://developers.braintreepayments.com/reference/request/transaction/search/php} + * + * @param mixed $query search query + * @param array $options options such as page number + * @return ResourceCollection + * @throws InvalidArgumentException + */ + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/transactions/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + if (array_key_exists('searchResults', $response)) { + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } else { + throw new Exception\RequestTimeout(); + } + } + + public function fetch($query, $ids) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + $criteria["ids"] = TransactionSearch::ids()->in($ids)->toparam(); + $path = $this->_config->merchantPath() . '/transactions/advanced_search'; + $response = $this->_http->post($path, ['search' => $criteria]); + + if (array_key_exists('creditCardTransactions', $response)) { + return Util::extractattributeasarray( + $response['creditCardTransactions'], + 'transaction' + ); + } else { + throw new Exception\RequestTimeout(); + } + } + + /** + * Adjusts the authorization amount of a transaction + * + * @access public + * @param string $transactionId + * @param string amount + * + * @return Result\Successful|Result\Error + * @throws Exception\Unexpected + */ + public function adjustAuthorization($transactionId, $amount) + { + self::_validateId($transactionId); + $params = ['amount' => $amount]; + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/adjust_authorization'; + $response = $this->_http->put($path, ['transaction' => $params]); + return $this->_verifyGatewayResponse($response); + } + + /** + * void a transaction by id + * + * @param string $id transaction id + * @return Result\Successful|Result\Error + */ + public function void($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/void'; + $response = $this->_http->put($path); + return $this->_verifyGatewayResponse($response); + } + /** + * + */ + public function voidNoValidate($transactionId) + { + $result = $this->void($transactionId); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + public function submitForSettlement($transactionId, $amount = null, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::submitForSettlementSignature(), $attribs); + $attribs['amount'] = $amount; + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/submit_for_settlement'; + $response = $this->_http->put($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function submitForSettlementNoValidate($transactionId, $amount = null, $attribs = []) + { + $result = $this->submitForSettlement($transactionId, $amount, $attribs); + return Util::returnObjectOrThrowException(__CLASS__, $result); + } + + public function updateDetails($transactionId, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::updateDetailsSignature(), $attribs); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/update_details'; + $response = $this->_http->put($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function submitForPartialSettlement($transactionId, $amount, $attribs = []) + { + $this->_validateId($transactionId); + Util::verifyKeys(self::submitForSettlementSignature(), $attribs); + $attribs['amount'] = $amount; + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/submit_for_partial_settlement'; + $response = $this->_http->post($path, ['transaction' => $attribs]); + return $this->_verifyGatewayResponse($response); + } + + public function holdInEscrow($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/hold_in_escrow'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function releaseFromEscrow($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/release_from_escrow'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function cancelRelease($transactionId) + { + $this->_validateId($transactionId); + + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/cancel_release'; + $response = $this->_http->put($path, []); + return $this->_verifyGatewayResponse($response); + } + + public function refund($transactionId, $amount_or_options = null) + { + self::_validateId($transactionId); + + if (gettype($amount_or_options) == "array") { + $options = $amount_or_options; + } else { + $options = [ + "amount" => $amount_or_options + ]; + } + Util::verifyKeys(self::refundSignature(), $options); + + $params = ['transaction' => $options]; + $path = $this->_config->merchantPath() . '/transactions/' . $transactionId . '/refund'; + $response = $this->_http->post($path, $params); + return $this->_verifyGatewayResponse($response); + } + + /** + * sends the create request to the gateway + * + * @ignore + * @param var $subPath + * @param array $params + * @return Result\Successful|Result\Error + */ + public function _doCreate($subPath, $params) + { + $fullPath = $this->_config->merchantPath() . $subPath; + $response = $this->_http->post($fullPath, $params); + + return $this->_verifyGatewayResponse($response); + } + + /** + * verifies that a valid transaction id is being used + * @ignore + * @param string transaction id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) + { + if (empty($id)) { + throw new InvalidArgumentException( + 'expected transaction id to be set' + ); + } + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new Transaction 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['transaction'])) { + // return a populated instance of Transaction + return new Result\Successful( + Transaction::factory($response['transaction']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + "Expected transaction or apiErrorResponse" + ); + } + } +} diff --git a/lib/Braintree/TransactionLineItem.php b/lib/Braintree/TransactionLineItem.php new file mode 100644 index 0000000..fa392f2 --- /dev/null +++ b/lib/Braintree/TransactionLineItem.php @@ -0,0 +1,53 @@ +transactionLineItem()->findAll($transactionId); + } +} diff --git a/lib/Braintree/TransactionLineItemGateway.php b/lib/Braintree/TransactionLineItemGateway.php new file mode 100644 index 0000000..6d6d066 --- /dev/null +++ b/lib/Braintree/TransactionLineItemGateway.php @@ -0,0 +1,68 @@ +_gateway = $gateway; + $this->_config = $gateway->config; + $this->_config->assertHasAccessTokenOrKeys(); + $this->_http = new Http($gateway->config); + } + + /** + * @access public + * @param string id + * @return Transaction + */ + public function findAll($id) + { + $this->_validateId($id); + try { + $path = $this->_config->merchantPath() . '/transactions/' . $id . '/line_items'; + $response = $this->_http->get($path); + + $lineItems = []; + if (isset($response['lineItems'])) { + foreach ($response['lineItems'] as $lineItem) { + $lineItems[] = new TransactionLineItem($lineItem); + } + } + return $lineItems; + } catch (Exception\NotFound $e) { + throw new Exception\NotFound('transaction line items with id ' . $id . ' not found'); + } + } + + /** + * verifies that a valid transaction id is being used + * @ignore + * @param string transaction id + * @throws InvalidArgumentException + */ + private function _validateId($id = null) + { + if (empty($id)) { + throw new InvalidArgumentException('expected transaction id to be set'); + } + if (!preg_match('/^[0-9a-z]+$/', $id)) { + throw new InvalidArgumentException($id . ' is an invalid transaction id.'); + } + } +} diff --git a/lib/Braintree/TransactionSearch.php b/lib/Braintree/TransactionSearch.php index 487d781..ee3e50d 100644 --- a/lib/Braintree/TransactionSearch.php +++ b/lib/Braintree/TransactionSearch.php @@ -1,125 +1,306 @@ - * $trData = Braintree_TransparentRedirect::createCustomerData(array( - * 'redirectUrl => 'http://example.com/redirect_back_to_merchant_site', - * )); - * - * - * In addition to the redirectUrl, any data that needs to be protected - * from user tampering should be included in the trData. - * For example, to prevent the user from tampering with the transaction - * amount, include the amount in the trData. - * - * - * $trData = Braintree_TransparentRedirect::transactionData(array( - * 'redirectUrl' => 'http://example.com/complete_transaction', - * 'transaction' => array('amount' => '100.00'), - * )); - * - * - * - * @package Braintree - * @category Resources - * @copyright 2010 Braintree Payment Solutions - */ -class Braintree_TransparentRedirect -{ - // Request Kinds - const CREATE_TRANSACTION = 'create_transaction'; - const CREATE_PAYMENT_METHOD = 'create_payment_method'; - const UPDATE_PAYMENT_METHOD = 'update_payment_method'; - const CREATE_CUSTOMER = 'create_customer'; - const UPDATE_CUSTOMER = 'update_customer'; - - /** - * - * @ignore - */ - private static $_transparentRedirectKeys = 'redirectUrl'; - private static $_createCustomerSignature; - private static $_updateCustomerSignature; - private static $_transactionSignature; - private static $_createCreditCardSignature; - private static $_updateCreditCardSignature; - - - /** - * @ignore - * don't permit an explicit call of the constructor! - * (like $t = new Braintree_TransparentRedirect()) - */ - protected function __construct() - { - - } - - /** - * create signatures for different call types - * @ignore - */ - public static function init() - { - - self::$_createCustomerSignature = array( - self::$_transparentRedirectKeys, - array('customer' => Braintree_Customer::createSignature()), - ); - self::$_updateCustomerSignature = array( - self::$_transparentRedirectKeys, - 'customerId', - array('customer' => Braintree_Customer::updateSignature()), - ); - self::$_transactionSignature = array( - self::$_transparentRedirectKeys, - array('transaction' => Braintree_Transaction::createSignature()), - ); - self::$_createCreditCardSignature = array( - self::$_transparentRedirectKeys, - array('creditCard' => Braintree_CreditCard::createSignature()), - ); - self::$_updateCreditCardSignature = array( - self::$_transparentRedirectKeys, - 'paymentMethodToken', - array('creditCard' => Braintree_CreditCard::updateSignature()), - ); - } - - public static function confirm($queryString) - { - $params = Braintree_TransparentRedirect::parseAndValidateQueryString( - $queryString - ); - $confirmationKlasses = array( - Braintree_TransparentRedirect::CREATE_TRANSACTION => 'Braintree_Transaction', - Braintree_TransparentRedirect::CREATE_CUSTOMER => 'Braintree_Customer', - Braintree_TransparentRedirect::UPDATE_CUSTOMER => 'Braintree_Customer', - Braintree_TransparentRedirect::CREATE_PAYMENT_METHOD => 'Braintree_CreditCard', - Braintree_TransparentRedirect::UPDATE_PAYMENT_METHOD => 'Braintree_CreditCard' - ); - return call_user_func(array($confirmationKlasses[$params["kind"]], '_doCreate'), - '/transparent_redirect_requests/' . $params['id'] . '/confirm', - array() - ); - } - - /** - * returns the trData string for creating a credit card, - * @param array $params - * @return string - */ - public static function createCreditCardData($params) - { - Braintree_Util::verifyKeys( - self::$_createCreditCardSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_PAYMENT_METHOD; - return self::_data($params); - } - - /** - * returns the trData string for creating a customer. - * @param array $params - * @return string - */ - public static function createCustomerData($params) - { - Braintree_Util::verifyKeys( - self::$_createCustomerSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_CUSTOMER; - return self::_data($params); - - } - - public static function url() - { - return Braintree_Configuration::merchantUrl() . "/transparent_redirect_requests"; - } - - /** - * returns the trData string for creating a transaction - * @param array $params - * @return string - */ - public static function transactionData($params) - { - Braintree_Util::verifyKeys( - self::$_transactionSignature, - $params - ); - $params["kind"] = Braintree_TransparentRedirect::CREATE_TRANSACTION; - $transactionType = isset($params['transaction']['type']) ? - $params['transaction']['type'] : - null; - if ($transactionType != Braintree_Transaction::SALE && $transactionType != Braintree_Transaction::CREDIT) { - throw new InvalidArgumentException( - 'expected transaction[type] of sale or credit, was: ' . - $transactionType - ); - } - - return self::_data($params); - } - - /** - * Returns the trData string for updating a credit card. - * - * The paymentMethodToken of the credit card to update is required. - * - * - * $trData = Braintree_TransparentRedirect::updateCreditCardData(array( - * 'redirectUrl' => 'http://example.com/redirect_here', - * 'paymentMethodToken' => 'token123', - * )); - * - * - * @param array $params - * @return string - */ - public static function updateCreditCardData($params) - { - Braintree_Util::verifyKeys( - self::$_updateCreditCardSignature, - $params - ); - if (!isset($params['paymentMethodToken'])) { - throw new InvalidArgumentException( - 'expected params to contain paymentMethodToken.' - ); - } - $params["kind"] = Braintree_TransparentRedirect::UPDATE_PAYMENT_METHOD; - return self::_data($params); - } - - /** - * Returns the trData string for updating a customer. - * - * The customerId of the customer to update is required. - * - * - * $trData = Braintree_TransparentRedirect::updateCustomerData(array( - * 'redirectUrl' => 'http://example.com/redirect_here', - * 'customerId' => 'customer123', - * )); - * - * - * @param array $params - * @return string - */ - public static function updateCustomerData($params) - { - Braintree_Util::verifyKeys( - self::$_updateCustomerSignature, - $params - ); - if (!isset($params['customerId'])) { - throw new InvalidArgumentException( - 'expected params to contain customerId of customer to update' - ); - } - $params["kind"] = Braintree_TransparentRedirect::UPDATE_CUSTOMER; - return self::_data($params); - } - - public static function parseAndValidateQueryString($queryString) - { - // parse the params into an array - parse_str($queryString, $params); - // remove the hash - $queryStringWithoutHash = null; - if(preg_match('/^(.*)&hash=[a-f0-9]+$/', $queryString, $match)) { - $queryStringWithoutHash = $match[1]; - } - - if($params['http_status'] != '200') { - $message = null; - if(array_key_exists('bt_message', $params)) { - $message = $params['bt_message']; - } - Braintree_Util::throwStatusCodeException($params['http_status'], $message); - } - - // recreate the hash and compare it - if(self::_hash($queryStringWithoutHash) == $params['hash']) { - return $params; - } else { - throw new Braintree_Exception_ForgedQueryString(); - } - } - - - /** - * - * @ignore - */ - private static function _data($params) - { - if (!isset($params['redirectUrl'])) { - throw new InvalidArgumentException( - 'expected params to contain redirectUrl' - ); - } - $params = self::_underscoreKeys($params); - $now = new DateTime('now', new DateTimeZone('UTC')); - $trDataParams = array_merge($params, - array( - 'api_version' => Braintree_Configuration::API_VERSION, - 'public_key' => Braintree_Configuration::publicKey(), - 'time' => $now->format('YmdHis'), - ) - ); - ksort($trDataParams); - $trDataSegment = http_build_query($trDataParams, null, '&'); - $trDataHash = self::_hash($trDataSegment); - return "$trDataHash|$trDataSegment"; - } - - private static function _underscoreKeys($array) - { - foreach($array as $key=>$value) - { - $newKey = Braintree_Util::camelCaseToDelimiter($key, '_'); - unset($array[$key]); - if (is_array($value)) - { - $array[$newKey] = self::_underscoreKeys($value); - } - else - { - $array[$newKey] = $value; - } - } - return $array; - } - - /** - * @ignore - */ - private static function _hash($string) - { - return Braintree_Digest::hexDigest($string); - } - -} -Braintree_TransparentRedirect::init(); diff --git a/lib/Braintree/UnknownPaymentMethod.php b/lib/Braintree/UnknownPaymentMethod.php new file mode 100644 index 0000000..dd754b5 --- /dev/null +++ b/lib/Braintree/UnknownPaymentMethod.php @@ -0,0 +1,68 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $token + * @property-read string $imageUrl + */ +class UnknownPaymentMethod extends Base +{ + + + /** + * factory method: returns an instance of UnknownPaymentMethod + * to the requesting method, with populated properties + * + * @ignore + * @return UnknownPaymentMethod + */ + public static function factory($attributes) + { + $instance = new self(); + $values = array_values($attributes); + $instance->_initialize(array_shift($values)); + 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 $unknownPaymentMethodAttribs array of unknownPaymentMethod data + * @return void + */ + protected function _initialize($unknownPaymentMethodAttribs) + { + // set the attributes + $this->imageUrl = 'https://assets.braintreegateway.com/payment_method_logo/unknown.png'; + $this->_attributes = $unknownPaymentMethodAttribs; + } +} diff --git a/lib/Braintree/UsBankAccount.php b/lib/Braintree/UsBankAccount.php new file mode 100644 index 0000000..0fb362f --- /dev/null +++ b/lib/Braintree/UsBankAccount.php @@ -0,0 +1,117 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + * @property-read string $customerId + * @property-read string $email + * @property-read string $token + * @property-read string $imageUrl + * @property-read string $routingNumber + * @property-read string $accountType + * @property-read string $accountHolderName + * @property-read string $last4 + * @property-read string $bankName + * @property-read string $achMandate + * @property-read boolean $default + * @property-read boolean $verified + */ +class UsBankAccount extends Base +{ + /** + * factory method: returns an instance of UsBankAccount + * to the requesting method, with populated properties + * + * @ignore + * @return UsBankAccount + */ + public static function factory($attributes) + { + $instance = new self(); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $usBankAccountAttribs array of usBankAccount data + * @return void + */ + protected function _initialize($usBankAccountAttribs) + { + // set the attributes + $this->_attributes = $usBankAccountAttribs; + + $achMandate = isset($usBankAccountAttribs['achMandate']) ? + AchMandate::factory($usBankAccountAttribs['achMandate']) : + null; + $this->_set('achMandate', $achMandate); + + if (isset($usBankAccountAttribs['verifications'])) { + $verification_records = $usBankAccountAttribs['verifications']; + + $verifications = array(); + for ($i = 0; $i < count($verification_records); $i++) { + $verifications[$i] = UsBankAccountVerification::factory($verification_records[$i]); + } + $this->_set('verifications', $verifications); + } else { + $this->_set('verifications', null); + } + } + + /** + * create a printable representation of the object as: + * ClassName[property=value, property=value] + * @return string + */ + public function __toString() + { + return __CLASS__ . '[' . + Util::attributesToString($this->_attributes) . ']'; + } + + /** + * returns false if default is null or false + * + * @return boolean + */ + public function isDefault() + { + return $this->default; + } + + // static methods redirecting to gateway + + public static function find($token) + { + return Configuration::gateway()->usBankAccount()->find($token); + } + + public static function sale($token, $transactionAttribs) + { + $transactionAttribs['options'] = [ + 'submitForSettlement' => true + ]; + return Configuration::gateway()->usBankAccount()->sale($token, $transactionAttribs); + } +} diff --git a/lib/Braintree/UsBankAccountGateway.php b/lib/Braintree/UsBankAccountGateway.php new file mode 100644 index 0000000..3c8aef2 --- /dev/null +++ b/lib/Braintree/UsBankAccountGateway.php @@ -0,0 +1,105 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class UsBankAccountGateway +{ + 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); + } + + + /** + * find a usBankAccount by token + * + * @access public + * @param string $token paypal accountunique id + * @return UsBankAccount + * @throws Exception\NotFound + */ + public function find($token) + { + try { + $path = $this->_config->merchantPath() . '/payment_methods/us_bank_account/' . $token; + $response = $this->_http->get($path); + return UsBankAccount::factory($response['usBankAccount']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'US bank account with token ' . $token . ' not found' + ); + } + } + + /** + * create a new sale for the current UsBank account + * + * @param string $token + * @param array $transactionAttribs + * @return Result\Successful|Result\Error + * @see Transaction::sale() + */ + public function sale($token, $transactionAttribs) + { + return Transaction::sale( + array_merge( + $transactionAttribs, + ['paymentMethodToken' => $token] + ) + ); + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new UsBankAccount 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['usBankAccount'])) { + // return a populated instance of UsBankAccount + return new Result\Successful( + UsBankAccount::factory($response['usBankAccount']) + ); + } elseif (isset($response['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } else { + throw new Exception\Unexpected( + 'Expected US bank account or apiErrorResponse' + ); + } + } +} diff --git a/lib/Braintree/UsBankAccountVerification.php b/lib/Braintree/UsBankAccountVerification.php new file mode 100644 index 0000000..8a9a554 --- /dev/null +++ b/lib/Braintree/UsBankAccountVerification.php @@ -0,0 +1,102 @@ +== More information == + * + * + * @package Braintree + * @category Resources + * + */ +class UsBankAccountVerification extends Result\UsBankAccountVerification +{ + /** + * factory method: returns an instance of UsBankAccountVerification + * to the requesting method, with populated properties + * + * @ignore + * @return UsBankAccountVerification + */ + public static function factory($attributes) + { + $instance = new self($attributes); + $instance->_initialize($attributes); + return $instance; + } + + /* instance methods */ + + /** + * sets instance properties from an array of values + * + * @access protected + * @param array $usBankAccountVerificationAttribs array of usBankAccountVerification data + * @return void + */ + protected function _initialize($usBankAccountVerificationAttribs) + { + // set the attributes + $this->_attributes = $usBankAccountVerificationAttribs; + } + + /** + * 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 + + /** + * finds a US bank account verification + * + * @access public + * @param string $token unique id + * @return UsBankAccountVerification + */ + public static function find($token) + { + return Configuration::gateway()->usBankAccountVerification()->find($token); + } + + /** + * Returns a ResourceCollection of US bank account verifications matching the search query. + * + * @access public + * @param mixed $query search query + * @return ResourceCollection + */ + public static function search($query) + { + return Configuration::gateway()->usBankAccountVerification()->search($query); + } + + /** + * Returns a ResourceCollection of US bank account verifications matching the search query. + * + * @access public + * @param string $token unique id + * @param array $amounts micro transfer amounts + * @return ResourceCollection + */ + public static function confirmMicroTransferAmounts($token, $amounts) + { + return Configuration::gateway()->usBankAccountVerification()->confirmMicroTransferAmounts($token, $amounts); + } +} diff --git a/lib/Braintree/UsBankAccountVerificationGateway.php b/lib/Braintree/UsBankAccountVerificationGateway.php new file mode 100644 index 0000000..918f12b --- /dev/null +++ b/lib/Braintree/UsBankAccountVerificationGateway.php @@ -0,0 +1,129 @@ +== More information == + * + * + * @package Braintree + * @category Resources + */ +class UsBankAccountVerificationGateway +{ + 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); + } + + /** + * find a usBankAccountVerification by token + * + * @access public + * @param string $token unique id + * @return UsBankAccountVerification + * @throws Exception\NotFound + */ + public function find($token) + { + try { + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/' . $token; + $response = $this->_http->get($path); + return UsBankAccountVerification::factory($response['usBankAccountVerification']); + } catch (Exception\NotFound $e) { + throw new Exception\NotFound( + 'US bank account with token ' . $token . ' not found' + ); + } + } + + public function search($query) + { + $criteria = []; + foreach ($query as $term) { + $criteria[$term->name] = $term->toparam(); + } + + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/advanced_search_ids'; + $response = $this->_http->post($path, ['search' => $criteria]); + $pager = [ + 'object' => $this, + 'method' => 'fetch', + 'methodArgs' => [$query] + ]; + + return new ResourceCollection($response, $pager); + } + + /** + * complete micro transfer verification by confirming the transfer amounts + * + * @access public + * @param string $token unique id + * @param array $amounts amounts deposited in micro transfer + * @return UsBankAccountVerification + * @throws Exception\Unexpected + */ + public function confirmMicroTransferAmounts($token, $amounts) + { + try { + // phpcs:ignore Generic.Files.LineLength + $path = $this->_config->merchantPath() . '/us_bank_account_verifications/' . $token . '/confirm_micro_transfer_amounts'; + $response = $this->_http->put($path, [ + "us_bank_account_verification" => ["deposit_amounts" => $amounts] + ]); + return $this->_verifyGatewayResponse($response); + } catch (Exception\Unexpected $e) { + throw new Exception\Unexpected( + 'Unexpected exception.' + ); + } + } + + /** + * generic method for validating incoming gateway responses + * + * creates a new UsBankAccountVerification 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['apiErrorResponse'])) { + return new Result\Error($response['apiErrorResponse']); + } elseif (isset($response['usBankAccountVerification'])) { + // return a populated instance of UsBankAccountVerification + return new Result\Successful( + UsBankAccountVerification::factory($response['usBankAccountVerification']) + ); + } else { + throw new Exception\Unexpected( + 'Expected US bank account or apiErrorResponse' + ); + } + } +} diff --git a/lib/Braintree/UsBankAccountVerificationSearch.php b/lib/Braintree/UsBankAccountVerificationSearch.php new file mode 100644 index 0000000..a3c22e9 --- /dev/null +++ b/lib/Braintree/UsBankAccountVerificationSearch.php @@ -0,0 +1,72 @@ +success) { + return $resultObj->$resultObjName; + } else { + throw new Exception\ValidationsFailed(); + } + } + + /** + * removes the header from a classname * - * @param string $name Braintree_ClassName - * @return camelCased classname minus Braintree_ header + * @param string $name ClassName + * @return camelCased classname minus header */ public static function cleanClassName($name) { - $classNamesToResponseKeys = array( - 'CreditCard' => 'creditCard', - 'Customer' => 'customer', - 'Subscription' => 'subscription', - 'Transaction' => 'transaction', - 'CreditCardVerification' => 'verification', - 'AddOn' => 'addOn', - 'Discount' => 'discount', - 'Plan' => 'plan', - 'Address' => 'address', - 'SettlementBatchSummary' => 'settlementBatchSummary', - 'MerchantAccount' => 'merchantAccount' - ); - - $name = str_replace('Braintree_', '', $name); + $classNamesToResponseKeys = [ + 'Braintree\CreditCard' => 'creditCard', + 'Braintree\CreditCardGateway' => 'creditCard', + 'Braintree\Customer' => 'customer', + 'Braintree\CustomerGateway' => 'customer', + 'Braintree\Subscription' => 'subscription', + 'Braintree\SubscriptionGateway' => 'subscription', + 'Braintree\Transaction' => 'transaction', + 'Braintree\TransactionGateway' => 'transaction', + 'Braintree\CreditCardVerification' => 'verification', + 'Braintree\CreditCardVerificationGateway' => 'verification', + 'Braintree\AddOn' => 'addOn', + 'Braintree\AddOnGateway' => 'addOn', + 'Braintree\Discount' => 'discount', + 'Braintree\DiscountGateway' => 'discount', + 'Braintree\Dispute' => 'dispute', + 'Braintree\Dispute\EvidenceDetails' => 'evidence', + 'Braintree\DocumentUpload' => 'documentUpload', + 'Braintree\Plan' => 'plan', + 'Braintree\PlanGateway' => 'plan', + 'Braintree\Address' => 'address', + 'Braintree\AddressGateway' => 'address', + 'Braintree\SettlementBatchSummary' => 'settlementBatchSummary', + 'Braintree\SettlementBatchSummaryGateway' => 'settlementBatchSummary', + 'Braintree\Merchant' => 'merchant', + 'Braintree\MerchantGateway' => 'merchant', + 'Braintree\MerchantAccount' => 'merchantAccount', + 'Braintree\MerchantAccountGateway' => 'merchantAccount', + 'Braintree\OAuthCredentials' => 'credentials', + 'Braintree\OAuthResult' => 'result', + 'Braintree\PayPalAccount' => 'paypalAccount', + 'Braintree\PayPalAccountGateway' => 'paypalAccount', + 'Braintree\UsBankAccountVerification' => 'usBankAccountVerification', + ]; + return $classNamesToResponseKeys[$name]; } /** * * @param string $name className - * @return string Braintree_ClassName + * @return string ClassName */ public static function buildClassName($name) { - $responseKeysToClassNames = array( - 'creditCard' => 'CreditCard', - 'customer' => 'Customer', - 'subscription' => 'Subscription', - 'transaction' => 'Transaction', - 'verification' => 'CreditCardVerification', - 'addOn' => 'AddOn', - 'discount' => 'Discount', - 'plan' => 'Plan', - 'address' => 'Address', - 'settlementBatchSummary' => 'SettlementBatchSummary', - 'merchantAccount' => 'MerchantAccount' - ); - - return 'Braintree_' . $responseKeysToClassNames[$name]; + $responseKeysToClassNames = [ + 'creditCard' => 'Braintree\CreditCard', + 'customer' => 'Braintree\Customer', + 'dispute' => 'Braintree\Dispute', + 'documentUpload' => 'Braintree\DocumentUpload', + 'subscription' => 'Braintree\Subscription', + 'transaction' => 'Braintree\Transaction', + 'verification' => 'Braintree\CreditCardVerification', + 'addOn' => 'Braintree\AddOn', + 'discount' => 'Braintree\Discount', + 'plan' => 'Braintree\Plan', + 'address' => 'Braintree\Address', + 'settlementBatchSummary' => 'Braintree\SettlementBatchSummary', + 'merchantAccount' => 'Braintree\MerchantAccount', + ]; + + return (string) $responseKeysToClassNames[$name]; } /** @@ -134,16 +218,16 @@ public static function buildClassName($name) * * @access public * @param string $string + * @param null|string $delimiter * @return string modified string */ public static function delimiterToCamelCase($string, $delimiter = '[\-\_]') { - // php doesn't garbage collect functions created by create_function() - // so use a static variable to avoid adding a new function to memory - // every time this function is called. static $callback = null; if ($callback === null) { - $callback = create_function('$matches', 'return strtoupper($matches[1]);'); + $callback = function ($matches) { + return strtoupper($matches[1]); + }; } return preg_replace_callback('/' . $delimiter . '(\w)/', $callback, $string); @@ -166,20 +250,60 @@ public static function delimiterToUnderscore($string) * find capitals and convert to delimiter + lowercase * * @access public - * @param var $string - * @return var modified string + * @param string $string + * @param null|string $delimiter + * @return string modified string */ public static function camelCaseToDelimiter($string, $delimiter = '-') { - // php doesn't garbage collect functions created by create_function() - // so use a static variable to avoid adding a new function to memory - // every time this function is called. - static $callbacks = array(); - if (!isset($callbacks[$delimiter])) { - $callbacks[$delimiter] = create_function('$matches', "return '$delimiter' . strtolower(\$matches[1]);"); + return strtolower(preg_replace('/([A-Z])/', "$delimiter\\1", $string)); + } + + public static function delimiterToCamelCaseArray($array, $delimiter = '[\-\_]') + { + $converted = []; + foreach ($array as $key => $value) { + if (is_string($key)) { + $key = self::delimiterToCamelCase($key, $delimiter); + } + + if (is_array($value)) { + // Make an exception for custom fields, which must be underscore (can't be + // camelCase). + if ($key === 'customFields') { + $value = self::delimiterToUnderscoreArray($value); + } else { + $value = self::delimiterToCamelCaseArray($value, $delimiter); + } + } + $converted[$key] = $value; + } + return $converted; + } + + public static function camelCaseToDelimiterArray($array, $delimiter = '-') + { + $converted = []; + foreach ($array as $key => $value) { + if (is_string($key)) { + $key = self::camelCaseToDelimiter($key, $delimiter); + } + if (is_array($value)) { + $value = self::camelCaseToDelimiterArray($value, $delimiter); + } + $converted[$key] = $value; } + return $converted; + } - return preg_replace_callback('/([A-Z])/', $callbacks[$delimiter], $string); + public static function delimiterToUnderscoreArray($array) + { + $converted = []; + foreach ($array as $key => $value) { + $key = self::delimiterToUnderscore($key); + $converted[$key] = $value; + } + return $converted; } /** @@ -187,32 +311,36 @@ public static function camelCaseToDelimiter($string, $delimiter = '-') * @param array $array associative array to implode * @param string $separator (optional, defaults to =) * @param string $glue (optional, defaults to ', ') + * @return bool */ public static function implodeAssociativeArray($array, $separator = '=', $glue = ', ') { // build a new array with joined keys and values $tmpArray = null; - foreach ($array AS $key => $value) { - $tmpArray[] = $key . $separator . $value; - + foreach ($array as $key => $value) { + if ($value instanceof DateTime) { + $value = $value->format('r'); + } + $tmpArray[] = $key . $separator . $value; } // implode and return the new array return (is_array($tmpArray)) ? implode($glue, $tmpArray) : false; } - public static function attributesToString($attributes) { - $printableAttribs = array(); - foreach ($attributes AS $key => $value) { + public static function attributesToString($attributes) + { + $printableAttribs = []; + foreach ($attributes as $key => $value) { if (is_array($value)) { - $pAttrib = Braintree_Util::attributesToString($value); - } else if ($value instanceof DateTime) { + $pAttrib = self::attributesToString($value); + } elseif ($value instanceof DateTime) { $pAttrib = $value->format(DateTime::RFC850); } else { $pAttrib = $value; } $printableAttribs[$key] = sprintf('%s', $pAttrib); } - return Braintree_Util::implodeAssociativeArray($printableAttribs); + return self::implodeAssociativeArray($printableAttribs); } /** @@ -231,12 +359,29 @@ public static function verifyKeys($signature, $attributes) $invalidKeys = array_diff($userKeys, $validKeys); $invalidKeys = self::_removeWildcardKeys($validKeys, $invalidKeys); - if(!empty($invalidKeys)) { + if (!empty($invalidKeys)) { asort($invalidKeys); $sortedList = join(', ', $invalidKeys); - throw new InvalidArgumentException('invalid keys: '. $sortedList); + throw new InvalidArgumentException('invalid keys: ' . $sortedList); } } + + /** + * replaces the value of a key in an array + * @param $array + * @param string $oldKey + * @param string $newKey + * @return array + */ + public static function replaceKey($array, $oldKey, $newKey) + { + if (array_key_exists($oldKey, $array)) { + $array[$newKey] = $array[$oldKey]; + unset($array[$oldKey]); + } + return $array; + } + /** * flattens a numerically indexed nested array to a single level * @param array $keys @@ -245,9 +390,9 @@ public static function verifyKeys($signature, $attributes) */ private static function _flattenArray($keys, $namespace = null) { - $flattenedArray = array(); - foreach($keys AS $key) { - if(is_array($key)) { + $flattenedArray = []; + foreach ($keys as $key) { + if (is_array($key)) { $theKeys = array_keys($key); $theValues = array_values($key); $scope = $theKeys[0]; @@ -264,25 +409,25 @@ private static function _flattenArray($keys, $namespace = null) private static function _flattenUserKeys($keys, $namespace = null) { - $flattenedArray = array(); - - foreach($keys AS $key => $value) { - $fullKey = empty($namespace) ? $key : $namespace; - if (!is_numeric($key) && $namespace != null) { - $fullKey .= '[' . $key . ']'; - } - if (is_numeric($key) && is_string($value)) { - $fullKey .= '[' . $value . ']'; - } - if(is_array($value)) { - $more = self::_flattenUserKeys($value, $fullKey); - $flattenedArray = array_merge($flattenedArray, $more); - } else { - $flattenedArray[] = $fullKey; - } - } - sort($flattenedArray); - return $flattenedArray; + $flattenedArray = []; + + foreach ($keys as $key => $value) { + $fullKey = empty($namespace) ? $key : $namespace; + if (!is_numeric($key) && $namespace != null) { + $fullKey .= '[' . $key . ']'; + } + if (is_numeric($key) && is_string($value)) { + $fullKey .= '[' . $value . ']'; + } + if (is_array($value)) { + $more = self::_flattenUserKeys($value, $fullKey); + $flattenedArray = array_merge($flattenedArray, $more); + } else { + $flattenedArray[] = $fullKey; + } + } + sort($flattenedArray); + return $flattenedArray; } /** @@ -293,10 +438,10 @@ private static function _flattenUserKeys($keys, $namespace = null) */ private static function _removeWildcardKeys($validKeys, $invalidKeys) { - foreach($validKeys AS $key) { + foreach ($validKeys as $key) { if (stristr($key, '[_anyKey_]')) { $wildcardKey = str_replace('[_anyKey_]', '', $key); - foreach ($invalidKeys AS $index => $invalidKey) { + foreach ($invalidKeys as $index => $invalidKey) { if (stristr($invalidKey, $wildcardKey)) { unset($invalidKeys[$index]); } diff --git a/lib/Braintree/VenmoAccount.php b/lib/Braintree/VenmoAccount.php new file mode 100644 index 0000000..bb24ced --- /dev/null +++ b/lib/Braintree/VenmoAccount.php @@ -0,0 +1,75 @@ +== More information == + * + * See {@link https://developers.braintreepayments.com/javascript+php}
+ * + * @package Braintree + * @category Resources + * + * @property-read \DateTime $createdAt + * @property-read string $customerId + * @property-read boolean $default + * @property-read string $imageUrl + * @property-read string $sourceDescription + * @property-read \Braintree\Subscription[] $subscriptions + * @property-read string $token + * @property-read \DateTime $updatedAt + * @property-read string $username + * @property-read string $venmoUserId + */ +class VenmoAccount 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 VenmoAccount + * to the requesting method, with populated properties + * + * @ignore + * @return VenmoAccount + */ + 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 $venmoAccountAttribs array of Venmo account properties + * @return void + */ + protected function _initialize($venmoAccountAttribs) + { + $this->_attributes = $venmoAccountAttribs; + + $subscriptionArray = array(); + if (isset($venmoAccountAttribs['subscriptions'])) { + foreach ($venmoAccountAttribs['subscriptions'] as $subscription) { + $subscriptionArray[] = Subscription::factory($subscription); + } + } + + $this->_set('subscriptions', $subscriptionArray); + } +} diff --git a/lib/Braintree/Version.php b/lib/Braintree/Version.php index 5a04005..42b5d43 100644 --- a/lib/Braintree/Version.php +++ b/lib/Braintree/Version.php @@ -1,30 +1,25 @@ == More information == + * + * // phpcs:ignore Generic.Files.LineLength + * 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 $callId + * @property-read string $cardType + * @property-read string $cardholderName + * @property-read string $commercial + * @property-read string $countryOfIssuance + * @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 string $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 + */ +class VisaCheckoutCard extends Base +{ + /* 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; + } + + /** + * 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 VisaCheckoutCard, + * or is a VisaCheckoutCard with a different id + * + * @param object $otherVisaCheckoutCard customer to compare against + * @return boolean + */ + public function isEqual($otherVisaCheckoutCard) + { + return !($otherVisaCheckoutCard instanceof self) ? false : $this->token === $otherVisaCheckoutCard->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 VisaCheckoutCard + * to the requesting method, with populated properties + * + * @ignore + * @return VisaCheckoutCard + */ + public static function factory($attributes) + { + $defaultAttributes = [ + 'bin' => '', + 'expirationMonth' => '', + 'expirationYear' => '', + 'last4' => '', + ]; + + $instance = new self(); + $instance->_initialize(array_merge($defaultAttributes, $attributes)); + return $instance; + } +} diff --git a/lib/Braintree/WebhookNotification.php b/lib/Braintree/WebhookNotification.php index 873211d..326ba3a 100644 --- a/lib/Braintree/WebhookNotification.php +++ b/lib/Braintree/WebhookNotification.php @@ -1,6 +1,32 @@ webhookNotification()->parse($signature, $payload); } public static function verify($challenge) { - $publicKey = Braintree_Configuration::publicKey(); - $digest = Braintree_Digest::hexDigest($challenge); - return "{$publicKey}|{$digest}"; + return Configuration::gateway()->webhookNotification()->verify($challenge); } public static function factory($attributes) @@ -40,34 +58,15 @@ public static function factory($attributes) return $instance; } - private static function _matchingSignature($signaturePairs) - { - foreach ($signaturePairs as $pair) - { - $components = preg_split("/\|/", $pair); - if ($components[0] == Braintree_Configuration::publicKey()) { - return $components[1]; - } - } - - return null; - } - - private static function _validateSignature($signature, $payload) - { - $signaturePairs = preg_split("/&/", $signature); - $matchingSignature = self::_matchingSignature($signaturePairs); - - $payloadSignature = Braintree_Digest::hexDigest($payload); - if (!Braintree_Digest::secureCompare($matchingSignature, $payloadSignature)) { - throw new Braintree_Exception_InvalidSignature("webhook notification signature invalid"); - } - } - protected function _initialize($attributes) { + // phpcs:disable Generic.Files.LineLength $this->_attributes = $attributes; + if (!isset($attributes['sourceMerchantId'])) { + $this->_set('sourceMerchantId', null); + } + if (isset($attributes['subject']['apiErrorResponse'])) { $wrapperNode = $attributes['subject']['apiErrorResponse']; } else { @@ -75,28 +74,65 @@ protected function _initialize($attributes) } if (isset($wrapperNode['subscription'])) { - $this->_set('subscription', Braintree_Subscription::factory($attributes['subject']['subscription'])); + $this->_set('subscription', Subscription::factory($attributes['subject']['subscription'])); } if (isset($wrapperNode['merchantAccount'])) { - $this->_set('merchantAccount', Braintree_MerchantAccount::factory($wrapperNode['merchantAccount'])); + $this->_set('merchantAccount', MerchantAccount::factory($wrapperNode['merchantAccount'])); } if (isset($wrapperNode['transaction'])) { - $this->_set('transaction', Braintree_Transaction::factory($wrapperNode['transaction'])); + $this->_set('transaction', Transaction::factory($wrapperNode['transaction'])); } if (isset($wrapperNode['disbursement'])) { - $this->_set('disbursement', Braintree_Disbursement::factory($wrapperNode['disbursement'])); + $this->_set('disbursement', Disbursement::factory($wrapperNode['disbursement'])); } if (isset($wrapperNode['partnerMerchant'])) { - $this->_set('partnerMerchant', Braintree_PartnerMerchant::factory($wrapperNode['partnerMerchant'])); + $this->_set('partnerMerchant', PartnerMerchant::factory($wrapperNode['partnerMerchant'])); + } + + if (isset($wrapperNode['oauthApplicationRevocation'])) { + $this->_set('oauthAccessRevocation', OAuthAccessRevocation::factory($wrapperNode['oauthApplicationRevocation'])); + } + + if (isset($wrapperNode['connectedMerchantStatusTransitioned'])) { + $this->_set('connectedMerchantStatusTransitioned', ConnectedMerchantStatusTransitioned::factory($wrapperNode['connectedMerchantStatusTransitioned'])); + } + + if (isset($wrapperNode['connectedMerchantPaypalStatusChanged'])) { + $this->_set('connectedMerchantPayPalStatusChanged', ConnectedMerchantPayPalStatusChanged::factory($wrapperNode['connectedMerchantPaypalStatusChanged'])); + } + + if (isset($wrapperNode['dispute'])) { + $this->_set('dispute', Dispute::factory($wrapperNode['dispute'])); + } + + if (isset($wrapperNode['accountUpdaterDailyReport'])) { + $this->_set('accountUpdaterDailyReport', AccountUpdaterDailyReport::factory($wrapperNode['accountUpdaterDailyReport'])); + } + + if (isset($wrapperNode['grantedPaymentInstrumentUpdate'])) { + $this->_set('grantedPaymentInstrumentUpdate', GrantedPaymentInstrumentUpdate::factory($wrapperNode['grantedPaymentInstrumentUpdate'])); + } + + if (in_array($attributes['kind'], [self::GRANTED_PAYMENT_METHOD_REVOKED, self::PAYMENT_METHOD_REVOKED_BY_CUSTOMER])) { + $this->_set('revokedPaymentMethodMetadata', RevokedPaymentMethodMetadata::factory($wrapperNode)); + } + + if (isset($wrapperNode['localPayment'])) { + $this->_set('localPaymentCompleted', LocalPaymentCompleted::factory($wrapperNode['localPayment'])); + } + + if (isset($wrapperNode['localPaymentReversed'])) { + $this->_set('localPaymentReversed', LocalPaymentReversed::factory($wrapperNode['localPaymentReversed'])); } if (isset($wrapperNode['errors'])) { - $this->_set('errors', new Braintree_Error_ValidationErrorCollection($wrapperNode['errors'])); + $this->_set('errors', new Error\ValidationErrorCollection($wrapperNode['errors'])); $this->_set('message', $wrapperNode['message']); } + // phpcs:enable Generic.Files.LineLength } } diff --git a/lib/Braintree/WebhookNotificationGateway.php b/lib/Braintree/WebhookNotificationGateway.php new file mode 100644 index 0000000..22bf07b --- /dev/null +++ b/lib/Braintree/WebhookNotificationGateway.php @@ -0,0 +1,75 @@ +config = $gateway->config; + $this->config->assertHasAccessTokenOrKeys(); + } + + public function parse($signature, $payload) + { + if (is_null($signature)) { + throw new Exception\InvalidSignature("signature cannot be null"); + } + + if (is_null($payload)) { + throw new Exception\InvalidSignature("payload cannot be null"); + } + + if (preg_match("/[^A-Za-z0-9+=\/\n]/", $payload) === 1) { + throw new Exception\InvalidSignature("payload contains illegal characters"); + } + + self::_validateSignature($signature, $payload); + + $xml = base64_decode($payload); + $attributes = Xml::buildArrayFromXml($xml); + return WebhookNotification::factory($attributes['notification']); + } + + public function verify($challenge) + { + if (!preg_match('/^[a-f0-9]{20,32}$/', $challenge)) { + throw new Exception\InvalidChallenge("challenge contains non-hex characters"); + } + $publicKey = $this->config->getPublicKey(); + $digest = Digest::hexDigestSha1($this->config->getPrivateKey(), $challenge); + return "{$publicKey}|{$digest}"; + } + + private function _payloadMatches($signature, $payload) + { + $payloadSignature = Digest::hexDigestSha1($this->config->getPrivateKey(), $payload); + return Digest::secureCompare($signature, $payloadSignature); + } + + private function _validateSignature($signatureString, $payload) + { + $signaturePairs = preg_split("/&/", $signatureString); + $signature = self::_matchingSignature($signaturePairs); + if (!$signature) { + throw new Exception\InvalidSignature("no matching public key"); + } + + if (!(self::_payloadMatches($signature, $payload) || self::_payloadMatches($signature, $payload . "\n"))) { + throw new Exception\InvalidSignature("signature does not match payload - one has been modified"); + } + } + + private function _matchingSignature($signaturePairs) + { + foreach ($signaturePairs as $pair) { + $components = preg_split("/\|/", $pair); + if ($components[0] == $this->config->getPublicKey()) { + return $components[1]; + } + } + + return null; + } +} diff --git a/lib/Braintree/WebhookTesting.php b/lib/Braintree/WebhookTesting.php index f8d74da..5a473f4 100644 --- a/lib/Braintree/WebhookTesting.php +++ b/lib/Braintree/WebhookTesting.php @@ -1,217 +1,11 @@ $signature, - 'payload' => $payload - ); - } - - private static function _sampleXml($kind, $id) - { - switch ($kind) { - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: - $subjectXml = self::_merchantAccountApprovedSampleXml($id); - break; - case Braintree_WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: - $subjectXml = self::_merchantAccountDeclinedSampleXml($id); - break; - case Braintree_WebhookNotification::TRANSACTION_DISBURSED: - $subjectXml = self::_transactionDisbursedSampleXml($id); - break; - case Braintree_WebhookNotification::DISBURSEMENT_EXCEPTION: - $subjectXml = self::_disbursementExceptionSampleXml($id); - break; - case Braintree_WebhookNotification::DISBURSEMENT: - $subjectXml = self::_disbursementSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_CONNECTED: - $subjectXml = self::_partnerMerchantConnectedSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: - $subjectXml = self::_partnerMerchantDisconnectedSampleXml($id); - break; - case Braintree_WebhookNotification::PARTNER_MERCHANT_DECLINED: - $subjectXml = self::_partnerMerchantDeclinedSampleXml($id); - break; - default: - $subjectXml = self::_subscriptionSampleXml($id); - break; - } - $timestamp = self::_timestamp(); - return " - - {$timestamp} - {$kind} - {$subjectXml} - - "; - } - - private static function _merchantAccountApprovedSampleXml($id) - { - return " - - {$id} - - master_ma_for_{$id} - active - - active - - "; - } - private static function _merchantAccountDeclinedSampleXml($id) - { - return " - - Credit score is too low - - - - - - 82621 - Credit score is too low - base - - - - - - {$id} - suspended - - master_ma_for_{$id} - suspended - - - - "; - } - - private static function _transactionDisbursedSampleXml($id) - { - return " - - ${id} - 100 - - 2013-07-09 - - - "; - } - - private static function _disbursementExceptionSampleXml($id) - { - return " - - ${id} - - asdfg - qwert - - false - false - - merchant_account_token - USD - false - active - - 100.00 - 2014-02-10 - bank_rejected - update_funding_information - - "; - } +namespace Braintree; - private static function _disbursementSampleXml($id) - { - return " - - ${id} - - asdfg - qwert - - true - false - - merchant_account_token - USD - false - active - - 100.00 - 2014-02-10 - - - - "; - } - - private static function _subscriptionSampleXml($id) - { - return " - - {$id} - - - - - - - - "; - } - - private static function _partnerMerchantConnectedSampleXml($id) - { - return " - - public_id - public_key - private_key - abc123 - cse_key - - "; - } - - private static function _partnerMerchantDisconnectedSampleXml($id) - { - return " - - abc123 - - "; - } - - private static function _partnerMerchantDeclinedSampleXml($id) - { - return " - - abc123 - - "; - } - - private static function _timestamp() +class WebhookTesting +{ + public static function sampleNotification($kind, $id, $sourceMerchantId = null) { - $originalZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $timestamp = strftime('%Y-%m-%dT%TZ'); - date_default_timezone_set($originalZone); - - return $timestamp; + return Configuration::gateway()->webhookTesting()->sampleNotification($kind, $id, $sourceMerchantId); } } diff --git a/lib/Braintree/WebhookTestingGateway.php b/lib/Braintree/WebhookTestingGateway.php new file mode 100644 index 0000000..cecf61e --- /dev/null +++ b/lib/Braintree/WebhookTestingGateway.php @@ -0,0 +1,723 @@ +config = $gateway->config; + $this->config->assertHasAccessTokenOrKeys(); + } + + public function sampleNotification($kind, $id, $sourceMerchantId = null) + { + $xml = self::_sampleXml($kind, $id, $sourceMerchantId); + $payload = base64_encode($xml) . "\n"; + $publicKey = $this->config->getPublicKey(); + $sha = Digest::hexDigestSha1($this->config->getPrivateKey(), $payload); + $signature = $publicKey . "|" . $sha; + + return [ + 'bt_signature' => $signature, + 'bt_payload' => $payload + ]; + } + + private static function _sampleXml($kind, $id, $sourceMerchantId) + { + switch ($kind) { + case WebhookNotification::SUB_MERCHANT_ACCOUNT_APPROVED: + $subjectXml = self::_merchantAccountApprovedSampleXml($id); + break; + case WebhookNotification::SUB_MERCHANT_ACCOUNT_DECLINED: + $subjectXml = self::_merchantAccountDeclinedSampleXml($id); + break; + case WebhookNotification::TRANSACTION_DISBURSED: + $subjectXml = self::_transactionDisbursedSampleXml($id); + break; + case WebhookNotification::TRANSACTION_SETTLED: + $subjectXml = self::_transactionSettledSampleXml($id); + break; + case WebhookNotification::TRANSACTION_SETTLEMENT_DECLINED: + $subjectXml = self::_transactionSettlementDeclinedSampleXml($id); + break; + case WebhookNotification::DISBURSEMENT_EXCEPTION: + $subjectXml = self::_disbursementExceptionSampleXml($id); + break; + case WebhookNotification::DISBURSEMENT: + $subjectXml = self::_disbursementSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_CONNECTED: + $subjectXml = self::_partnerMerchantConnectedSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_DISCONNECTED: + $subjectXml = self::_partnerMerchantDisconnectedSampleXml($id); + break; + case WebhookNotification::PARTNER_MERCHANT_DECLINED: + $subjectXml = self::_partnerMerchantDeclinedSampleXml($id); + break; + case WebhookNotification::OAUTH_ACCESS_REVOKED: + $subjectXml = self::_oauthAccessRevocationSampleXml($id); + break; + case WebhookNotification::CONNECTED_MERCHANT_STATUS_TRANSITIONED: + $subjectXml = self::_connectedMerchantStatusTransitionedSampleXml($id); + break; + case WebhookNotification::CONNECTED_MERCHANT_PAYPAL_STATUS_CHANGED: + $subjectXml = self::_connectedMerchantPayPalStatusChangedSampleXml($id); + break; + case WebhookNotification::DISPUTE_OPENED: + $subjectXml = self::_disputeOpenedSampleXml($id); + break; + case WebhookNotification::DISPUTE_LOST: + $subjectXml = self::_disputeLostSampleXml($id); + break; + case WebhookNotification::DISPUTE_WON: + $subjectXml = self::_disputeWonSampleXml($id); + break; + case WebhookNotification::DISPUTE_ACCEPTED: + $subjectXml = self::_disputeAcceptedSampleXml($id); + break; + case WebhookNotification::DISPUTE_DISPUTED: + $subjectXml = self::_disputeDisputedSampleXml($id); + break; + case WebhookNotification::DISPUTE_EXPIRED: + $subjectXml = self::_disputeExpiredSampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_CHARGED_SUCCESSFULLY: + $subjectXml = self::_subscriptionChargedSuccessfullySampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_CHARGED_UNSUCCESSFULLY: + $subjectXml = self::_subscriptionChargedUnsuccessfullySampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_EXPIRED: + $subjectXml = self::_subscriptionExpiredSampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_CANCELED: + $subjectXml = self::_subscriptionCanceledSampleXml($id); + break; + case WebhookNotification::SUBSCRIPTION_WENT_PAST_DUE: + $subjectXml = self::_subscriptionWentPastDueSampleXml($id); + break; + case WebhookNotification::CHECK: + $subjectXml = self::_checkSampleXml(); + break; + case WebhookNotification::ACCOUNT_UPDATER_DAILY_REPORT: + $subjectXml = self::_accountUpdaterDailyReportSampleXml($id); + break; + case WebhookNotification::GRANTOR_UPDATED_GRANTED_PAYMENT_METHOD: + $subjectXml = self::_grantedPaymentInstrumentUpdateSampleXml(); + break; + case WebhookNotification::RECIPIENT_UPDATED_GRANTED_PAYMENT_METHOD: + $subjectXml = self::_grantedPaymentInstrumentUpdateSampleXml(); + break; + case WebhookNotification::GRANTED_PAYMENT_METHOD_REVOKED: + $subjectXml = self::_grantedPaymentMethodRevokedXml($id); + break; + case WebhookNotification::PAYMENT_METHOD_REVOKED_BY_CUSTOMER: + $subjectXml = self::_paymentMethodRevokedByCustomerSampleXml($id); + break; + case WebhookNotification::LOCAL_PAYMENT_COMPLETED: + $subjectXml = self::_localPaymentCompletedSampleXml(); + break; + case WebhookNotification::LOCAL_PAYMENT_REVERSED: + $subjectXml = self::_localPaymentReversedSampleXml(); + break; + default: + $subjectXml = self::_subscriptionSampleXml($id); + break; + } + $timestamp = self::_timestamp(); + + $sourceMerchantIdXml = ''; + if (!is_null($sourceMerchantId)) { + $sourceMerchantIdXml = "{$sourceMerchantId}"; + } + + return " + + {$timestamp} + {$kind} + {$sourceMerchantIdXml} + {$subjectXml} + + "; + } + + private static function _merchantAccountApprovedSampleXml($id) + { + return " + + {$id} + + master_ma_for_{$id} + active + + active + + "; + } + + private static function _merchantAccountDeclinedSampleXml($id) + { + return " + + Credit score is too low + + + + + + 82621 + Credit score is too low + base + + + + + + {$id} + suspended + + master_ma_for_{$id} + suspended + + + + "; + } + + private static function _transactionDisbursedSampleXml($id) + { + return " + + ${id} + 100 + + 2013-07-09 + + + "; + } + + private static function _transactionSettledSampleXml($id) + { + return " + + ${id} + settled + sale + USD + 100.00 + ogaotkivejpfayqfeaimuktty + us_bank_account + + 123456789 + 1234 + checking + Dan Schulman + + + "; + } + + private static function _transactionSettlementDeclinedSampleXml($id) + { + return " + + ${id} + settlement_declined + sale + USD + 100.00 + ogaotkivejpfayqfeaimuktty + us_bank_account + + 123456789 + 1234 + checking + Dan Schulman + + + "; + } + + private static function _disbursementExceptionSampleXml($id) + { + return " + + ${id} + + asdfg + qwert + + false + false + + merchant_account_token + USD + false + active + + 100.00 + 2014-02-10 + bank_rejected + update_funding_information + + "; + } + + private static function _disbursementSampleXml($id) + { + return " + + ${id} + + asdfg + qwert + + true + false + + merchant_account_token + USD + false + active + + 100.00 + 2014-02-10 + + + + "; + } + + private static function _disputeOpenedSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + open + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _disputeLostSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + lost + fraud + ${id} + + ${id} + 250.00 + 2020-02-10 + + 2014-03-21 + + "; + } + + private static function _disputeWonSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + won + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + 2014-03-22 + + "; + } + + private static function _disputeAcceptedSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + accepted + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _disputeDisputedSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + disputed + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _disputeExpiredSampleXml($id) + { + return " + + 250.00 + 250.0 + 245.00 + USD + 2014-03-01 + 2014-03-21 + chargeback + expired + fraud + ${id} + + ${id} + 250.00 + + 2014-03-21 + + "; + } + + private static function _subscriptionSampleXml($id) + { + return " + + {$id} + Active + + + + + + + + "; + } + + private static function _subscriptionChargedSuccessfullySampleXml($id) + { + return " + + {$id} + Active + 2016-03-21 + 2017-03-31 + + + {$id} + submitted_for_settlement + 49.99 + + + + + + + + "; + } + + private static function _subscriptionChargedUnsuccessfullySampleXml($id) + { + return " + + {$id} + Active + 2016-03-21 + 2017-03-31 + + + {$id} + failed + 49.99 + + + + + + + + "; + } + + private static function _subscriptionExpiredSampleXml($id) + { + return " + + {$id} + Expired + + + + + + + + "; + } + + private static function _subscriptionCanceledSampleXml($id) + { + return " + + {$id} + Canceled + + + + + + + + "; + } + + private static function _subscriptionWentPastDueSampleXml($id) + { + return " + + {$id} + Past Due + + + + + + + + "; + } + + private static function _checkSampleXml() + { + return " + true + "; + } + + private static function _partnerMerchantConnectedSampleXml($id) + { + return " + + public_id + public_key + private_key + abc123 + cse_key + + "; + } + + private static function _partnerMerchantDisconnectedSampleXml($id) + { + return " + + abc123 + + "; + } + + private static function _partnerMerchantDeclinedSampleXml($id) + { + return " + + abc123 + + "; + } + + private static function _oauthAccessRevocationSampleXml($id) + { + return " + + {$id} + oauth_application_client_id + + "; + } + + private static function _accountUpdaterDailyReportSampleXml($id) + { + return " + + 2016-01-14 + link-to-csv-report + + "; + } + + private static function _connectedMerchantStatusTransitionedSampleXml($id) + { + return " + + {$id} + new_status + oauth_application_client_id + + "; + } + + private static function _connectedMerchantPayPalStatusChangedSampleXml($id) + { + return " + + {$id} + link + oauth_application_client_id + + "; + } + + private static function _grantedPaymentInstrumentUpdateSampleXml() + { + return " + + vczo7jqrpwrsi2px + cf0i8wgarszuy6hc + + ee257d98-de40-47e8-96b3-a6954ea7a9a4 + false + false + + abc123z + + expiration-month + expiration-year + + + "; + } + + private static function _grantedPaymentMethodRevokedXml($id) + { + return " + + 2018-10-11T21:28:37Z + 2018-10-11T21:28:37Z + true + https://assets.braintreegateway.com/payment_method_logo/venmo.png?environment=test + {$id} + Venmo Account: venmojoe + venmojoe + 456 + + venmo_customer_id + cGF5bWVudG1ldGhvZF92ZW5tb2FjY291bnQ + + "; + } + + private static function _paymentMethodRevokedByCustomerSampleXml($id) + { + return " + + a-billing-agreement-id + 2019-01-01T12:00:00Z + a-customer-id + true + name@email.com + cGF5bWVudG1ldGhvZF9jaDZieXNz + https://assets.braintreegateway.com/payment_method_logo/paypal.png?environment=test + + {$id} + 2019-01-02T12:00:00Z + + a-payer-id + + + 2019-01-02T12:00:00Z + + "; + } + + private static function _localPaymentCompletedSampleXml() + { + return " + + a-payment-id + a-payer-id + ee257d98-de40-47e8-96b3-a6954ea7a9a4 + + 1 + authorizing + 10.00 + order1234 + + + "; + } + + private static function _localPaymentReversedSampleXml() + { + return " + + a-payment-id + + "; + } + + private static function _timestamp() + { + $originalZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $timestamp = strftime('%Y-%m-%dT%TZ'); + date_default_timezone_set($originalZone); + + return $timestamp; + } +} diff --git a/lib/Braintree/Xml.php b/lib/Braintree/Xml.php index a6e5119..ee04924 100644 --- a/lib/Braintree/Xml.php +++ b/lib/Braintree/Xml.php @@ -1,24 +1,18 @@ openMemory(); @@ -33,7 +34,7 @@ public static function arrayToXml($aData) $aKeys = array_keys($aData); $rootElementName = $aKeys[0]; // open the root element - $writer->startElement(Braintree_Util::camelCaseToDelimiter($rootElementName)); + $writer->startElement($rootElementName); // create the body self::_createElementsFromArray($writer, $aData[$rootElementName], $rootElementName); @@ -52,7 +53,7 @@ public static function arrayToXml($aData) * @static * @param object $writer XMLWriter object * @param array $aData contains attributes and values - * @return none + * @return void */ private static function _createElementsFromArray(&$writer, $aData) { @@ -62,23 +63,20 @@ private static function _createElementsFromArray(&$writer, $aData) } else { $writer->text($aData); } - return; + return; } - foreach ($aData AS $index => $element) { - // convert the style back to gateway format - $elementName = Braintree_Util::camelCaseToDelimiter($index, '-'); + foreach ($aData as $elementName => $element) { // handle child elements $writer->startElement($elementName); if (is_array($element)) { if (array_key_exists(0, $element) || empty($element)) { $writer->writeAttribute('type', 'array'); - foreach ($element AS $ignored => $itemInArray) { + foreach ($element as $ignored => $itemInArray) { $writer->startElement('item'); self::_createElementsFromArray($writer, $itemInArray); $writer->endElement(); } - } - else { + } else { self::_createElementsFromArray($writer, $element); } } else { @@ -103,39 +101,45 @@ private static function _createElementsFromArray(&$writer, $aData) */ private static function _generateXmlAttribute($value) { - if ($value instanceof DateTime) { - return array('type', 'datetime', self::_dateTimeToXmlTimestamp($value)); + if ($value instanceof DateTime || is_a($value, 'DateTimeImmutable')) { + return ['type', 'datetime', self::_convertDateTimeObjectToXmlTimestamp($value)]; } if (is_int($value)) { - return array('type', 'integer', $value); + return ['type', 'integer', $value]; } if (is_bool($value)) { - return array('type', 'boolean', ($value ? 'true' : 'false')); + return ['type', 'boolean', ($value ? 'true' : 'false')]; } - if ($value === NULL) { - return array('nil', 'true', $value); + if ($value === null) { + return ['nil', 'true', $value]; } } /** * converts datetime back to xml schema format * @access protected * @param object $dateTime - * @return var XML schema formatted timestamp + * @return string XML schema formatted timestamp */ - private static function _dateTimeToXmlTimestamp($dateTime) + private static function _convertDateTimeObjectToXmlTimestamp($dateTime) { - $dateTime->setTimeZone(new DateTimeZone('UTC')); - return ($dateTime->format('Y-m-d\TH:i:s') . 'Z'); + if (is_a($dateTime, 'DateTimeImmutable')) { + $dateTimeForUTC = DateTime::createFromImmutable($dateTime); + } else { + $dateTimeForUTC = clone $dateTime; + } + + $dateTimeForUTC->setTimeZone(new DateTimeZone('UTC')); + return ($dateTimeForUTC->format('Y-m-d\TH:i:s') . 'Z'); } private static function _castDateTime($string) { try { if (empty($string)) { - return false; + return false; } $dateTime = new DateTime($string); - return self::_dateTimeToXmlTimestamp($dateTime); + return self::_convertDateTimeObjectToXmlTimestamp($dateTime); } catch (Exception $e) { // not a datetime return false; diff --git a/lib/Braintree/Xml/Parser.php b/lib/Braintree/Xml/Parser.php index e4fea27..0614c79 100644 --- a/lib/Braintree/Xml/Parser.php +++ b/lib/Braintree/Xml/Parser.php @@ -1,177 +1,138 @@ getName()); - $type = $iterator->attributes()->type; - - self::$_xmlRoot = $iterator->getName(); - self::$_responseType = $type; + $document = new DOMDocument('1.0', 'UTF-8'); + $document->loadXML($xml); - // return the mapped array with the root element as the header - return array($xmlRoot => self::_iteratorToArray($iterator)); + $root = $document->documentElement->nodeName; + return Util::delimiterToCamelCaseArray([ + $root => self::_nodeToValue($document->childNodes->item(0)), + ]); } /** - * processes SimpleXMLIterator objects recursively + * Converts a node to an array of values or nodes * - * @access protected - * @param object $iterator - * @return array xml converted to array + * @param DOMNode @node + * @return mixed */ - private static function _iteratorToArray($iterator) + private static function _nodeToArray($node) { - $xmlArray = array(); - $value = null; - - // rewind the iterator and check if the position is valid - // if not, return the string it contains - $iterator->rewind(); - if (!$iterator->valid()) { - return self::_typecastXmlValue($iterator); + $type = null; + if ($node instanceof DOMElement) { + $type = $node->getAttribute('type'); } - for ($iterator->rewind(); $iterator->valid(); $iterator->next()) { - - $tmpArray = null; - $value = null; - - // get the attribute type string for use in conditions below - $attributeType = $iterator->attributes()->type; - - // extract the parent element via xpath query - $parentElement = $iterator->xpath($iterator->key() . '/..'); - if ($parentElement[0] instanceof SimpleXMLIterator) { - $parentElement = $parentElement[0]; - $parentKey = Braintree_Util::delimiterToCamelCase($parentElement->getName()); - } else { - $parentElement = null; - } - - if ($parentKey == "customFields") { - $key = Braintree_Util::delimiterToUnderscore($iterator->key()); - } else { - $key = Braintree_Util::delimiterToCamelCase($iterator->key()); - } - - // process children recursively - if ($iterator->hasChildren()) { - // return the child elements - $value = self::_iteratorToArray($iterator->current()); - - // if the element is an array type, - // use numeric keys to allow multiple values - if ($attributeType != 'array') { - $tmpArray[$key] = $value; + switch ($type) { + case 'array': + $array = []; + foreach ($node->childNodes as $child) { + $value = self::_nodeToValue($child); + if ($value !== null) { + $array[] = $value; + } + } + return $array; + case 'collection': + $collection = []; + foreach ($node->childNodes as $child) { + $value = self::_nodetoValue($child); + if ($value !== null) { + if (!isset($collection[$child->nodeName])) { + $collection[$child->nodeName] = []; + } + $collection[$child->nodeName][] = self::_nodeToValue($child); + } + } + return $collection; + default: + $values = []; + if ($node->childNodes->length === 1 && $node->childNodes->item(0) instanceof DOMText) { + return $node->childNodes->item(0)->nodeValue; + } else { + foreach ($node->childNodes as $child) { + if (!$child instanceof DOMText) { + $values[$child->nodeName] = self::_nodeToValue($child); + } + } + return $values; } - } else { - // cast values according to attributes - $tmpArray[$key] = self::_typecastXmlValue($iterator->current()); - } - - // set the output string - $output = isset($value) ? $value : $tmpArray[$key]; - - // determine if there are multiple tags of this name at the same level - if (isset($parentElement) && - ($parentElement->attributes()->type == 'collection') && - $iterator->hasChildren()) { - $xmlArray[$key][] = $output; - continue; - } - - // if the element was an array type, output to a numbered key - // otherwise, use the element name - if ($attributeType == 'array') { - $xmlArray[] = $output; - } else { - $xmlArray[$key] = $output; - } } - - return $xmlArray; } /** - * typecast xml value based on attributes - * @param object $valueObj SimpleXMLElement - * @return mixed value for placing into array + * Converts a node to a PHP value + * + * @param DOMNode $node + * @return mixed */ - private static function _typecastXmlValue($valueObj) + private static function _nodeToValue($node) { - // get the element attributes - $attribs = $valueObj->attributes(); - // the element is null, so jump out here - if (isset($attribs->nil) && $attribs->nil) { - return null; + $type = null; + if ($node instanceof DOMElement) { + $type = $node->getAttribute('type'); } - // switch on the type attribute - // switch works even if $attribs->type isn't set - switch ($attribs->type) { + + switch ($type) { case 'datetime': - return self::_timestampToUTC((string) $valueObj); - break; + return self::_timestampToUTC((string) $node->nodeValue); case 'date': - return new DateTime((string)$valueObj); - break; + return new DateTime((string) $node->nodeValue); case 'integer': - return (int) $valueObj; - break; + return (int) $node->nodeValue; case 'boolean': - $value = (string) $valueObj; - // look for a number inside the string - if(is_numeric($value)) { + $value = (string) $node->nodeValue; + if (is_numeric($value)) { return (bool) $value; } else { - // look for the string "true", return false in all other cases - return ($value != "true") ? FALSE : TRUE; + return ($value !== "true") ? false : true; } - break; case 'array': - return array(); + case 'collection': + return self::_nodeToArray($node); default: - return (string) $valueObj; + if ($node->hasChildNodes()) { + return self::_nodeToArray($node); + } elseif (trim($node->nodeValue) === '') { + return null; + } else { + return $node->nodeValue; + } } - } + /** - * convert xml timestamps into DateTime + * Converts XML timestamps into DateTime instances + * * @param string $timestamp - * @return string UTC formatted datetime string + * @return DateTime */ private static function _timestampToUTC($timestamp) { $tz = new DateTimeZone('UTC'); - // strangely DateTime requires an explicit set below - // to show the proper time zone $dateTime = new DateTime($timestamp, $tz); $dateTime->setTimezone($tz); return $dateTime; diff --git a/lib/angelleye-gravity-forms-payment-logger.php b/lib/angelleye-gravity-forms-payment-logger.php new file mode 100644 index 0000000..8cd032a --- /dev/null +++ b/lib/angelleye-gravity-forms-payment-logger.php @@ -0,0 +1,113 @@ +api_url = 'https://gtctgyk7fh.execute-api.us-east-2.amazonaws.com/default/PayPalPaymentsTracker'; + $this->api_key = 'srGiuJFpDO4W7YCDXF56g2c9nT1JhlURVGqYD7oa'; + $this->allow_method = array('Braintree'); + add_action('angelleye_gravity_forms_response_data', array($this, 'own_angelleye_gravity_forms_response_data'), 10, 6); + } + + public function own_angelleye_gravity_forms_response_data($result_data, $request_data, $product_id = 1, $sandbox = false, $is_nvp = true, $payment_method = 'express_checkout') { + $request_param = array(); + if (isset($result_data) && is_array($result_data) && !empty($result_data['CURL_ERROR'])) { + return $result_data; + } else { + $result = $result_data; + $request = $request_data; + if ($payment_method == 'braintree') { + $request['METHOD'] = 'Braintree'; + } + if (isset($request['METHOD']) && !empty($request['METHOD']) && in_array($request['METHOD'], $this->allow_method)) { + $opt_in_log = get_option('angelleye_send_opt_in_logging_details', 'no'); + $request_param['site_url'] = ''; + if ($opt_in_log == 'yes') { + $request_param['site_url'] = get_bloginfo('url'); + } + $request_param['type'] = $request['METHOD']; + $request_param['mode'] = ($sandbox) ? 'sandbox' : 'live'; + $request_param['product_id'] = $product_id; + if ($request['METHOD'] == 'Braintree') { + if ($result->success) { + $request_param['status'] = 'Success'; + } else { + $request_param['status'] = 'Failure'; + } + if ($opt_in_log == 'yes') { + if (isset($result->transaction->statusHistory[0]->user) && !empty($result->transaction->statusHistory[0]->user)) { + $request_param['merchant_id'] = $result->transaction->statusHistory[0]->user; + } + } + $request_param['correlation_id'] = ''; + $request_param['transaction_id'] = isset($result->transaction->id) ? $result->transaction->id : ''; + $request_param['amount'] = isset($result->transaction->amount) ? $result->transaction->amount : '0.00'; + $this->angelleye_tpv_request($request_param); + } + } + } + return $result_data; + } + + public function angelleye_tpv_request($request_param) { + try { + $payment_type = $request_param['type']; + $amount = $request_param['amount']; + $status = $request_param['status']; + $site_url = $request_param['site_url']; + $payment_mode = $request_param['mode']; + $merchant_id = @$request_param['merchant_id']; + $correlation_id = $request_param['correlation_id']; + $transaction_id = $request_param['transaction_id']; + $product_id = $request_param['product_id']; + $params = [ + "product_id" => $product_id, + "type" => $payment_type, + "amount" => $amount, + "status" => $status, + "site_url" => $site_url, + "mode" => $payment_mode, + "merchant_id" => $merchant_id, + "correlation_id" => $correlation_id, + "transaction_id" => $transaction_id + ]; + $params = apply_filters('angelleye_log_params', $params); + $post_args = array( + 'headers' => array( + 'Content-Type' => 'application/json; charset=utf-8', + 'x-api-key' => $this->api_key + ), + 'body' => json_encode($params), + 'method' => 'POST', + 'data_format' => 'body', + ); + $response = wp_remote_post($this->api_url, $post_args); + if (is_wp_error($response)) { + $error_message = $response->get_error_message(); + error_log(print_r($error_message, true)); + return false; + } else { + $body = json_decode(wp_remote_retrieve_body($response), true); + if ($body['status']) { + return true; + } + } + return false; + } catch (Exception $ex) { + + } + } + +} diff --git a/lib/autoload.php b/lib/autoload.php new file mode 100644 index 0000000..1b5edc0 --- /dev/null +++ b/lib/autoload.php @@ -0,0 +1,21 @@ +", $item['id'] ); + + return $img; + + } + +} + +?> diff --git a/lib/class.plugify-gform-braintree.php b/lib/class.plugify-gform-braintree.php index 5128597..5e8b0c1 100644 --- a/lib/class.plugify-gform-braintree.php +++ b/lib/class.plugify-gform-braintree.php @@ -4,319 +4,1092 @@ final class Plugify_GForm_Braintree extends GFPaymentAddOn { - protected $_version = '1.0'; - - protected $_min_gravityforms_version = '1.8.7.16'; - protected $_slug = 'gravity-forms-braintree'; - protected $_path = 'gravity-forms-braintree/lib/class.plugify-gform-braintree.php'; - protected $_full_path = __FILE__; - protected $_title = 'Braintree'; - protected $_short_title = 'Braintree'; - protected $_requires_credit_card = true; - protected $_supports_callbacks = false; - protected $_enable_rg_autoupgrade = true; - - /** - * Class constructor. Send __construct call to parent - * @since 1.0 - * @return void - */ - public function __construct () { - - // Build parent - parent::__construct(); - - } - - /** - * Override init_frontend to assign front end based filters and actions required for operation - * - * @since 1.0 - * @return void - */ - public function init_frontend () { - - // init_frontend on GFPaymentAddOn - parent::init_frontend(); - - } - - /** - * After form has been submitted, send CC details to Braintree and ensure the card is going to work - * If not, void the validation result (processed elsewhere) and have the submit the form again - * - * @param $feed - Current configured payment feed - * @param $submission_data - Contains form field data submitted by the user as well as payment information (i.e. payment amount, setup fee, line items, etc...) - * @param $form - Current form array containing all form settings - * @param $entry - Current entry array containing entry information (i.e data submitted by users). NOTE: the entry hasn't been saved to the database at this point, so this $entry object does not have the "ID" property and is only a memory representation of the entry. - * @return array - Return an $authorization array in the following format: - * [ - * "is_authorized" => true|false, - * "error_message" => "Error message", - * "transaction_id" => "XXX", - * - * //If the payment is captured in this method, return a "captured_payment" array with the following information about the payment - * "captured_payment" => ["is_success"=>true|false, "error_message" => "error message", "transaction_id" => "xxx", "amount" => 20] - * ] - * @since 1.0 - * @return void - */ - public function authorize( $feed, $submission_data, $form, $entry ) { - - // Prepare authorization response payload - $authorization = array( - 'is_authorized' => false, - 'error_message' => apply_filters( 'gform_braintree_credit_card_failure_message', __( 'Your card could not be billed. Please ensure the details you entered are correct and try again.', 'gravity-forms-braintree' ) ), - 'transaction_id' => '', - 'captured_payment' => array( - 'is_success' => false, - 'error_message' => '', - 'transaction_id' => '', - 'amount' => $submission_data['payment_amount'] - ) - ); - - - // Perform capture in this function. For this version, we won't authorize and then capture later - // at least, not in this version - if( $settings = $this->get_plugin_settings() ) { - - // Sanitize card number, removing dashes and spaces - $card_number = str_replace( array( '-', ' ' ), '', $submission_data['card_number'] ); - - // Prepare Braintree payload - $args = array( - 'amount' => $submission_data['payment_amount'], - 'creditCard' => array( - 'number' => $card_number, - 'expirationDate' => sprintf( '%s/%s', $submission_data['card_expiration_date'][0], $submission_data['card_expiration_date'][1]), - 'cardholderName' => $submission_data['card_name'], - 'cvv' => $submission_data['card_security_code'] - ), - 'customer' => array( - 'firstName' => $submission_data['card_name'] - ), - 'billing' => array( - 'firstName' => $submission_data['card_name'], - 'streetAddress' => $submission_data['address'], - 'locality' => $submission_data['city'], - 'postalCode' => $submission_data['zip'] - ) - ); - - try { - - // Configure Braintree environment - Braintree_Configuration::environment( strtolower( $settings['environment'] ) ); - Braintree_Configuration::merchantId( $settings['merchant-id']); - Braintree_Configuration::publicKey( $settings['public-key'] ); - Braintree_Configuration::privateKey( $settings['private-key'] ); - - // Set to auto settlemt if applicable - if( $settings['settlement'] == 'Yes' ) { - $args['options']['submitForSettlement'] = 'true'; - } - - // Send transaction to Braintree - $result = Braintree_Transaction::sale( $args ); - - // Update response to reflect successful payment - if( $result->success == '1' ) { - - $authorization['is_authorized'] = true; - $authorization['error_message'] = ''; - $authorization['transaction_id'] = $result->transaction->_attributes['id']; - - $authorization['captured_payment'] = array( - 'is_success' => true, - 'transaction_id' => $result->transaction->_attributes['id'], - 'amount' => $result->transaction->_attributes['amount'], - 'error_message' => '', - 'payment_method' => 'Credit Card' - ); - - } - else { - - // Append gateway response text to error message if it exists. If it doesn't exist, a more hardcore - // failure has occured and it won't do the user any good to see it other than a general error message - if( isset( $result->_attributes['transaction']->_attributes['processorResponseText'] ) ) { - $authorization['error_message'] .= sprintf( '. Your bank said: %s.', $result->_attributes['transaction']->_attributes['processorResponseText'] ); - } - - } - - } - catch( Exception $e ) { - // Do nothing with exception object, just fallback to generic failure - } - - return $authorization; - - } - - return false; - - } - - /** - * Create and display feed settings fields. - * - * @since 1.0 - * @return void - */ - public function feed_settings_fields () { - - // Get defaults from GFPaymentAddOn - $settings = parent::feed_settings_fields(); - - // Remove billing information - //$settings = $this->remove_field( 'billingInformation', $settings ); - - // Remove options - $settings = $this->remove_field( 'options', $settings ); - - // Remove the subscription option from transaction type dropdown - $transaction_type = $this->get_field( 'transactionType', $settings ); - - foreach( $transaction_type['choices'] as $index => $choice ) { - if( $choice['value'] == 'subscription' ) { - unset( $transaction_type['choices'][$index] ); - } - } - - $settings = $this->replace_field( 'transactionType', $transaction_type, $settings ); - - // Return sanitized settings - return $settings; - - } - - /** - * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed - * - * @since 1.0 - * @return void - */ - public function plugin_settings_fields () { - - return array( - - array( - 'title' => 'Account Settings', - 'fields' => array( - array( - 'name' => 'merchant-id', - 'tooltip' => 'Your Braintree Merchant ID', - 'label' => 'Merchant ID', - 'type' => 'text', - 'class' => 'medium' - ), - array( - 'name' => 'public-key', - 'tooltip' => 'Your Braintree Account Public Key', - 'label' => 'Public Key', - 'type' => 'text', - 'class' => 'medium' - ), - array( - 'name' => 'private-key', - 'tooltip' => 'Your Braintree Account Private Key', - 'label' => 'Private Key', - 'type' => 'text', - 'class' => 'medium' - ) - ) - ), - array( - 'title' => 'Payment Settings', - 'fields' => array( - array( - 'name' => 'settlement', - 'tooltip' => 'Choosing \'Yes\' will tell Braintree to automatically submit your transactions for settlement upon receipt', - 'label' => 'Automatic Settlement Submission', - 'type' => 'radio', - 'choices' => array( - array( - 'label' => 'Yes', - 'name' => 'yes' - ), - array( - 'label' => 'No', - 'name' => 'no' - ) - ) - ) - ) - ), - array( - 'title' => 'Environment Settings', - 'fields' => array( - array( - 'name' => 'environment', - 'tooltip' => 'Do you want to process test payments or real payments?', - 'label' => 'API Endpoint', - 'type' => 'radio', - 'choices' => array( - array( - 'label' => 'Sandbox', - 'name' => 'sandbox' - ), - array( - 'label' => 'Production', - 'name' => 'production' - ) - ) - ) - ) - ) - - ); - - } - - /** - * Helper function to determine if all Braintree settings have been set. - * Does not check if they are correct, only that they have been set, IE not null - * @param @settings Plugin settings to check if valid - * @since 1.0 - * @return void - */ - public function settings_are_valid ( $settings ) { - - if( empty( $settings ) ) { - return false; - } - - foreach( $settings as $setting ) { - if( '' == $setting ) { - return false; - } - } - - return true; - - } - - /** - * Get plugin settings - * - * @since 1.0 - * @return void - */ - public function get_plugin_settings () { - - $settings = parent::get_plugin_settings(); - - if( $this->settings_are_valid( $settings ) ) { - return $settings; - } - else { - return false; - } - - } + protected $_version = '4.0.6'; + protected $_min_gravityforms_version = '1.8.7.16'; + protected $_slug = 'gravity-forms-braintree'; + protected $_path = 'gravity-forms-braintree/lib/class.plugify-gform-braintree.php'; + protected $_full_path = __FILE__; + protected $_title = 'Braintree'; + protected $_short_title = 'Braintree'; + protected $_requires_credit_card = true; + protected $_supports_callbacks = true; + protected $_enable_rg_autoupgrade = true; + protected $is_payment_gateway = true; + protected $current_feed = true; + protected $selected_payment_method = 'creditcard'; -} + /** + * Class constructor. Send __construct call to parent + * @since 1.0 + * @return void + */ + public function __construct() { + + add_action('wp_ajax_angelleye_gform_braintree_adismiss_notice', array($this, 'angelleye_gform_braintree_adismiss_notice'), 10); + add_action('admin_notices', array($this, 'angelleye_gform_braintree_display_push_notification'), 10); + + add_action('admin_enqueue_scripts', array($this, 'enqueue_styles_css'), 10); + add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts_js'), 10); + add_filter('gform_noconflict_scripts', [$this, 'include_angelleye_braintree_script_noconflict']); + add_filter('gform_noconflict_styles', [$this, 'include_angelleye_braintree_style_noconflict']); + // Build parent + parent::__construct(); + } + + /** + * Override credit card field check, so that we can return true when someone has ach form + * If any user will have any form then same payment gateway class will be used + * @param array $form + * + * @return bool + */ + public function has_credit_card_field($form) { + if (isset($form['fields'])) { + foreach ($form['fields'] as $single_field) { + if ($single_field->type == 'creditcard' || $single_field->type == 'braintree_ach' || $single_field->type == 'braintree_credit_card') { + return true; + } + } + } + return $this->get_credit_card_field($form) !== false; + } + + /** + * Override default message for Gravity Form Braintree Feeds + * @return string + */ + public function requires_credit_card_message() { + $url = add_query_arg(array('view' => null, 'subview' => null)); + + return sprintf(esc_html__("You must add a Credit Card/ACH Payment field to your form before creating a feed. Let's go %sadd one%s!", 'gravityforms'), "", ''); + } + + /** + * Override init_frontend to assign front end based filters and actions required for operation + * + * @since 1.0 + * @return void + */ + public function init_frontend() { + + // init_frontend on GFPaymentAddOn + parent::init_frontend(); + } + + /** + * Init the Braintree configuration and return gateway for transactions, etc. + * @return bool|\Braintree\Gateway + * @throws \Braintree\Exception\Configuration + */ + public function getBraintreeGateway() { + $settings = $this->get_plugin_settings(); + if (!$settings) + return false; + + // Configure Braintree environment + $braintree_config = new \Braintree\Configuration([ + 'environment' => strtolower($settings['environment']), + 'merchantId' => $settings['merchant-id'], + 'publicKey' => $settings['public-key'], + 'privateKey' => $settings['private-key'] + ]); + + $braintree_config->timeout(60); + + $gateway = new Braintree\Gateway($braintree_config); + return $gateway; + } + + /** + * ACH Payment authorization + * @param $feed + * @param $submission_data + * @param $form + * @param $entry + * + * @return array|bool + * @throws \Braintree\Exception\Configuration + */ + public function ach_authorize($feed, $submission_data, $form, $entry) { + $this->log_debug("Braintree_ACH_Authorize::START"); + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + try { + // Prepare authorization response payload + $authorization = array( + 'is_authorized' => false, + 'error_message' => apply_filters('gform_braintree_credit_card_failure_message', __('We are unable to authorize the bank account, Please try again.', 'gravity-forms-braintree')), + 'transaction_id' => '', + 'captured_payment' => array( + 'is_success' => false, + 'error_message' => '', + 'transaction_id' => '', + 'amount' => $submission_data['payment_amount'] + ) + ); + + $ach_device_corelation = rgpost('ach_device_corelation'); + $ach_token = rgpost('ach_token'); + $payment_amount = number_format($submission_data['payment_amount'], 2, '.', ''); + + $settings = $this->get_plugin_settings(); + $response = getAngelleyeBraintreePaymentFields($form); + $braintree_ach_field = $response['braintree_ach']; + /* $account_number = rgpost( 'input_' . $braintree_ach_field->id . '_1' ); + $account_type = rgpost( 'input_' . $braintree_ach_field->id . '_2' ); + $routing_number = rgpost( 'input_' . $braintree_ach_field->id . '_3' ); */ + $account_holder_name = rgpost('input_' . $braintree_ach_field->id . '_4'); + + $account_holder_name = explode(' ', $account_holder_name); + /** + * Create customer in Braintree + */ + $customer_request = [ + 'firstName' => @$account_holder_name[0], + 'lastName' => end($account_holder_name), + ]; + $this->log_debug("Braintree_ACH_Customer::create REQUEST => " . print_r($customer_request, 1)); + $customer_result = $gateway->customer()->create($customer_request); + $this->log_debug("Braintree_ACH_Customer::create RESPONSE => " . print_r($customer_result, 1)); + + if ($customer_result->success) { + $payment_method_request = [ + 'customerId' => $customer_result->customer->id, + 'paymentMethodNonce' => $ach_token, + 'options' => [ + 'usBankAccountVerificationMethod' => Braintree\Result\UsBankAccountVerification::NETWORK_CHECK + ] + ]; + + $this->log_debug("Braintree_ACH_PaymentRequest::create REQUEST => " . print_r($payment_method_request, 1)); + $payment_method_response = $gateway->paymentMethod()->create($payment_method_request); + $this->log_debug("Braintree_ACH_PaymentRequest::create RESPONSE => " . print_r($payment_method_response, 1)); + + if (isset($payment_method_response->paymentMethod->token)) { + + $sale_request = [ + 'amount' => $payment_amount, + 'paymentMethodToken' => $payment_method_response->paymentMethod->token, + 'deviceData' => $ach_device_corelation, + 'options' => [ + 'submitForSettlement' => true + ] + ]; + + $sale_request = apply_filters('angelleye_braintree_parameter', $sale_request, $submission_data, $form, $entry); + + $this->log_debug("Braintree_ACH_Transaction::sale REQUEST => " . print_r($sale_request, 1)); + $sale_response = $gateway->transaction()->sale($sale_request); + $this->log_debug("Braintree_ACH_Transaction::sale RESPONSE => " . print_r($sale_response, 1)); + + if ($sale_response->success) { + do_action('angelleye_gravity_forms_response_data', $sale_response, $submission_data, '16', ( strtolower($settings['environment']) == 'sandbox' ) ? true : false, false, 'braintree_ach'); + $authorization['is_authorized'] = true; + $authorization['error_message'] = ''; + $authorization['transaction_id'] = $sale_response->transaction->id; + + $authorization['captured_payment'] = array( + 'is_success' => true, + 'transaction_id' => $sale_response->transaction->id, + 'amount' => $sale_response->transaction->amount, + 'error_message' => '', + 'payment_method' => 'Braintree ACH' + ); + + $this->log_debug("Braintree_ACH::SUCCESS"); + } else { + if (isset($sale_response->transaction->processorResponseText)) { + $authorization['error_message'] = sprintf('Your bank did not authorized the transaction: %s.', $sale_response->transaction->processorResponseText); + } else { + $authorization['error_message'] = sprintf('Your bank declined the transaction, please try again or contact bank.'); + } + $this->log_debug("Braintree_ACH::FAILED_ERROR"); + } + } else { + $authorization['error_message'] = __('We are unable to authorize bank account, This may have happened due to expired token, please try again.', 'gravity-forms-braintree'); + } + } else { + $authorization['error_message'] = __('Unable to proceed with the transaction due to invalid name.', 'gravity-forms-braintree'); + } + } catch (Exception $exception) { + $this->log_debug("Braintree_ACH::EXCEPTION: " . $exception->getTraceAsString()); + $exception['error_message'] = __('An internal error occurred, Please try later. ERROR: ' . $exception->getMessage()); + } + return $authorization; + } + + $this->log_debug("Braintree_ACH::FAILED"); + return false; + } + + /** + * Gets the payment validation result. + * + * @since Unknown + * @access public + * + * @used-by GFPaymentAddOn::validation() + * + * @param array $validation_result Contains the form validation results. + * @param array $authorization_result Contains the form authorization results. + * + * @return array The validation result for the credit card field. + */ + public function get_validation_result($validation_result, $authorization_result) { + + $credit_card_page = 0; + if ($this->selected_payment_method == 'braintree_ach') { + foreach ($validation_result['form']['fields'] as &$field) { + if ($field->type == 'braintree_ach') { + $field->failed_validation = true; + $field->validation_message = $authorization_result['error_message']; + $credit_card_page = $field->pageNumber; + break; + } + } + } else { + foreach ($validation_result['form']['fields'] as &$field) { + if ($field->type == 'creditcard') { + $field->failed_validation = true; + $field->validation_message = $authorization_result['error_message']; + $credit_card_page = $field->pageNumber; + break; + } + } + } + $validation_result['credit_card_page'] = $credit_card_page; + $validation_result['is_valid'] = false; + + return $validation_result; + } + + /** + * Braintree credit card Payment authorization + * @param $feed + * @param $submission_data + * @param $form + * @param $entry + * + * @return array|bool + * @throws \Braintree\Exception\Configuration + */ + public function braintree_cc_authorize($feed, $submission_data, $form, $entry) { + try { + $settings = $this->get_plugin_settings(); + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + $authorization = array( + 'is_authorized' => false, + 'error_message' => apply_filters('gform_braintree_credit_card_failure_message', __('Your card could not be billed. Please ensure the details you entered are correct and try again.', 'gravity-forms-braintree')), + 'transaction_id' => '', + 'captured_payment' => array( + 'is_success' => false, + 'error_message' => '', + 'transaction_id' => '', + 'amount' => $submission_data['payment_amount'] + ) + ); + if (empty($_POST['payment_method_nonce'])) { + return $authorization; + } + $args = array( + 'amount' => $submission_data['payment_amount'], + 'paymentMethodNonce' => $_POST['payment_method_nonce'] + ); + $args = apply_filters('angelleye_braintree_parameter', $args, $submission_data, $form, $entry); + if ($settings['settlement'] == 'Yes') { + $args['options']['submitForSettlement'] = 'true'; + } + $result = $gateway->transaction()->sale($args); + if ($result->success) { + do_action('angelleye_gravity_forms_response_data', $result, $submission_data, '16', (strtolower($settings['environment']) == 'sandbox') ? true : false, false, 'braintree'); + $authorization['is_authorized'] = true; + $authorization['error_message'] = ''; + $authorization['transaction_id'] = $result->transaction->id; + $authorization['captured_payment'] = array( + 'is_success' => true, + 'transaction_id' => $result->transaction->id, + 'amount' => $result->transaction->amount, + 'error_message' => '', + 'payment_method' => 'Credit Card' + ); + } else { + if (isset($result->transaction->processorResponseText)) { + $authorization['error_message'] .= sprintf('. Your bank said: %s.', $result->transaction->processorResponseText); + } + } + } + } catch (Exception $e) { + // Do nothing with exception object, just fallback to generic failure + } + return $authorization; + } + + /** + * After form has been submitted, send CC details to Braintree and ensure the card is going to work + * If not, void the validation result (processed elsewhere) and have the submit the form again + * + * @param $feed - Current configured payment feed + * @param $submission_data - Contains form field data submitted by the user as well as payment information (i.e. payment amount, setup fee, line items, etc...) + * @param $form - Current form array containing all form settings + * @param $entry - Current entry array containing entry information (i.e data submitted by users). NOTE: the entry hasn't been saved to the database at this point, so this $entry object does not have the "ID" property and is only a memory representation of the entry. + * @return array - Return an $authorization array in the following format: + * [ + * "is_authorized" => true|false, + * "error_message" => "Error message", + * "transaction_id" => "XXX", + * + * //If the payment is captured in this method, return a "captured_payment" array with the following information about the payment + * "captured_payment" => ["is_success"=>true|false, "error_message" => "error message", "transaction_id" => "xxx", "amount" => 20] + * ] + * @since 1.0 + * @return void + */ + public function authorize($feed, $submission_data, $form, $entry) { + $this->selected_payment_method = getAngelleyeBraintreePaymentMethod($form); + if ($this->selected_payment_method == 'braintree_ach') { + return $this->ach_authorize($feed, $submission_data, $form, $entry); + } + if ($this->selected_payment_method == 'braintree_credit_card') { + return $this->braintree_cc_authorize($feed, $submission_data, $form, $entry); + } + // Prepare authorization response payload + $authorization = array( + 'is_authorized' => false, + 'error_message' => apply_filters('gform_braintree_credit_card_failure_message', __('Your card could not be billed. Please ensure the details you entered are correct and try again.', 'gravity-forms-braintree')), + 'transaction_id' => '', + 'captured_payment' => array( + 'is_success' => false, + 'error_message' => '', + 'transaction_id' => '', + 'amount' => $submission_data['payment_amount'] + ) + ); + // Perform capture in this function. For this version, we won't authorize and then capture later + // at least, not in this version + if ($settings = $this->get_plugin_settings()) { + // Sanitize card number, removing dashes and spaces + $card_number = str_replace(array('-', ' '), '', $submission_data['card_number']); + // Prepare Braintree payload + $args = array( + 'amount' => $submission_data['payment_amount'], + 'creditCard' => array( + 'number' => $card_number, + 'expirationDate' => sprintf('%s/%s', $submission_data['card_expiration_date'][0], $submission_data['card_expiration_date'][1]), + 'cardholderName' => $submission_data['card_name'], + 'cvv' => $submission_data['card_security_code'] + ) + ); + $args = apply_filters('angelleye_braintree_parameter', $args, $submission_data, $form, $entry); + try { + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + // Set to auto settlemt if applicable + if ($settings['settlement'] == 'Yes') { + $args['options']['submitForSettlement'] = 'true'; + } + // Send transaction to Braintree + $result = $gateway->transaction()->sale($args); + $this->log_debug("Braintree_Transaction::sale RESPONSE => " . print_r($result, 1)); + // Update response to reflect successful payment + if ($result->success) { + do_action('angelleye_gravity_forms_response_data', $result, $submission_data, '16', (strtolower($settings['environment']) == 'sandbox') ? true : false, false, 'braintree'); + $authorization['is_authorized'] = true; + $authorization['error_message'] = ''; + $authorization['transaction_id'] = $result->transaction->id; + $authorization['captured_payment'] = array( + 'is_success' => true, + 'transaction_id' => $result->transaction->id, + 'amount' => $result->transaction->amount, + 'error_message' => '', + 'payment_method' => 'Credit Card' + ); + } else { + // Append gateway response text to error message if it exists. If it doesn't exist, a more hardcore + // failure has occured and it won't do the user any good to see it other than a general error message + if (isset($result->transaction->processorResponseText)) { + $authorization['error_message'] .= sprintf('. Your bank said: %s.', $result->transaction->processorResponseText); + } + } + } + } catch (Exception $e) { + // Do nothing with exception object, just fallback to generic failure + } + return $authorization; + } + + return false; + } + + public function process_capture($authorization, $feed, $submission_data, $form, $entry) { + + do_action('gform_braintree_post_capture', rgar($authorization, 'is_authorized'), rgars($authorization, 'captured_payment/amount'), $entry, $form, $this->_args_for_deprecated_hooks['config'], $this->_args_for_deprecated_hooks['aim_response']); + + return parent::process_capture($authorization, $feed, $submission_data, $form, $entry); + } + + /** + * Braintree Override this method to add integration code to the payment processor in order to create a subscription. + * + * This method is executed during the form validation process and allows the form submission process to fail with a + * validation error if there is anything wrong when creating the subscription. + * + * @param array $feed Current configured payment feed. + * @param array $submission_data Contains form field data submitted by the user as well as payment information + * (i.e. payment amount, setup fee, line items, etc...). + * @param array $form Current form array containing all form settings. + * @param array $entry Current entry array containing entry information (i.e data submitted by users). + * NOTE: the entry hasn't been saved to the database at this point, so this $entry + * object does not have the 'ID' property and is only a memory representation of the entry. + * + * @return array { + * Return an $subscription array + * @type bool $is_success If the subscription is successful. + * @type string $error_message The error message, if applicable. + * @type string $subscription_id The subscription ID. + * @type int $amount The subscription amount. + * @type array $captured_payment { + * If payment is captured, an additional array is created. + * @type bool $is_success If the payment capture is successful. + * @type string $error_message The error message, if any. + * @type string $transaction_id The transaction ID of the captured payment. + * @type int $amount The amount of the captured payment, if successful. + * } + * + * To implement an initial/setup fee for gateways that don't support setup fees as part of subscriptions, manually + * capture the funds for the setup fee as a separate transaction and send that payment information in the + * following 'captured_payment' array: + * + * 'captured_payment' => [ + * 'name' => 'Setup Fee', + * 'is_success' => true|false, + * 'error_message' => 'error message', + * 'transaction_id' => 'xxx', + * 'amount' => XX + * ] + * } + * @throws \Braintree\Exception\Configuration + */ + public function subscribe($feed, $submission_data, $form, $entry) { + $authorization = array( + 'is_authorized' => false, + 'is_success' => false, + 'error_message' => apply_filters('gform_braintree_credit_card_failure_message', __('Your card could not be billed. Please ensure the details you entered are correct and try again.', 'angelleye-gravity-forms-braintree')), + ); + if (empty($_POST['payment_method_nonce'])) { + return $authorization; + } + $settings = $this->get_plugin_settings(); + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + $args = array( + 'amount' => $submission_data['payment_amount'], + 'creditCard' => array( + 'number' => !empty($submission_data['card_number']) ? str_replace(array('-', ' '), '', $submission_data['card_number']) : '', + 'expirationDate' => sprintf('%s/%s', $submission_data['card_expiration_date'][0], $submission_data['card_expiration_date'][1]), + 'cardholderName' => $submission_data['card_name'], + 'cvv' => $submission_data['card_security_code'] + ) + ); + $args = apply_filters('angelleye_braintree_parameter', $args, $submission_data, $form, $entry); + $customerArgs = !empty($args['customer']) ? $args['customer'] : array(); + $customer_id = $this->get_customer_id($customerArgs); + $paymentMethod = $gateway->paymentMethod()->create([ + 'customerId' => $customer_id, + 'paymentMethodNonce' => $_POST['payment_method_nonce'] + ]); + $fee_amount = !empty($submission_data['setup_fee']) ? $submission_data['setup_fee'] : 0; + $setup_fee_result = true; + if (!empty($fee_amount) && $fee_amount > 0) { + $feeArgs = array( + 'amount' => $fee_amount, + 'paymentMethodToken' => $paymentMethod->paymentMethod->token, + ); + if ($settings['settlement'] == 'Yes') { + $feeArgs['options']['submitForSettlement'] = 'true'; + } + $feeArgs = apply_filters('angelleye_braintree_parameter', $feeArgs, $submission_data, $form, $entry); + $feeResult = $gateway->transaction()->sale($feeArgs); + if ($feeResult->success) { + $authorization['captured_payment'] = array( + 'is_success' => true, + 'transaction_id' => $feeResult->transaction->id, + 'amount' => $feeResult->transaction->amount, + 'error_message' => '', + 'payment_method' => 'Credit Card' + ); + } else { + $setup_fee_result = false; + if (isset($result->transaction->processorResponseText)) { + $authorization['error_message'] .= sprintf('. Your bank said: %s.', $result->transaction->processorResponseText); + } + } + } + if ($setup_fee_result) { + try { + $subscriptionArgs = array( + 'paymentMethodToken' => $paymentMethod->paymentMethod->token, + 'planId' => !empty($feed['meta']['subscriptionPlan']) ? $feed['meta']['subscriptionPlan'] : '', + 'price' => $submission_data['payment_amount'], + ); + if ($feed['meta']['recurringTimes'] == 0) { + $subscriptionArgs['neverExpires'] = true; + } else { + $subscriptionArgs['numberOfBillingCycles'] = $feed['meta']['recurringTimes']; + } + if (!empty($feed['meta']['trial_enabled'])) { + $subscriptionArgs['trialDuration'] = ''; + $subscriptionArgs['trialDurationUnit'] = ''; + $subscriptionArgs['trialPeriod'] = true; + } else { + $subscriptionArgs['firstBillingDate'] = ''; + } + $subscriptionArgs = apply_filters('angelleye_gravity_braintree_subscription_args', $subscriptionArgs); + $subscription = $gateway->subscription()->create($subscriptionArgs); + if ($subscription->success) { + $authorization['is_authorized'] = true; + $authorization['is_success'] = true; + $authorization['error_message'] = ''; + $authorization['paymentMethodToken'] = $subscription->subscription->paymentMethodToken; + $authorization['subscription_id'] = $subscription->subscription->id; + $authorization['amount'] = $subscription->subscription->price; + $authorization['subscription_trial_amount'] = $subscription->subscription->price; + $authorization['subscription_start_date'] = $subscription->subscription->firstBillingDate->date; + } + } catch (Exception $e) { + + } + } + } + return $authorization; + } + + /** + * Braintree override this method to add integration code to the payment processor in order to cancel a subscription. + * + * This method is executed when a subscription is canceled from the braintree Payment Gateway. + * + * @param array $entry Current entry array containing entry information (i.e data submitted by users). + * @param array $feed Current configured payment feed. + * + * @return bool Returns true if the subscription was cancelled successfully and false otherwise. + * + * @throws \Braintree\Exception\Configuration + */ + public function cancel($entry, $feed) { + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + $result = $gateway->subscription()->cancel($entry['transaction_id']); + if ($result->success) { + return true; + } + } + return false; + } + + public function is_payment_gateway($entry_id) { + + if ($this->is_payment_gateway) { + return true; + } + + $gateway = gform_get_meta($entry_id, 'payment_gateway'); + + return in_array($gateway, array('Braintree', $this->_slug)); + } + + /** + * Create and display feed settings fields. + * + * @since 1.0 + * @return void + */ + public function feed_settings_fields() { + + // Get defaults from GFPaymentAddOn + $settings = parent::feed_settings_fields(); -?> + // Remove billing information + $settings = $this->remove_field('billingInformation', $settings); + + // Remove options + $settings = $this->remove_field('options', $settings); + + // Remove the subscription option from transaction type dropdown + $transaction_type = $this->get_field('transactionType', $settings); + + //foreach( $transaction_type['choices'] as $index => $choice ) { + //if( $choice['value'] == 'subscription' ) { + //unset( $transaction_type['choices'][$index] ); + //} + //} + + $transactionType = ''; + foreach ($settings as $index => $setting) { + if (!empty($setting['dependency']['field']) && $setting['dependency']['field'] == 'transactionType') { + $transactionType = !empty($setting['dependency']['values'][0]) ? $setting['dependency']['values'][0] : ''; + } + } + + if ((!empty($_POST['_gaddon_setting_transactionType']) && $_POST['_gaddon_setting_transactionType'] == 'subscription') || ( empty($_POST['_gaddon_setting_transactionType']) && !empty($transactionType) && $transactionType == 'subscription')) { + $form_page_link = add_query_arg([ + 'id' => !empty($_REQUEST['id']) ? $_REQUEST['id'] : '', + ], menu_page_url('gf_edit_forms', false)); + $transaction_type['description'] = sprintf(__('When building your subscription form, make sure to use the %sBraintree CC%s field instead of the basic Credit Card field.', 'angelleye-gravity-forms-braintree'), '', ''); + } + + $settings = $this->replace_field('transactionType', $transaction_type, $settings); + + $settings = parent::remove_field('trial', $settings); + + $createBraintreePlanUrl = $this->merchant_url('plans/new'); + $api_settings_field = array( + array( + 'name' => 'braintree_trial', + 'label' => esc_html__('Trial', 'angelleye-gravity-forms-braintree'), + 'type' => 'braintree_trial', + 'hidden' => '', + 'tooltip' => '' + ), + array( + 'name' => 'subscriptionPlan', + 'label' => esc_html__('Plan', 'angelleye-gravity-forms-braintree'), + 'type' => 'select', + 'choices' => $this->get_plans(), + 'required' => true, + 'tooltip' => sprintf(__('Plugin will fetch and display the subscription plans. Create the %splan%s in your Braintree account.', 'angelleye-gravity-forms-braintree'), '', ''), + ), + ); + + $settings = $this->add_field_after('setupFee', $api_settings_field, $settings); + + // Return sanitized settings + return $settings; + } + + public function merchant_url($tab = 'plans') { + + $settings = $this->get_plugin_settings(); + + $braintreeUrl = '#'; + if (!empty($settings['merchant-id'])) { + $environment = !empty($settings['environment']) ? strtolower($settings['environment']) : 'sandbox'; + $environmentUrl = ( $environment == 'sandbox' ) ? 'sandbox.' : ''; + + $braintree_config = new \Braintree\Configuration([ + 'environment' => strtolower($settings['environment']), + 'merchantId' => $settings['merchant-id'], + 'publicKey' => $settings['public-key'], + 'privateKey' => $settings['private-key'] + ]); + + $merchantPath = $braintree_config->merchantPath(); + $braintreeUrl = "https://{$environmentUrl}braintreegateway.com{$merchantPath}/{$tab}"; + } + + return $braintreeUrl; + } + + /** + * This function is callback for braintree_trial setting field. + * + * @param array $field Settings fields + * @param bool $echo Display or return + * + * @return string $html + * + * @throws \Braintree\Exception\Configuration + */ + public function settings_braintree_trial($field, $echo = true) { + + $braintreePlans = $this->merchant_url(); + $html = sprintf(__('Select your product trial form %sBraintree Plans%s', 'angelleye-gravity-forms-braintree'), '', ''); + + if ($echo) { + echo $html; + } + + return $html; + } + + /** + * Create and display plugin settings fields. These are settings for Braintree in particular, not a feed + * + * @since 1.0 + * @return void + */ + public function plugin_settings_fields() { + + return array( + array( + 'title' => 'Account Settings', + 'fields' => array( + array( + 'name' => 'merchant-id', + 'tooltip' => 'Your Braintree Merchant ID', + 'label' => 'Merchant ID', + 'type' => 'text', + 'class' => 'medium', + ), + array( + 'name' => 'public-key', + 'tooltip' => 'Your Braintree Account Public Key', + 'label' => 'Public Key', + 'type' => 'text', + 'class' => 'medium' + ), + array( + 'name' => 'private-key', + 'tooltip' => 'Your Braintree Account Private Key', + 'label' => 'Private Key', + 'type' => 'text', + 'class' => 'medium' + ) + ) + ), + array( + 'title' => 'Braintree ACH Settings', + 'fields' => array( + array( + 'name' => 'tokenization-key', + 'tooltip' => 'Your Braintree Tokenization Key', + 'label' => 'Tokenization Key', + 'type' => 'text', + 'class' => 'medium' + ), + array( + 'name' => 'business-name', + 'tooltip' => 'For all ACH transactions, you are required to collect a mandate or “proof of authorization” from the customer to prove that you have their explicit permission to debit their bank account. We will put your business name in authorization text', + 'label' => 'Business name', + 'type' => 'text', + 'class' => 'medium' + ) + ) + ), + array( + 'title' => 'Payment Settings', + 'fields' => array( + array( + 'name' => 'settlement', + 'tooltip' => 'Choosing \'Yes\' will tell Braintree to automatically submit your transactions for settlement upon receipt', + 'label' => 'Automatic Settlement Submission', + 'type' => 'radio', + 'choices' => array( + array( + 'label' => 'Yes', + 'name' => 'yes' + ), + array( + 'label' => 'No', + 'name' => 'no' + ) + ) + ) + ) + ), + array( + 'title' => 'Environment Settings', + 'fields' => array( + array( + 'name' => 'environment', + 'tooltip' => 'Do you want to process test payments or real payments?', + 'label' => 'API Endpoint', + 'type' => 'radio', + 'choices' => array( + array( + 'label' => 'Sandbox', + 'name' => 'sandbox' + ), + array( + 'label' => 'Production', + 'name' => 'production' + ) + ) + ) + ) + ) + ); + } + + /** + * Helper function to determine if all Braintree settings have been set. + * Does not check if they are correct, only that they have been set, IE not null + * @param @settings Plugin settings to check if valid + * @since 1.0 + * @return void + */ + public function settings_are_valid($settings) { + if (empty($settings)) { + return false; + } + if (!empty($settings['merchant-id']) && !empty($settings['public-key']) && !empty($settings['public-key'])) { + return true; + } else { + return false; + } + } + + /** + * Get plugin settings + * + * @since 1.0 + * @return void + */ + public function get_plugin_settings() { + + $settings = parent::get_plugin_settings(); + + if ($this->settings_are_valid($settings)) { + return $settings; + } else { + return false; + } + } + + public function angelleye_gform_braintree_display_push_notification() { + global $current_user; + $user_id = $current_user->ID; + if (false === ( $response = get_transient('angelleye_gravity_braintree_push_notification_result') )) { + $response = $this->angelleye_get_push_notifications(); + if (is_object($response)) { + set_transient('angelleye_gravity_braintree_push_notification_result', $response, 12 * HOUR_IN_SECONDS); + } + } + if (is_object($response)) { + foreach ($response->data as $key => $response_data) { + if (!get_user_meta($user_id, $response_data->id)) { + $this->angelleye_display_push_notification($response_data); + } + } + } + } + + public function angelleye_get_push_notifications() { + $args = array( + 'plugin_name' => 'angelleye-gravity-forms-braintree', + ); + $api_url = PAYPAL_FOR_WOOCOMMERCE_PUSH_NOTIFICATION_WEB_URL . '?Wordpress_Plugin_Notification_Sender'; + $api_url .= '&action=angelleye_get_plugin_notification'; + $request = wp_remote_post($api_url, array( + 'method' => 'POST', + 'timeout' => 45, + 'redirection' => 5, + 'httpversion' => '1.0', + 'blocking' => true, + 'headers' => array('user-agent' => 'AngellEYE'), + 'body' => $args, + 'cookies' => array(), + 'sslverify' => false + )); + if (is_wp_error($request) or wp_remote_retrieve_response_code($request) != 200) { + return false; + } + if ($request != '') { + $response = json_decode(wp_remote_retrieve_body($request)); + } else { + $response = false; + } + return $response; + } + + public function angelleye_display_push_notification($response_data) { + echo ''; + } + + public function angelleye_gform_braintree_adismiss_notice() { + global $current_user; + $user_id = $current_user->ID; + if (!empty($_POST['action']) && $_POST['action'] == 'angelleye_gform_braintree_adismiss_notice') { + add_user_meta($user_id, wc_clean($_POST['data']), 'true', true); + wp_send_json_success(); + } + } + + public function include_angelleye_braintree_style_noconflict($styles) { + $styles[] = 'gravity-forms-braintree-admin-css'; + return $styles; + } + + public function include_angelleye_braintree_script_noconflict($scripts) { + $scripts[] = 'gravity-forms-braintree-admin'; + return $scripts; + } + + public function enqueue_scripts_js() { + if (GFForms::is_gravity_page()) { + wp_enqueue_script('gravity-forms-braintree-admin', GRAVITY_FORMS_BRAINTREE_ASSET_URL . 'assets/js/gravity-forms-braintree-admin.js', array('jquery'), $this->_version, false); + } + } + + public function enqueue_styles_css() { + if (GFForms::is_gravity_page()) { + wp_enqueue_style('gravity-forms-braintree-admin-css', GRAVITY_FORMS_BRAINTREE_ASSET_URL . 'assets/css/gravity-forms-braintree-admin.css', array(), $this->_version, 'all'); + } + } + + /** + * Load the Braintree JS and Custom JS in frontend + * @return array + */ + public function scripts() { + $translation_array = []; + $settings = $this->get_plugin_settings(); + if ($settings !== false) { + $translation_array['ach_bt_token'] = @$settings['tokenization-key']; + $translation_array['ach_business_name'] = @$settings['business-name']; + } + + $scripts = array( + array( + 'handle' => 'angelleye-gravity-form-braintree-client', + 'src' => 'https://js.braintreegateway.com/web/3.61.0/js/client.min.js', + 'version' => $this->_version, + 'deps' => array('jquery'), + 'in_footer' => false, + 'callback' => array($this, 'localize_scripts'), + 'enqueue' => array( + array('field_types' => array('braintree_ach')) + ) + ), + array( + 'handle' => 'angelleye-gravity-form-braintree-data-collector', + 'src' => 'https://js.braintreegateway.com/web/3.61.0/js/data-collector.min.js', + 'version' => $this->_version, + 'deps' => array(), + 'in_footer' => false, + 'callback' => array($this, 'localize_scripts'), + 'enqueue' => array( + array('field_types' => array('braintree_ach')) + ) + ), + array( + 'handle' => 'angelleye-gravity-form-braintree-usbankaccount', + 'src' => 'https://js.braintreegateway.com/web/3.61.0/js/us-bank-account.min.js', + 'version' => $this->_version, + 'deps' => array(), + 'in_footer' => false, + 'callback' => array($this, 'localize_scripts'), + 'enqueue' => array( + array('field_types' => array('braintree_ach')) + ) + ), + array( + 'handle' => 'angelleye_gravity_form_braintree_ach_handler', + 'src' => GRAVITY_FORMS_BRAINTREE_ASSET_URL . 'assets/js/angelleye-braintree-ach-cc.js', + 'version' => $this->_version, + 'deps' => array('jquery', 'angelleye-gravity-form-braintree-client', 'angelleye-gravity-form-braintree-data-collector', + 'angelleye-gravity-form-braintree-usbankaccount'), + 'in_footer' => false, + 'callback' => array($this, 'localize_scripts'), + 'strings' => $translation_array, + 'enqueue' => array( +// array( +// 'admin_page' => array( 'form_settings' ), +// 'tab' => 'simpleaddon' +// ) + array('field_types' => array('braintree_ach')) + ) + ), + ); + + return array_merge(parent::scripts(), $scripts); + } + + /** + * Override default billing cycles intervals for subscription plan. + * + * @return array $billing_cycles + */ + public function supported_billing_intervals() { + $billing_cycles = array( + 'month' => array('label' => esc_html__('month(s)', 'angelleye-gravity-forms-braintree'), 'min' => 1, 'max' => 24) + ); + + return $billing_cycles; + } + + /** + * Get all Braintree plans using Braintree payment Gateway settings. + * + * @return array $plans + * + * @throws \Braintree\Exception\Configuration + */ + public function get_plans() { + try { + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + $plan_lists = $gateway->plan()->all(); + $plans = array(array( + 'label' => __('Select a plan', 'angelleye-gravity-forms-braintree'), + 'value' => '', + )); + if (!empty($plan_lists)) { + foreach ($plan_lists as $plan) { + $plans[] = array( + 'label' => $plan->name, + 'value' => $plan->id, + ); + } + } + return $plans; + } + } catch (Exception $ex) { + + } + } + + /** + * Get customer id using customer email address. + * + * If customer email address already exists in braintree customer lists then provide customer id, + * Otherwise create a new customer using customer details and then provide a customer id. + * + * @param array $args + * + * @return int|string $customer_id Customer id. + * + * @throws \Braintree\Exception\Configuration + */ + public function get_customer_id($args) { + //check if customer detail is empty or not array then return. + if (empty($args) || !is_array($args)) { + return ''; + } + $email = !empty($args['email']) ? $args['email'] : ''; + $gateway = $this->getBraintreeGateway(); + if ($gateway) { + //search customer using email address + $collections = $gateway->customer()->search([ + Braintree\CustomerSearch::email()->is($email) + ]); + $customer_id = 0; + foreach ($collections as $key => $collection) { + if (!empty($collection->id)) { + $customer_id = $collection->id; + } + } + //check $customer_id is empty then create a new customer. + if (empty($customer_id)) { + $customer = $gateway->customer()->create($args); + if (!empty($customer->customer->id)) { + $customer_id = $customer->customer->id; + } + } + return $customer_id; + } + } + +} diff --git a/lib/ssl/api_braintreegateway_com.ca.crt b/lib/ssl/api_braintreegateway_com.ca.crt index e142318..7dcf4af 100644 --- a/lib/ssl/api_braintreegateway_com.ca.crt +++ b/lib/ssl/api_braintreegateway_com.ca.crt @@ -1,68 +1,4 @@ -----BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp @@ -91,20 +27,6 @@ WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz @@ -150,35 +72,71 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI 4uJEvlz36hz1 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW @@ -212,27 +170,6 @@ DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG @@ -253,99 +190,52 @@ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= -----END CERTIFICATE----- diff --git a/readme.txt b/readme.txt index 05daa4d..c1367b5 100644 --- a/readme.txt +++ b/readme.txt @@ -1,66 +1,140 @@ -=== Gravity Forms Braintree Add-On === -Contributors: Plugify, hello@lukerollans.me -Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hello%40plugify%2eio&lc=GB&item_name=Plugin%20Development%20Donation¤cy_code=USD -Tags: credit card,braintree,gravity form,payment -Requires at least: 3.8 -Tested up to: 3.9 -Stable tag: 1.1.1 -License: GPLv2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html +=== Gravity Forms Braintree Payments === +Contributors: angelleye, Plugify, hello@lukerollans.me, gravityplus +Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9CQZZGGMF78VY&source=url +Tags: gravity form, gravity forms, credit card, credit cards, payment, payments, braintree +Requires at least: 5.0 +Tested up to: 6.2 +Stable tag: 4.0.6 +License: GPLv3 +License URI: https://www.gnu.org/licenses/gpl-3.0.html Allow your customers to purchase goods and services through Gravity Forms via Braintree Payments == Description == -Braintree Payments is a payment gateway provider owned by eBAY Inc, which allows you to proces credit card payments without the need for a bank merchant account and full PCI-compliance. No sensitive data such as credit card numbers are stored on your server, Braintree takes care of everything. +Braintree Payments is a payment gateway provider owned by PayPal which allows you to process credit card payments without the need for a bank merchant account and full PCI-compliance. No sensitive data such as credit card numbers are stored on your server, Braintree takes care of everything. > Requires at least WordPress 3.8 and Gravity Forms 1.8 There are just a few simple steps to begin leveraging your Braintree Payments account: -1. Install Gravity Forms Braintree Add-On -2. Go to the Form Settings page for the form you wish to create a Braintree feed on +1. Install Gravity Forms Braintree Payments. +2. Go to the Form Settings page for the form you wish to create a Braintree feed on. 3. You will be prompted to configure your Braintree settings. Click the link provided to do so. 4. Once you have configured your Braintree settings, return to the Form Settings page and follow the prompts. = Features = -* Seamlessly integrates your Gravity Forms credit card forms with Braintree Payments -* Supports both production and sandbox environments, enabling you to test payments before going live -* Form entries will only be created when payment is successful -* Quick and easy setup - -If you have found this plugin useful, consider taking a moment to rate it, or perhaps even a small donation. +* Seamlessly integrates your Gravity Forms credit card forms with Braintree Payments. +* Supports both production and sandbox environments, enabling you to test payments before going live. +* Form entries will only be created when payment is successful. +* Quick and easy setup. == Installation == -1. Upload the `gravity-forms-braintree` folder to the `/wp-content/plugins/` directory. -2. Activate the plugin through the 'Plugins' menu in WordPress. -3. Navigate to the Form you wish to setup with a Braintree feed. -4. Under Form Settings, choose the Braintree option. += Automatic installation = + +Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't need to leave your web browser. To do an automatic install of Gravity Forms Braintree Payments, log in to your WordPress dashboard, navigate to the Plugins menu and click Add New. + +In the search field type Gravity Forms Braintree Payments and click Search Plugins. Once you've found our plugin (make sure it says "by Angell EYE") you can view details about it such as the the rating and description. Most importantly, of course, you can install it by simply clicking Install Now. + += Manual Installation = + +1. Unzip the files and upload the folder into your plugins folder (/wp-content/plugins/) overwriting older versions if they exist +2. Activate the plugin in your WordPress admin area. + += Usage = + +1. Navigate to the Form you wish to setup with a Braintree feed. +2. Under Form Settings, choose the Braintree option. == Frequently asked questions == = What type of Braintree payments can be accepted? = -For this early version, only one off payments can be accepted. Subscriptions will be available in version 1.1 - -= Can I use conditional logic? EG, I only want to register a user if the Braintree payment was successful = -In version 1.0, no. This is planned for version 1.2, coming very soon +* For this early version, only one off payments can be accepted. = Does this plugin support Braintree subscriptions? = -Not currently, no. This will be released very shortly in version 1.1 - -= Available filters and actions = -No filters are currently available for this pre-release version +* Not yet. This will be added based on future demand. == Screenshots == -1. You will be initially greeted with the empty feed page, prompting you to configure your Braintree settings. -2. Page for configuring your Braintree settings, such as Merchant ID -3. Configuring a Gravity Forms Braintree feed -4. List of active feeds +1. Drop a credit card field collection directly into any Gravity Form. +2. Easily configure your Braintree settings, allowing for quick and efficient setup. +3. Quickly and easily configure payment feeds under Form Settings of any Gravity Form. +4. List of active feeds on the current form. == Changelog == += 4.0.6 - 04.18.2023 = +* Fix - Adjustments to resolve theme conflict. ([GFB-40](https://github.com/angelleye/gravity-forms-braintree/pull/44)) + += 4.0.5 - 01.26.2022 = +* Feature - Added Gravity Forms version 2.5.16 capability. ([GFB-42](https://github.com/angelleye/gravity-forms-braintree/pull/39)) + += 4.0.4 - 01.17.2022 = +* Fix - Resolved PHP Fatal error. ([GFB-41](https://github.com/angelleye/gravity-forms-braintree/pull/38)) + += 4.0.3 - 01.10.2022 = +* Fix - Resolved PHP Fatal error. ([GFB-37](https://github.com/angelleye/gravity-forms-braintree/pull/37)) + += 4.0.2 - 07.12.2021 = +* Tweak - Updates Update Braintree SDK. ([GFB-37](https://github.com/angelleye/gravity-forms-braintree/pull/33)) + += 4.0.1 - 03.16.2021 = +* Tweak - Adding label for Braintree CC while setting up Subscription method ([GFB-36](https://github.com/angelleye/gravity-forms-braintree/pull/31)) + += 4.0 - 03.01.2021 = +* Feature - Added Braintree Subscription ([GFB-31](https://github.com/angelleye/gravity-forms-braintree/pull/30)) + += 3.1.2 - 05.14.2020 = +* Fix - Resolved Braintree ACH/CC form validation issuw with multiple Payment Methods ([GFB-27](https://github.com/angelleye/gravity-forms-braintree/pull/28)) + += 3.1.1 - 05.14.2020 = +* Feature - Added Braintree ACH Direct Debit + CC compatibility with custom radio fields and conditions ([GFB-25](https://github.com/angelleye/gravity-forms-braintree/pull/26)) +* Feature - Pass custom field mapping data with ACH Direct Debit payments ([GFB-24](https://github.com/angelleye/gravity-forms-braintree/pull/27)) + += 3.1.0 - 05.13.2020 = +* Feature - Added Braintree ACH Direct Debit Payment Gateway ([GFB-17](https://github.com/angelleye/gravity-forms-braintree/pull/25)) +* Feature - Added custom plugin requirement checker to validate server configuration ([GFB-22](https://github.com/angelleye/gravity-forms-braintree/pull/24)) + += 3.0.2 - 05.04.2020 = +* Fix - Resolved the issue with PHP Version comparison ([GFB-21](https://github.com/angelleye/gravity-forms-braintree/pull/23)) + += 3.0.1 - 04.28.2020 = +* Fix - Compatibility issue with PayPal for WooCommerce in loading Braintree library ([GFB-18](https://github.com/angelleye/gravity-forms-braintree/pull/22)) + += 3.0.0 - 04.28.2020 = +* Upgrade - Braintree Library Upgraded from 3.36.0 to 5.0.0 ([GFB-18](https://github.com/angelleye/gravity-forms-braintree/pull/21)) +* Tweak - Support Gravity Form No Conflict Mode issue with Braintree script loading in backend. ([GFB-19](https://github.com/angelleye/gravity-forms-braintree/pull/20)) + += 2.2.2 - 12.30.2019 = +* Tweak - Adjustment to Updater plugin notice dismissible. ([GFB-16](https://github.com/angelleye/gravity-forms-braintree/pull/17)) + += 2.2.0 = 11.20.2019 = +* Verification - WordPress 5.3 compatibility. + += 2.2.0 = 10.16.2019 = +* Feature - Adds Braintree field mapping capability. ([GFB-12](https://github.com/angelleye/gravity-forms-braintree/pull/14)) ([GFB-15](https://github.com/angelleye/gravity-forms-braintree/pull/16)) +* Tweak - Adds a notice if you try to activate the Braintree Payments extension without Gravity Forms active. + += 2.1.3 - 07.23.2019 = +* Tweak - Update push notification system sync interval time. ([GFB-9](https://github.com/angelleye/gravity-forms-braintree/pull/11)) + += 2.1.2 - 07.09.2019 = +* Tweak - Minor adjustments to API request. + += 2.1.1 - 05.31.2019 = +* Feature - Adds AE notification system. ([GFB-8](https://github.com/angelleye/gravity-forms-braintree/pull/10)) +* Tweak - Adds text domain. ([GFB-7](https://github.com/angelleye/gravity-forms-braintree/pull/9)) + += 2.1.0 = +* Feature - Adds AE Updater compatibility for future notices and automated updates. [GFB-4] ([GFB-5](https://github.com/angelleye/gravity-forms-braintree/pull/8)) + += 2.0.0 = +* Fix - Updates Braintree Payments SDK and resolves failures with latest version of Gravity Forms. ([#1](https://github.com/angelleye/gravity-forms-braintree/issues/1)) + += 1.1.2 = +* Internal maintenance release. Version 1.2 is coming soon and it's going to be big! + = 1.1.1 = * Dashes and spaces are now removed from credit card number before sending to Braintree @@ -80,8 +154,4 @@ No filters are currently available for this pre-release version * Most of plugin functionality = 0.1 = -* Initial version of the plugin - -== Upgrade notice == - -IMPORTANT! Version 1.0 is a complete overhaul from the previous version. Your existing feeds will not work. Please make sure you check all your feeds and ensure they function correctly. +* Initial version of the plugin \ No newline at end of file