GFPaymentAddOn

The Gravity Forms GFPaymentAddOn class.

Defined (1)

The class is defined in the following location(s).

/includes/addon/class-gf-payment-addon.php  
  1. abstract class GFPaymentAddOn extends GFFeedAddOn { 
  2.  
  3. private $_payment_version = '1.2'; 
  4.  
  5. /** 
  6. * If set to true, user will not be able to create feeds for a form until a credit card field has been added. 
  7. * @var bool 
  8. */ 
  9. protected $_requires_credit_card = false; 
  10.  
  11. /** 
  12. * If set to true, callbacks/webhooks/IPN will be enabled and the appropriate database table will be created. 
  13. * @var bool 
  14. */ 
  15. protected $_supports_callbacks = false; 
  16.  
  17. protected $authorization = array(); 
  18. protected $redirect_url = ''; 
  19.  
  20. protected $current_feed = false; 
  21. protected $current_submission_data = false; 
  22. protected $is_payment_gateway = false; 
  23.  
  24. protected $_single_feed_submission = true; 
  25.  
  26. /** 
  27. * Indicates if the payment gateway requires monetary amounts to be formatted as the smallest unit for the currency being used e.g. cents. 
  28. * @var bool 
  29. */ 
  30. protected $_requires_smallest_unit = false; 
  31.  
  32. //--------- Initialization ---------- 
  33. public function pre_init() { 
  34. parent::pre_init(); 
  35.  
  36. // Intercepting callback requests 
  37. add_action( 'parse_request', array( $this, 'maybe_process_callback' ) ); 
  38.  
  39. if ( $this->payment_method_is_overridden( 'check_status' ) ) { 
  40. $this->setup_cron(); 
  41.  
  42.  
  43. public function init() { 
  44.  
  45. parent::init(); 
  46.  
  47. add_filter( 'gform_confirmation', array( $this, 'confirmation' ), 20, 4 ); 
  48.  
  49. add_filter( 'gform_validation', array( $this, 'maybe_validate' ), 20 ); 
  50. add_filter( 'gform_entry_post_save', array( $this, 'entry_post_save' ), 10, 2 ); 
  51.  
  52.  
  53. public function init_admin() { 
  54.  
  55. parent::init_admin(); 
  56.  
  57. //enables credit card field 
  58. add_filter( 'gform_enable_credit_card_field', '__return_true' ); 
  59.  
  60. add_filter( 'gform_currencies', array( $this, 'supported_currencies' ) ); 
  61.  
  62. add_filter( 'gform_delete_lead', array( $this, 'entry_deleted' ) ); 
  63.  
  64.  
  65. if ( rgget( 'page' ) == 'gf_entries' ) { 
  66. add_action( 'gform_payment_details', array( $this, 'entry_info' ), 10, 2 ); 
  67. add_filter( 'gform_enable_entry_info_payment_details', array( $this, 'disable_entry_info_payment' ), 10, 2 ); 
  68.  
  69. public function init_frontend() { 
  70.  
  71. parent::init_frontend(); 
  72.  
  73. add_filter( 'gform_register_init_scripts', array( $this, 'register_creditcard_token_script' ), 10, 3 ); 
  74. add_filter( 'gform_field_content', array( $this, 'add_creditcard_token_input' ), 10, 5 ); 
  75. add_filter( 'gform_form_args', array( $this, 'force_ajax_for_creditcard_tokens' ), 10, 1 ); 
  76.  
  77.  
  78. public function init_ajax() { 
  79. parent::init_ajax(); 
  80.  
  81. add_action( 'wp_ajax_gaddon_cancel_subscription', array( $this, 'ajax_cancel_subscription' ) ); 
  82. add_action( 'gform_before_delete_field', array( $this, 'before_delete_field' ), 10, 2 ); 
  83.  
  84. protected function setup() { 
  85. parent::setup(); 
  86.  
  87. $installed_version = get_option( 'gravityformsaddon_payment_version' ); 
  88.  
  89. $installed_addons = get_option( 'gravityformsaddon_payment_addons' ); 
  90. if ( ! is_array( $installed_addons ) ) { 
  91. $installed_addons = array(); 
  92.  
  93. if ( $installed_version != $this->_payment_version ) { 
  94. $this->upgrade_payment( $installed_version ); 
  95.  
  96. $installed_addons = array( $this->_slug ); 
  97. update_option( 'gravityformsaddon_payment_addons', $installed_addons ); 
  98. } elseif ( ! in_array( $this->_slug, $installed_addons ) ) { 
  99. $this->upgrade_payment( $installed_version ); 
  100.  
  101. $installed_addons[] = $this->_slug; 
  102. update_option( 'gravityformsaddon_payment_addons', $installed_addons ); 
  103.  
  104. update_option( 'gravityformsaddon_payment_version', $this->_payment_version ); 
  105.  
  106.  
  107. private function upgrade_payment( $previous_versions ) { 
  108. global $wpdb; 
  109.  
  110. $charset_collate = GFFormsModel::get_db_charset(); 
  111.  
  112. $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_transaction ( 
  113. id int(10) unsigned not null auto_increment,  
  114. lead_id int(10) unsigned not null,  
  115. transaction_type varchar(30) not null,  
  116. transaction_id varchar(50),  
  117. is_recurring tinyint(1) not null default 0,  
  118. amount decimal(19, 2),  
  119. date_created datetime,  
  120. PRIMARY KEY (id),  
  121. KEY lead_id (lead_id),  
  122. KEY transaction_type (transaction_type),  
  123. KEY type_lead (lead_id, transaction_type) 
  124. ) $charset_collate;"; 
  125.  
  126. GFFormsModel::dbDelta( $sql ); 
  127.  
  128.  
  129. if ( $this->_supports_callbacks ) { 
  130. $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_callback ( 
  131. id int(10) unsigned not null auto_increment,  
  132. lead_id int(10) unsigned not null,  
  133. addon_slug varchar(250) not null,  
  134. callback_id varchar(250),  
  135. date_created datetime,  
  136. PRIMARY KEY (id),  
  137. KEY addon_slug_callback_id (addon_slug(50), callback_id(100)) 
  138. ) $charset_collate;"; 
  139.  
  140. GFFormsModel::dbDelta( $sql ); 
  141.  
  142. //droping legacy index 
  143. GFForms::drop_index( "{$wpdb->prefix}gf_addon_payment_callback", 'slug_callback_id' ); 
  144.  
  145.  
  146.  
  147. //--------- Submission Process ------ 
  148. public function confirmation( $confirmation, $form, $entry, $ajax ) { 
  149.  
  150. if ( empty( $this->redirect_url ) ) { 
  151. return $confirmation; 
  152.  
  153. $confirmation = array( 'redirect' => $this->redirect_url ); 
  154.  
  155. return $confirmation; 
  156.  
  157. /** 
  158. * Override this function to specify a URL to the third party payment processor. Useful when developing a payment gateway that processes the payment outside of the website (i.e. PayPal Standard). 
  159. * @param $feed - Active payment feed containing all the configuration data 
  160. * @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...) 
  161. * @param $form - Current form array containing all form settings 
  162. * @param $entry - Current entry array containing entry information (i.e data submitted by users) 
  163. * @return string - Return a full URL (inlucing http:// or https://) to the payment processor 
  164. */ 
  165. protected function redirect_url( $feed, $submission_data, $form, $entry ) { 
  166.  
  167.  
  168. /** 
  169. * Check if the rest of the form has passed validation, is the last page, and that the honeypot field has not been completed. 
  170. * @param array $validation_result Contains the validation result, the form object, and the failed validation page number. 
  171. * @return array $validation_result 
  172. */ 
  173. public function maybe_validate( $validation_result ) { 
  174.  
  175. $form = $validation_result['form']; 
  176. $is_last_page = GFFormDisplay::is_last_page( $form ); 
  177. $failed_honeypot = false; 
  178.  
  179. if ( $is_last_page && rgar( $form, 'enableHoneypot' ) ) { 
  180. $honeypot_id = GFFormDisplay::get_max_field_id( $form ) + 1; 
  181. $failed_honeypot = ! rgempty( "input_{$honeypot_id}" ); 
  182.  
  183. if ( ! $validation_result['is_valid'] || ! $is_last_page || $failed_honeypot ) { 
  184. return $validation_result; 
  185.  
  186. return $this->validation( $validation_result ); 
  187.  
  188. public function validation( $validation_result ) { 
  189.  
  190. if ( ! $validation_result['is_valid'] ) { 
  191. return $validation_result; 
  192.  
  193. $form = $validation_result['form']; 
  194. $entry = GFFormsModel::create_lead( $form ); 
  195. $feed = $this->get_payment_feed( $entry, $form ); 
  196.  
  197. if ( ! $feed ) { 
  198. return $validation_result; 
  199.  
  200. $submission_data = $this->get_submission_data( $feed, $form, $entry ); 
  201.  
  202. //Do not process payment if payment amount is 0 
  203. if ( floatval( $submission_data['payment_amount'] ) <= 0 ) { 
  204.  
  205. $this->log_debug( __METHOD__ . '(): Payment amount is $0.00 or less. Not sending to payment gateway.' ); 
  206.  
  207. return $validation_result; 
  208.  
  209.  
  210. $this->is_payment_gateway = true; 
  211. $this->current_feed = $this->_single_submission_feed = $feed; 
  212. $this->current_submission_data = $submission_data; 
  213.  
  214. $performed_authorization = false; 
  215. $is_subscription = $feed['meta']['transactionType'] == 'subscription'; 
  216.  
  217. if ( $this->payment_method_is_overridden( 'authorize' ) && ! $is_subscription ) { 
  218.  
  219. //Running an authorization only transaction if function is implemented and this is a single payment 
  220. $this->authorization = $this->authorize( $feed, $submission_data, $form, $entry ); 
  221.  
  222. $performed_authorization = true; 
  223.  
  224. } elseif ( $this->payment_method_is_overridden( 'subscribe' ) && $is_subscription ) { 
  225.  
  226. $subscription = $this->subscribe( $feed, $submission_data, $form, $entry ); 
  227.  
  228. $this->authorization['is_authorized'] = $subscription['is_success']; 
  229. $this->authorization['error_message'] = rgar( $subscription, 'error_message' ); 
  230. $this->authorization['subscription'] = $subscription; 
  231.  
  232. $performed_authorization = true; 
  233.  
  234. if ( $performed_authorization ) { 
  235. $this->log_debug( __METHOD__ . "(): Authorization result for form #{$form['id']} submission => " . print_r( $this->authorization, 1 ) ); 
  236.  
  237. if ( $performed_authorization && ! $this->authorization['is_authorized'] ) { 
  238. $validation_result = $this->get_validation_result( $validation_result, $this->authorization ); 
  239.  
  240. //Setting up current page to point to the credit card page since that will be the highlighted field 
  241. GFFormDisplay::set_current_page( $validation_result['form']['id'], $validation_result['credit_card_page'] ); 
  242.  
  243. return $validation_result; 
  244.  
  245. /** 
  246. * Override this method to add integration code to the payment processor in order to authorize a credit card with or without capturing payment. This method is executed during the form validation process and allows 
  247. * the form submission process to fail with a validation error if there is anything wrong with the payment/authorization. This method is only supported by single payments. 
  248. * For subscriptions or recurring payments, use the subscribe() method. 
  249. * @param $feed - Current configured payment feed 
  250. * @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...) 
  251. * @param $form - Current form array containing all form settings 
  252. * @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. 
  253. * @return array - Return an $authorization array in the following format: 
  254. * [ 
  255. * 'is_authorized' => true|false,  
  256. * 'error_message' => 'Error message',  
  257. * 'transaction_id' => 'XXX',  
  258. * //If the payment is captured in this method, return a 'captured_payment' array with the following information about the payment 
  259. * 'captured_payment' => ['is_success'=>true|false, 'error_message' => 'error message', 'transaction_id' => 'xxx', 'amount' => 20] 
  260. * ] 
  261. */ 
  262. protected function authorize( $feed, $submission_data, $form, $entry ) { 
  263.  
  264.  
  265. /** 
  266. * Override this method to capture a single payment that has been authorized via the authorize() method. Use only with single payments. For subscriptions, use subscribe() instead. 
  267. * @param $authorization - Contains the result of the authorize() function 
  268. * @param $feed - Current configured payment feed 
  269. * @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...) 
  270. * @param $form - Current form array containing all form settings 
  271. * @param $entry - Current entry array containing entry information (i.e data submitted by users). 
  272. * @return array - Return an array with the information about the captured payment in the following format: 
  273. * [ 
  274. * 'is_success'=>true|false,  
  275. * 'error_message' => 'error message',  
  276. * 'transaction_id' => 'xxx',  
  277. * 'amount' => 20,  
  278. * 'payment_method' => 'Visa' 
  279. * ] 
  280. */ 
  281. protected function capture( $authorization, $feed, $submission_data, $form, $entry ) { 
  282.  
  283.  
  284. /** 
  285. * 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 
  286. * the form submission process to fail with a validation error if there is anything wrong when creating the subscription. 
  287. * @param $feed - Current configured payment feed 
  288. * @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...) 
  289. * @param $form - Current form array containing all form settings 
  290. * @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. 
  291. * @return array - Return an $subscription array in the following format: 
  292. * [ 
  293. * 'is_success'=>true|false,  
  294. * 'error_message' => 'error message',  
  295. * 'subscription_id' => 'xxx',  
  296. * 'amount' => 10 
  297. * //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 
  298. * //information in the following 'captured_payment' array 
  299. * 'captured_payment' => ['name' => 'Setup Fee', 'is_success'=>true|false, 'error_message' => 'error message', 'transaction_id' => 'xxx', 'amount' => 20] 
  300. * ] 
  301. */ 
  302. protected function subscribe( $feed, $submission_data, $form, $entry ) { 
  303.  
  304.  
  305. /** 
  306. * 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 Payment Gateway (i.e. Stripe or PayPal) 
  307. * @param $entry - Current entry array containing entry information (i.e data submitted by users). 
  308. * @param $feed - Current configured payment feed 
  309. * @return bool - Returns true if the subscription was cancelled successfully and false otherwise. 
  310. */ 
  311. protected function cancel( $entry, $feed ) { 
  312. return false; 
  313.  
  314. protected function get_validation_result( $validation_result, $authorization_result ) { 
  315.  
  316. $credit_card_page = 0; 
  317. foreach ( $validation_result['form']['fields'] as &$field ) { 
  318. if ( $field->type == 'creditcard' ) { 
  319. $field->failed_validation = true; 
  320. $field->validation_message = $authorization_result['error_message']; 
  321. $credit_card_page = $field->pageNumber; 
  322. break; 
  323.  
  324. $validation_result['credit_card_page'] = $credit_card_page; 
  325. $validation_result['is_valid'] = false; 
  326.  
  327. return $validation_result; 
  328.  
  329.  
  330. public function entry_post_save( $entry, $form ) { 
  331.  
  332. if ( ! $this->is_payment_gateway ) { 
  333. return $entry; 
  334.  
  335. $feed = $this->current_feed; 
  336.  
  337. if ( ! empty( $this->authorization ) ) { 
  338. //If an authorization was done, capture it 
  339.  
  340. if ( $feed['meta']['transactionType'] == 'subscription' ) { 
  341.  
  342. $entry = $this->process_subscription( $this->authorization, $feed, $this->current_submission_data, $form, $entry ); 
  343.  
  344. } else { 
  345.  
  346. if ( $this->payment_method_is_overridden( 'capture' ) && rgempty( 'captured_payment', $this->authorization ) ) { 
  347.  
  348. $this->authorization['captured_payment'] = $this->capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry ); 
  349.  
  350.  
  351. $entry = $this->process_capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry ); 
  352. } elseif ( $this->payment_method_is_overridden( 'redirect_url' ) ) { 
  353.  
  354. //If the url_redirect() function is overridden, call it. 
  355.  
  356. //Getting URL to redirect to ( saved to be used by the confirmation() function ) 
  357. $this->redirect_url = $this->redirect_url( $feed, $this->current_submission_data, $form, $entry ); 
  358.  
  359. //Setting transaction_type to subscription or one time payment 
  360. $entry['transaction_type'] = rgars( $feed, 'meta/transactionType' ) == 'subscription' ? 2 : 1; 
  361. $entry['payment_status'] = 'Processing'; 
  362.  
  363.  
  364. //Saving which gateway was used to process this entry 
  365. gform_update_meta( $entry['id'], 'payment_gateway', $this->_slug ); 
  366.  
  367. return $entry; 
  368.  
  369. protected function process_capture( $authorization, $feed, $submission_data, $form, $entry ) { 
  370.  
  371. $payment = rgar( $authorization, 'captured_payment' ); 
  372. if ( empty( $payment ) ) { 
  373. return $entry; 
  374.  
  375. $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $payment, 1 ) ); 
  376.  
  377. if ( $payment['is_success'] ) { 
  378.  
  379. $entry['is_fulfilled'] = '1'; 
  380. $payment['payment_status'] = 'Paid'; 
  381. $payment['payment_date'] = gmdate( 'Y-m-d H:i:s' ); 
  382. $payment['type'] = 'complete_payment'; 
  383. $this->complete_payment( $entry, $payment ); 
  384.  
  385. } else { 
  386.  
  387. $entry['payment_status'] = 'Failed'; 
  388. $payment['type'] = 'fail_payment'; 
  389. $payment['note'] = sprintf( esc_html__( 'Payment failed to be captured. Reason: %s', 'gravityforms' ), $payment['error_message'] ); 
  390. $this->fail_payment( $entry, $payment ); 
  391.  
  392.  
  393. return $entry; 
  394.  
  395.  
  396. protected function process_subscription( $authorization, $feed, $submission_data, $form, $entry ) { 
  397.  
  398. $subscription = rgar( $authorization, 'subscription' ); 
  399. if ( empty( $subscription ) ) { 
  400. return $entry; 
  401.  
  402. $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $subscription, 1 ) ); 
  403.  
  404. // if setup fee / trial is captured as part of a separate transaction 
  405. $payment = rgar( $subscription, 'captured_payment' ); 
  406. $payment_name = rgempty( 'name', $payment ) ? esc_html__( 'Initial payment', 'gravityforms' ) : $payment['name']; 
  407.  
  408. if ( $payment && $payment['is_success'] ) { 
  409.  
  410. $this->insert_transaction( $entry['id'], 'payment', $payment['transaction_id'], $payment['amount'], false ); 
  411.  
  412. $amount_formatted = GFCommon::to_money( $payment['amount'], $entry['currency'] ); 
  413. $note = sprintf( esc_html__( '%s has been captured successfully. Amount: %s. Transaction Id: %s', 'gravityforms' ), $payment_name, $amount_formatted, $payment['transaction_id'] ); 
  414. $this->add_note( $entry['id'], $note, 'success' ); 
  415.  
  416. } elseif ( $payment && ! $payment['is_success'] ) { 
  417.  
  418. $this->add_note( $entry['id'], sprintf( esc_html__( 'Failed to capture %s. Reason: %s.', 'gravityforms' ), $payment['error_message'], $payment_name ), 'error' ); 
  419.  
  420.  
  421. // updating subscription information 
  422. if ( $subscription['is_success'] ) { 
  423.  
  424. $entry = $this->start_subscription( $entry, $subscription ); 
  425.  
  426. } else { 
  427.  
  428. $entry['payment_status'] = 'Failed'; 
  429. GFAPI::update_entry( $entry ); 
  430.  
  431. $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription failed to be created. Reason: %s', 'gravityforms' ), $subscription['error_message'] ), 'error' ); 
  432.  
  433. $subscription['type'] = 'fail_create_subscription'; 
  434. $this->post_payment_action( $entry, $subscription ); 
  435.  
  436.  
  437. return $entry; 
  438.  
  439.  
  440. protected function insert_transaction( $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring = null ) { 
  441. global $wpdb; 
  442.  
  443. // @todo: make sure stats does not show setup fee as a recurring payment 
  444. $payment_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(id) FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE lead_id=%d", $entry_id ) ); 
  445. $is_recurring = $payment_count > 0 && $transaction_type == 'payment' ? 1 : 0; 
  446.  
  447. $sql = $wpdb->prepare( 
  448. " INSERT INTO {$wpdb->prefix}gf_addon_payment_transaction (lead_id, transaction_type, transaction_id, amount, is_recurring, date_created) 
  449. values(%d, %s, %s, %f, %d, utc_timestamp())", $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring 
  450. ); 
  451. $wpdb->query( $sql ); 
  452.  
  453. $txn_id = $wpdb->insert_id; 
  454.  
  455. /** 
  456. * Fires after a payment transaction is created in Gravity Forms 
  457. * @param int $txn_id The overall Transaction ID 
  458. * @param int $entry_id The new Entry ID 
  459. * @param string $transaction_type The Type of transaction that was made 
  460. * @param int $transaction_id The transaction ID 
  461. * @param string $amount The amount payed in the transaction 
  462. * @param bool $is_recurring True or false if this is an ongoing payment 
  463. */ 
  464. do_action( 'gform_post_payment_transaction', $txn_id, $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring ); 
  465. if ( has_filter( 'gform_post_payment_transaction' ) ) { 
  466. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_transaction.' ); 
  467.  
  468. return $txn_id; 
  469.  
  470. public function get_payment_feed( $entry, $form = false ) { 
  471. $submission_feed = false; 
  472.  
  473. if ( $entry['id'] ) { 
  474. $feeds = $this->get_feeds_by_entry( $entry['id'] ); 
  475. $submission_feed = empty( $feeds ) ? false : $this->get_feed( $feeds[0] ); 
  476. } elseif ( $form ) { 
  477. // getting all feeds 
  478. $feeds = $this->get_feeds( $form['id'] ); 
  479.  
  480. foreach ( $feeds as $feed ) { 
  481. if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) { 
  482. $submission_feed = $feed; 
  483. break; 
  484.  
  485.  
  486. return $submission_feed; 
  487.  
  488. protected function is_payment_gateway( $entry_id ) { 
  489.  
  490. if ( $this->is_payment_gateway ) { 
  491. return true; 
  492.  
  493. $gateway = gform_get_meta( $entry_id, 'payment_gateway' ); 
  494.  
  495. return $gateway == $this->_slug; 
  496.  
  497. public function get_submission_data( $feed, $form, $entry ) { 
  498.  
  499. $submission_data = array(); 
  500.  
  501. $submission_data['form_title'] = $form['title']; 
  502.  
  503. //getting mapped field data 
  504. $billing_fields = $this->billing_info_fields(); 
  505. foreach ( $billing_fields as $billing_field ) { 
  506. $field_name = $billing_field['name']; 
  507. $input_id = rgar( $feed['meta'], "billingInformation_{$field_name}" ); 
  508. $submission_data[ $field_name ] = $this->get_field_value( $form, $entry, $input_id ); 
  509.  
  510. //getting credit card field data 
  511. $card_field = $this->get_credit_card_field( $form ); 
  512. if ( $card_field ) { 
  513.  
  514. $submission_data['card_number'] = $this->remove_spaces_from_card_number( rgpost( "input_{$card_field->id}_1" ) ); 
  515. $submission_data['card_expiration_date'] = rgpost( "input_{$card_field->id}_2" ); 
  516. $submission_data['card_security_code'] = rgpost( "input_{$card_field->id}_3" ); 
  517. $submission_data['card_name'] = rgpost( "input_{$card_field->id}_5" ); 
  518.  
  519.  
  520. //getting product field data 
  521. $order_info = $this->get_order_data( $feed, $form, $entry ); 
  522. $submission_data = array_merge( $submission_data, $order_info ); 
  523.  
  524. /** 
  525. * Enables the Submission Data to be modified before it is used during feed processing by the payment add-on. 
  526. * @since 1.9.12.8 
  527. * @param array $submission_data The customer and transaction data. 
  528. * @param array $feed The Feed Object. 
  529. * @param array $form The Form Object. 
  530. * @param array $entry The Entry Object. 
  531. * @return array $submission_data 
  532. */ 
  533.  
  534. return gf_apply_filters( array( 'gform_submission_data_pre_process_payment', $form['id'] ), $submission_data, $feed, $form, $entry ); 
  535.  
  536. protected function get_credit_card_field( $form ) { 
  537. $fields = GFAPI::get_fields_by_type( $form, array( 'creditcard' ) ); 
  538.  
  539. return empty( $fields ) ? false : $fields[0]; 
  540.  
  541. protected function has_credit_card_field( $form ) { 
  542. return $this->get_credit_card_field( $form ) !== false; 
  543.  
  544. protected function get_order_data( $feed, $form, $entry ) { 
  545.  
  546. $products = GFCommon::get_product_fields( $form, $entry ); 
  547.  
  548. $payment_field = $feed['meta']['transactionType'] == 'product' ? rgars( $feed, 'meta/paymentAmount' ) : rgars( $feed, 'meta/recurringAmount' ); 
  549. $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false; 
  550. $trial_field = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false; 
  551.  
  552. $amount = 0; 
  553. $line_items = array(); 
  554. $discounts = array(); 
  555. $fee_amount = 0; 
  556. $trial_amount = 0; 
  557. foreach ( $products['products'] as $field_id => $product ) { 
  558.  
  559. $quantity = $product['quantity'] ? $product['quantity'] : 1; 
  560. $product_price = GFCommon::to_number( $product['price'], $entry['currency'] ); 
  561.  
  562. $options = array(); 
  563. if ( is_array( rgar( $product, 'options' ) ) ) { 
  564. foreach ( $product['options'] as $option ) { 
  565. $options[] = $option['option_name']; 
  566. $product_price += $option['price']; 
  567.  
  568. $is_trial_or_setup_fee = false; 
  569.  
  570. if ( ! empty( $trial_field ) && $trial_field == $field_id ) { 
  571.  
  572. $trial_amount = $product_price * $quantity; 
  573. $is_trial_or_setup_fee = true; 
  574.  
  575. } elseif ( ! empty( $setup_fee_field ) && $setup_fee_field == $field_id ) { 
  576.  
  577. $fee_amount = $product_price * $quantity; 
  578. $is_trial_or_setup_fee = true; 
  579.  
  580. //Do not add to line items if the payment field selected in the feed is not the current field. 
  581. if ( is_numeric( $payment_field ) && $payment_field != $field_id ) { 
  582. continue; 
  583.  
  584. //Do not add to line items if the payment field is set to "Form Total" and the current field was used for trial or setup fee. 
  585. if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ) { 
  586. continue; 
  587.  
  588. $amount += $product_price * $quantity; 
  589.  
  590. $description = ''; 
  591. if ( ! empty( $options ) ) { 
  592. $description = esc_html__( 'options: ', 'gravityforms' ) . ' ' . implode( ', ', $options ); 
  593.  
  594. if ( $product_price >= 0 ) { 
  595. $line_items[] = array( 
  596. 'id' => $field_id,  
  597. 'name' => $product['name'],  
  598. 'description' => $description,  
  599. 'quantity' => $quantity,  
  600. 'unit_price' => GFCommon::to_number( $product_price, $entry['currency'] ),  
  601. 'options' => rgar( $product, 'options' ) 
  602. ); 
  603. } else { 
  604. $discounts[] = array( 
  605. 'id' => $field_id,  
  606. 'name' => $product['name'],  
  607. 'description' => $description,  
  608. 'quantity' => $quantity,  
  609. 'unit_price' => GFCommon::to_number( $product_price, $entry['currency'] ),  
  610. 'options' => rgar( $product, 'options' ) 
  611. ); 
  612.  
  613. if ( $trial_field == 'enter_amount' ) { 
  614. $trial_amount = rgar( $feed['meta'], 'trial_amount' ) ? GFCommon::to_number( rgar( $feed['meta'], 'trial_amount' ), $entry['currency'] ) : 0; 
  615.  
  616. if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) { 
  617. $line_items[] = array( 
  618. 'id' => $products['shipping']['id'],  
  619. 'name' => $products['shipping']['name'],  
  620. 'description' => '',  
  621. 'quantity' => 1,  
  622. 'unit_price' => GFCommon::to_number( $products['shipping']['price'], $entry['currency'] ),  
  623. 'is_shipping' => 1 
  624. ); 
  625. $amount += $products['shipping']['price']; 
  626.  
  627. return array( 
  628. 'payment_amount' => $amount,  
  629. 'setup_fee' => $fee_amount,  
  630. 'trial' => $trial_amount,  
  631. 'line_items' => $line_items,  
  632. 'discounts' => $discounts 
  633. ); 
  634.  
  635. protected function is_callback_valid() { 
  636. if ( rgget( 'callback' ) != $this->_slug ) { 
  637. return false; 
  638.  
  639. return true; 
  640.  
  641.  
  642. //--------- Callback (aka Webhook)---------------- 
  643.  
  644. public function maybe_process_callback() { 
  645.  
  646. // ignoring requests that are not this addon's callbacks 
  647. if ( ! $this->is_callback_valid() ) { 
  648. return; 
  649.  
  650. // returns either false or an array of data about the callback request which payment add-on will then use 
  651. // to generically process the callback data 
  652. $this->log_debug( __METHOD__ . '(): Initializing callback processing for: ' . $this->_slug ); 
  653.  
  654. $callback_action = $this->callback(); 
  655.  
  656. $this->log_debug( __METHOD__ . '(): Result from gateway callback => ' . print_r( $callback_action, true ) ); 
  657.  
  658. $result = false; 
  659. if ( is_wp_error( $callback_action ) ) { 
  660. $this->display_callback_error( $callback_action ); 
  661. } elseif ( $callback_action && is_array( $callback_action ) && rgar( $callback_action, 'type' ) && ! rgar( $callback_action, 'abort_callback' ) ) { 
  662.  
  663. $result = $this->process_callback_action( $callback_action ); 
  664.  
  665. $this->log_debug( __METHOD__ . '(): Result of callback action => ' . print_r( $result, true ) ); 
  666.  
  667. if ( is_wp_error( $result ) ) { 
  668. $this->display_callback_error( $result ); 
  669. } elseif ( ! $result ) { 
  670. status_header( 200 ); 
  671. echo 'Callback could not be processed.'; 
  672. } else { 
  673. status_header( 200 ); 
  674. echo 'Callback processed successfully.'; 
  675. } else { 
  676. status_header( 200 ); 
  677. echo 'Callback bypassed'; 
  678.  
  679. $this->post_callback( $callback_action, $result ); 
  680.  
  681. die(); 
  682.  
  683. private function display_callback_error( $error ) { 
  684.  
  685. $data = $error->get_error_data(); 
  686. $status = ! rgempty( 'status_header', $data ) ? $data['status_header'] : 200; 
  687.  
  688. status_header( $status ); 
  689. echo $error->get_error_message(); 
  690.  
  691. /** 
  692. * Processes callback based on provided data. 
  693. * $action = array( 
  694. * 'type' => 'cancel_subscription', // required 
  695. * 'transaction_id' => '', // required (if payment) 
  696. * 'subscription_id' => '', // required (if subscription) 
  697. * 'amount' => '0.00', // required (some exceptions) 
  698. * 'entry_id' => 1, // required (some exceptions) 
  699. * 'transaction_type' => '',  
  700. * 'payment_status' => '',  
  701. * 'note' => '' 
  702. * ); 
  703. * @param array $action 
  704. * @return mixed 
  705. */ 
  706. private function process_callback_action( $action ) { 
  707. $this->log_debug( __METHOD__ . '(): Processing callback action.' ); 
  708. $action = wp_parse_args( 
  709. $action, array( 
  710. 'type' => false,  
  711. 'amount' => false,  
  712. 'transaction_type' => false,  
  713. 'transaction_id' => false,  
  714. 'subscription_id' => false,  
  715. 'entry_id' => false,  
  716. 'payment_status' => false,  
  717. 'note' => false,  
  718. ); 
  719.  
  720. $result = false; 
  721.  
  722. if ( rgar( $action, 'id' ) && $this->is_duplicate_callback( $action['id'] ) ) { 
  723. return new WP_Error( 'duplicate', sprintf( esc_html__( 'This webhook has already been processed (Event Id: %s)', 'gravityforms' ), $action['id'] ) ); 
  724.  
  725. $entry = GFAPI::get_entry( $action['entry_id'] ); 
  726. if ( ! $entry || is_wp_error( $entry ) ) { 
  727. return $result; 
  728.  
  729. //$action = do_action('gform_action_pre_payment_callback', $action, $entry); 
  730. do_action( 'gform_action_pre_payment_callback', $action, $entry ); 
  731. if ( has_filter( 'gform_action_pre_payment_callback' ) ) { 
  732. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_action_pre_payment_callback.' ); 
  733.  
  734. switch ( $action['type'] ) { 
  735. case 'complete_payment': 
  736. $result = $this->complete_payment( $entry, $action ); 
  737. break; 
  738. case 'refund_payment': 
  739. $result = $this->refund_payment( $entry, $action ); 
  740. break; 
  741. case 'fail_payment': 
  742. $result = $this->fail_payment( $entry, $action ); 
  743. break; 
  744. case 'add_pending_payment': 
  745. $result = $this->add_pending_payment( $entry, $action ); 
  746. break; 
  747. case 'void_authorization': 
  748. $result = $this->void_authorization( $entry, $action ); 
  749. break; 
  750. case 'create_subscription': 
  751. $result = $this->start_subscription( $entry, $action ); 
  752. $result = rgar( $result, 'payment_status' ) == 'Active' && rgar( $result, 'transaction_id' ) == rgar( $action, 'subscription_id' ); 
  753. break; 
  754. case 'cancel_subscription': 
  755. $feed = $this->get_payment_feed( $entry ); 
  756. $result = $this->cancel_subscription( $entry, $feed, $action['note'] ); 
  757. break; 
  758. case 'expire_subscription': 
  759. $result = $this->expire_subscription( $entry, $action ); 
  760. break; 
  761. case 'add_subscription_payment': 
  762. $result = $this->add_subscription_payment( $entry, $action ); 
  763. break; 
  764. case 'fail_subscription_payment': 
  765. $result = $this->fail_subscription_payment( $entry, $action ); 
  766. break; 
  767. default: 
  768. // handle custom events 
  769. if ( is_callable( array( $this, rgar( $action, 'callback' ) ) ) ) { 
  770. $result = call_user_func_array( array( $this, $action['callback'] ), array( $entry, $action ) ); 
  771. break; 
  772.  
  773. if ( rgar( $action, 'id' ) && $result ) { 
  774. $this->register_callback( $action['id'], $action['entry_id'] ); 
  775.  
  776. /** 
  777. * Fires right after the payment callback 
  778. * @param array $entry The Entry Object 
  779. * @param array $action The Action Object 
  780. * $action = array( 
  781. * 'type' => 'cancel_subscription', // See Below 
  782. * 'transaction_id' => '', // What is the ID of the transaction made? 
  783. * 'subscription_id' => '', // What is the ID of the Subscription made? 
  784. * 'amount' => '0.00', // Amount to charge? 
  785. * 'entry_id' => 1, // What entry to check? 
  786. * 'transaction_type' => '',  
  787. * 'payment_status' => '',  
  788. * 'note' => '' 
  789. * ); 
  790. * 'type' can be: 
  791. * - complete_payment 
  792. * - refund_payment 
  793. * - fail_payment 
  794. * - add_pending_payment 
  795. * - void_authorization 
  796. * - create_subscription 
  797. * - cancel_subscription 
  798. * - expire_subscription 
  799. * - add_subscription_payment 
  800. * - fail_subscription_payment 
  801. * @param mixed $result The Result Object 
  802. */ 
  803. do_action( 'gform_post_payment_callback', $entry, $action, $result ); 
  804. if ( has_filter( 'gform_post_payment_callback' ) ) { 
  805. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_callback.' ); 
  806.  
  807. return $result; 
  808.  
  809. protected function register_callback( $callback_id, $entry_id ) { 
  810. global $wpdb; 
  811.  
  812. $wpdb->insert( "{$wpdb->prefix}gf_addon_payment_callback", array( 
  813. 'addon_slug' => $this->_slug,  
  814. 'callback_id' => $callback_id,  
  815. 'lead_id' => $entry_id,  
  816. 'date_created' => gmdate( 'Y-m-d H:i:s' ) 
  817. ) ); 
  818.  
  819. protected function is_duplicate_callback( $callback_id ) { 
  820. global $wpdb; 
  821.  
  822. $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s AND callback_id=%s", $this->_slug, $callback_id ); 
  823. if ( $wpdb->get_var( $sql ) ) { 
  824. return true; 
  825.  
  826. return false; 
  827.  
  828. protected function callback() { 
  829.  
  830. protected function post_callback( $callback_action, $result ) { 
  831.  
  832.  
  833. // # PAYMENT INTERACTION FUNCTIONS 
  834.  
  835. public function add_pending_payment( $entry, $action ) { 
  836. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  837. if ( ! $action['payment_status'] ) { 
  838. $action['payment_status'] = 'Pending'; 
  839.  
  840. if ( ! $action['note'] ) { 
  841. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  842. $action['note'] = sprintf( esc_html__( 'Payment is pending. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $amount_formatted, $action['transaction_id'] ); 
  843.  
  844. GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] ); 
  845. $this->add_note( $entry['id'], $action['note'] ); 
  846. $this->post_payment_action( $entry, $action ); 
  847.  
  848. return true; 
  849.  
  850. public function complete_payment( &$entry, $action ) { 
  851. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  852. if ( ! rgar( $action, 'payment_status' ) ) { 
  853. $action['payment_status'] = 'Paid'; 
  854.  
  855. if ( ! rgar( $action, 'transaction_type' ) ) { 
  856. $action['transaction_type'] = 'payment'; 
  857.  
  858. if ( ! rgar( $action, 'payment_date' ) ) { 
  859. $action['payment_date'] = gmdate( 'y-m-d H:i:s' ); 
  860.  
  861. $entry['is_fulfilled'] = '1'; 
  862. $entry['transaction_id'] = rgar( $action, 'transaction_id' ); 
  863. $entry['transaction_type'] = '1'; 
  864. $entry['payment_status'] = $action['payment_status']; 
  865. $entry['payment_amount'] = rgar( $action, 'amount' ); 
  866. $entry['payment_date'] = $action['payment_date']; 
  867. $entry['payment_method'] = rgar( $action, 'payment_method' ); 
  868.  
  869. if ( ! rgar( $action, 'note' ) ) { 
  870. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  871. $action['note'] = sprintf( esc_html__( 'Payment has been completed. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $amount_formatted, $action['transaction_id'] ); 
  872.  
  873. GFAPI::update_entry( $entry ); 
  874. $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] ); 
  875. $this->add_note( $entry['id'], $action['note'], 'success' ); 
  876.  
  877. /** 
  878. * Fires after a payment is completed through a form 
  879. * @param array $entry The Entry object 
  880. * @param array $action The Action Object 
  881. * $action = array( 
  882. * 'type' => 'cancel_subscription', // See Below 
  883. * 'transaction_id' => '', // What is the ID of the transaction made? 
  884. * 'subscription_id' => '', // What is the ID of the Subscription made? 
  885. * 'amount' => '0.00', // Amount to charge? 
  886. * 'entry_id' => 1, // What entry to check? 
  887. * 'transaction_type' => '',  
  888. * 'payment_status' => '',  
  889. * 'note' => '' 
  890. * ); 
  891. * 'type' can be: 
  892. * - complete_payment 
  893. * - refund_payment 
  894. * - fail_payment 
  895. * - add_pending_payment 
  896. * - void_authorization 
  897. * - create_subscription 
  898. * - cancel_subscription 
  899. * - expire_subscription 
  900. * - add_subscription_payment 
  901. * - fail_subscription_payment 
  902. */ 
  903. do_action( 'gform_post_payment_completed', $entry, $action ); 
  904. if ( has_filter( 'gform_post_payment_completed' ) ) { 
  905. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_completed.' ); 
  906. $this->post_payment_action( $entry, $action ); 
  907.  
  908. return true; 
  909.  
  910. public function refund_payment( $entry, $action ) { 
  911. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  912. if ( ! $action['payment_status'] ) { 
  913. $action['payment_status'] = 'Refunded'; 
  914.  
  915. if ( ! $action['transaction_type'] ) { 
  916. $action['transaction_type'] = 'refund'; 
  917.  
  918. if ( ! $action['note'] ) { 
  919. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  920. $action['note'] = sprintf( esc_html__( 'Payment has been refunded. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $amount_formatted, $action['transaction_id'] ); 
  921.  
  922. GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] ); 
  923. $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] ); 
  924. $this->add_note( $entry['id'], $action['note'] ); 
  925.  
  926. /** 
  927. * Fires after a payment is refunded 
  928. * @param array $entry The Entry object 
  929. * @param array $action The Action Object 
  930. * $action = array( 
  931. * 'type' => 'cancel_subscription', // See Below 
  932. * 'transaction_id' => '', // What is the ID of the transaction made? 
  933. * 'subscription_id' => '', // What is the ID of the Subscription made? 
  934. * 'amount' => '0.00', // Amount to charge? 
  935. * 'entry_id' => 1, // What entry to check? 
  936. * 'transaction_type' => '',  
  937. * 'payment_status' => '',  
  938. * 'note' => '' 
  939. * ); 
  940. * 'type' can be: 
  941. * - complete_payment 
  942. * - refund_payment 
  943. * - fail_payment 
  944. * - add_pending_payment 
  945. * - void_authorization 
  946. * - create_subscription 
  947. * - cancel_subscription 
  948. * - expire_subscription 
  949. * - add_subscription_payment 
  950. * - fail_subscription_payment 
  951. */ 
  952. do_action( 'gform_post_payment_refunded', $entry, $action ); 
  953. if ( has_filter( 'gform_post_payment_refunded' ) ) { 
  954. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_refunded.' ); 
  955. $this->post_payment_action( $entry, $action ); 
  956.  
  957. return true; 
  958.  
  959. public function fail_payment( $entry, $action ) { 
  960. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  961. if ( ! $action['payment_status'] ) { 
  962. $action['payment_status'] = 'Failed'; 
  963.  
  964. if ( ! $action['note'] ) { 
  965. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  966. $action['note'] = sprintf( esc_html__( 'Payment has failed. Amount: %s.', 'gravityforms' ), $amount_formatted ); 
  967.  
  968. GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] ); 
  969. $this->add_note( $entry['id'], $action['note'] ); 
  970. $this->post_payment_action( $entry, $action ); 
  971.  
  972. return true; 
  973.  
  974. public function void_authorization( $entry, $action ) { 
  975. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  976. if ( ! $action['payment_status'] ) { 
  977. $action['payment_status'] = 'Voided'; 
  978.  
  979. if ( ! $action['note'] ) { 
  980. $action['note'] = sprintf( esc_html__( 'Authorization has been voided. Transaction Id: %s', 'gravityforms' ), $action['transaction_id'] ); 
  981.  
  982. GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] ); 
  983. $this->add_note( $entry['id'], $action['note'] ); 
  984. $this->post_payment_action( $entry, $action ); 
  985.  
  986. return true; 
  987.  
  988. /** 
  989. * Used to start a new subscription. Updates the associcated entry with the payment and transaction details and adds an entry note. 
  990. * @param [array] $entry Entry object 
  991. * @param [string] $subscription_id ID of the subscription 
  992. * @param [float] $amount Numeric amount of the initial subscription payment 
  993. * @return [array] $entry Entry Object 
  994. */ 
  995.  
  996. public function start_subscription( $entry, $subscription ) { 
  997. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  998. if ( ! $this->has_subscription( $entry ) ) { 
  999. $entry['payment_status'] = 'Active'; 
  1000. $entry['payment_amount'] = $subscription['amount']; 
  1001. $entry['payment_date'] = gmdate( 'y-m-d H:i:s' ); 
  1002. $entry['transaction_id'] = $subscription['subscription_id']; 
  1003. $entry['transaction_type'] = '2'; // subscription 
  1004. $entry['is_fulfilled'] = '1'; 
  1005.  
  1006. $result = GFAPI::update_entry( $entry ); 
  1007. $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription has been created. Subscription Id: %s.', 'gravityforms' ), $subscription['subscription_id'] ), 'success' ); 
  1008.  
  1009.  
  1010. /** 
  1011. * Fires when someone starts a subscription 
  1012. * @param array $entry Entry Object 
  1013. * @param array $subscription The new Subscription object 
  1014. */ 
  1015. do_action( 'gform_post_subscription_started', $entry, $subscription ); 
  1016. if ( has_filter( 'gform_post_subscription_started' ) ) { 
  1017. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_subscription_started.' ); 
  1018.  
  1019. $subscription['type'] = 'create_subscription'; 
  1020. $this->post_payment_action( $entry, $subscription ); 
  1021.  
  1022.  
  1023. return $entry; 
  1024.  
  1025. /** 
  1026. * A payment on an existing subscription. 
  1027. * @param [array] $data Transaction data including 'amount' and 'subscriber_id' 
  1028. * @param [array] $entry Entry object 
  1029. * @return true 
  1030. */ 
  1031. public function add_subscription_payment( $entry, $action ) { 
  1032. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  1033. if ( ! $action['transaction_type'] ) { 
  1034. $action['transaction_type'] = 'payment'; 
  1035.  
  1036. if ( ! $action['note'] ) { 
  1037. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  1038. $action['note'] = sprintf( esc_html__( 'Subscription has been paid. Amount: %s. Subscription Id: %s', 'gravityforms' ), $amount_formatted, $action['subscription_id'] ); 
  1039.  
  1040. $transaction_id = ! empty( $action['transaction_id'] ) ? $action['transaction_id'] : $action['subscription_id']; 
  1041.  
  1042. $this->insert_transaction( $entry['id'], $action['transaction_type'], $transaction_id, $action['amount'] ); 
  1043. $this->add_note( $entry['id'], $action['note'], 'success' ); 
  1044.  
  1045. /** 
  1046. * Fires after a payment is made on an existing subscription. 
  1047. * @param array $entry The Entry Object 
  1048. * @param array $action The Action Object 
  1049. * $action = array( 
  1050. * 'type' => 'cancel_subscription', // See Below 
  1051. * 'transaction_id' => '', // What is the ID of the transaction made? 
  1052. * 'subscription_id' => '', // What is the ID of the Subscription made? 
  1053. * 'amount' => '0.00', // Amount to charge? 
  1054. * 'entry_id' => 1, // What entry to check? 
  1055. * 'transaction_type' => '',  
  1056. * 'payment_status' => '',  
  1057. * 'note' => '' 
  1058. * ); 
  1059. * 'type' can be: 
  1060. * - complete_payment 
  1061. * - refund_payment 
  1062. * - fail_payment 
  1063. * - add_pending_payment 
  1064. * - void_authorization 
  1065. * - create_subscription 
  1066. * - cancel_subscription 
  1067. * - expire_subscription 
  1068. * - add_subscription_payment 
  1069. * - fail_subscription_payment 
  1070. */ 
  1071. do_action( 'gform_post_add_subscription_payment', $entry, $action ); 
  1072. if ( has_filter( 'gform_post_add_subscription_payment' ) ) { 
  1073. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_add_subscription_payment.' ); 
  1074. $this->post_payment_action( $entry, $action ); 
  1075.  
  1076. return true; 
  1077.  
  1078. public function fail_subscription_payment( $entry, $action ) { 
  1079. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  1080. if ( ! $action['note'] ) { 
  1081. $amount_formatted = GFCommon::to_money( $action['amount'], $entry['currency'] ); 
  1082. $action['note'] = sprintf( esc_html__( 'Subscription payment has failed. Amount: %s. Subscription Id: %s.', 'gravityforms' ), $amount_formatted, $action['subscription_id'] ); 
  1083.  
  1084. GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Failed' ); 
  1085. $this->add_note( $entry['id'], $action['note'], 'error' ); 
  1086.  
  1087. // keep 'gform_subscription_payment_failed' for backward compatability 
  1088. /** 
  1089. * @deprecated Use gform_post_fail_subscription_payment now 
  1090. */ 
  1091. do_action( 'gform_subscription_payment_failed', $entry, $action['subscription_id'] ); 
  1092. if ( has_filter( 'gform_subscription_payment_failed' ) ) { 
  1093. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_payment_failed.' ); 
  1094. /** 
  1095. * Fires after a subscription payment has failed 
  1096. * @param array $entry The Entry Object 
  1097. * @param array $action The Action Object 
  1098. * $action = array( 
  1099. * 'type' => 'cancel_subscription', // See Below 
  1100. * 'transaction_id' => '', // What is the ID of the transaction made? 
  1101. * 'subscription_id' => '', // What is the ID of the Subscription made? 
  1102. * 'amount' => '0.00', // Amount to charge? 
  1103. * 'entry_id' => 1, // What entry to check? 
  1104. * 'transaction_type' => '',  
  1105. * 'payment_status' => '',  
  1106. * 'note' => '' 
  1107. * ); 
  1108. * 'type' can be: 
  1109. * - complete_payment 
  1110. * - refund_payment 
  1111. * - fail_payment 
  1112. * - add_pending_payment 
  1113. * - void_authorization 
  1114. * - create_subscription 
  1115. * - cancel_subscription 
  1116. * - expire_subscription 
  1117. * - add_subscription_payment 
  1118. * - fail_subscription_payment 
  1119. */ 
  1120. do_action( 'gform_post_fail_subscription_payment', $entry, $action ); 
  1121. if ( has_filter( 'gform_post_fail_subscription_payment' ) ) { 
  1122. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_fail_subscription_payment.' ); 
  1123. $this->post_payment_action( $entry, $action ); 
  1124.  
  1125. return true; 
  1126.  
  1127. public function cancel_subscription( $entry, $feed, $note = null ) { 
  1128. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  1129. if ( ! $note ) { 
  1130. $note = sprintf( esc_html__( 'Subscription has been cancelled. Subscription Id: %s.', 'gravityforms' ), $entry['transaction_id'] ); 
  1131.  
  1132. if ( strtolower( $entry['payment_status'] ) == 'cancelled' ) { 
  1133. $this->log_debug( __METHOD__ . '(): Subscription is already canceled.' ); 
  1134.  
  1135. return false; 
  1136.  
  1137. GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Cancelled' ); 
  1138. $this->add_note( $entry['id'], $note ); 
  1139.  
  1140. // include $subscriber_id as 3rd parameter for backwards compatibility 
  1141. do_action( 'gform_subscription_canceled', $entry, $feed, $entry['transaction_id'] ); 
  1142. if ( has_filter( 'gform_subscription_canceled' ) ) { 
  1143. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_canceled.' ); 
  1144.  
  1145. $action = array( 
  1146. 'type' => 'cancel_subscription',  
  1147. 'subscription_id' => $entry['transaction_id'],  
  1148. 'entry_id' => $entry['id'],  
  1149. 'payment_status' => 'Cancelled',  
  1150. 'note' => $note,  
  1151. ); 
  1152. $this->post_payment_action( $entry, $action ); 
  1153.  
  1154. return true; 
  1155.  
  1156. public function expire_subscription( $entry, $action ) { 
  1157. $this->log_debug( __METHOD__ . '(): Processing request.' ); 
  1158. if ( ! $action['note'] ) { 
  1159. $action['note'] = sprintf( esc_html__( 'Subscription has expired. Subscriber Id: %s', 'gravityforms' ), $action['subscription_id'] ); 
  1160.  
  1161. GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Expired' ); 
  1162. $this->add_note( $entry['id'], $action['note'] ); 
  1163. $this->post_payment_action( $entry, $action ); 
  1164.  
  1165. return true; 
  1166.  
  1167. public function has_subscription( $entry ) { 
  1168. if ( rgar( $entry, 'transaction_type' ) == 2 && ! rgempty( 'transaction_id', $entry ) ) { 
  1169. return true; 
  1170. } else { 
  1171. return false; 
  1172.  
  1173. protected function get_entry_by_transaction_id( $transaction_id ) { 
  1174. global $wpdb; 
  1175.  
  1176. $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}rg_lead WHERE transaction_id = %s", $transaction_id ); 
  1177. $entry_id = $wpdb->get_var( $sql ); 
  1178.  
  1179. // //Try transaction table 
  1180. // if( empty($entry_id) ) { 
  1181. // $sql = $wpdb->prepare( "SELECT lead_id FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE transaction_id = %s", $transaction_id ); 
  1182. // $entry_id = $wpdb->get_var( $sql ); 
  1183. // } 
  1184.  
  1185. return $entry_id ? $entry_id : false; 
  1186.  
  1187. /** 
  1188. * Helper for making the gform_post_payment_action hook available to the various payment interaction methods. Also handles sending notifications for payment events. 
  1189. * @param array $entry 
  1190. * @param array $action 
  1191. */ 
  1192. public function post_payment_action( $entry, $action ) { 
  1193. do_action( 'gform_post_payment_action', $entry, $action ); 
  1194. if ( has_filter( 'gform_post_payment_action' ) ) { 
  1195. $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_action.' ); 
  1196.  
  1197. $form = GFAPI::get_form( $entry['form_id'] ); 
  1198. $supported_events = $this->supported_notification_events( $form ); 
  1199. if ( ! empty( $supported_events ) ) { 
  1200. GFAPI::send_notifications( $form, $entry, rgar( $action, 'type' ) ); 
  1201.  
  1202.  
  1203. // -------- Cron -------------------- 
  1204. protected function setup_cron() { 
  1205. // Setting up cron 
  1206. $cron_name = "{$this->_slug}_cron"; 
  1207.  
  1208. add_action( $cron_name, array( $this, 'check_status' ) ); 
  1209.  
  1210. if ( ! wp_next_scheduled( $cron_name ) ) { 
  1211. wp_schedule_event( time(), 'hourly', $cron_name ); 
  1212.  
  1213.  
  1214.  
  1215. public function check_status() { 
  1216.  
  1217.  
  1218. //--------- List Columns ------------ 
  1219. protected function feed_list_columns() { 
  1220. return array( 
  1221. 'feedName' => esc_html__( 'Name', 'gravityforms' ),  
  1222. 'transactionType' => esc_html__( 'Transaction Type', 'gravityforms' ),  
  1223. 'amount' => esc_html__( 'Amount', 'gravityforms' ) 
  1224. ); 
  1225.  
  1226. public function get_column_value_transactionType( $feed ) { 
  1227. switch ( rgar( $feed['meta'], 'transactionType' ) ) { 
  1228. case 'subscription' : 
  1229. return esc_html__( 'Subscription', 'gravityforms' ); 
  1230. break; 
  1231. case 'product' : 
  1232. return esc_html__( 'Products and Services', 'gravityforms' ); 
  1233. break; 
  1234. case 'donation' : 
  1235. return esc_html__( 'Donations', 'gravityforms' ); 
  1236. break; 
  1237.  
  1238.  
  1239. return esc_html__( 'Unsupported transaction type', 'gravityforms' ); 
  1240.  
  1241. public function get_column_value_amount( $feed ) { 
  1242. $form = $this->get_current_form(); 
  1243. $field_id = $feed['meta']['transactionType'] == 'subscription' ? rgars( $feed, 'meta/recurringAmount' ) : rgars( $feed, 'meta/paymentAmount' ); 
  1244. if ( $field_id == 'form_total' ) { 
  1245. $label = esc_html__( 'Form Total', 'gravityforms' ); 
  1246. } else { 
  1247. $field = GFFormsModel::get_field( $form, $field_id ); 
  1248. $label = GFCommon::get_label( $field ); 
  1249.  
  1250. return $label; 
  1251.  
  1252.  
  1253. //--------- Feed Settings ---------------- 
  1254.  
  1255. public function feed_list_message() { 
  1256.  
  1257. if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) { 
  1258. return $this->requires_credit_card_message(); 
  1259.  
  1260. return parent::feed_list_message(); 
  1261.  
  1262. public function requires_credit_card_message() { 
  1263. $url = add_query_arg( array( 'view' => null, 'subview' => null ) ); 
  1264.  
  1265. return sprintf( esc_html__( "You must add a Credit Card field to your form before creating a feed. Let's go %sadd one%s!", 'gravityforms' ), "<a href='" . esc_url( $url ) . "'>", '</a>' ); 
  1266.  
  1267. public function feed_settings_fields() { 
  1268.  
  1269. return array( 
  1270.  
  1271. array( 
  1272. 'description' => '',  
  1273. 'fields' => array( 
  1274. array( 
  1275. 'name' => 'feedName',  
  1276. 'label' => esc_html__( 'Name', 'gravityforms' ),  
  1277. 'type' => 'text',  
  1278. 'class' => 'medium',  
  1279. 'required' => true,  
  1280. 'tooltip' => '<h6>' . esc_html__( 'Name', 'gravityforms' ) . '</h6>' . esc_html__( 'Enter a feed name to uniquely identify this setup.', 'gravityforms' ) 
  1281. ),  
  1282. array( 
  1283. 'name' => 'transactionType',  
  1284. 'label' => esc_html__( 'Transaction Type', 'gravityforms' ),  
  1285. 'type' => 'select',  
  1286. 'onchange' => "jQuery(this).parents('form').submit();",  
  1287. 'choices' => array( 
  1288. array( 
  1289. 'label' => esc_html__( 'Select a transaction type', 'gravityforms' ),  
  1290. 'value' => '' 
  1291. ),  
  1292. array( 
  1293. 'label' => esc_html__( 'Products and Services', 'gravityforms' ),  
  1294. 'value' => 'product' 
  1295. ),  
  1296. array( 'label' => esc_html__( 'Subscription', 'gravityforms' ), 'value' => 'subscription' ),  
  1297. ),  
  1298. 'tooltip' => '<h6>' . esc_html__( 'Transaction Type', 'gravityforms' ) . '</h6>' . esc_html__( 'Select a transaction type.', 'gravityforms' ) 
  1299. ),  
  1300. ),  
  1301. array( 
  1302. 'title' => 'Subscription Settings',  
  1303. 'dependency' => array( 
  1304. 'field' => 'transactionType',  
  1305. 'values' => array( 'subscription' ) 
  1306. ),  
  1307. 'fields' => array( 
  1308. array( 
  1309. 'name' => 'recurringAmount',  
  1310. 'label' => esc_html__( 'Recurring Amount', 'gravityforms' ),  
  1311. 'type' => 'select',  
  1312. 'choices' => $this->recurring_amount_choices(),  
  1313. 'required' => true,  
  1314. 'tooltip' => '<h6>' . esc_html__( 'Recurring Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the recurring payment amount, or select 'Form Total' to use the total of all pricing fields as the recurring amount.", 'gravityforms' ) 
  1315. ),  
  1316. array( 
  1317. 'name' => 'billingCycle',  
  1318. 'label' => esc_html__( 'Billing Cycle', 'gravityforms' ),  
  1319. 'type' => 'billing_cycle',  
  1320. 'tooltip' => '<h6>' . esc_html__( 'Billing Cycle', 'gravityforms' ) . '</h6>' . esc_html__( 'Select your billing cycle. This determines how often the recurring payment should occur.', 'gravityforms' ) 
  1321. ),  
  1322. array( 
  1323. 'name' => 'recurringTimes',  
  1324. 'label' => esc_html__( 'Recurring Times', 'gravityforms' ),  
  1325. 'type' => 'select',  
  1326. 'choices' => array( 
  1327. array( 
  1328. 'label' => esc_html__( 'infinite', 'gravityforms' ),  
  1329. 'value' => '0' 
  1330. ) + $this->get_numeric_choices( 1, 100 ),  
  1331. 'tooltip' => '<h6>' . esc_html__( 'Recurring Times', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how many times the recurring payment should be made. The default is to bill the customer until the subscription is canceled.', 'gravityforms' ) 
  1332. ),  
  1333. array( 
  1334. 'name' => 'setupFee',  
  1335. 'label' => esc_html__( 'Setup Fee', 'gravityforms' ),  
  1336. 'type' => 'setup_fee',  
  1337. ),  
  1338. array( 
  1339. 'name' => 'trial',  
  1340. 'label' => esc_html__( 'Trial', 'gravityforms' ),  
  1341. 'type' => 'trial',  
  1342. 'hidden' => $this->get_setting( 'setupFee_enabled' ),  
  1343. 'tooltip' => '<h6>' . esc_html__( 'Trial Period', 'gravityforms' ) . '</h6>' . esc_html__( 'Enable a trial period. The user\'s recurring payment will not begin until after this trial period.', 'gravityforms' ) 
  1344. ),  
  1345. ),  
  1346. array( 
  1347. 'title' => 'Products & Services Settings',  
  1348. 'dependency' => array( 
  1349. 'field' => 'transactionType',  
  1350. 'values' => array( 'product', 'donation' ) 
  1351. ),  
  1352. 'fields' => array( 
  1353. array( 
  1354. 'name' => 'paymentAmount',  
  1355. 'label' => esc_html__( 'Payment Amount', 'gravityforms' ),  
  1356. 'type' => 'select',  
  1357. 'choices' => $this->product_amount_choices(),  
  1358. 'required' => true,  
  1359. 'default_value' => 'form_total',  
  1360. 'tooltip' => '<h6>' . esc_html__( 'Payment Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the payment amount, or select 'Form Total' to use the total of all pricing fields as the payment amount.", 'gravityforms' ) 
  1361. ),  
  1362. ),  
  1363. array( 
  1364. 'title' => esc_html__( 'Other Settings', 'gravityforms' ),  
  1365. 'dependency' => array( 
  1366. 'field' => 'transactionType',  
  1367. 'values' => array( 'subscription', 'product', 'donation' ) 
  1368. ),  
  1369. 'fields' => $this->other_settings_fields() 
  1370. ),  
  1371.  
  1372. ); 
  1373.  
  1374. public function other_settings_fields() { 
  1375. $other_settings = array( 
  1376. array( 
  1377. 'name' => 'billingInformation',  
  1378. 'label' => esc_html__( 'Billing Information', 'gravityforms' ),  
  1379. 'type' => 'field_map',  
  1380. 'field_map' => $this->billing_info_fields(),  
  1381. 'tooltip' => '<h6>' . esc_html__( 'Billing Information', 'gravityforms' ) . '</h6>' . esc_html__( 'Map your Form Fields to the available listed fields.', 'gravityforms' ) 
  1382. ),  
  1383. ); 
  1384.  
  1385. $option_choices = $this->option_choices(); 
  1386. if ( ! empty( $option_choices ) ) { 
  1387. $other_settings[] = array( 
  1388. 'name' => 'options',  
  1389. 'label' => esc_html__( 'Options', 'gravityforms' ),  
  1390. 'type' => 'checkbox',  
  1391. 'choices' => $option_choices,  
  1392. ); 
  1393.  
  1394. $other_settings[] = array( 
  1395. 'name' => 'conditionalLogic',  
  1396. 'label' => esc_html__( 'Conditional Logic', 'gravityforms' ),  
  1397. 'type' => 'feed_condition',  
  1398. 'tooltip' => '<h6>' . esc_html__( 'Conditional Logic', 'gravityforms' ) . '</h6>' . esc_html__( 'When conditions are enabled, form submissions will only be sent to the payment gateway when the conditions are met. When disabled, all form submissions will be sent to the payment gateway.', 'gravityforms' ) 
  1399. ); 
  1400.  
  1401. return $other_settings; 
  1402.  
  1403. public function settings_billing_cycle( $field, $echo = true ) { 
  1404.  
  1405. $intervals = $this->supported_billing_intervals(); 
  1406. //get unit so the length drop down is populated with the appropriate numbers for initial load 
  1407. $unit = $this->get_setting( $field['name'] . '_unit' ); 
  1408. //Length drop down 
  1409. $interval_keys = array_keys( $intervals ); 
  1410. if ( ! $unit ) { 
  1411. $first_interval = $intervals[ $interval_keys[0] ]; 
  1412. } else { 
  1413. $first_interval = $intervals[ $unit ]; 
  1414. $length_field = array( 
  1415. 'name' => $field['name'] . '_length',  
  1416. 'type' => 'select',  
  1417. 'choices' => $this->get_numeric_choices( $first_interval['min'], $first_interval['max'] ) 
  1418. ); 
  1419.  
  1420. $html = $this->settings_select( $length_field, false ); 
  1421.  
  1422. //Unit drop down 
  1423. $choices = array(); 
  1424. foreach ( $intervals as $unit => $interval ) { 
  1425. if ( ! empty( $interval ) ) { 
  1426. $choices[] = array( 'value' => $unit, 'label' => $interval['label'] ); 
  1427.  
  1428. $unit_field = array( 
  1429. 'name' => $field['name'] . '_unit',  
  1430. 'type' => 'select',  
  1431. 'onchange' => "loadBillingLength('" . esc_attr( $field['name'] ) . "')",  
  1432. 'choices' => $choices,  
  1433. ); 
  1434.  
  1435. $html .= ' ' . $this->settings_select( $unit_field, false ); 
  1436.  
  1437. $html .= "<script type='text/javascript'>var " . $field['name'] . '_intervals = ' . json_encode( $intervals ) . ';</script>'; 
  1438.  
  1439. if ( $echo ) { 
  1440. echo $html; 
  1441.  
  1442. return $html; 
  1443.  
  1444. public function settings_setup_fee( $field, $echo = true ) { 
  1445.  
  1446. $enabled_field = array( 
  1447. 'name' => $field['name'] . '_checkbox',  
  1448. 'type' => 'checkbox',  
  1449. 'horizontal' => true,  
  1450. 'choices' => array( 
  1451. array( 
  1452. 'label' => esc_html__( 'Enabled', 'gravityforms' ),  
  1453. 'name' => $field['name'] . '_enabled',  
  1454. 'value' => '1',  
  1455. 'onchange' => "if(jQuery(this).prop('checked')) {jQuery('#{$field['name']}_product').show('slow'); jQuery('#gaddon-setting-row-trial').hide('slow');} else {jQuery('#{$field['name']}_product').hide('slow'); jQuery('#gaddon-setting-row-trial').show('slow');}",  
  1456. ),  
  1457. ); 
  1458.  
  1459. $html = $this->settings_checkbox( $enabled_field, false ); 
  1460.  
  1461. $form = $this->get_current_form(); 
  1462.  
  1463. $is_enabled = $this->get_setting( "{$field['name']}_enabled" ); 
  1464.  
  1465. $product_field = array( 
  1466. 'name' => $field['name'] . '_product',  
  1467. 'type' => 'select',  
  1468. 'class' => $is_enabled ? '' : 'hidden',  
  1469. 'choices' => $this->get_payment_choices( $form ) 
  1470. ); 
  1471.  
  1472. $html .= ' ' . $this->settings_select( $product_field, false ); 
  1473.  
  1474. if ( $echo ) { 
  1475. echo $html; 
  1476.  
  1477. return $html; 
  1478.  
  1479. public function set_trial_onchange( $field ) { 
  1480.  
  1481. return "if(jQuery(this).prop('checked')) {jQuery('#{$field['name']}_product').show('slow');if (jQuery('#{$field['name']}_product').val() == 'enter_amount') {jQuery('#{$field['name']}_amount').show();}} else {jQuery('#{$field['name']}_product').hide('slow');jQuery('#{$field['name']}_amount').hide();}"; 
  1482.  
  1483.  
  1484. public function settings_trial( $field, $echo = true ) { 
  1485.  
  1486. //--- Enabled field --- 
  1487. $enabled_field = array( 
  1488. 'name' => $field['name'] . '_checkbox',  
  1489. 'type' => 'checkbox',  
  1490. 'horizontal' => true,  
  1491. 'choices' => array( 
  1492. array( 
  1493. 'label' => esc_html__( 'Enabled', 'gravityforms' ),  
  1494. 'name' => $field['name'] . '_enabled',  
  1495. 'value' => '1',  
  1496. 'onchange' => $this->set_trial_onchange( $field ) 
  1497. ),  
  1498. ); 
  1499.  
  1500. $html = $this->settings_checkbox( $enabled_field, false ); 
  1501.  
  1502. //--- Select Product field --- 
  1503. $form = $this->get_current_form(); 
  1504. $payment_choices = array_merge( $this->get_payment_choices( $form ), array( 
  1505. array( 
  1506. 'label' => esc_html__( 'Enter an amount', 'gravityforms' ),  
  1507. 'value' => 'enter_amount' 
  1508. ) ); 
  1509.  
  1510. $product_field = array( 
  1511. 'name' => $field['name'] . '_product',  
  1512. 'type' => 'select',  
  1513. 'class' => $this->get_setting( "{$field['name']}_enabled" ) ? '' : 'hidden',  
  1514. 'onchange' => "if(jQuery(this).val() == 'enter_amount') { jQuery('#{$field['name']}_amount').show();} else { jQuery('#{$field['name']}_amount').hide(); }",  
  1515. 'choices' => $payment_choices,  
  1516. ); 
  1517.  
  1518. $html .= ' ' . $this->settings_select( $product_field, false ); 
  1519.  
  1520. //--- Trial Amount field ---- 
  1521. $amount_field = array( 
  1522. 'type' => 'text',  
  1523. 'name' => "{$field['name']}_amount",  
  1524. 'class' => $this->get_setting( "{$field['name']}_enabled" ) && $this->get_setting( "{$field['name']}_product" ) == 'enter_amount' ? 'gform_currency' : 'hidden gform_currency',  
  1525. ); 
  1526.  
  1527. $html .= ' ' . $this->settings_text( $amount_field, false ); 
  1528.  
  1529.  
  1530. if ( $echo ) { 
  1531. echo $html; 
  1532.  
  1533. return $html; 
  1534.  
  1535. protected function recurring_amount_choices() { 
  1536. $form = $this->get_current_form(); 
  1537. $recurring_choices = $this->get_payment_choices( $form ); 
  1538. $recurring_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' ); 
  1539.  
  1540. return $recurring_choices; 
  1541.  
  1542. protected function product_amount_choices() { 
  1543. $form = $this->get_current_form(); 
  1544. $product_choices = $this->get_payment_choices( $form ); 
  1545. $product_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' ); 
  1546.  
  1547. return $product_choices; 
  1548.  
  1549. protected function option_choices() { 
  1550.  
  1551. $option_choices = array( 
  1552. array( 
  1553. 'label' => esc_html__( 'Sample Option', 'gravityforms' ),  
  1554. 'name' => 'sample_option',  
  1555. 'value' => 'sample_option' 
  1556. ),  
  1557. ); 
  1558.  
  1559. return $option_choices; 
  1560.  
  1561. protected function billing_info_fields() { 
  1562.  
  1563. $fields = array( 
  1564. array( 'name' => 'email', 'label' => esc_html__( 'Email', 'gravityforms' ), 'required' => false ),  
  1565. array( 'name' => 'address', 'label' => esc_html__( 'Address', 'gravityforms' ), 'required' => false ),  
  1566. array( 'name' => 'address2', 'label' => esc_html__( 'Address 2', 'gravityforms' ), 'required' => false ),  
  1567. array( 'name' => 'city', 'label' => esc_html__( 'City', 'gravityforms' ), 'required' => false ),  
  1568. array( 'name' => 'state', 'label' => esc_html__( 'State', 'gravityforms' ), 'required' => false ),  
  1569. array( 'name' => 'zip', 'label' => esc_html__( 'Zip', 'gravityforms' ), 'required' => false ),  
  1570. array( 'name' => 'country', 'label' => esc_html__( 'Country', 'gravityforms' ), 'required' => false ),  
  1571. ); 
  1572.  
  1573. return $fields; 
  1574.  
  1575. public function get_numeric_choices( $min, $max ) { 
  1576. $choices = array(); 
  1577. for ( $i = $min; $i <= $max; $i ++ ) { 
  1578. $choices[] = array( 'label' => $i, 'value' => $i ); 
  1579.  
  1580. return $choices; 
  1581.  
  1582. protected function supported_billing_intervals() { 
  1583.  
  1584. $billing_cycles = array( 
  1585. 'day' => array( 'label' => esc_html__( 'day(s)', 'gravityforms' ), 'min' => 1, 'max' => 365 ),  
  1586. 'week' => array( 'label' => esc_html__( 'week(s)', 'gravityforms' ), 'min' => 1, 'max' => 52 ),  
  1587. 'month' => array( 'label' => esc_html__( 'month(s)', 'gravityforms' ), 'min' => 1, 'max' => 12 ),  
  1588. 'year' => array( 'label' => esc_html__( 'year(s)', 'gravityforms' ), 'min' => 1, 'max' => 10 ) 
  1589. ); 
  1590.  
  1591. return $billing_cycles; 
  1592.  
  1593. protected function get_payment_choices( $form ) { 
  1594. $fields = GFAPI::get_fields_by_type( $form, array( 'product' ) ); 
  1595. $choices = array( 
  1596. array( 'label' => esc_html__( 'Select a product field', 'gravityforms' ), 'value' => '' ),  
  1597. ); 
  1598.  
  1599. foreach ( $fields as $field ) { 
  1600. $field_id = $field['id']; 
  1601. $field_label = RGFormsModel::get_label( $field ); 
  1602. $choices[] = array( 'value' => $field_id, 'label' => $field_label ); 
  1603.  
  1604. return $choices; 
  1605.  
  1606. //--------- Stats Page ------------------- 
  1607. public function get_results_page_config() { 
  1608.  
  1609. return array( 
  1610. 'title' => _x( 'Sales', 'toolbar label', 'gravityforms' ),  
  1611. 'search_title' => _x( 'Filter', 'metabox title', 'gravityforms' ),  
  1612. 'capabilities' => array( 'gravityforms_view_entries' ),  
  1613. 'callbacks' => array( 
  1614. 'fields' => array( $this, 'results_fields' ),  
  1615. 'data' => array( $this, 'results_data' ),  
  1616. 'markup' => array( $this, 'results_markup' ),  
  1617. 'filter_ui' => array( $this, 'results_filter_ui' ) 
  1618. ); 
  1619.  
  1620. public function results_fields( $form ) { 
  1621.  
  1622. if ( $this->has_feed( $form['id'] ) ) { 
  1623. return $form['fields']; 
  1624. } else { 
  1625. return false; 
  1626.  
  1627.  
  1628.  
  1629. public function results_markup( $html, $data, $form, $fields ) { 
  1630.  
  1631. $html = "<table width='100%' id='gaddon-results-summary'> 
  1632. <tr> 
  1633. <td class='gaddon-results-summary-label'>" . esc_html__( 'Today', 'gravityforms' ) . "</td> 
  1634. <td class='gaddon-results-summary-label'>" . esc_html__( 'Yesterday', 'gravityforms' ) . "</td> 
  1635. <td class='gaddon-results-summary-label'>" . esc_html__( 'Last 30 Days', 'gravityforms' ) . "</td> 
  1636. <td class='gaddon-results-summary-label'>" . esc_html__( 'Total', 'gravityforms' ) . "</td> 
  1637. </tr> 
  1638. <tr> 
  1639. <td class='gaddon-results-summary-data'> 
  1640. <div class='gaddon-results-summary-data-box'> 
  1641. <div class='gaddon-results-summary-primary'>{$data['summary']['today']['revenue']}</div> 
  1642. <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div> 
  1643. <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div> 
  1644. </div> 
  1645. </td> 
  1646. <td class='gaddon-results-summary-data'> 
  1647. <div class='gaddon-results-summary-data-box'> 
  1648. <div class='gaddon-results-summary-primary'>{$data['summary']['yesterday']['revenue']}</div> 
  1649. <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div> 
  1650. <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div> 
  1651. </div> 
  1652. </td> 
  1653.  
  1654. <td class='gaddon-results-summary-data'> 
  1655. <div class='gaddon-results-summary-data-box'> 
  1656. <div class='gaddon-results-summary-primary'>{$data['summary']['last30']['revenue']}</div> 
  1657. <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div> 
  1658. <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div> 
  1659. </div> 
  1660. </td> 
  1661. <td class='gaddon-results-summary-data'> 
  1662. <div class='gaddon-results-summary-data-box'> 
  1663. <div class='gaddon-results-summary-primary'>{$data['summary']['total']['revenue']}</div> 
  1664. <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div> 
  1665. <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . '</div> 
  1666. </div> 
  1667. </td> 
  1668.  
  1669. </tr> 
  1670. </table>'; 
  1671.  
  1672. if ( $data['row_count'] == '0' ) { 
  1673. $html .= "<div class='updated' style='padding:20px; margin-top:40px;'>" . esc_html__( "There aren't any transactions that match your criteria.", 'gravityforms' ) . '</div>'; 
  1674. } else { 
  1675. $chart_data = $this->get_chart_data( $data ); 
  1676. $html .= $this->get_sales_chart( $chart_data ); 
  1677.  
  1678. //Getting sales table markup 
  1679. $sales_table = new GFPaymentStatsTable( $data['table']['header'], $data['data'], $data['row_count'], $data['page_size'] ); 
  1680. $sales_table->prepare_items(); 
  1681. ob_start(); 
  1682. $sales_table->display(); 
  1683. $html .= ob_get_clean(); 
  1684.  
  1685. $html .= '</form>'; 
  1686.  
  1687. return $html; 
  1688.  
  1689. protected function get_chart_data( $data ) { 
  1690. $hAxis_column = $data['chart']['hAxis']['column']; 
  1691. $vAxis_column = $data['chart']['vAxis']['column']; 
  1692.  
  1693. $chart_data = array(); 
  1694. foreach ( $data['data'] as $row ) { 
  1695. $hAxis_value = $row[ $hAxis_column ]; 
  1696. $chart_data[ $hAxis_value ] = $row[ $vAxis_column ]; 
  1697.  
  1698. return array( 
  1699. 'hAxis_title' => $data['chart']['hAxis']['label'],  
  1700. 'vAxis_title' => $data['chart']['vAxis']['label'],  
  1701. 'data' => $chart_data 
  1702. ); 
  1703.  
  1704. public static function get_sales_chart( $sales_data ) { 
  1705. $markup = ''; 
  1706.  
  1707. $data_table = array(); 
  1708. $data_table[] = array( $sales_data['hAxis_title'], $sales_data['vAxis_title'] ); 
  1709.  
  1710. foreach ( $sales_data['data'] as $key => $value ) { 
  1711. $data_table[] = array( (string) $key, $value ); 
  1712.  
  1713. $chart_options = array( 
  1714. 'series' => array( 
  1715. '0' => array( 
  1716. 'color' => '#66CCFF',  
  1717. 'visibleInLegend' => 'false',  
  1718. ),  
  1719. ),  
  1720. 'hAxis' => array( 
  1721. 'title' => $sales_data['hAxis_title'],  
  1722. ),  
  1723. 'vAxis' => array( 
  1724. 'title' => $sales_data['vAxis_title'],  
  1725. ); 
  1726.  
  1727. $data_table_json = json_encode( $data_table ); 
  1728. $options_json = json_encode( $chart_options ); 
  1729. $div_id = 'gquiz-results-chart-field-score-frequencies'; 
  1730. $markup .= "<div class='gresults-chart-wrapper' style='width:100%;height:250px' id='{$div_id}'></div>"; 
  1731. $markup .= "<script> 
  1732. jQuery('#{$div_id}') 
  1733. .data('datatable', {$data_table_json}) 
  1734. .data('options', {$options_json}) 
  1735. .data('charttype', 'column'); 
  1736. </script>"; 
  1737.  
  1738. return $markup; 
  1739.  
  1740.  
  1741. public function results_data( $form, $fields, $search_criteria, $state_array ) { 
  1742.  
  1743. $summary = $this->get_sales_summary( $form['id'] ); 
  1744.  
  1745. $data = $this->get_sales_data( $form['id'], $search_criteria, $state_array ); 
  1746.  
  1747. return array( 
  1748. 'entry_count' => $data['row_count'],  
  1749. 'row_count' => $data['row_count'],  
  1750. 'page_size' => $data['page_size'],  
  1751. 'status' => 'complete',  
  1752. 'summary' => $summary,  
  1753. 'data' => $data['rows'],  
  1754. 'chart' => $data['chart'],  
  1755. 'table' => $data['table'],  
  1756. ); 
  1757.  
  1758. private function get_mysql_tz_offset() { 
  1759. $tz_offset = get_option( 'gmt_offset' ); 
  1760.  
  1761. //add + if offset starts with a number 
  1762. if ( is_numeric( substr( $tz_offset, 0, 1 ) ) ) { 
  1763. $tz_offset = '+' . $tz_offset; 
  1764.  
  1765. return $tz_offset . ':00'; 
  1766.  
  1767. protected function get_sales_data( $form_id, $search, $state ) { 
  1768. global $wpdb; 
  1769.  
  1770. $data = array( 
  1771. 'chart' => array( 
  1772. 'hAxis' => array(),  
  1773. 'vAxis' => array( 
  1774. 'column' => 'revenue',  
  1775. 'label' => esc_html__( 'Revenue', 'gravityforms' ) 
  1776. ),  
  1777. 'table' => array( 
  1778. 'header' => array( 
  1779. 'orders' => esc_html__( 'Orders', 'gravityforms' ),  
  1780. 'subscriptions' => esc_html__( 'Subscriptions', 'gravityforms' ),  
  1781. 'recurring_payments' => esc_html__( 'Recurring Payments', 'gravityforms' ),  
  1782. 'refunds' => esc_html__( 'Refunds', 'gravityforms' ),  
  1783. 'revenue' => esc_html__( 'Revenue', 'gravityforms' ) 
  1784. ),  
  1785. 'rows' => array() 
  1786. ); 
  1787.  
  1788. $tz_offset = $this->get_mysql_tz_offset(); 
  1789.  
  1790. $page_size = 10; 
  1791. $group = strtolower( rgpost( 'group' ) ); 
  1792. switch ( $group ) { 
  1793.  
  1794. case 'weekly' : 
  1795. $select = "concat(left(lead.week, 4), ' - ', right(lead.week, 2)) as week"; 
  1796. $select_inner1 = "yearweek(CONVERT_TZ(date_created, '+00:00', '" . $tz_offset . "')) week"; 
  1797. $select_inner2 = "yearweek(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) week"; 
  1798. $group_by = 'week'; 
  1799. $order_by = 'week desc'; 
  1800. $join = 'lead.week = transaction.week'; 
  1801.  
  1802. $data['chart']['hAxis']['column'] = 'week'; 
  1803. $data['chart']['hAxis']['label'] = esc_html__( 'Week', 'gravityforms' ); 
  1804. $data['table']['header'] = array_merge( array( 'week' => esc_html__( 'Week', 'gravityforms' ) ), $data['table']['header'] ); 
  1805. break; 
  1806.  
  1807. case 'monthly' : 
  1808. $select = "date_format(lead.month, '%%Y') as year, date_format(lead.month, '%%c') as month, '' as month_abbrev, '' as month_year"; 
  1809. $select_inner1 = "date_format(CONVERT_TZ(date_created, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') month"; 
  1810. $select_inner2 = "date_format(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') month"; 
  1811. $group_by = 'month'; 
  1812. $order_by = 'year desc, month desc'; 
  1813. $join = 'lead.month = transaction.month'; 
  1814.  
  1815. $data['chart']['hAxis']['column'] = 'month_abbrev'; 
  1816. $data['chart']['hAxis']['label'] = esc_html__( 'Month', 'gravityforms' ); 
  1817. $data['table']['header'] = array_merge( array( 'month_year' => esc_html__( 'Month', 'gravityforms' ) ), $data['table']['header'] ); 
  1818. break; 
  1819.  
  1820. default : //daily 
  1821. $select = "lead.date, date_format(lead.date, '%%c') as month, day(lead.date) as day, dayname(lead.date) as day_of_week, '' as month_day"; 
  1822. $select_inner1 = "date(CONVERT_TZ(date_created, '+00:00', '" . $tz_offset . "')) as date"; 
  1823. $select_inner2 = "date(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) as date"; 
  1824. $group_by = 'date'; 
  1825. $order_by = 'date desc'; 
  1826. $join = 'lead.date = transaction.date'; 
  1827.  
  1828. $data['chart']['hAxis']['column'] = 'month_day'; 
  1829. $data['chart']['hAxis']['label'] = esc_html__( 'Day', 'gravityforms' ); 
  1830. $data['table']['header'] = array_merge( array( 
  1831. 'date' => esc_html__( 'Date', 'gravityforms' ),  
  1832. 'day_of_week' => esc_html__( 'Day', 'gravityforms' ) 
  1833. ), $data['table']['header'] ); 
  1834. break; 
  1835.  
  1836. $lead_date_filter = ''; 
  1837. $transaction_date_filter = ''; 
  1838. if ( isset( $search['start_date'] ) ) { 
  1839. $lead_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.date_created, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] ); 
  1840. $transaction_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] ); 
  1841.  
  1842. if ( isset( $search['end_date'] ) ) { 
  1843. $lead_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.date_created, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] ); 
  1844. $transaction_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] ); 
  1845.  
  1846. $payment_method = rgpost( 'payment_method' ); 
  1847. $payment_method_filter = ''; 
  1848. if ( ! empty( $payment_method ) ) { 
  1849. $payment_method_filter = $wpdb->prepare( ' AND l.payment_method=%s', $payment_method ); 
  1850.  
  1851. $current_page = rgempty( 'paged' ) ? 1 : absint( rgpost( 'paged' ) ); 
  1852. $offset = $page_size * ( $current_page - 1 ); 
  1853.  
  1854. $sql = $wpdb->prepare( 
  1855. " SELECT SQL_CALC_FOUND_ROWS {$select}, lead.orders, lead.subscriptions, transaction.refunds, transaction.recurring_payments, transaction.revenue 
  1856. FROM ( 
  1857. SELECT {$select_inner1},  
  1858. sum( if(transaction_type = 1, 1, 0) ) as orders,  
  1859. sum( if(transaction_type = 2, 1, 0) ) as subscriptions 
  1860. FROM {$wpdb->prefix}rg_lead l 
  1861. WHERE l.status='active' AND form_id=%d {$lead_date_filter} {$payment_method_filter} 
  1862. GROUP BY {$group_by} 
  1863. ) AS lead 
  1864.  
  1865. LEFT OUTER JOIN( 
  1866. SELECT {$select_inner2},  
  1867. sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue,  
  1868. sum( if(t.transaction_type = 'refund', 1, 0) ) as refunds,  
  1869. sum( if(t.transaction_type = 'payment' AND t.is_recurring = 1, 1, 0) ) as recurring_payments 
  1870. FROM {$wpdb->prefix}gf_addon_payment_transaction t 
  1871. INNER JOIN {$wpdb->prefix}rg_lead l ON l.id = t.lead_id 
  1872. WHERE l.status='active' AND l.form_id=%d {$lead_date_filter} {$transaction_date_filter} {$payment_method_filter} 
  1873. GROUP BY {$group_by} 
  1874.  
  1875. ) AS transaction on {$join} 
  1876. ORDER BY {$order_by} 
  1877. LIMIT $page_size OFFSET $offset 
  1878. ", $form_id, $form_id 
  1879. ); 
  1880.  
  1881.  
  1882. $results = $wpdb->get_results( $sql, ARRAY_A ); 
  1883. foreach ( $results as &$result ) { 
  1884. $result['orders'] = intval( $result['orders'] ); 
  1885. $result['subscriptions'] = intval( $result['subscriptions'] ); 
  1886. $result['refunds'] = intval( $result['refunds'] ); 
  1887. $result['recurring_payments'] = intval( $result['recurring_payments'] ); 
  1888. $result['revenue'] = floatval( $result['revenue'] ); 
  1889.  
  1890. $result = $this->format_chart_h_axis( $result ); 
  1891.  
  1892.  
  1893. $data['row_count'] = $wpdb->get_var( 'SELECT FOUND_ROWS()' ); 
  1894. $data['page_size'] = $page_size; 
  1895.  
  1896. $data['rows'] = $results; 
  1897.  
  1898. return $data; 
  1899.  
  1900.  
  1901. protected function format_chart_h_axis( $result ) { 
  1902. $months = array( 
  1903. esc_html__( 'Jan', 'gravityforms' ),  
  1904. esc_html__( 'Feb', 'gravityforms' ),  
  1905. esc_html__( 'Mar', 'gravityforms' ),  
  1906. esc_html__( 'Apr', 'gravityforms' ),  
  1907. esc_html__( 'May', 'gravityforms' ),  
  1908. esc_html__( 'Jun', 'gravityforms' ),  
  1909. esc_html__( 'Jul', 'gravityforms' ),  
  1910. esc_html__( 'Aug', 'gravityforms' ),  
  1911. esc_html__( 'Sep', 'gravityforms' ),  
  1912. esc_html__( 'Oct', 'gravityforms' ),  
  1913. esc_html__( 'Nov', 'gravityforms' ),  
  1914. esc_html__( 'Dec', 'gravityforms' ) 
  1915. ); 
  1916.  
  1917. if ( isset( $result['month_abbrev'] ) ) { 
  1918. $result['month_abbrev'] = $months[ intval( $result['month'] ) - 1 ]; 
  1919. $result['month_year'] = $months[ intval( $result['month'] ) - 1 ] . ', ' . $result['year']; 
  1920.  
  1921. return $result; 
  1922. } elseif ( isset( $result['month_day'] ) ) { 
  1923. $result['month_day'] = $months[ intval( $result['month'] ) - 1 ] . ' ' . $result['day']; 
  1924.  
  1925. return $result; 
  1926.  
  1927. return $result; 
  1928.  
  1929. protected function get_sales_summary( $form_id ) { 
  1930. global $wpdb; 
  1931.  
  1932. $tz_offset = $this->get_mysql_tz_offset(); 
  1933.  
  1934. $summary = $wpdb->get_results( 
  1935. $wpdb->prepare( 
  1936. SELECT lead.date, lead.orders, lead.subscriptions, transaction.revenue 
  1937. FROM ( 
  1938. SELECT date( CONVERT_TZ(date_created, '+00:00', '" . $tz_offset . "') ) as date,  
  1939. sum( if(transaction_type = 1, 1, 0) ) as orders,  
  1940. sum( if(transaction_type = 2, 1, 0) ) as subscriptions 
  1941. FROM {$wpdb->prefix}rg_lead 
  1942. WHERE status='active' AND form_id = %d AND datediff(now(), CONVERT_TZ(date_created, '+00:00', '" . $tz_offset . "') ) <= 30 
  1943. GROUP BY date 
  1944. ) AS lead 
  1945.  
  1946. LEFT OUTER JOIN( 
  1947. SELECT date( CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "') ) as date,  
  1948. sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue 
  1949. FROM {$wpdb->prefix}gf_addon_payment_transaction t 
  1950. INNER JOIN {$wpdb->prefix}rg_lead l ON l.id = t.lead_id 
  1951. WHERE l.form_id=%d AND l.status='active' 
  1952. GROUP BY date 
  1953. ) AS transaction on lead.date = transaction.date 
  1954. ORDER BY date desc", $form_id, $form_id 
  1955. ), ARRAY_A 
  1956. ); 
  1957.  
  1958. $total_summary = $wpdb->get_results( 
  1959. $wpdb->prepare( 
  1960. SELECT sum( if(transaction_type = 1, 1, 0) ) as orders,  
  1961. sum( if(transaction_type = 2, 1, 0) ) as subscriptions 
  1962. FROM {$wpdb->prefix}rg_lead 
  1963. WHERE form_id=%d AND status='active'", $form_id 
  1964. ), ARRAY_A 
  1965. ); 
  1966.  
  1967. $total_revenue = $wpdb->get_var( 
  1968. $wpdb->prepare( 
  1969. SELECT sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue 
  1970. FROM {$wpdb->prefix}gf_addon_payment_transaction t 
  1971. INNER JOIN {$wpdb->prefix}rg_lead l ON l.id = t.lead_id 
  1972. WHERE l.form_id=%d AND status='active'", $form_id 
  1973. ); 
  1974.  
  1975.  
  1976. $result = array( 
  1977. 'today' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),  
  1978. 'yesterday' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),  
  1979. 'last30' => array( 'revenue' => 0, 'orders' => 0, 'subscriptions' => 0 ),  
  1980. 'total' => array( 
  1981. 'revenue' => GFCommon::to_money( $total_revenue ),  
  1982. 'orders' => $total_summary[0]['orders'],  
  1983. 'subscriptions' => $total_summary[0]['subscriptions'] 
  1984. ); 
  1985.  
  1986. $local_time = GFCommon::get_local_timestamp(); 
  1987. $today = gmdate( 'Y-m-d', $local_time ); 
  1988. $yesterday = gmdate( 'Y-m-d', strtotime( '-1 day', $local_time ) ); 
  1989.  
  1990. foreach ( $summary as $day ) { 
  1991. if ( $day['date'] == $today ) { 
  1992. $result['today']['revenue'] = GFCommon::to_money( $day['revenue'] ); 
  1993. $result['today']['orders'] = $day['orders']; 
  1994. $result['today']['subscriptions'] = $day['subscriptions']; 
  1995. } elseif ( $day['date'] == $yesterday ) { 
  1996. $result['yesterday']['revenue'] = GFCommon::to_money( $day['revenue'] ); 
  1997. $result['yesterday']['orders'] = $day['orders']; 
  1998. $result['yesterday']['subscriptions'] = $day['subscriptions']; 
  1999.  
  2000. $is_within_30_days = strtotime( $day['date'] ) >= strtotime( '-30 days', $local_time ); 
  2001. if ( $is_within_30_days ) { 
  2002. $result['last30']['revenue'] += floatval( $day['revenue'] ); 
  2003. $result['last30']['orders'] += floatval( $day['orders'] ); 
  2004. $result['last30']['subscriptions'] += floatval( $day['subscriptions'] ); 
  2005.  
  2006. $result['last30']['revenue'] = GFCommon::to_money( $result['last30']['revenue'] ); 
  2007.  
  2008. return $result; 
  2009.  
  2010. public function results_filter_ui( $filter_ui, $form_id, $page_title, $gf_page, $gf_view ) { 
  2011.  
  2012. if ( $gf_view == "gf_results_{$this->_slug}" ) { 
  2013. unset( $filter_ui['fields'] ); 
  2014.  
  2015. $view_markup = "<div> 
  2016. <select id='gaddon-sales-group' name='group'> 
  2017. <option value='daily' " . selected( 'daily', rgget( 'group' ), false ) . '>' . esc_html__( 'Daily', 'gravityforms' ) . "</option> 
  2018. <option value='weekly' " . selected( 'weekly', rgget( 'group' ), false ) . '>' . esc_html__( 'Weekly', 'gravityforms' ) . "</option> 
  2019. <option value='monthly' " . selected( 'monthly', rgget( 'group' ), false ) . '>' . esc_html__( 'Monthly', 'gravityforms' ) . '</option> 
  2020. </select> 
  2021. </div>'; 
  2022. $view_filter = array( 
  2023. 'view' => array( 
  2024. 'label' => esc_html__( 'View', 'gravityforms' ),  
  2025. 'tooltip' => '<h6>' . esc_html__( 'View', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how you would like the sales data to be displayed.', 'gravityforms' ),  
  2026. 'markup' => $view_markup 
  2027. ); 
  2028.  
  2029. $payment_methods = $this->get_payment_methods( $form_id ); 
  2030.  
  2031. $payment_method_markup = " 
  2032. <div> 
  2033. <select id='gaddon-sales-group' name='payment_method'> 
  2034. <option value=''>" . esc_html__( _x( 'Any', 'regarding a payment method', 'gravityforms' ) ) . '</option>'; 
  2035.  
  2036. foreach ( $payment_methods as $payment_method ) { 
  2037. $payment_method_markup .= "<option value='" . esc_attr( $payment_method ) . "' " . selected( $payment_method, rgget( 'payment_method' ), false ) . '>' . $payment_method . '</option>'; 
  2038. $payment_method_markup .= ' 
  2039. </select> 
  2040. </div>'; 
  2041.  
  2042. $payment_method_filter = array( 
  2043. 'payment_method' => array( 
  2044. 'label' => esc_html__( 'Payment Method', 'gravityforms' ),  
  2045. 'tooltip' => '',  
  2046. 'markup' => $payment_method_markup 
  2047. ); 
  2048.  
  2049.  
  2050. $filter_ui = array_merge( $view_filter, $payment_method_filter, $filter_ui ); 
  2051.  
  2052. return $filter_ui; 
  2053.  
  2054.  
  2055. protected function get_payment_methods( $form_id ) { 
  2056. global $wpdb; 
  2057.  
  2058. $payment_methods = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT payment_method FROM {$wpdb->prefix}rg_lead WHERE form_id=%d", $form_id ) ); 
  2059.  
  2060. return array_filter( $payment_methods, array( $this, 'array_filter_non_blank' ) ); 
  2061.  
  2062. protected function array_filter_non_blank( $value ) { 
  2063. if ( empty( $value ) || $value == 'null' ) { 
  2064. return false; 
  2065.  
  2066. return true; 
  2067.  
  2068.  
  2069. //-------- Uninstall --------------------- 
  2070. protected function uninstall() { 
  2071. global $wpdb; 
  2072.  
  2073. // deleting transactions 
  2074. $sql = $wpdb->prepare( 
  2075. "DELETE FROM {$wpdb->prefix}gf_addon_payment_transaction 
  2076. WHERE lead_id IN 
  2077. (SELECT lead_id FROM {$wpdb->prefix}rg_lead_meta WHERE meta_key='payment_gateway' AND meta_value=%s)", $this->_slug 
  2078. ); 
  2079. $wpdb->query( $sql ); 
  2080.  
  2081. // deleting callback log 
  2082. $sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s", $this->_slug ); 
  2083. $wpdb->query( $sql ); 
  2084.  
  2085. //clear cron 
  2086. wp_clear_scheduled_hook( $this->_slug . '_cron' ); 
  2087.  
  2088. parent::uninstall(); 
  2089.  
  2090. //-------- Scripts ----------------------- 
  2091. public function scripts() { 
  2092. $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min'; 
  2093. $scripts = array( 
  2094. array( 
  2095. 'handle' => 'gaddon_payment',  
  2096. 'src' => $this->get_gfaddon_base_url() . "/js/gaddon_payment{$min}.js",  
  2097. 'version' => GFCommon::$version,  
  2098. 'strings' => array( 
  2099. 'subscriptionCancelWarning' => __( "Warning! This subscription will be canceled. This cannot be undone. 'OK' to cancel subscription, 'Cancel' to stop", 'gravityforms' ),  
  2100. 'subscriptionCancelNonce' => wp_create_nonce( 'gaddon_cancel_subscription' ),  
  2101. 'subscriptionCanceled' => __( 'Canceled', 'gravityforms' ),  
  2102. 'subscriptionError' => __( 'The subscription could not be canceled. Please try again later.', 'gravityforms' ) 
  2103. ),  
  2104. 'enqueue' => array( 
  2105. array( 'admin_page' => array( 'form_settings' ), 'tab' => $this->_slug ),  
  2106. array( 'admin_page' => array( 'entry_view' ) ),  
  2107. ),  
  2108. array( 
  2109. 'handle' => 'gaddon_token',  
  2110. 'src' => $this->get_gfaddon_base_url() . "/js/gaddon_token{$min}.js",  
  2111. 'version' => GFCommon::$version,  
  2112. 'deps' => array( 'jquery' ),  
  2113. 'in_footer' => false,  
  2114. 'enqueue' => array( 
  2115. array( $this, 'enqueue_creditcard_token_script' ) 
  2116. ),  
  2117. array( 
  2118. 'handle' => 'gform_form_admin',  
  2119. 'enqueue' => array( 
  2120. array( 'admin_page' => array( 'entry_edit' ) ),  
  2121. ),  
  2122. ),  
  2123. ); 
  2124.  
  2125. return array_merge( parent::scripts(), $scripts ); 
  2126.  
  2127.  
  2128. //----- Javascript Credit Card Tokens ---- 
  2129. /** 
  2130. * Override to support creating credit card tokens via Javascript. 
  2131. * @access public 
  2132. * @param mixed $form 
  2133. * @return array 
  2134. */ 
  2135. public function creditcard_token_info( $form ) { 
  2136.  
  2137. return array(); 
  2138.  
  2139.  
  2140. /** 
  2141. * Add input field for credit card token response. 
  2142. * @access public 
  2143. * @param string $content 
  2144. * @param array $field 
  2145. * @param string $value 
  2146. * @param string $entry_id 
  2147. * @param string $form_id 
  2148. * @return string 
  2149. */ 
  2150. public function add_creditcard_token_input( $content, $field, $value, $entry_id, $form_id ) { 
  2151.  
  2152. if ( ! $this->has_feed( $form_id ) || GFFormsModel::get_input_type( $field ) != 'creditcard' ) { 
  2153. return $content; 
  2154.  
  2155. $form = GFAPI::get_form( $form_id ); 
  2156. if ( ! $this->creditcard_token_info( $form ) ) { 
  2157. return $content; 
  2158.  
  2159. $slug = str_replace( 'gravityforms', '', $this->_slug ); 
  2160. $content .= '<input type=\'hidden\' name=\'' . $slug . '_response\' id=\'gf_' . $slug . '_response\' value=\'' . rgpost( $slug . '_response' ) . '\' />'; 
  2161.  
  2162. return $content; 
  2163.  
  2164.  
  2165. /** 
  2166. * Enables AJAX for forms that create credit card tokens via Javascript. 
  2167. * @access public 
  2168. * @param array $args 
  2169. * @return array 
  2170. */ 
  2171. public function force_ajax_for_creditcard_tokens( $args ) { 
  2172.  
  2173. $form = GFAPI::get_form( rgar( $args, 'form_id' ) ); 
  2174.  
  2175. $args['ajax'] = $this->enqueue_creditcard_token_script( $form ) ? true : $args['ajax']; 
  2176.  
  2177. return $args; 
  2178.  
  2179.  
  2180. /** 
  2181. * Determines if GFToken script should be enqueued. 
  2182. * @access public 
  2183. * @param array $form 
  2184. * @return bool 
  2185. */ 
  2186. public function enqueue_creditcard_token_script( $form ) { 
  2187.  
  2188. return $form && $this->has_feed( $form['id'] ) && $this->creditcard_token_info( $form ); 
  2189.  
  2190.  
  2191. /** 
  2192. * Prepare Javascript for creating credit card tokens. 
  2193. * @access public 
  2194. * @param array $form 
  2195. * @param array $field_values 
  2196. * @param bool $is_ajax 
  2197. * @return void 
  2198. */ 
  2199. public function register_creditcard_token_script( $form, $field_values, $is_ajax ) { 
  2200.  
  2201. if ( ! $this->enqueue_creditcard_token_script( $form ) ) { 
  2202. return; 
  2203.  
  2204. /** Prepare GFToken object. */ 
  2205. $gftoken = array( 
  2206. 'callback' => 'GF_' . str_replace( ' ', '', $this->_short_title ),  
  2207. 'feeds' => $this->creditcard_token_info( $form ),  
  2208. 'formId' => rgar( $form, 'id' ),  
  2209. 'hasPages' => GFCommon::has_pages( $form ),  
  2210. 'pageCount' => GFFormDisplay::get_max_page_number( $form ),  
  2211. 'responseField' => '#gf_' . str_replace( 'gravityforms', '', $this->_slug ) . '_response' 
  2212. ); 
  2213.  
  2214. /** Get needed fields. */ 
  2215. $gftoken['fields'] = $this->get_creditcard_token_entry_fields( $gftoken['feeds'] ); 
  2216.  
  2217. $script = 'new GFToken( ' . json_encode( $gftoken ) . ' );'; 
  2218. GFFormDisplay::add_init_script( $form['id'], 'GFToken', GFFormDisplay::ON_PAGE_RENDER, $script ); 
  2219.  
  2220.  
  2221. /** 
  2222. * Get needed fields for creating credit card tokens. 
  2223. * @access public 
  2224. * @param array $feeds 
  2225. * @return array $fields 
  2226. */ 
  2227. public function get_creditcard_token_entry_fields( $feeds ) { 
  2228.  
  2229. $fields = array(); 
  2230.  
  2231. foreach ( $feeds as $feed ) { 
  2232. foreach ( $feed['billing_fields'] as $billing_field ) { 
  2233. $fields[] = $billing_field; 
  2234.  
  2235. return array_unique( $fields ); 
  2236.  
  2237.  
  2238. //-------- Currency ---------------------- 
  2239. /** 
  2240. * Override this function to add or remove currencies from the list of supported currencies 
  2241. * @param $currencies - Currently supported currencies 
  2242. * @return mixed - A filtered list of supported currencies 
  2243. */ 
  2244. public function supported_currencies( $currencies ) { 
  2245. return $currencies; 
  2246.  
  2247. /** 
  2248. * Retrieve the currency object for the specified currency code. 
  2249. * @param string $currency_code 
  2250. * @return RGCurrency 
  2251. */ 
  2252. public function get_currency( $currency_code = '' ) { 
  2253. if ( ! class_exists( 'RGCurrency' ) ) { 
  2254. require_once( GFCommon::get_base_path() . 'currency.php' ); 
  2255.  
  2256. if ( empty( $currency_code ) ) { 
  2257. $currency_code = GFCommon::get_currency(); 
  2258.  
  2259. return new RGCurrency( $currency_code ); 
  2260.  
  2261. /** 
  2262. * Format the amount for export to the payment gateway. 
  2263. * Removes currency symbol and if required converts the amount to the smallest unit required by the gateway (e.g. dollars to cents). 
  2264. * @param int|float $amount The value to be formatted. 
  2265. * @param string $currency_code The currency code. 
  2266. * @return int|float 
  2267. */ 
  2268. public function get_amount_export( $amount, $currency_code = '' ) { 
  2269. $currency = $this->get_currency( $currency_code ); 
  2270. $amount = $currency->to_number( $amount ); 
  2271.  
  2272. if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) { 
  2273. return $amount * 100; 
  2274.  
  2275. return $amount; 
  2276.  
  2277. /** 
  2278. * If necessary convert the amount back from the smallest unit required by the gateway (e.g cents to dollars). 
  2279. * @param int|float $amount The value to be formatted. 
  2280. * @param string $currency_code The currency code. 
  2281. * @return int|float 
  2282. */ 
  2283. public function get_amount_import( $amount, $currency_code = '' ) { 
  2284. $currency = $this->get_currency( $currency_code ); 
  2285.  
  2286. if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) { 
  2287. return $amount / 100; 
  2288.  
  2289. return $amount; 
  2290.  
  2291.  
  2292. //-------- Cancel Subscription ----------- 
  2293. public function entry_info( $form_id, $entry ) { 
  2294.  
  2295. //abort if subscription cancellation isn't supported by the addon or if it has already been canceled 
  2296. if ( ! $this->payment_method_is_overridden( 'cancel' ) ) { 
  2297. return; 
  2298.  
  2299. // adding cancel subscription button and script to entry info section 
  2300. $cancelsub_button = ''; 
  2301. if ( $entry['transaction_type'] == '2' && $entry['payment_status'] <> 'Cancelled' && $this->is_payment_gateway( $entry['id'] ) ) { 
  2302. ?> 
  2303. <input id="cancelsub" type="button" name="cancelsub" 
  2304. value="<?php esc_html_e( 'Cancel Subscription', 'gravityforms' ) ?>" class="button" 
  2305. onclick="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"/> 
  2306. <img src="<?php echo GFCommon::get_base_url() ?>/images/spinner.gif" id="subscription_cancel_spinner" 
  2307. style="display: none;"/> 
  2308.  
  2309. <script type="text/javascript"> 
  2310.  
  2311. </script> 
  2312.  
  2313. <?php 
  2314.  
  2315. /** 
  2316. * Target of gform_delete_lead hook. Deletes all transactions and callbacks when an entry is deleted. 
  2317. * @param $entry_id . ID of entry that is being deleted 
  2318. */ 
  2319. public function entry_deleted( $entry_id ) { 
  2320. global $wpdb; 
  2321.  
  2322. //deleting from transaction table 
  2323. $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_transaction", array( 'lead_id' => $entry_id ), array( '%d' ) ); 
  2324.  
  2325. //deleting from callback table 
  2326. $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_callback", array( 'lead_id' => $entry_id ), array( '%d' ) ); 
  2327.  
  2328. public function disable_entry_info_payment( $is_enabled, $entry ) { 
  2329.  
  2330. $is_my_entry = $this->is_payment_gateway( $entry['id'] ); 
  2331.  
  2332. return $is_my_entry ? false : $is_enabled; 
  2333.  
  2334. public function ajax_cancel_subscription() { 
  2335. check_ajax_referer( 'gaddon_cancel_subscription', 'gaddon_cancel_subscription' ); 
  2336.  
  2337. $entry_id = $_POST['entry_id']; 
  2338. $entry = GFAPI::get_entry( $entry_id ); 
  2339.  
  2340. $form = GFAPI::get_form( $entry['form_id'] ); 
  2341. $feed = $this->get_payment_feed( $entry, $form ); 
  2342.  
  2343. if ( $this->cancel( $entry, $feed ) ) { 
  2344. $this->cancel_subscription( $entry, $feed ); 
  2345. die( '1' ); 
  2346. } else { 
  2347. die( '0' ); 
  2348.  
  2349.  
  2350. /** 
  2351. * Target of gform_before_delete_field hook. Sets relevant payment feeds to inactive when the credit card field is deleted. 
  2352. * @param $form_id . ID of the form being edited. 
  2353. * @param $field_id . ID of the field being deleted. 
  2354. */ 
  2355. public function before_delete_field( $form_id, $field_id ) { 
  2356. if ( $this->_requires_credit_card ) { 
  2357. $form = GFAPI::get_form( $form_id ); 
  2358. $field = $this->get_credit_card_field( $form ); 
  2359.  
  2360. if ( is_object( $field ) && $field->id == $field_id ) { 
  2361. $feeds = $this->get_feeds( $form_id ); 
  2362. foreach ( $feeds as $feed ) { 
  2363. if ( $feed['is_active'] ) { 
  2364. $this->update_feed_active( $feed['id'], 0 ); 
  2365.  
  2366.  
  2367. // # HELPERS 
  2368.  
  2369. private function payment_method_is_overridden( $method_name, $base_class = 'GFPaymentAddOn' ) { 
  2370. return parent::method_is_overridden( $method_name, $base_class ); 
  2371.  
  2372. public function authorization_error( $error_message ) { 
  2373. return array( 'error_message' => $error_message, 'is_success' => false, 'is_authorized' => false ); 
  2374.  
  2375. public function remove_spaces_from_card_number( $card_number ) { 
  2376. $card_number = str_replace( array( "\t", "\n", "\r", ' ' ), '', $card_number ); 
  2377.  
  2378. return $card_number;