1: <?php
2: 3: 4: 5: 6:
7:
8:
9: if ( ! class_exists( 'GFForms' ) ) {
10: die();
11: }
12:
13:
14: require_once( 'class-gf-feed-addon.php' );
15:
16: 17: 18: 19: 20: 21: 22: 23: 24:
25: abstract class GFPaymentAddOn extends GFFeedAddOn {
26:
27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
37: private $_payment_version = '1.3';
38:
39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52:
53: protected $_requires_credit_card = false;
54:
55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66:
67: protected $_supports_callbacks = false;
68:
69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79:
80: protected $authorization = array();
81:
82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92:
93: protected $redirect_url = '';
94:
95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105:
106: protected $current_feed = false;
107:
108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118:
119: protected $current_submission_data = false;
120:
121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132:
133: protected $is_payment_gateway = false;
134:
135: 136: 137: 138: 139: 140: 141: 142: 143: 144:
145: protected $_single_feed_submission = true;
146:
147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
160: protected $_requires_smallest_unit = false;
161:
162:
163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176:
177: public function pre_init() {
178: parent::pre_init();
179:
180:
181: add_action( 'parse_request', array( $this, 'maybe_process_callback' ) );
182:
183: if ( $this->payment_method_is_overridden( 'check_status' ) ) {
184: $this->setup_cron();
185: }
186:
187: }
188:
189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201:
202: public function init() {
203:
204: parent::init();
205:
206: add_filter( 'gform_confirmation', array( $this, 'confirmation' ), 20, 4 );
207:
208: add_filter( 'gform_validation', array( $this, 'maybe_validate' ), 20 );
209: add_filter( 'gform_entry_post_save', array( $this, 'entry_post_save' ), 10, 2 );
210:
211: if ( $this->_requires_credit_card ) {
212: add_filter( 'gform_register_init_scripts', array( $this, 'register_creditcard_token_script' ), 10, 3 );
213: add_filter( 'gform_field_content', array( $this, 'add_creditcard_token_input' ), 10, 5 );
214: add_filter( 'gform_form_args', array( $this, 'force_ajax_for_creditcard_tokens' ), 10, 1 );
215: }
216:
217: }
218:
219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232:
233: public function init_admin() {
234:
235: parent::init_admin();
236:
237: if ( $this->_requires_credit_card ) {
238:
239: add_filter( 'gform_enable_credit_card_field', '__return_true' );
240: }
241:
242: add_filter( 'gform_currencies', array( $this, 'supported_currencies' ) );
243:
244: add_filter( 'gform_delete_lead', array( $this, 'entry_deleted' ) );
245: add_action( 'gform_before_delete_field', array( $this, 'before_delete_field' ), 10, 2 );
246:
247: if ( rgget( 'page' ) == 'gf_entries' ) {
248: add_action( 'gform_payment_details', array( $this, 'entry_info' ), 10, 2 );
249: }
250: }
251:
252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263:
264: public function init_ajax() {
265: parent::init_ajax();
266:
267: add_action( 'wp_ajax_gaddon_cancel_subscription', array( $this, 'ajax_cancel_subscription' ) );
268: }
269:
270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282:
283: public function setup() {
284:
285: parent::setup();
286:
287: $installed_version = get_option( 'gravityformsaddon_payment_version' );
288:
289:
290: $installed_addons = get_option( 'gravityformsaddon_payment_addons' );
291: if ( ! is_array( $installed_addons ) ) {
292: $installed_addons = array();
293: }
294:
295: if ( $installed_version != $this->_payment_version ) {
296: $this->upgrade_payment( $installed_version );
297:
298: $installed_addons = array( $this->_slug );
299: update_option( 'gravityformsaddon_payment_addons', $installed_addons );
300: }
301: elseif ( ! in_array( $this->_slug, $installed_addons ) ) {
302:
303: $this->upgrade_payment( $installed_version );
304:
305: $installed_addons[] = $this->_slug;
306: update_option( 'gravityformsaddon_payment_addons', $installed_addons );
307: }
308:
309:
310: update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
311:
312: }
313:
314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330:
331: private function upgrade_payment( $previous_versions ) {
332: global $wpdb;
333:
334: $charset_collate = GFFormsModel::get_db_charset();
335:
336: $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_transaction (
337: id int(10) unsigned not null auto_increment,
338: lead_id int(10) unsigned not null,
339: transaction_type varchar(30) not null,
340: transaction_id varchar(50),
341: subscription_id varchar(50),
342: is_recurring tinyint(1) not null default 0,
343: amount decimal(19,2),
344: date_created datetime,
345: PRIMARY KEY (id),
346: KEY lead_id (lead_id),
347: KEY transaction_type (transaction_type),
348: KEY type_lead (lead_id,transaction_type)
349: ) $charset_collate;";
350:
351: gf_upgrade()->dbDelta( $sql );
352:
353:
354: if ( $this->_supports_callbacks ) {
355: $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_callback (
356: id int(10) unsigned not null auto_increment,
357: lead_id int(10) unsigned not null,
358: addon_slug varchar(250) not null,
359: callback_id varchar(250),
360: date_created datetime,
361: PRIMARY KEY (id),
362: KEY addon_slug_callback_id (addon_slug(50),callback_id(100))
363: ) $charset_collate;";
364:
365: gf_upgrade()->dbDelta( $sql );
366:
367:
368: gf_upgrade()->drop_index( "{$wpdb->prefix}gf_addon_payment_callback", 'slug_callback_id' );
369: }
370:
371:
372: }
373:
374: 375: 376: 377: 378: 379:
380: public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ){
381:
382:
383: if( $force_upgrade ){
384:
385: $installed_version = get_option( 'gravityformsaddon_payment_version' );
386:
387: $this->upgrade_payment( $installed_version );
388:
389: update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
390:
391: }
392:
393: parent::post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade );
394: }
395:
396:
397:
398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412:
413: public function confirmation( $confirmation, $form, $entry, $ajax ) {
414:
415: if ( empty( $this->redirect_url ) ) {
416: return $confirmation;
417: }
418:
419: $confirmation = array( 'redirect' => $this->redirect_url );
420:
421: return $confirmation;
422: }
423:
424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440:
441: public function redirect_url( $feed, $submission_data, $form, $entry ) {
442:
443: }
444:
445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459:
460: public function maybe_validate( $validation_result ) {
461:
462: $form = $validation_result['form'];
463: $is_last_page = GFFormDisplay::is_last_page( $form );
464: $failed_honeypot = false;
465:
466: if ( $is_last_page && rgar( $form, 'enableHoneypot' ) ) {
467: $honeypot_id = GFFormDisplay::get_max_field_id( $form ) + 1;
468: $failed_honeypot = ! rgempty( "input_{$honeypot_id}" );
469: }
470:
471:
472: $is_heartbeat = rgpost('action') == 'heartbeat';
473:
474: if ( ! $validation_result['is_valid'] || ! $is_last_page || $failed_honeypot || $is_heartbeat) {
475: return $validation_result;
476: }
477:
478: return $this->validation( $validation_result );
479: }
480:
481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505:
506: public function validation( $validation_result ) {
507:
508: if ( ! $validation_result['is_valid'] ) {
509: return $validation_result;
510: }
511:
512: $form = $validation_result['form'];
513: $entry = GFFormsModel::create_lead( $form );
514: $feed = $this->get_payment_feed( $entry, $form );
515:
516: if ( ! $feed ) {
517: return $validation_result;
518: }
519:
520: $submission_data = $this->get_submission_data( $feed, $form, $entry );
521:
522:
523: if ( floatval( $submission_data['payment_amount'] ) <= 0 ) {
524:
525: $this->log_debug( __METHOD__ . '(): Payment amount is zero or less. Not sending to payment gateway.' );
526:
527: return $validation_result;
528: }
529:
530:
531: $this->is_payment_gateway = true;
532: $this->current_feed = $this->_single_submission_feed = $feed;
533: $this->current_submission_data = $submission_data;
534:
535: $performed_authorization = false;
536: $is_subscription = $feed['meta']['transactionType'] == 'subscription';
537:
538: if ( $this->payment_method_is_overridden( 'authorize' ) && ! $is_subscription ) {
539:
540:
541: $this->authorization = $this->authorize( $feed, $submission_data, $form, $entry );
542:
543: $performed_authorization = true;
544:
545: } elseif ( $this->payment_method_is_overridden( 'subscribe' ) && $is_subscription ) {
546:
547: $subscription = $this->subscribe( $feed, $submission_data, $form, $entry );
548:
549: $this->authorization['is_authorized'] = rgar($subscription,'is_success');
550: $this->authorization['error_message'] = rgar( $subscription, 'error_message' );
551: $this->authorization['subscription'] = $subscription;
552:
553: $performed_authorization = true;
554: }
555:
556: if ( $performed_authorization ) {
557: $this->log_debug( __METHOD__ . "(): Authorization result for form #{$form['id']} submission => " . print_r( $this->authorization, 1 ) );
558: }
559:
560: if ( $performed_authorization && ! rgar( $this->authorization, 'is_authorized' ) ) {
561: $validation_result = $this->get_validation_result( $validation_result, $this->authorization );
562:
563:
564: GFFormDisplay::set_current_page( $validation_result['form']['id'], $validation_result['credit_card_page'] );
565: }
566:
567: return $validation_result;
568: }
569:
570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606:
607: public function authorize( $feed, $submission_data, $form, $entry ) {
608:
609: }
610:
611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637:
638: public function capture( $authorization, $feed, $submission_data, $form, $entry ) {
639:
640: }
641:
642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688:
689: public function subscribe( $feed, $submission_data, $form, $entry ) {
690:
691: }
692:
693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708:
709: public function cancel( $entry, $feed ) {
710: return false;
711: }
712:
713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725:
726: public function get_validation_result( $validation_result, $authorization_result ) {
727:
728: $credit_card_page = 0;
729: foreach ( $validation_result['form']['fields'] as &$field ) {
730: if ( $field->type == 'creditcard' ) {
731: $field->failed_validation = true;
732: $field->validation_message = $authorization_result['error_message'];
733: $credit_card_page = $field->pageNumber;
734: break;
735: }
736: }
737:
738: $validation_result['credit_card_page'] = $credit_card_page;
739: $validation_result['is_valid'] = false;
740:
741: return $validation_result;
742:
743: }
744:
745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764:
765: public function entry_post_save( $entry, $form ) {
766:
767: if ( ! $this->is_payment_gateway ) {
768: return $entry;
769: }
770:
771:
772: gform_update_meta( $entry['id'], 'payment_gateway', $this->_slug );
773:
774: $feed = $this->current_feed;
775:
776: if ( ! empty( $this->authorization ) ) {
777:
778:
779: if ( $feed['meta']['transactionType'] == 'subscription' ) {
780:
781: $entry = $this->process_subscription( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
782:
783: } else {
784:
785: if ( $this->payment_method_is_overridden( 'capture' ) && rgempty( 'captured_payment', $this->authorization ) ) {
786:
787: $this->authorization['captured_payment'] = $this->capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
788:
789: }
790:
791: $entry = $this->process_capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
792: }
793: } elseif ( $this->payment_method_is_overridden( 'redirect_url' ) ) {
794:
795:
796:
797:
798: $this->redirect_url = $this->redirect_url( $feed, $this->current_submission_data, $form, $entry );
799:
800:
801: $entry['transaction_type'] = rgars( $feed, 'meta/transactionType' ) == 'subscription' ? 2 : 1;
802: $entry['payment_status'] = 'Processing';
803:
804: }
805:
806: return $entry;
807: }
808:
809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824: 825: 826: 827:
828: public function process_capture( $authorization, $feed, $submission_data, $form, $entry ) {
829:
830: $payment = rgar( $authorization, 'captured_payment' );
831: if ( empty( $payment ) && rgar( $authorization, 'is_authorized' ) ) {
832: if ( ! rgar( $authorization, 'amount' ) ) {
833: $authorization['amount'] = rgar( $submission_data, 'payment_amount' );
834: }
835:
836: $this->complete_authorization( $entry, $authorization );
837:
838: return $entry;
839: }
840:
841: $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $payment, 1 ) );
842:
843: if ( $payment['is_success'] ) {
844:
845: $entry['is_fulfilled'] = '1';
846: $payment['payment_status'] = 'Paid';
847: $payment['payment_date'] = gmdate( 'Y-m-d H:i:s' );
848: $payment['type'] = 'complete_payment';
849: $this->complete_payment( $entry, $payment );
850:
851: } else {
852:
853: $entry['payment_status'] = 'Failed';
854: $payment['type'] = 'fail_payment';
855: $payment['note'] = sprintf( esc_html__( 'Payment failed to be captured. Reason: %s', 'gravityforms' ), $payment['error_message'] );
856: $this->fail_payment( $entry, $payment );
857:
858: }
859:
860: return $entry;
861:
862: }
863:
864: 865: 866: 867: 868: 869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885:
886: public function process_subscription( $authorization, $feed, $submission_data, $form, $entry ) {
887:
888: $subscription = rgar( $authorization, 'subscription' );
889: if ( empty( $subscription ) ) {
890: return $entry;
891: }
892:
893: $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $subscription, 1 ) );
894:
895:
896: $payment = rgar( $subscription, 'captured_payment' );
897: $payment_name = rgempty( 'name', $payment ) ? esc_html__( 'Initial payment', 'gravityforms' ) : $payment['name'];
898:
899: if ( $payment && $payment['is_success'] ) {
900:
901: $this->insert_transaction( $entry['id'], 'payment', $payment['transaction_id'], $payment['amount'], false, rgar( $subscription, 'subscription_id' ) );
902:
903: $amount_formatted = GFCommon::to_money( $payment['amount'], $entry['currency'] );
904: $note = sprintf( esc_html__( '%s has been captured successfully. Amount: %s. Transaction Id: %s', 'gravityforms' ), $payment_name, $amount_formatted, $payment['transaction_id'] );
905: $this->add_note( $entry['id'], $note, 'success' );
906:
907: } elseif ( $payment && ! $payment['is_success'] ) {
908:
909: $this->add_note( $entry['id'], sprintf( esc_html__( 'Failed to capture %s. Reason: %s.', 'gravityforms' ), $payment['error_message'], $payment_name ), 'error' );
910:
911: }
912:
913:
914: if ( $subscription['is_success'] ) {
915:
916: $entry = $this->start_subscription( $entry, $subscription );
917:
918: } else {
919:
920: $entry['payment_status'] = 'Failed';
921: GFAPI::update_entry( $entry );
922:
923: $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription failed to be created. Reason: %s', 'gravityforms' ), $subscription['error_message'] ), 'error' );
924:
925: $subscription['type'] = 'fail_create_subscription';
926: $this->post_payment_action( $entry, $subscription );
927:
928: }
929:
930: return $entry;
931:
932: }
933:
934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954: 955: 956: 957: 958: 959:
960: public function insert_transaction( $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring = null, $subscription_id = null ) {
961: global $wpdb;
962:
963:
964: $payment_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(id) FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE lead_id=%d", $entry_id ) );
965: $is_recurring = $payment_count > 0 && $transaction_type == 'payment' ? 1 : 0;
966: $subscription_id = empty( $subscription_id ) ? '' : $subscription_id;
967:
968: $sql = $wpdb->prepare(
969: " INSERT INTO {$wpdb->prefix}gf_addon_payment_transaction (lead_id, transaction_type, transaction_id, amount, is_recurring, date_created, subscription_id)
970: values(%d, %s, %s, %f, %d, utc_timestamp(), %s)", $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id
971: );
972: $wpdb->query( $sql );
973:
974: $txn_id = $wpdb->insert_id;
975:
976: 977: 978: 979: 980: 981: 982: 983: 984: 985: 986: 987:
988: do_action( 'gform_post_payment_transaction', $txn_id, $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id );
989: if ( has_filter( 'gform_post_payment_transaction' ) ) {
990: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_transaction.' );
991: }
992:
993: return $txn_id;
994: }
995:
996: 997: 998: 999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015:
1016: public function get_payment_feed( $entry, $form = false ) {
1017: $submission_feed = false;
1018:
1019:
1020: if ( $entry['id'] ) {
1021: $feeds = $this->get_feeds_by_entry( $entry['id'] );
1022: $submission_feed = empty( $feeds ) ? false : $this->get_feed( $feeds[0] );
1023: } elseif ( $form ) {
1024:
1025:
1026: $feeds = $this->get_feeds( $form['id'] );
1027: $feeds = $this->pre_process_feeds( $feeds, $entry, $form );
1028:
1029: foreach ( $feeds as $feed ) {
1030: if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) {
1031: $submission_feed = $feed;
1032: break;
1033: }
1034: }
1035: }
1036:
1037:
1038: return $submission_feed;
1039: }
1040:
1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054:
1055: public function is_payment_gateway( $entry_id ) {
1056:
1057: if ( $this->is_payment_gateway ) {
1058: return true;
1059: }
1060:
1061: $gateway = gform_get_meta( $entry_id, 'payment_gateway' );
1062:
1063: return $gateway == $this->_slug;
1064: }
1065:
1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084:
1085: public function get_submission_data( $feed, $form, $entry ) {
1086:
1087: $submission_data = array();
1088:
1089: $submission_data['form_title'] = $form['title'];
1090:
1091:
1092: $billing_fields = $this->billing_info_fields();
1093: foreach ( $billing_fields as $billing_field ) {
1094: $field_name = $billing_field['name'];
1095: $input_id = rgar( $feed['meta'], "billingInformation_{$field_name}" );
1096: $submission_data[ $field_name ] = $this->get_field_value( $form, $entry, $input_id );
1097: }
1098:
1099:
1100: $card_field = $this->get_credit_card_field( $form );
1101: if ( $card_field ) {
1102:
1103: $submission_data['card_number'] = $this->remove_spaces_from_card_number( rgpost( "input_{$card_field->id}_1" ) );
1104: $submission_data['card_expiration_date'] = rgpost( "input_{$card_field->id}_2" );
1105: $submission_data['card_security_code'] = rgpost( "input_{$card_field->id}_3" );
1106: $submission_data['card_name'] = rgpost( "input_{$card_field->id}_5" );
1107:
1108: }
1109:
1110:
1111: $order_info = $this->get_order_data( $feed, $form, $entry );
1112: $submission_data = array_merge( $submission_data, $order_info );
1113:
1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125:
1126:
1127: return gf_apply_filters( array( 'gform_submission_data_pre_process_payment', $form['id'] ), $submission_data, $feed, $form, $entry );
1128: }
1129:
1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144:
1145: public function get_credit_card_field( $form ) {
1146: $fields = GFAPI::get_fields_by_type( $form, array( 'creditcard' ) );
1147:
1148: return empty( $fields ) ? false : $fields[0];
1149: }
1150:
1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163:
1164: public function has_credit_card_field( $form ) {
1165: return $this->get_credit_card_field( $form ) !== false;
1166: }
1167:
1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190:
1191: public function get_order_data( $feed, $form, $entry ) {
1192:
1193: $products = GFCommon::get_product_fields( $form, $entry );
1194:
1195: $payment_field = $feed['meta']['transactionType'] == 'product' ? rgars( $feed, 'meta/paymentAmount' ) : rgars( $feed, 'meta/recurringAmount' );
1196: $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false;
1197: $trial_field = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false;
1198:
1199: $amount = 0;
1200: $line_items = array();
1201: $discounts = array();
1202: $fee_amount = 0;
1203: $trial_amount = 0;
1204: foreach ( $products['products'] as $field_id => $product ) {
1205:
1206: $quantity = $product['quantity'] ? $product['quantity'] : 1;
1207: $product_price = GFCommon::to_number( $product['price'], $entry['currency'] );
1208:
1209: $options = array();
1210: if ( is_array( rgar( $product, 'options' ) ) ) {
1211: foreach ( $product['options'] as $option ) {
1212: $options[] = $option['option_name'];
1213: $product_price += $option['price'];
1214: }
1215: }
1216:
1217: $is_trial_or_setup_fee = false;
1218:
1219: if ( ! empty( $trial_field ) && $trial_field == $field_id ) {
1220:
1221: $trial_amount = $product_price * $quantity;
1222: $is_trial_or_setup_fee = true;
1223:
1224: } elseif ( ! empty( $setup_fee_field ) && $setup_fee_field == $field_id ) {
1225:
1226: $fee_amount = $product_price * $quantity;
1227: $is_trial_or_setup_fee = true;
1228: }
1229:
1230:
1231: if ( is_numeric( $payment_field ) && $payment_field != $field_id ) {
1232: continue;
1233: }
1234:
1235:
1236: if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ) {
1237: continue;
1238: }
1239:
1240: $amount += $product_price * $quantity;
1241:
1242: $description = '';
1243: if ( ! empty( $options ) ) {
1244: $description = esc_html__( 'options: ', 'gravityforms' ) . ' ' . implode( ', ', $options );
1245: }
1246:
1247: if ( $product_price >= 0 ) {
1248: $line_items[] = array(
1249: 'id' => $field_id,
1250: 'name' => $product['name'],
1251: 'description' => $description,
1252: 'quantity' => $quantity,
1253: 'unit_price' => GFCommon::to_number( $product_price, $entry['currency'] ),
1254: 'options' => rgar( $product, 'options' )
1255: );
1256: } else {
1257: $discounts[] = array(
1258: 'id' => $field_id,
1259: 'name' => $product['name'],
1260: 'description' => $description,
1261: 'quantity' => $quantity,
1262: 'unit_price' => GFCommon::to_number( $product_price, $entry['currency'] ),
1263: 'options' => rgar( $product, 'options' )
1264: );
1265: }
1266: }
1267:
1268: if ( $trial_field == 'enter_amount' ) {
1269: $trial_amount = rgar( $feed['meta'], 'trial_amount' ) ? GFCommon::to_number( rgar( $feed['meta'], 'trial_amount' ), $entry['currency'] ) : 0;
1270: }
1271:
1272: if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) {
1273: $line_items[] = array(
1274: 'id' => $products['shipping']['id'],
1275: 'name' => $products['shipping']['name'],
1276: 'description' => '',
1277: 'quantity' => 1,
1278: 'unit_price' => GFCommon::to_number( $products['shipping']['price'], $entry['currency'] ),
1279: 'is_shipping' => 1
1280: );
1281: $amount += $products['shipping']['price'];
1282: }
1283:
1284: return array(
1285: 'payment_amount' => $amount,
1286: 'setup_fee' => $fee_amount,
1287: 'trial' => $trial_amount,
1288: 'line_items' => $line_items,
1289: 'discounts' => $discounts
1290: );
1291: }
1292:
1293: 1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303:
1304: public function is_callback_valid() {
1305: if ( rgget( 'callback' ) != $this->_slug ) {
1306: return false;
1307: }
1308:
1309: return true;
1310: }
1311:
1312:
1313:
1314:
1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332:
1333: public function maybe_process_callback() {
1334:
1335:
1336: if ( ! $this->is_callback_valid() ) {
1337: return;
1338: }
1339:
1340:
1341:
1342: $this->log_debug( __METHOD__ . '(): Initializing callback processing for: ' . $this->_slug );
1343:
1344: $callback_action = $this->callback();
1345:
1346: $this->log_debug( __METHOD__ . '(): Result from gateway callback => ' . print_r( $callback_action, true ) );
1347:
1348: $result = false;
1349: if ( is_wp_error( $callback_action ) ) {
1350: $this->display_callback_error( $callback_action );
1351: } elseif ( $callback_action && is_array( $callback_action ) && rgar( $callback_action, 'type' ) && ! rgar( $callback_action, 'abort_callback' ) ) {
1352:
1353: $result = $this->process_callback_action( $callback_action );
1354:
1355: $this->log_debug( __METHOD__ . '(): Result of callback action => ' . print_r( $result, true ) );
1356:
1357: if ( is_wp_error( $result ) ) {
1358: $this->display_callback_error( $result );
1359: } elseif ( ! $result ) {
1360: status_header( 200 );
1361: echo 'Callback could not be processed.';
1362: } else {
1363: status_header( 200 );
1364: echo 'Callback processed successfully.';
1365: }
1366: } else {
1367: status_header( 200 );
1368: echo 'Callback bypassed';
1369: }
1370:
1371: $this->post_callback( $callback_action, $result );
1372:
1373: die();
1374: }
1375:
1376: 1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386: 1387: 1388:
1389: private function display_callback_error( $error ) {
1390:
1391: $data = $error->get_error_data();
1392: $status = ! rgempty( 'status_header', $data ) ? $data['status_header'] : 200;
1393:
1394: status_header( $status );
1395: echo $error->get_error_message();
1396: }
1397:
1398: 1399: 1400: 1401: 1402: 1403: 1404: 1405: 1406: 1407: 1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427: 1428: 1429: 1430: 1431: 1432: 1433:
1434: private function process_callback_action( $action ) {
1435: $this->log_debug( __METHOD__ . '(): Processing callback action.' );
1436: $action = wp_parse_args(
1437: $action, array(
1438: 'type' => false,
1439: 'amount' => false,
1440: 'amount_formatted' => false,
1441: 'transaction_type' => false,
1442: 'transaction_id' => false,
1443: 'subscription_id' => false,
1444: 'entry_id' => false,
1445: 'payment_status' => false,
1446: 'note' => false,
1447: )
1448: );
1449:
1450: $result = false;
1451:
1452: if ( rgar( $action, 'id' ) && $this->is_duplicate_callback( $action['id'] ) ) {
1453: return new WP_Error( 'duplicate', sprintf( esc_html__( 'This webhook has already been processed (Event Id: %s)', 'gravityforms' ), $action['id'] ) );
1454: }
1455:
1456: $entry = GFAPI::get_entry( $action['entry_id'] );
1457: if ( ! $entry || is_wp_error( $entry ) ) {
1458: return $result;
1459: }
1460:
1461: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1462:
1463: 1464: 1465: 1466: 1467: 1468: 1469: 1470:
1471: do_action( 'gform_action_pre_payment_callback', $action, $entry );
1472: if ( has_filter( 'gform_action_pre_payment_callback' ) ) {
1473: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_action_pre_payment_callback.' );
1474: }
1475:
1476: switch ( $action['type'] ) {
1477: case 'complete_payment':
1478: $result = $this->complete_payment( $entry, $action );
1479: break;
1480: case 'refund_payment':
1481: $result = $this->refund_payment( $entry, $action );
1482: break;
1483: case 'fail_payment':
1484: $result = $this->fail_payment( $entry, $action );
1485: break;
1486: case 'add_pending_payment':
1487: $result = $this->add_pending_payment( $entry, $action );
1488: break;
1489: case 'void_authorization':
1490: $result = $this->void_authorization( $entry, $action );
1491: break;
1492: case 'create_subscription':
1493: $result = $this->start_subscription( $entry, $action );
1494: $result = rgar( $result, 'payment_status' ) == 'Active' && rgar( $result, 'transaction_id' ) == rgar( $action, 'subscription_id' );
1495: break;
1496: case 'cancel_subscription':
1497: $feed = $this->get_payment_feed( $entry );
1498: $result = $this->cancel_subscription( $entry, $feed, $action['note'] );
1499: break;
1500: case 'expire_subscription':
1501: $result = $this->expire_subscription( $entry, $action );
1502: break;
1503: case 'add_subscription_payment':
1504: $result = $this->add_subscription_payment( $entry, $action );
1505: break;
1506: case 'fail_subscription_payment':
1507: $result = $this->fail_subscription_payment( $entry, $action );
1508: break;
1509: default:
1510:
1511: if ( is_callable( array( $this, rgar( $action, 'callback' ) ) ) ) {
1512: $result = call_user_func_array( array( $this, $action['callback'] ), array( $entry, $action ) );
1513: }
1514: break;
1515: }
1516:
1517: if ( rgar( $action, 'id' ) && $result ) {
1518: $this->register_callback( $action['id'], $action['entry_id'] );
1519: }
1520:
1521: 1522: 1523: 1524: 1525: 1526: 1527: 1528: 1529: 1530: 1531: 1532: 1533: 1534: 1535: 1536: 1537: 1538: 1539: 1540:
1541: do_action( 'gform_post_payment_callback', $entry, $action, $result );
1542: if ( has_filter( 'gform_post_payment_callback' ) ) {
1543: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_callback.' );
1544: }
1545:
1546: return $result;
1547: }
1548:
1549: 1550: 1551: 1552: 1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562: 1563:
1564: public function register_callback( $callback_id, $entry_id ) {
1565: global $wpdb;
1566:
1567: $wpdb->insert( "{$wpdb->prefix}gf_addon_payment_callback", array(
1568: 'addon_slug' => $this->_slug,
1569: 'callback_id' => $callback_id,
1570: 'lead_id' => $entry_id,
1571: 'date_created' => gmdate( 'Y-m-d H:i:s' )
1572: ) );
1573: }
1574:
1575: 1576: 1577: 1578: 1579: 1580: 1581: 1582: 1583: 1584: 1585: 1586: 1587: 1588: 1589:
1590: public function is_duplicate_callback( $callback_id ) {
1591: global $wpdb;
1592:
1593: $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s AND callback_id=%s", $this->_slug, $callback_id );
1594: if ( $wpdb->get_var( $sql ) ) {
1595: return true;
1596: }
1597:
1598: return false;
1599: }
1600:
1601: public function callback() {
1602: }
1603:
1604: public function post_callback( $callback_action, $result ) {
1605: }
1606:
1607:
1608:
1609:
1610: public function add_pending_payment( $entry, $action ) {
1611: $this->log_debug( __METHOD__ . '(): Processing request.' );
1612: if ( empty( $action['payment_status'] ) ) {
1613: $action['payment_status'] = 'Pending';
1614: }
1615:
1616: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1617:
1618: if ( empty( $action['note'] ) ) {
1619: $action['note'] = sprintf( esc_html__( 'Payment is pending. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1620: }
1621:
1622: GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1623: $this->add_note( $entry['id'], $action['note'] );
1624: $this->post_payment_action( $entry, $action );
1625:
1626: return true;
1627: }
1628:
1629: public function complete_authorization( &$entry, $action ) {
1630: $this->log_debug( __METHOD__ . '(): Processing request.' );
1631: if ( ! rgar( $action, 'payment_status' ) ) {
1632: $action['payment_status'] = 'Authorized';
1633: }
1634:
1635: if ( ! rgar( $action, 'transaction_type' ) ) {
1636: $action['transaction_type'] = 'authorization';
1637: }
1638:
1639: if ( ! rgar( $action, 'payment_date' ) ) {
1640: $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1641: }
1642:
1643: $entry['transaction_id'] = rgar( $action, 'transaction_id' );
1644: $entry['transaction_type'] = '1';
1645: $entry['payment_status'] = $action['payment_status'];
1646:
1647: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1648:
1649: if ( ! rgar( $action, 'note' ) ) {
1650: $action['note'] = sprintf( esc_html__( 'Payment has been authorized. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1651: }
1652:
1653: GFAPI::update_entry( $entry );
1654: $this->add_note( $entry['id'], $action['note'], 'success' );
1655: $this->post_payment_action( $entry, $action );
1656:
1657: return true;
1658: }
1659:
1660: public function complete_payment( &$entry, $action ) {
1661: $this->log_debug( __METHOD__ . '(): Processing request.' );
1662: if ( ! rgar( $action, 'payment_status' ) ) {
1663: $action['payment_status'] = 'Paid';
1664: }
1665:
1666: if ( ! rgar( $action, 'transaction_type' ) ) {
1667: $action['transaction_type'] = 'payment';
1668: }
1669:
1670: if ( ! rgar( $action, 'payment_date' ) ) {
1671: $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1672: }
1673:
1674: $entry['is_fulfilled'] = '1';
1675: $entry['transaction_id'] = rgar( $action, 'transaction_id' );
1676: $entry['transaction_type'] = '1';
1677: $entry['payment_status'] = $action['payment_status'];
1678: $entry['payment_amount'] = rgar( $action, 'amount' );
1679: $entry['payment_date'] = $action['payment_date'];
1680: $entry['payment_method'] = rgar( $action, 'payment_method' );
1681:
1682: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1683:
1684: if ( ! rgar( $action, 'note' ) ) {
1685: $action['note'] = sprintf( esc_html__( 'Payment has been completed. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1686: }
1687:
1688: GFAPI::update_entry( $entry );
1689: $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1690: $this->add_note( $entry['id'], $action['note'], 'success' );
1691:
1692: 1693: 1694: 1695: 1696: 1697: 1698: 1699: 1700: 1701: 1702: 1703: 1704: 1705: 1706: 1707: 1708: 1709: 1710: 1711: 1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720:
1721: do_action( 'gform_post_payment_completed', $entry, $action );
1722: if ( has_filter( 'gform_post_payment_completed' ) ) {
1723: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_completed.' );
1724: }
1725: $this->post_payment_action( $entry, $action );
1726:
1727: return true;
1728: }
1729:
1730: public function refund_payment( $entry, $action ) {
1731: $this->log_debug( __METHOD__ . '(): Processing request.' );
1732: if ( empty( $action['payment_status'] ) ) {
1733: $action['payment_status'] = 'Refunded';
1734: }
1735:
1736: if ( empty( $action['transaction_type'] ) ) {
1737: $action['transaction_type'] = 'refund';
1738: }
1739:
1740: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1741:
1742: if ( empty( $action['note'] ) ) {
1743: $action['note'] = sprintf( esc_html__( 'Payment has been refunded. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1744: }
1745:
1746: GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1747: $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1748: $this->add_note( $entry['id'], $action['note'] );
1749:
1750: 1751: 1752: 1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778:
1779: do_action( 'gform_post_payment_refunded', $entry, $action );
1780: if ( has_filter( 'gform_post_payment_refunded' ) ) {
1781: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_refunded.' );
1782: }
1783: $this->post_payment_action( $entry, $action );
1784:
1785: return true;
1786: }
1787:
1788: public function fail_payment( $entry, $action ) {
1789: $this->log_debug( __METHOD__ . '(): Processing request.' );
1790: if ( empty( $action['payment_status'] ) ) {
1791: $action['payment_status'] = 'Failed';
1792: }
1793:
1794: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1795:
1796: if ( empty( $action['note'] ) ) {
1797: $action['note'] = sprintf( esc_html__( 'Payment has failed. Amount: %s.', 'gravityforms' ), $action['amount_formatted'] );
1798: }
1799:
1800: GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1801: $this->add_note( $entry['id'], $action['note'] );
1802: $this->post_payment_action( $entry, $action );
1803:
1804: return true;
1805: }
1806:
1807: public function void_authorization( $entry, $action ) {
1808: $this->log_debug( __METHOD__ . '(): Processing request.' );
1809: if ( empty( $action['payment_status'] ) ) {
1810: $action['payment_status'] = 'Voided';
1811: }
1812:
1813: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1814:
1815: if ( empty( $action['note'] ) ) {
1816: $action['note'] = sprintf( esc_html__( 'Authorization has been voided. Transaction Id: %s', 'gravityforms' ), $action['transaction_id'] );
1817: }
1818:
1819: GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1820: $this->add_note( $entry['id'], $action['note'] );
1821: $this->post_payment_action( $entry, $action );
1822:
1823: return true;
1824: }
1825:
1826: 1827: 1828: 1829: 1830: 1831: 1832: 1833: 1834:
1835:
1836: public function start_subscription( $entry, $subscription ) {
1837: $this->log_debug( __METHOD__ . '(): Processing request.' );
1838: if ( ! $this->has_subscription( $entry ) ) {
1839: $entry['payment_status'] = 'Active';
1840: $entry['payment_amount'] = $subscription['amount'];
1841: $entry['payment_date'] = ! rgempty( 'subscription_start_date', $subscription ) ? $subscription['subscription_start_date'] : gmdate( 'Y-m-d H:i:s' );
1842: $entry['transaction_id'] = $subscription['subscription_id'];
1843: $entry['transaction_type'] = '2';
1844: $entry['is_fulfilled'] = '1';
1845:
1846: $result = GFAPI::update_entry( $entry );
1847: $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription has been created. Subscription Id: %s.', 'gravityforms' ), $subscription['subscription_id'] ), 'success' );
1848:
1849: $subscription = $this->maybe_add_action_amount_formatted( $subscription, $entry['currency'] );
1850:
1851: if ( empty( $subscription['payment_status'] ) ) {
1852: $subscription['payment_status'] = 'Active';
1853: }
1854:
1855: 1856: 1857: 1858: 1859: 1860:
1861: do_action( 'gform_post_subscription_started', $entry, $subscription );
1862: if ( has_filter( 'gform_post_subscription_started' ) ) {
1863: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_subscription_started.' );
1864: }
1865:
1866: $subscription['type'] = 'create_subscription';
1867: $this->post_payment_action( $entry, $subscription );
1868:
1869: }
1870:
1871: return $entry;
1872: }
1873:
1874: 1875: 1876: 1877: 1878: 1879: 1880: 1881:
1882: public function add_subscription_payment( $entry, $action ) {
1883: $this->log_debug( __METHOD__ . '(): Processing request.' );
1884: if ( empty( $action['transaction_type'] ) ) {
1885: $action['transaction_type'] = 'payment';
1886: }
1887:
1888: if ( empty( $action['payment_status'] ) ) {
1889: $action['payment_status'] = 'Active';
1890: }
1891:
1892:
1893: if ( strtolower( $entry['payment_status'] ) != 'active' ) {
1894: $entry['payment_status'] = 'Active';
1895: GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Active' );
1896: }
1897:
1898: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1899:
1900: if ( empty( $action['note'] ) ) {
1901: $action['note'] = sprintf( esc_html__( 'Subscription has been paid. Amount: %s. Subscription Id: %s', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1902: }
1903:
1904: $transaction_id = ! empty( $action['transaction_id'] ) ? $action['transaction_id'] : $action['subscription_id'];
1905:
1906: $this->insert_transaction( $entry['id'], $action['transaction_type'], $transaction_id, $action['amount'], null, rgar( $action, 'subscription_id') );
1907: $this->add_note( $entry['id'], $action['note'], 'success' );
1908:
1909: 1910: 1911: 1912: 1913: 1914: 1915: 1916: 1917: 1918: 1919: 1920: 1921: 1922: 1923: 1924: 1925: 1926: 1927: 1928: 1929: 1930: 1931: 1932: 1933: 1934: 1935: 1936: 1937:
1938: do_action( 'gform_post_add_subscription_payment', $entry, $action );
1939: if ( has_filter( 'gform_post_add_subscription_payment' ) ) {
1940: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_add_subscription_payment.' );
1941: }
1942: $this->post_payment_action( $entry, $action );
1943:
1944: return true;
1945: }
1946:
1947: public function fail_subscription_payment( $entry, $action ) {
1948: $this->log_debug( __METHOD__ . '(): Processing request.' );
1949: if ( empty( $action['payment_status'] ) ) {
1950: $action['payment_status'] = 'Failed';
1951: }
1952:
1953: $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1954:
1955: if ( empty( $action['note'] ) ) {
1956: $action['note'] = sprintf( esc_html__( 'Subscription payment has failed. Amount: %s. Subscription Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1957: }
1958:
1959: GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Failed' );
1960: $this->add_note( $entry['id'], $action['note'], 'error' );
1961:
1962:
1963: 1964: 1965:
1966: do_action( 'gform_subscription_payment_failed', $entry, $action['subscription_id'] );
1967: if ( has_filter( 'gform_subscription_payment_failed' ) ) {
1968: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_payment_failed.' );
1969: }
1970: 1971: 1972: 1973: 1974: 1975: 1976: 1977: 1978: 1979: 1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987: 1988: 1989: 1990: 1991: 1992: 1993: 1994: 1995: 1996: 1997: 1998:
1999: do_action( 'gform_post_fail_subscription_payment', $entry, $action );
2000: if ( has_filter( 'gform_post_fail_subscription_payment' ) ) {
2001: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_fail_subscription_payment.' );
2002: }
2003: $this->post_payment_action( $entry, $action );
2004:
2005: return true;
2006: }
2007:
2008: public function cancel_subscription( $entry, $feed, $note = null ) {
2009: $this->log_debug( __METHOD__ . '(): Processing request.' );
2010: if ( ! $note ) {
2011: $note = sprintf( esc_html__( 'Subscription has been cancelled. Subscription Id: %s.', 'gravityforms' ), $entry['transaction_id'] );
2012: }
2013:
2014: if ( strtolower( $entry['payment_status'] ) == 'cancelled' ) {
2015: $this->log_debug( __METHOD__ . '(): Subscription is already canceled.' );
2016:
2017: return false;
2018: }
2019:
2020: GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Cancelled' );
2021: $this->add_note( $entry['id'], $note );
2022:
2023:
2024: do_action( 'gform_subscription_canceled', $entry, $feed, $entry['transaction_id'] );
2025:
2026:
2027: do_action( 'gform_subscription_cancelled', $entry, $feed, $entry['transaction_id'] );
2028:
2029: if ( has_filter( 'gform_subscription_canceled' ) || has_filter( 'gform_subscription_cancelled' ) ) {
2030: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_canceled.' );
2031: }
2032:
2033: $action = array(
2034: 'type' => 'cancel_subscription',
2035: 'subscription_id' => $entry['transaction_id'],
2036: 'entry_id' => $entry['id'],
2037: 'payment_status' => 'Cancelled',
2038: 'note' => $note,
2039: );
2040: $this->post_payment_action( $entry, $action );
2041:
2042: return true;
2043: }
2044:
2045: public function expire_subscription( $entry, $action ) {
2046: $this->log_debug( __METHOD__ . '(): Processing request.' );
2047: if ( empty( $action['payment_status'] ) ) {
2048: $action['payment_status'] = 'Expired';
2049: }
2050:
2051: if ( empty( $action['note'] ) ) {
2052: $action['note'] = sprintf( esc_html__( 'Subscription has expired. Subscriber Id: %s', 'gravityforms' ), $action['subscription_id'] );
2053: }
2054:
2055: GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Expired' );
2056: $this->add_note( $entry['id'], $action['note'] );
2057: $this->post_payment_action( $entry, $action );
2058:
2059: return true;
2060: }
2061:
2062: public function has_subscription( $entry ) {
2063: if ( rgar( $entry, 'transaction_type' ) == 2 && ! rgempty( 'transaction_id', $entry ) ) {
2064: return true;
2065: } else {
2066: return false;
2067: }
2068: }
2069:
2070: 2071: 2072: 2073: 2074: 2075: 2076: 2077: 2078: 2079:
2080: public function get_entry_by_transaction_id( $transaction_id ) {
2081: if ( empty( $transaction_id ) ) {
2082: return false;
2083: }
2084:
2085: global $wpdb;
2086:
2087: $entry_table_name = self::get_entry_table_name();
2088:
2089: $sql = $wpdb->prepare( "SELECT id FROM {$entry_table_name} WHERE transaction_id = %s", $transaction_id );
2090: $entry_id = $wpdb->get_var( $sql );
2091:
2092: if ( ! $entry_id ) {
2093: $sql = $wpdb->prepare( "SELECT lead_id FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE transaction_id = %s", $transaction_id );
2094: $entry_id = $wpdb->get_var( $sql );
2095: }
2096:
2097: return $entry_id ? $entry_id : false;
2098: }
2099:
2100: 2101: 2102: 2103: 2104: 2105: 2106: 2107: 2108:
2109: public function post_payment_action( $entry, $action ) {
2110: do_action( 'gform_post_payment_action', $entry, $action );
2111: if ( has_filter( 'gform_post_payment_action' ) ) {
2112: $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_action.' );
2113: }
2114:
2115: $form = GFAPI::get_form( $entry['form_id'] );
2116: $supported_events = $this->supported_notification_events( $form );
2117: if ( ! empty( $supported_events ) ) {
2118: if ( ! empty( $action['payment_status'] ) ) {
2119: $action['payment_status'] = GFCommon::get_entry_payment_status_text( $action['payment_status'] );
2120: }
2121: GFAPI::send_notifications( $form, $entry, rgar( $action, 'type' ), array( 'payment_action' => $action ) );
2122: }
2123: }
2124:
2125:
2126:
2127: public function setup_cron() {
2128:
2129: $cron_name = "{$this->_slug}_cron";
2130:
2131: add_action( $cron_name, array( $this, 'check_status' ) );
2132:
2133: if ( ! wp_next_scheduled( $cron_name ) ) {
2134: wp_schedule_event( time(), 'hourly', $cron_name );
2135: }
2136:
2137:
2138: }
2139:
2140: public function check_status() {
2141:
2142: }
2143:
2144:
2145: public function feed_list_columns() {
2146: return array(
2147: 'feedName' => esc_html__( 'Name', 'gravityforms' ),
2148: 'transactionType' => esc_html__( 'Transaction Type', 'gravityforms' ),
2149: 'amount' => esc_html__( 'Amount', 'gravityforms' )
2150: );
2151: }
2152:
2153: public function get_column_value_transactionType( $feed ) {
2154: switch ( rgar( $feed['meta'], 'transactionType' ) ) {
2155: case 'subscription' :
2156: return esc_html__( 'Subscription', 'gravityforms' );
2157: break;
2158: case 'product' :
2159: return esc_html__( 'Products and Services', 'gravityforms' );
2160: break;
2161: case 'donation' :
2162: return esc_html__( 'Donations', 'gravityforms' );
2163: break;
2164:
2165: }
2166:
2167: return esc_html__( 'Unsupported transaction type', 'gravityforms' );
2168: }
2169:
2170: public function get_column_value_amount( $feed ) {
2171: $form = $this->get_current_form();
2172: $field_id = $feed['meta']['transactionType'] == 'subscription' ? rgars( $feed, 'meta/recurringAmount' ) : rgars( $feed, 'meta/paymentAmount' );
2173: if ( $field_id == 'form_total' ) {
2174: $label = esc_html__( 'Form Total', 'gravityforms' );
2175: } else {
2176: $field = GFFormsModel::get_field( $form, $field_id );
2177: $label = GFCommon::get_label( $field );
2178: }
2179:
2180: return $label;
2181: }
2182:
2183:
2184:
2185:
2186: 2187: 2188: 2189: 2190:
2191: public function feed_list_title() {
2192: if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2193: return $this->form_settings_title();
2194: }
2195:
2196: return parent::feed_list_title();
2197: }
2198:
2199: public function feed_list_message() {
2200:
2201: if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2202: return $this->requires_credit_card_message();
2203: }
2204:
2205: return parent::feed_list_message();
2206: }
2207:
2208: public function requires_credit_card_message() {
2209: $url = add_query_arg( array( 'view' => null, 'subview' => null ) );
2210:
2211: 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>' );
2212: }
2213:
2214: public function feed_settings_fields() {
2215:
2216: return array(
2217:
2218: array(
2219: 'description' => '',
2220: 'fields' => array(
2221: array(
2222: 'name' => 'feedName',
2223: 'label' => esc_html__( 'Name', 'gravityforms' ),
2224: 'type' => 'text',
2225: 'class' => 'medium',
2226: 'required' => true,
2227: 'tooltip' => '<h6>' . esc_html__( 'Name', 'gravityforms' ) . '</h6>' . esc_html__( 'Enter a feed name to uniquely identify this setup.', 'gravityforms' )
2228: ),
2229: array(
2230: 'name' => 'transactionType',
2231: 'label' => esc_html__( 'Transaction Type', 'gravityforms' ),
2232: 'type' => 'select',
2233: 'onchange' => "jQuery(this).parents('form').submit();",
2234: 'choices' => array(
2235: array(
2236: 'label' => esc_html__( 'Select a transaction type', 'gravityforms' ),
2237: 'value' => ''
2238: ),
2239: array(
2240: 'label' => esc_html__( 'Products and Services', 'gravityforms' ),
2241: 'value' => 'product'
2242: ),
2243: array( 'label' => esc_html__( 'Subscription', 'gravityforms' ), 'value' => 'subscription' ),
2244: ),
2245: 'tooltip' => '<h6>' . esc_html__( 'Transaction Type', 'gravityforms' ) . '</h6>' . esc_html__( 'Select a transaction type.', 'gravityforms' )
2246: ),
2247: )
2248: ),
2249: array(
2250: 'title' => esc_html__( 'Subscription Settings', 'gravityforms' ),
2251: 'dependency' => array(
2252: 'field' => 'transactionType',
2253: 'values' => array( 'subscription' )
2254: ),
2255: 'fields' => array(
2256: array(
2257: 'name' => 'recurringAmount',
2258: 'label' => esc_html__( 'Recurring Amount', 'gravityforms' ),
2259: 'type' => 'select',
2260: 'choices' => $this->recurring_amount_choices(),
2261: 'required' => true,
2262: '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' )
2263: ),
2264: array(
2265: 'name' => 'billingCycle',
2266: 'label' => esc_html__( 'Billing Cycle', 'gravityforms' ),
2267: 'type' => 'billing_cycle',
2268: 'tooltip' => '<h6>' . esc_html__( 'Billing Cycle', 'gravityforms' ) . '</h6>' . esc_html__( 'Select your billing cycle. This determines how often the recurring payment should occur.', 'gravityforms' )
2269: ),
2270: array(
2271: 'name' => 'recurringTimes',
2272: 'label' => esc_html__( 'Recurring Times', 'gravityforms' ),
2273: 'type' => 'select',
2274: 'choices' => array(
2275: array(
2276: 'label' => esc_html__( 'infinite', 'gravityforms' ),
2277: 'value' => '0'
2278: )
2279: ) + $this->get_numeric_choices( 1, 100 ),
2280: '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' )
2281: ),
2282: array(
2283: 'name' => 'setupFee',
2284: 'label' => esc_html__( 'Setup Fee', 'gravityforms' ),
2285: 'type' => 'setup_fee',
2286: ),
2287: array(
2288: 'name' => 'trial',
2289: 'label' => esc_html__( 'Trial', 'gravityforms' ),
2290: 'type' => 'trial',
2291: 'hidden' => $this->get_setting( 'setupFee_enabled' ),
2292: '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' )
2293: ),
2294: )
2295: ),
2296: array(
2297: 'title' => esc_html__( 'Products & Services Settings', 'gravityforms' ),
2298: 'dependency' => array(
2299: 'field' => 'transactionType',
2300: 'values' => array( 'product', 'donation' )
2301: ),
2302: 'fields' => array(
2303: array(
2304: 'name' => 'paymentAmount',
2305: 'label' => esc_html__( 'Payment Amount', 'gravityforms' ),
2306: 'type' => 'select',
2307: 'choices' => $this->product_amount_choices(),
2308: 'required' => true,
2309: 'default_value' => 'form_total',
2310: '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' )
2311: ),
2312: )
2313: ),
2314: array(
2315: 'title' => esc_html__( 'Other Settings', 'gravityforms' ),
2316: 'dependency' => array(
2317: 'field' => 'transactionType',
2318: 'values' => array( 'subscription', 'product', 'donation' )
2319: ),
2320: 'fields' => $this->other_settings_fields()
2321: ),
2322:
2323: );
2324: }
2325:
2326: public function other_settings_fields() {
2327: $other_settings = array(
2328: array(
2329: 'name' => 'billingInformation',
2330: 'label' => esc_html__( 'Billing Information', 'gravityforms' ),
2331: 'type' => 'field_map',
2332: 'field_map' => $this->billing_info_fields(),
2333: 'tooltip' => '<h6>' . esc_html__( 'Billing Information', 'gravityforms' ) . '</h6>' . esc_html__( 'Map your Form Fields to the available listed fields.', 'gravityforms' )
2334: ),
2335: );
2336:
2337: $option_choices = $this->option_choices();
2338: if ( ! empty( $option_choices ) ) {
2339: $other_settings[] = array(
2340: 'name' => 'options',
2341: 'label' => esc_html__( 'Options', 'gravityforms' ),
2342: 'type' => 'checkbox',
2343: 'choices' => $option_choices,
2344: );
2345: }
2346:
2347: $other_settings[] = array(
2348: 'name' => 'conditionalLogic',
2349: 'label' => esc_html__( 'Conditional Logic', 'gravityforms' ),
2350: 'type' => 'feed_condition',
2351: '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' )
2352: );
2353:
2354: return $other_settings;
2355: }
2356:
2357: public function settings_billing_cycle( $field, $echo = true ) {
2358:
2359: $intervals = $this->supported_billing_intervals();
2360:
2361: $unit = $this->get_setting( $field['name'] . '_unit' );
2362:
2363: $interval_keys = array_keys( $intervals );
2364: if ( ! $unit ) {
2365: $first_interval = $intervals[ $interval_keys[0] ];
2366: } else {
2367: $first_interval = $intervals[ $unit ];
2368: }
2369: $length_field = array(
2370: 'name' => $field['name'] . '_length',
2371: 'type' => 'select',
2372: 'choices' => $this->get_numeric_choices( $first_interval['min'], $first_interval['max'] )
2373: );
2374:
2375: $html = $this->settings_select( $length_field, false );
2376:
2377:
2378: $choices = array();
2379: foreach ( $intervals as $unit => $interval ) {
2380: if ( ! empty( $interval ) ) {
2381: $choices[] = array( 'value' => $unit, 'label' => $interval['label'] );
2382: }
2383: }
2384:
2385: $unit_field = array(
2386: 'name' => $field['name'] . '_unit',
2387: 'type' => 'select',
2388: 'onchange' => "loadBillingLength('" . esc_attr( $field['name'] ) . "')",
2389: 'choices' => $choices,
2390: );
2391:
2392: $html .= ' ' . $this->settings_select( $unit_field, false );
2393:
2394: $html .= "<script type='text/javascript'>var " . $field['name'] . '_intervals = ' . json_encode( $intervals ) . ';</script>';
2395:
2396: if ( $echo ) {
2397: echo $html;
2398: }
2399:
2400: return $html;
2401: }
2402:
2403: public function settings_setup_fee( $field, $echo = true ) {
2404:
2405: $enabled_field = array(
2406: 'name' => $field['name'] . '_checkbox',
2407: 'type' => 'checkbox',
2408: 'horizontal' => true,
2409: 'choices' => array(
2410: array(
2411: 'label' => esc_html__( 'Enabled', 'gravityforms' ),
2412: 'name' => $field['name'] . '_enabled',
2413: 'value' => '1',
2414: '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');}",
2415: ),
2416: )
2417: );
2418:
2419: $html = $this->settings_checkbox( $enabled_field, false );
2420:
2421: $form = $this->get_current_form();
2422:
2423: $is_enabled = $this->get_setting( "{$field['name']}_enabled" );
2424:
2425: $product_field = array(
2426: 'name' => $field['name'] . '_product',
2427: 'type' => 'select',
2428: 'class' => $is_enabled ? '' : 'hidden',
2429: 'choices' => $this->get_payment_choices( $form )
2430: );
2431:
2432: $html .= ' ' . $this->settings_select( $product_field, false );
2433:
2434: if ( $echo ) {
2435: echo $html;
2436: }
2437:
2438: return $html;
2439: }
2440:
2441: public function set_trial_onchange( $field ) {
2442:
2443: 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();}";
2444:
2445: }
2446:
2447: public function settings_trial( $field, $echo = true ) {
2448:
2449:
2450: $enabled_field = array(
2451: 'name' => $field['name'] . '_checkbox',
2452: 'type' => 'checkbox',
2453: 'horizontal' => true,
2454: 'choices' => array(
2455: array(
2456: 'label' => esc_html__( 'Enabled', 'gravityforms' ),
2457: 'name' => $field['name'] . '_enabled',
2458: 'value' => '1',
2459: 'onchange' => $this->set_trial_onchange( $field )
2460: ),
2461: )
2462: );
2463:
2464: $html = $this->settings_checkbox( $enabled_field, false );
2465:
2466:
2467: $form = $this->get_current_form();
2468: $payment_choices = array_merge( $this->get_payment_choices( $form ), array(
2469: array(
2470: 'label' => esc_html__( 'Enter an amount', 'gravityforms' ),
2471: 'value' => 'enter_amount'
2472: )
2473: ) );
2474:
2475: $product_field = array(
2476: 'name' => $field['name'] . '_product',
2477: 'type' => 'select',
2478: 'class' => $this->get_setting( "{$field['name']}_enabled" ) ? '' : 'hidden',
2479: 'onchange' => "if(jQuery(this).val() == 'enter_amount'){ jQuery('#{$field['name']}_amount').show();} else { jQuery('#{$field['name']}_amount').hide(); }",
2480: 'choices' => $payment_choices,
2481: );
2482:
2483: $html .= ' ' . $this->settings_select( $product_field, false );
2484:
2485:
2486: $amount_field = array(
2487: 'type' => 'text',
2488: 'name' => "{$field['name']}_amount",
2489: 'class' => $this->get_setting( "{$field['name']}_enabled" ) && $this->get_setting( "{$field['name']}_product" ) == 'enter_amount' ? 'gform_currency' : 'hidden gform_currency',
2490: );
2491:
2492: $html .= ' ' . $this->settings_text( $amount_field, false );
2493:
2494:
2495: if ( $echo ) {
2496: echo $html;
2497: }
2498:
2499: return $html;
2500: }
2501:
2502: public function recurring_amount_choices() {
2503: $form = $this->get_current_form();
2504: $recurring_choices = $this->get_payment_choices( $form );
2505: $recurring_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2506:
2507: return $recurring_choices;
2508: }
2509:
2510: public function product_amount_choices() {
2511: $form = $this->get_current_form();
2512: $product_choices = $this->get_payment_choices( $form );
2513: $product_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2514:
2515: return $product_choices;
2516: }
2517:
2518: public function option_choices() {
2519:
2520: $option_choices = array(
2521: array(
2522: 'label' => esc_html__( 'Sample Option', 'gravityforms' ),
2523: 'name' => 'sample_option',
2524: 'value' => 'sample_option'
2525: ),
2526: );
2527:
2528: return $option_choices;
2529: }
2530:
2531: public function billing_info_fields() {
2532:
2533: $fields = array(
2534: array( 'name' => 'email', 'label' => esc_html__( 'Email', 'gravityforms' ), 'required' => false ),
2535: array( 'name' => 'address', 'label' => esc_html__( 'Address', 'gravityforms' ), 'required' => false ),
2536: array( 'name' => 'address2', 'label' => esc_html__( 'Address 2', 'gravityforms' ), 'required' => false ),
2537: array( 'name' => 'city', 'label' => esc_html__( 'City', 'gravityforms' ), 'required' => false ),
2538: array( 'name' => 'state', 'label' => esc_html__( 'State', 'gravityforms' ), 'required' => false ),
2539: array( 'name' => 'zip', 'label' => esc_html__( 'Zip', 'gravityforms' ), 'required' => false ),
2540: array( 'name' => 'country', 'label' => esc_html__( 'Country', 'gravityforms' ), 'required' => false ),
2541: );
2542:
2543: return $fields;
2544: }
2545:
2546: public function get_numeric_choices( $min, $max ) {
2547: $choices = array();
2548: for ( $i = $min; $i <= $max; $i ++ ) {
2549: $choices[] = array( 'label' => $i, 'value' => $i );
2550: }
2551:
2552: return $choices;
2553: }
2554:
2555: public function supported_billing_intervals() {
2556:
2557: $billing_cycles = array(
2558: 'day' => array( 'label' => esc_html__( 'day(s)', 'gravityforms' ), 'min' => 1, 'max' => 365 ),
2559: 'week' => array( 'label' => esc_html__( 'week(s)', 'gravityforms' ), 'min' => 1, 'max' => 52 ),
2560: 'month' => array( 'label' => esc_html__( 'month(s)', 'gravityforms' ), 'min' => 1, 'max' => 12 ),
2561: 'year' => array( 'label' => esc_html__( 'year(s)', 'gravityforms' ), 'min' => 1, 'max' => 10 )
2562: );
2563:
2564: return $billing_cycles;
2565: }
2566:
2567: public function get_payment_choices( $form ) {
2568: $fields = GFAPI::get_fields_by_type( $form, array( 'product' ) );
2569: $choices = array(
2570: array( 'label' => esc_html__( 'Select a product field', 'gravityforms' ), 'value' => '' ),
2571: );
2572:
2573: foreach ( $fields as $field ) {
2574: $field_id = $field->id;
2575: $field_label = RGFormsModel::get_label( $field );
2576: $choices[] = array( 'value' => $field_id, 'label' => $field_label );
2577: }
2578:
2579: return $choices;
2580: }
2581:
2582:
2583: public function get_results_page_config() {
2584:
2585: return array(
2586: 'title' => _x( 'Sales', 'toolbar label', 'gravityforms' ),
2587: 'search_title' => _x( 'Filter', 'metabox title', 'gravityforms' ),
2588: 'capabilities' => array( 'gravityforms_view_entries' ),
2589: 'callbacks' => array(
2590: 'fields' => array( $this, 'results_fields' ),
2591: 'data' => array( $this, 'results_data' ),
2592: 'markup' => array( $this, 'results_markup' ),
2593: 'filter_ui' => array( $this, 'results_filter_ui' )
2594: )
2595: );
2596: }
2597:
2598: public function results_fields( $form ) {
2599:
2600: if ( $this->has_feed( $form['id'] ) ) {
2601: return $form['fields'];
2602: } else {
2603: return false;
2604: }
2605:
2606: }
2607:
2608:
2609: public function results_markup( $html, $data, $form, $fields ) {
2610:
2611: $html = "<table width='100%' id='gaddon-results-summary'>
2612: <tr>
2613: <td class='gaddon-results-summary-label'>" . esc_html__( 'Today', 'gravityforms' ) . "</td>
2614: <td class='gaddon-results-summary-label'>" . esc_html__( 'Yesterday', 'gravityforms' ) . "</td>
2615: <td class='gaddon-results-summary-label'>" . esc_html__( 'Last 30 Days', 'gravityforms' ) . "</td>
2616: <td class='gaddon-results-summary-label'>" . esc_html__( 'Total', 'gravityforms' ) . "</td>
2617: </tr>
2618: <tr>
2619: <td class='gaddon-results-summary-data'>
2620: <div class='gaddon-results-summary-data-box'>
2621: <div class='gaddon-results-summary-primary'>{$data['summary']['today']['revenue']}</div>
2622: <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2623: <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2624: </div>
2625: </td>
2626: <td class='gaddon-results-summary-data'>
2627: <div class='gaddon-results-summary-data-box'>
2628: <div class='gaddon-results-summary-primary'>{$data['summary']['yesterday']['revenue']}</div>
2629: <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2630: <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2631: </div>
2632: </td>
2633:
2634: <td class='gaddon-results-summary-data'>
2635: <div class='gaddon-results-summary-data-box'>
2636: <div class='gaddon-results-summary-primary'>{$data['summary']['last30']['revenue']}</div>
2637: <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2638: <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2639: </div>
2640: </td>
2641: <td class='gaddon-results-summary-data'>
2642: <div class='gaddon-results-summary-data-box'>
2643: <div class='gaddon-results-summary-primary'>{$data['summary']['total']['revenue']}</div>
2644: <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2645: <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . '</div>
2646: </div>
2647: </td>
2648:
2649: </tr>
2650: </table>';
2651:
2652: if ( $data['row_count'] == '0' ) {
2653: $html .= "<div class='updated' style='padding:20px; margin-top:40px;'>" . esc_html__( "There aren't any transactions that match your criteria.", 'gravityforms' ) . '</div>';
2654: } else {
2655: $chart_data = $this->get_chart_data( $data );
2656: $html .= $this->get_sales_chart( $chart_data );
2657:
2658:
2659: $sales_table = new GFPaymentStatsTable( $data['table']['header'], $data['data'], $data['row_count'], $data['page_size'] );
2660: $sales_table->prepare_items();
2661: ob_start();
2662: $sales_table->display();
2663: $html .= ob_get_clean();
2664: }
2665:
2666: $html .= '</form>';
2667:
2668: return $html;
2669: }
2670:
2671: public function get_chart_data( $data ) {
2672: $hAxis_column = $data['chart']['hAxis']['column'];
2673: $vAxis_column = $data['chart']['vAxis']['column'];
2674:
2675: $chart_data = array();
2676: foreach ( $data['data'] as $row ) {
2677: $hAxis_value = $row[ $hAxis_column ];
2678: $chart_data[ $hAxis_value ] = $row[ $vAxis_column ];
2679: }
2680:
2681: return array(
2682: 'hAxis_title' => $data['chart']['hAxis']['label'],
2683: 'vAxis_title' => $data['chart']['vAxis']['label'],
2684: 'data' => $chart_data
2685: );
2686: }
2687:
2688: public static function get_sales_chart( $sales_data ) {
2689: $markup = '';
2690:
2691: $data_table = array();
2692: $data_table[] = array( $sales_data['hAxis_title'], $sales_data['vAxis_title'] );
2693:
2694: foreach ( $sales_data['data'] as $key => $value ) {
2695: $data_table[] = array( (string) $key, $value );
2696: }
2697:
2698: $chart_options = array(
2699: 'series' => array(
2700: '0' => array(
2701: 'color' => '#66CCFF',
2702: 'visibleInLegend' => 'false',
2703: ),
2704: ),
2705: 'hAxis' => array(
2706: 'title' => $sales_data['hAxis_title'],
2707: ),
2708: 'vAxis' => array(
2709: 'title' => $sales_data['vAxis_title'],
2710: )
2711: );
2712:
2713: $data_table_json = json_encode( $data_table );
2714: $options_json = json_encode( $chart_options );
2715: $div_id = 'gquiz-results-chart-field-score-frequencies';
2716: $markup .= "<div class='gresults-chart-wrapper' style='width:100%;height:250px' id='{$div_id}'></div>";
2717: $markup .= "<script>
2718: jQuery('#{$div_id}')
2719: .data('datatable',{$data_table_json})
2720: .data('options', {$options_json})
2721: .data('charttype', 'column');
2722: </script>";
2723:
2724: return $markup;
2725:
2726: }
2727:
2728: public function results_data( $form, $fields, $search_criteria, $state_array ) {
2729:
2730: $summary = $this->get_sales_summary( $form['id'] );
2731:
2732: $data = $this->get_sales_data( $form['id'], $search_criteria, $state_array );
2733:
2734: return array(
2735: 'entry_count' => $data['row_count'],
2736: 'row_count' => $data['row_count'],
2737: 'page_size' => $data['page_size'],
2738: 'status' => 'complete',
2739: 'summary' => $summary,
2740: 'data' => $data['rows'],
2741: 'chart' => $data['chart'],
2742: 'table' => $data['table'],
2743: );
2744: }
2745:
2746: private function get_mysql_tz_offset() {
2747: $tz_offset = get_option( 'gmt_offset' );
2748:
2749:
2750: if ( is_numeric( substr( $tz_offset, 0, 1 ) ) ) {
2751: $tz_offset = '+' . $tz_offset;
2752: }
2753:
2754: return $tz_offset . ':00';
2755: }
2756:
2757: public function get_sales_data( $form_id, $search, $state ) {
2758: global $wpdb;
2759:
2760: $data = array(
2761: 'chart' => array(
2762: 'hAxis' => array(),
2763: 'vAxis' => array(
2764: 'column' => 'revenue',
2765: 'label' => esc_html__( 'Revenue', 'gravityforms' )
2766: )
2767: ),
2768: 'table' => array(
2769: 'header' => array(
2770: 'orders' => esc_html__( 'Orders', 'gravityforms' ),
2771: 'subscriptions' => esc_html__( 'Subscriptions', 'gravityforms' ),
2772: 'recurring_payments' => esc_html__( 'Recurring Payments', 'gravityforms' ),
2773: 'refunds' => esc_html__( 'Refunds', 'gravityforms' ),
2774: 'revenue' => esc_html__( 'Revenue', 'gravityforms' )
2775: )
2776: ),
2777: 'rows' => array()
2778: );
2779:
2780: $tz_offset = $this->get_mysql_tz_offset();
2781:
2782: $page_size = 10;
2783: $group = strtolower( rgpost( 'group' ) );
2784: switch ( $group ) {
2785:
2786: case 'weekly' :
2787: $select = "concat(left(transaction.week,4), ' - ', right(transaction.week,2)) as week";
2788: $select_inner1 = "yearweek(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) week";
2789: $select_inner2 = "yearweek(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) week";
2790: $group_by = 'week';
2791: $order_by = 'week desc';
2792: $join = 'lead.week = transaction.week';
2793:
2794: $data['chart']['hAxis']['column'] = 'week';
2795: $data['chart']['hAxis']['label'] = esc_html__( 'Week', 'gravityforms' );
2796: $data['table']['header'] = array_merge( array( 'week' => esc_html__( 'Week', 'gravityforms' ) ), $data['table']['header'] );
2797:
2798: $current_period_format = 'o - W';
2799: $decrement_period = 'week';
2800: $result_period = 'week';
2801: break;
2802:
2803: case 'monthly' :
2804: $select = "date_format(transaction.inner_month, '%%Y') as year, date_format(transaction.inner_month, '%%c') as month, '' as month_abbrev, '' as month_year";
2805: $select_inner1 = "date_format(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2806: $select_inner2 = "date_format(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2807: $group_by = 'inner_month';
2808: $order_by = 'year desc, (month+0) desc';
2809: $join = 'lead.inner_month = transaction.inner_month';
2810:
2811: $data['chart']['hAxis']['column'] = 'month_year';
2812: $data['chart']['hAxis']['label'] = esc_html__( 'Month', 'gravityforms' );
2813: $data['table']['header'] = array_merge( array( 'month_year' => esc_html__( 'Month', 'gravityforms' ) ), $data['table']['header'] );
2814:
2815: $current_period_format = 'n';
2816: $decrement_period = 'month';
2817: $result_period = 'month';
2818: break;
2819:
2820: default :
2821: $select = "transaction.date, date_format(transaction.date, '%%c') as month, day(transaction.date) as day, dayname(transaction.date) as day_of_week, '' as month_day";
2822: $select_inner1 = "date(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) as date";
2823: $select_inner2 = "date(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) as date";
2824: $group_by = 'date';
2825: $order_by = 'date desc';
2826: $join = 'lead.date = transaction.date';
2827:
2828: $data['chart']['hAxis']['column'] = 'month_day';
2829: $data['chart']['hAxis']['label'] = esc_html__( 'Day', 'gravityforms' );
2830: $data['table']['header'] = array_merge( array(
2831: 'date' => esc_html__( 'Date', 'gravityforms' ),
2832: 'day_of_week' => esc_html__( 'Day', 'gravityforms' )
2833: ), $data['table']['header'] );
2834:
2835: $current_period_format = 'Y-m-d';
2836: $decrement_period = 'day';
2837: $result_period = 'date';
2838: break;
2839: }
2840:
2841: $lead_date_filter = '';
2842: $transaction_date_filter = '';
2843: if ( isset( $search['start_date'] ) ) {
2844: $lead_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2845: $transaction_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2846: }
2847:
2848: if ( isset( $search['end_date'] ) ) {
2849: $search['end_date'] .= ' 23:59:59';
2850: $lead_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2851: $transaction_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2852: }
2853:
2854: $payment_method = rgpost( 'payment_method' );
2855: $payment_method_filter = '';
2856: if ( ! empty( $payment_method ) ) {
2857: $payment_method_filter = $wpdb->prepare( ' AND l.payment_method=%s', $payment_method );
2858: }
2859:
2860: $current_page = rgempty( 'paged' ) ? 1 : absint( rgpost( 'paged' ) );
2861: $offset = $page_size * ( $current_page - 1 );
2862:
2863: $entry_table_name = self::get_entry_table_name();
2864:
2865: $sql = $wpdb->prepare(
2866: " SELECT SQL_CALC_FOUND_ROWS {$select}, lead.orders, lead.subscriptions, transaction.refunds, transaction.recurring_payments, transaction.revenue
2867: FROM (
2868: SELECT {$select_inner1},
2869: sum( if(transaction_type = 1,1,0) ) as orders,
2870: sum( if(transaction_type = 2,1,0) ) as subscriptions
2871: FROM {$entry_table_name} l
2872: WHERE l.status='active' AND form_id=%d {$lead_date_filter} {$payment_method_filter}
2873: GROUP BY {$group_by}
2874: ) AS lead
2875:
2876: RIGHT OUTER JOIN(
2877: SELECT {$select_inner2},
2878: sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue,
2879: sum( if(t.transaction_type = 'refund', 1, 0) ) as refunds,
2880: sum( if(t.transaction_type = 'payment' AND t.is_recurring = 1, 1, 0) ) as recurring_payments
2881: FROM {$wpdb->prefix}gf_addon_payment_transaction t
2882: INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
2883: WHERE l.status='active' AND l.form_id=%d {$lead_date_filter} {$transaction_date_filter} {$payment_method_filter}
2884: GROUP BY {$group_by}
2885:
2886: ) AS transaction on {$join}
2887: ORDER BY {$order_by}
2888: LIMIT $page_size OFFSET $offset
2889: ", $form_id, $form_id
2890: );
2891:
2892: GFCommon::log_debug( "sales sql: {$sql}" );
2893:
2894: $results = $wpdb->get_results( $sql, ARRAY_A );
2895:
2896: $display_results = array();
2897: $current_period = date( $current_period_format );
2898:
2899: if ( isset( $search['start_date'] ) || isset( $search['end_date'] ) ) {
2900: foreach ( $results as &$result ) {
2901: $result['orders'] = intval( $result['orders'] );
2902: $result['subscriptions'] = intval( $result['subscriptions'] );
2903: $result['refunds'] = intval( $result['refunds'] );
2904: $result['recurring_payments'] = intval( $result['recurring_payments'] );
2905: $result['revenue'] = floatval( $result['revenue'] );
2906:
2907: $result = $this->format_chart_h_axis( $result );
2908:
2909: }
2910:
2911: $data['row_count'] = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
2912: $data['page_size'] = $page_size;
2913:
2914: $data['rows'] = $results;
2915:
2916: } else {
2917: $current_date = date( 'Y-m-d' );
2918: $current_period_timestamp = strtotime( $current_date );
2919: for ( $i = 1; $i <= 10 ; $i++ ) {
2920: $result_for_date = false;
2921: foreach ( $results as $result ) {
2922: if ( $result[ $result_period ] == $current_period ) {
2923: $display_result = $result;
2924: $result_for_date = true;
2925: break;
2926: }
2927: }
2928: if ( ! $result_for_date ) {
2929: $display_result = array(
2930: $result_period => $current_period,
2931: 'month' => date( 'm', $current_period_timestamp ),
2932: 'day' => date( 'd', $current_period_timestamp ),
2933: 'day_of_week' => date( 'l', $current_period_timestamp ),
2934: 'month_day' => '',
2935: 'year' => date( 'Y', $current_period_timestamp ),
2936: 'month_abbrev' => '',
2937: 'orders' => '0',
2938: 'subscriptions' => '0',
2939: 'refunds' => '0',
2940: 'recurring_payments' => '0',
2941: 'revenue' => '0.00',
2942: );
2943: }
2944:
2945: $display_result['orders'] = intval( $display_result['orders'] );
2946: $display_result['subscriptions'] = intval( $display_result['subscriptions'] );
2947: $display_result['refunds'] = intval( $display_result['refunds'] );
2948: $display_result['recurring_payments'] = intval( $display_result['recurring_payments'] );
2949: $display_result['revenue'] = floatval( $display_result['revenue'] );
2950: $display_result = $this->format_chart_h_axis( $display_result );
2951:
2952: $display_results[] = $display_result;
2953:
2954: $decremented_date = $current_date . ' ' . ( $i * -1 ) . ' ' . $decrement_period;
2955:
2956: $current_period_timestamp = strtotime( $decremented_date );
2957:
2958: $current_period = date( $current_period_format, $current_period_timestamp );
2959:
2960: }
2961: $data['row_count'] = $page_size;
2962: $data['page_size'] = $page_size;
2963:
2964: $data['rows'] = $display_results;
2965: }
2966:
2967: return $data;
2968:
2969: }
2970:
2971: public function format_chart_h_axis( $result ) {
2972: $months = array(
2973: esc_html__( 'Jan', 'gravityforms' ),
2974: esc_html__( 'Feb', 'gravityforms' ),
2975: esc_html__( 'Mar', 'gravityforms' ),
2976: esc_html__( 'Apr', 'gravityforms' ),
2977: esc_html__( 'May', 'gravityforms' ),
2978: esc_html__( 'Jun', 'gravityforms' ),
2979: esc_html__( 'Jul', 'gravityforms' ),
2980: esc_html__( 'Aug', 'gravityforms' ),
2981: esc_html__( 'Sep', 'gravityforms' ),
2982: esc_html__( 'Oct', 'gravityforms' ),
2983: esc_html__( 'Nov', 'gravityforms' ),
2984: esc_html__( 'Dec', 'gravityforms' ),
2985: );
2986:
2987: if ( isset( $result['month_abbrev'] ) ) {
2988: $result['month_abbrev'] = $months[ intval( $result['month'] ) - 1 ];
2989: $result['month_year'] = $months[ intval( $result['month'] ) - 1 ] . ', ' . $result['year'];
2990:
2991: return $result;
2992: } elseif ( isset( $result['month_day'] ) ) {
2993: $result['month_day'] = $months[ intval( $result['month'] ) - 1 ] . ' ' . $result['day'];
2994:
2995: return $result;
2996: }
2997:
2998: return $result;
2999: }
3000:
3001: public function get_sales_summary( $form_id ) {
3002: global $wpdb;
3003:
3004: $tz_offset = $this->get_mysql_tz_offset();
3005: $entry_table_name = self::get_entry_table_name();
3006:
3007: $summary = $wpdb->get_results(
3008: $wpdb->prepare(
3009: "
3010: SELECT transaction.date, lead.orders, lead.subscriptions, transaction.revenue
3011: FROM (
3012: SELECT date( CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) as date,
3013: sum( if(transaction_type = 1,1,0) ) as orders,
3014: sum( if(transaction_type = 2,1,0) ) as subscriptions
3015: FROM {$entry_table_name}
3016: WHERE status='active' AND form_id = %d AND datediff(now(), CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) <= 30
3017: GROUP BY date
3018: ) AS lead
3019:
3020: RIGHT OUTER JOIN(
3021: SELECT date( CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "') ) as date,
3022: sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3023: FROM {$wpdb->prefix}gf_addon_payment_transaction t
3024: INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3025: WHERE l.form_id=%d AND l.status='active'
3026: GROUP BY date
3027: ) AS transaction on lead.date = transaction.date
3028: ORDER BY date desc", $form_id, $form_id
3029: ), ARRAY_A
3030: );
3031:
3032: $total_summary = $wpdb->get_results(
3033: $wpdb->prepare(
3034: "
3035: SELECT sum( if(transaction_type = 1,1,0) ) as orders,
3036: sum( if(transaction_type = 2,1,0) ) as subscriptions
3037: FROM {$entry_table_name}
3038: WHERE form_id=%d AND status='active'", $form_id
3039: ), ARRAY_A
3040: );
3041:
3042: $total_revenue = $wpdb->get_var(
3043: $wpdb->prepare(
3044: "
3045: SELECT sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3046: FROM {$wpdb->prefix}gf_addon_payment_transaction t
3047: INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3048: WHERE l.form_id=%d AND status='active'", $form_id
3049: )
3050: );
3051:
3052:
3053: $result = array(
3054: 'today' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3055: 'yesterday' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3056: 'last30' => array( 'revenue' => 0, 'orders' => 0, 'subscriptions' => 0 ),
3057: 'total' => array(
3058: 'revenue' => GFCommon::to_money( $total_revenue ),
3059: 'orders' => $total_summary[0]['orders'],
3060: 'subscriptions' => $total_summary[0]['subscriptions']
3061: )
3062: );
3063:
3064: $local_time = GFCommon::get_local_timestamp();
3065: $today = gmdate( 'Y-m-d', $local_time );
3066: $yesterday = gmdate( 'Y-m-d', strtotime( '-1 day', $local_time ) );
3067:
3068: foreach ( $summary as $day ) {
3069: if ( $day['date'] == $today ) {
3070: $result['today']['revenue'] = GFCommon::to_money( $day['revenue'] );
3071: $result['today']['orders'] = $day['orders'];
3072: $result['today']['subscriptions'] = $day['subscriptions'];
3073: } elseif ( $day['date'] == $yesterday ) {
3074: $result['yesterday']['revenue'] = GFCommon::to_money( $day['revenue'] );
3075: $result['yesterday']['orders'] = $day['orders'];
3076: $result['yesterday']['subscriptions'] = $day['subscriptions'];
3077: }
3078:
3079: $is_within_30_days = strtotime( $day['date'] ) >= strtotime( '-30 days', $local_time );
3080: if ( $is_within_30_days ) {
3081: $result['last30']['revenue'] += floatval( $day['revenue'] );
3082: $result['last30']['orders'] += floatval( $day['orders'] );
3083: $result['last30']['subscriptions'] += floatval( $day['subscriptions'] );
3084: }
3085: }
3086:
3087: $result['last30']['revenue'] = GFCommon::to_money( $result['last30']['revenue'] );
3088:
3089: return $result;
3090: }
3091:
3092: public function results_filter_ui( $filter_ui, $form_id, $page_title, $gf_page, $gf_view ) {
3093:
3094: if ( $gf_view == "gf_results_{$this->_slug}" ) {
3095: unset( $filter_ui['fields'] );
3096: }
3097:
3098: $view_markup = "<div>
3099: <select id='gaddon-sales-group' name='group'>
3100: <option value='daily' " . selected( 'daily', rgget( 'group' ), false ) . '>' . esc_html__( 'Daily', 'gravityforms' ) . "</option>
3101: <option value='weekly' " . selected( 'weekly', rgget( 'group' ), false ) . '>' . esc_html__( 'Weekly', 'gravityforms' ) . "</option>
3102: <option value='monthly' " . selected( 'monthly', rgget( 'group' ), false ) . '>' . esc_html__( 'Monthly', 'gravityforms' ) . '</option>
3103: </select>
3104: </div>';
3105: $view_filter = array(
3106: 'view' => array(
3107: 'label' => esc_html__( 'View', 'gravityforms' ),
3108: 'tooltip' => '<h6>' . esc_html__( 'View', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how you would like the sales data to be displayed.', 'gravityforms' ),
3109: 'markup' => $view_markup
3110: )
3111: );
3112:
3113: $payment_methods = $this->get_payment_methods( $form_id );
3114:
3115: $payment_method_markup = "
3116: <div>
3117: <select id='gaddon-sales-group' name='payment_method'>
3118: <option value=''>" . esc_html__( _x( 'Any', 'regarding a payment method', 'gravityforms' ) ) . '</option>';
3119:
3120: foreach ( $payment_methods as $payment_method ) {
3121: $payment_method_markup .= "<option value='" . esc_attr( $payment_method ) . "' " . selected( $payment_method, rgget( 'payment_method' ), false ) . '>' . $payment_method . '</option>';
3122: }
3123: $payment_method_markup .= '
3124: </select>
3125: </div>';
3126:
3127: $payment_method_filter = array(
3128: 'payment_method' => array(
3129: 'label' => esc_html__( 'Payment Method', 'gravityforms' ),
3130: 'tooltip' => '',
3131: 'markup' => $payment_method_markup
3132: )
3133: );
3134:
3135:
3136: $filter_ui = array_merge( $view_filter, $payment_method_filter, $filter_ui );
3137:
3138: return $filter_ui;
3139:
3140: }
3141:
3142: public function get_payment_methods( $form_id ) {
3143: global $wpdb;
3144:
3145: $entry_table_name = self::get_entry_table_name();
3146:
3147: $payment_methods = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT payment_method FROM {$entry_table_name} WHERE form_id=%d", $form_id ) );
3148:
3149: return array_filter( $payment_methods, array( $this, 'array_filter_non_blank' ) );
3150: }
3151:
3152: public function array_filter_non_blank( $value ) {
3153: if ( empty( $value ) || $value == 'null' ) {
3154: return false;
3155: }
3156:
3157: return true;
3158: }
3159:
3160: 3161: 3162: 3163: 3164: 3165: 3166: 3167: 3168: 3169: 3170: 3171:
3172: public static function get_entry_table_name() {
3173:
3174: return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_table_name() : GFFormsModel::get_entry_table_name();
3175:
3176: }
3177:
3178: 3179: 3180: 3181: 3182: 3183: 3184: 3185: 3186: 3187: 3188: 3189:
3190: public static function get_entry_meta_table_name() {
3191:
3192: return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name();
3193:
3194: }
3195:
3196: 3197: 3198: 3199: 3200: 3201: 3202: 3203: 3204: 3205:
3206: public static function get_gravityforms_db_version() {
3207:
3208: return method_exists( 'GFFormsModel', 'get_database_version' ) ? GFFormsModel::get_database_version() : GFForms::$version;
3209:
3210: }
3211:
3212:
3213: public function uninstall() {
3214: global $wpdb;
3215:
3216: $entry_meta_table_name = self::get_entry_meta_table_name();
3217:
3218:
3219: $sql = $wpdb->prepare(
3220: "DELETE FROM {$wpdb->prefix}gf_addon_payment_transaction
3221: WHERE lead_id IN
3222: (SELECT lead_id FROM {$entry_meta_table_name} WHERE meta_key='payment_gateway' AND meta_value=%s)", $this->_slug
3223: );
3224: $wpdb->query( $sql );
3225:
3226:
3227: $sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s", $this->_slug );
3228: $wpdb->query( $sql );
3229:
3230:
3231: wp_clear_scheduled_hook( $this->_slug . '_cron' );
3232:
3233: parent::uninstall();
3234: }
3235:
3236:
3237: public function scripts() {
3238: $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
3239: $scripts = array(
3240: array(
3241: 'handle' => 'gaddon_payment',
3242: 'src' => $this->get_gfaddon_base_url() . "/js/gaddon_payment{$min}.js",
3243: 'version' => GFCommon::$version,
3244: 'strings' => array(
3245: 'subscriptionCancelWarning' => __( "Warning! This subscription will be canceled. This cannot be undone. 'OK' to cancel subscription, 'Cancel' to stop", 'gravityforms' ),
3246: 'subscriptionCancelNonce' => wp_create_nonce( 'gaddon_cancel_subscription' ),
3247: 'subscriptionCanceled' => __( 'Canceled', 'gravityforms' ),
3248: 'subscriptionError' => __( 'The subscription could not be canceled. Please try again later.', 'gravityforms' )
3249: ),
3250: 'enqueue' => array(
3251: array( 'admin_page' => array( 'form_settings' ), 'tab' => $this->_slug ),
3252: array( 'admin_page' => array( 'entry_view' ) ),
3253: )
3254: ),
3255: array(
3256: 'handle' => 'gaddon_token',
3257: 'src' => $this->get_gfaddon_base_url() . "/js/gaddon_token{$min}.js",
3258: 'version' => GFCommon::$version,
3259: 'deps' => array( 'jquery' ),
3260: 'in_footer' => false,
3261: 'enqueue' => array(
3262: array( $this, 'enqueue_creditcard_token_script' )
3263: )
3264: ),
3265: array(
3266: 'handle' => 'gform_form_admin',
3267: 'enqueue' => array(
3268: array( 'admin_page' => array( 'entry_edit' ) ),
3269: ),
3270: ),
3271: );
3272:
3273: return array_merge( parent::scripts(), $scripts );
3274: }
3275:
3276:
3277:
3278: 3279: 3280: 3281: 3282: 3283: 3284: 3285: 3286:
3287: public function creditcard_token_info( $form ) {
3288:
3289: return array();
3290:
3291: }
3292:
3293: 3294: 3295: 3296: 3297: 3298: 3299: 3300: 3301: 3302: 3303: 3304: 3305:
3306: public function add_creditcard_token_input( $content, $field, $value, $entry_id, $form_id ) {
3307:
3308: if ( ! $this->has_feed( $form_id ) || GFFormsModel::get_input_type( $field ) != 'creditcard' ) {
3309: return $content;
3310: }
3311:
3312: $form = GFAPI::get_form( $form_id );
3313: if ( ! $this->creditcard_token_info( $form ) ) {
3314: return $content;
3315: }
3316:
3317: $slug = str_replace( 'gravityforms', '', $this->_slug );
3318: $content .= '<input type=\'hidden\' name=\'' . $slug . '_response\' id=\'gf_' . $slug . '_response\' value=\'' . rgpost( $slug . '_response' ) . '\' />';
3319:
3320: return $content;
3321:
3322: }
3323:
3324: 3325: 3326: 3327: 3328: 3329: 3330: 3331: 3332:
3333: public function force_ajax_for_creditcard_tokens( $args ) {
3334:
3335: $form = GFAPI::get_form( rgar( $args, 'form_id' ) );
3336:
3337: $args['ajax'] = $this->enqueue_creditcard_token_script( $form ) ? true : $args['ajax'];
3338:
3339: return $args;
3340:
3341: }
3342:
3343: 3344: 3345: 3346: 3347: 3348: 3349: 3350: 3351:
3352: public function enqueue_creditcard_token_script( $form ) {
3353:
3354: return $form && $this->has_feed( $form['id'] ) && $this->creditcard_token_info( $form );
3355:
3356: }
3357:
3358: 3359: 3360: 3361: 3362: 3363: 3364: 3365: 3366: 3367: 3368:
3369: public function register_creditcard_token_script( $form, $field_values, $is_ajax ) {
3370:
3371: if ( ! $this->enqueue_creditcard_token_script( $form ) ) {
3372: return;
3373: }
3374:
3375:
3376: $gftoken = array(
3377: 'callback' => 'GF_' . str_replace( ' ', '', $this->_short_title ),
3378: 'feeds' => $this->creditcard_token_info( $form ),
3379: 'formId' => rgar( $form, 'id' ),
3380: 'hasPages' => GFCommon::has_pages( $form ),
3381: 'pageCount' => GFFormDisplay::get_max_page_number( $form ),
3382: 'responseField' => '#gf_' . str_replace( 'gravityforms', '', $this->_slug ) . '_response'
3383: );
3384:
3385:
3386: $gftoken['fields'] = $this->get_creditcard_token_entry_fields( $gftoken['feeds'] );
3387:
3388: $script = 'new GFToken( ' . json_encode( $gftoken ) . ' );';
3389: GFFormDisplay::add_init_script( $form['id'], 'GFToken', GFFormDisplay::ON_PAGE_RENDER, $script );
3390:
3391: }
3392:
3393: 3394: 3395: 3396: 3397: 3398: 3399: 3400: 3401:
3402: public function get_creditcard_token_entry_fields( $feeds ) {
3403:
3404: $fields = array();
3405:
3406: foreach ( $feeds as $feed ) {
3407: foreach ( $feed['billing_fields'] as $billing_field ) {
3408: $fields[] = $billing_field;
3409: }
3410: }
3411:
3412: return array_unique( $fields );
3413:
3414: }
3415:
3416:
3417: 3418: 3419: 3420: 3421: 3422: 3423:
3424: public function supported_currencies( $currencies ) {
3425: return $currencies;
3426: }
3427:
3428: 3429: 3430: 3431: 3432: 3433: 3434:
3435: public function get_currency( $currency_code = '' ) {
3436: if ( empty( $currency_code ) ) {
3437: $currency_code = GFCommon::get_currency();
3438: }
3439:
3440: return new RGCurrency( $currency_code );
3441: }
3442:
3443: 3444: 3445: 3446: 3447: 3448: 3449: 3450: 3451: 3452:
3453: public function get_amount_export( $amount, $currency_code = '' ) {
3454: $currency = $this->get_currency( $currency_code );
3455: $amount = $currency->to_number( $amount );
3456:
3457: if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3458: return (int) round( $amount * 100 );
3459: }
3460:
3461: return $amount;
3462: }
3463:
3464: 3465: 3466: 3467: 3468: 3469: 3470: 3471:
3472: public function get_amount_import( $amount, $currency_code = '' ) {
3473: $currency = $this->get_currency( $currency_code );
3474:
3475: if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3476: return $amount / 100;
3477: }
3478:
3479: return $amount;
3480: }
3481:
3482: 3483: 3484: 3485: 3486: 3487: 3488: 3489: 3490: 3491:
3492: public function maybe_add_action_amount_formatted( $action, $currency_code = '' ) {
3493: if ( ! empty( $action['amount'] ) && empty( $action['amount_formatted'] ) ) {
3494: $action['amount_formatted'] = GFCommon::to_money( $action['amount'], $currency_code );
3495: }
3496:
3497: return $action;
3498: }
3499:
3500:
3501:
3502: public function entry_info( $form_id, $entry ) {
3503:
3504:
3505: if ( ! $this->payment_method_is_overridden( 'cancel' ) ) {
3506: return;
3507: }
3508:
3509:
3510: $cancelsub_button = '';
3511: if ( $entry['transaction_type'] == '2' && $entry['payment_status'] <> 'Cancelled' && $this->is_payment_gateway( $entry['id'] ) ) {
3512: ?>
3513: <input id="cancelsub" type="button" name="cancelsub"
3514: value="<?php esc_html_e( 'Cancel Subscription', 'gravityforms' ) ?>" class="button"
3515: onclick="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"
3516: onkeypress="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"/>
3517: <img src="<?php echo GFCommon::get_base_url() ?>/images/spinner.gif" id="subscription_cancel_spinner"
3518: style="display: none;"/>
3519:
3520: <script type="text/javascript">
3521:
3522: </script>
3523:
3524: <?php
3525: }
3526: }
3527:
3528: 3529: 3530: 3531: 3532:
3533: public function entry_deleted( $entry_id ) {
3534: global $wpdb;
3535:
3536:
3537: $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_transaction", array( 'lead_id' => $entry_id ), array( '%d' ) );
3538:
3539:
3540: $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_callback", array( 'lead_id' => $entry_id ), array( '%d' ) );
3541: }
3542:
3543: public function ajax_cancel_subscription() {
3544: check_ajax_referer( 'gaddon_cancel_subscription', 'gaddon_cancel_subscription' );
3545:
3546: $entry_id = $_POST['entry_id'];
3547:
3548: $this->log_debug( __METHOD__ . '(): Processing request for entry #' . $entry_id );
3549:
3550: $entry = GFAPI::get_entry( $entry_id );
3551: $form = GFAPI::get_form( $entry['form_id'] );
3552: $feed = $this->get_payment_feed( $entry, $form );
3553:
3554:
3555: if ( empty ( $feed ) ) {
3556: $this->log_debug( __METHOD__ . '(): Aborting. Entry does not have a feed.' );
3557:
3558: return;
3559: }
3560:
3561: if ( $this->cancel( $entry, $feed ) ) {
3562: $this->cancel_subscription( $entry, $feed );
3563: die( '1' );
3564: } else {
3565: $this->log_debug( __METHOD__ . '(): Aborting. Unable to cancel subscription.' );
3566: die( '0' );
3567: }
3568:
3569: }
3570:
3571: 3572: 3573: 3574: 3575: 3576: 3577:
3578: public function before_delete_field( $form_id, $field_id ) {
3579: if ( $this->_requires_credit_card ) {
3580: $form = GFAPI::get_form( $form_id );
3581: $field = $this->get_credit_card_field( $form );
3582:
3583: if ( is_object( $field ) && $field->id == $field_id ) {
3584: $feeds = $this->get_feeds( $form_id );
3585: foreach ( $feeds as $feed ) {
3586: if ( $feed['is_active'] ) {
3587: $this->update_feed_active( $feed['id'], 0 );
3588: }
3589: }
3590: }
3591: }
3592: }
3593:
3594:
3595:
3596:
3597: private function payment_method_is_overridden( $method_name, $base_class = 'GFPaymentAddOn' ) {
3598: return parent::method_is_overridden( $method_name, $base_class );
3599: }
3600:
3601: public function authorization_error( $error_message ) {
3602: return array( 'error_message' => $error_message, 'is_success' => false, 'is_authorized' => false );
3603: }
3604:
3605: public function remove_spaces_from_card_number( $card_number ) {
3606: $card_number = str_replace( array( "\t", "\n", "\r", ' ' ), '', $card_number );
3607:
3608: return $card_number;
3609: }
3610:
3611: public function get_supports_callback(){
3612: return $this->_supports_callbacks;
3613: }
3614: }
3615:
3616: if ( ! class_exists( 'WP_List_Table' ) ) {
3617: require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
3618: }
3619:
3620:
3621: class GFPaymentStatsTable extends WP_List_Table {
3622:
3623: private $_rows = array();
3624: private $_page_size = 10;
3625: private $_total_items = 0;
3626:
3627: function __construct( $columns, $rows, $total_count, $page_size ) {
3628: $this->_rows = $rows;
3629: $this->_total_items = $total_count;
3630: $this->_page_size = $page_size;
3631:
3632: $this->_column_headers = array(
3633: $columns,
3634: array(),
3635: array(),
3636: rgar( array_values( $columns ), 2 ),
3637: );
3638:
3639: parent::__construct(
3640: array(
3641: 'singular' => esc_html__( 'sale', 'gravityforms' ),
3642: 'plural' => esc_html__( 'sales', 'gravityforms' ),
3643: 'ajax' => false,
3644: 'screen' => 'gaddon_sales',
3645: )
3646: );
3647: }
3648:
3649: function prepare_items() {
3650: $this->items = $this->_rows;
3651:
3652: $this->set_pagination_args( array( 'total_items' => $this->_total_items, 'per_page' => $this->_page_size ) );
3653: }
3654:
3655: function no_items() {
3656: esc_html_e( "There hasn't been any sales in the specified date range.", 'gravityforms' );
3657: }
3658:
3659: function get_columns() {
3660: return $this->_column_headers[0];
3661: }
3662:
3663: function column_default( $item, $column ) {
3664: return rgar( $item, $column );
3665: }
3666:
3667: function column_revenue( $item ) {
3668: return GFCommon::to_money( $item['revenue'] );
3669: }
3670:
3671: function ( $which ) {
3672: if ( empty( $this->_pagination_args ) ) {
3673: return;
3674: }
3675:
3676: $total_items = $this->get_pagination_arg( 'total_items' );
3677: $total_pages = $this->get_pagination_arg( 'total_pages' );
3678:
3679: $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items, 'gravityforms' ), number_format_i18n( $total_items ) ) . '</span>';
3680:
3681: $current = $this->get_pagenum();
3682:
3683: $page_links = array();
3684:
3685: $disable_first = $disable_last = '';
3686: if ( $current == 1 ) {
3687: $disable_first = ' disabled';
3688: }
3689: if ( $current == $total_pages ) {
3690: $disable_last = ' disabled';
3691: }
3692:
3693: $page_links[] = sprintf(
3694: "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();' onkeypress='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();'>%s</a>",
3695: 'first-page' . $disable_first,
3696: esc_attr__( 'Go to the first page', 'gravityforms' ),
3697: '«'
3698: );
3699:
3700: $page_links[] = sprintf(
3701: "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3702: 'prev-page' . $disable_first,
3703: esc_attr__( 'Go to the previous page', 'gravityforms' ),
3704: max( 1, $current - 1 ),
3705: max( 1, $current - 1 ),
3706: '‹'
3707: );
3708:
3709:
3710: $html_current_page = $current;
3711:
3712: $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
3713: $page_links[] = '<span class="paging-input">' . sprintf( esc_html_x( '%1$s of %2$s', 'paging', 'gravityforms' ), $html_current_page, $html_total_pages ) . '</span>';
3714:
3715: $page_links[] = sprintf(
3716: "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3717: 'next-page' . $disable_last,
3718: esc_attr__( 'Go to the next page', 'gravityforms' ),
3719: min( $total_pages, $current + 1 ),
3720: min( $total_pages, $current + 1 ),
3721: '›'
3722: );
3723:
3724: $page_links[] = sprintf(
3725: "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3726: 'last-page' . $disable_last,
3727: esc_attr__( 'Go to the last page', 'gravityforms' ),
3728: $total_pages,
3729: '»'
3730: );
3731:
3732: $pagination_links_class = 'pagination-links';
3733: if ( ! empty( $infinite_scroll ) ) {
3734: $pagination_links_class = ' hide-if-js';
3735: }
3736: $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
3737:
3738: if ( $total_pages ) {
3739: $page_class = $total_pages < 2 ? ' one-page' : '';
3740: } else {
3741: $page_class = ' no-pages';
3742: }
3743:
3744: $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
3745:
3746: echo $this->_pagination;
3747: }
3748:
3749: }
3750: