PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.1.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.1.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
← All changes | inc/api/donations-api.php +199 -78 0.0.1 → 1.1.0 View file →
@@ -8,8 +8,9 @@
8 8 namespace SureDonation\Inc\API;
9 9
10 10 use SureDonation\Inc\Database\Tables\Donations;
11 11 use SureDonation\Inc\Database\Tables\Donors;
12 +use SureDonation\Inc\Emails\Email_Handler;
12 13 use SureDonation\Inc\Helper;
13 14 use SureDonation\Inc\Payments\Payment_Helper;
14 15 use SureDonation\Inc\Payments\Stripe\Stripe_Helper;
15 16 use WP_Error;
@@ -41,8 +42,18 @@
41 42 [
42 43 'methods' => WP_REST_Server::READABLE,
43 44 'callback' => [ $this, 'get_donations' ],
44 45 'permission_callback' => [ $this, 'check_permissions' ],
46 + 'args' => [
47 + 'after' => [
48 + 'sanitize_callback' => 'sanitize_text_field',
49 + 'validate_callback' => [ $this, 'validate_date_param' ],
50 + ],
51 + 'before' => [
52 + 'sanitize_callback' => 'sanitize_text_field',
53 + 'validate_callback' => [ $this, 'validate_date_param' ],
54 + ],
55 + ],
45 56 ],
46 57 [
47 58 'methods' => WP_REST_Server::CREATABLE,
48 59 'callback' => [ $this, 'create_donation' ],
@@ -109,10 +120,11 @@
109 120 return is_numeric( $param );
110 121 },
111 122 ],
112 123 'status' => [
113 - 'required' => true,
114 - 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
124 + 'required' => true,
125 + 'sanitize_callback' => 'sanitize_text_field',
126 + 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
115 127 ],
116 128 ],
117 129 ],
118 130
@@ -137,10 +149,11 @@
137 149 'callback' => [ $this, 'bulk_action' ],
138 150 'permission_callback' => [ $this, 'check_permissions' ],
139 151 'args' => [
140 152 'action' => [
141 - 'required' => true,
142 - 'enum' => [ 'delete', 'update_status' ],
153 + 'required' => true,
154 + 'sanitize_callback' => 'sanitize_text_field',
155 + 'enum' => [ 'delete', 'update_status' ],
143 156 ],
144 157 'ids' => [
145 158 'required' => true,
146 159 'validate_callback' => static function ( $param ) {
@@ -147,9 +160,10 @@
147 160 return is_array( $param ) && ! empty( $param );
148 161 },
149 162 ],
150 163 'status' => [
151 - 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
164 + 'sanitize_callback' => 'sanitize_text_field',
165 + 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
152 166 ],
153 167 ],
154 168 ],
155 169
@@ -173,10 +187,11 @@
173 187 'required' => true,
174 188 'sanitize_callback' => 'absint',
175 189 ],
176 190 'refund_type' => [
177 - 'required' => true,
178 - 'enum' => [ 'full', 'partial' ],
191 + 'required' => true,
192 + 'sanitize_callback' => 'sanitize_text_field',
193 + 'enum' => [ 'full', 'partial' ],
179 194 ],
180 195 'refund_notes' => [
181 196 'sanitize_callback' => 'sanitize_textarea_field',
182 197 ],
@@ -312,8 +327,9 @@
312 327 $per_page = $request->get_param( 'per_page' ) ?? 20;
313 328 $search = $request->get_param( 'search' ) ?? '';
314 329 $status = $request->get_param( 'status' ) ?? 'all';
315 330 $campaign = $request->get_param( 'campaign' ) ?? '';
331 + $donor = $request->get_param( 'donor' ) ?? '';
316 332 $sort_by = $request->get_param( 'sort_by' ) ?? 'created_at';
317 333 $order = $request->get_param( 'order' ) ?? 'desc';
318 334
319 335 // Calculate pagination.
@@ -319,21 +335,28 @@
319 335 // Calculate pagination.
320 336 $limit = absint( $per_page );
321 337 $offset = ( absint( $page ) - 1 ) * $limit;
322 338
323 - // Get donations from database using admin list method with filters.
324 - $results = Donations::get_admin_list(
325 - $status,
326 - ! empty( $campaign ) ? absint( $campaign ) : 0,
327 - sanitize_text_field( $search ),
328 - $limit,
329 - $offset,
330 - $sort_by, // using whitelist validation in the method.
331 - strtoupper( $order ) // using whitelist validation in the method.
332 - );
339 + // If filtering by donor, use the donor-specific query.
340 + if ( ! empty( $donor ) ) {
341 + $donor_data = Donations::get_by_donor_id( absint( $donor ), $limit, $offset );
342 + $results = $donor_data['donations'];
343 + $total = $donor_data['total'];
344 + } else {
345 + // Get donations from database using admin list method with filters.
346 + $results = Donations::get_admin_list(
347 + $status,
348 + ! empty( $campaign ) ? absint( $campaign ) : 0,
349 + sanitize_text_field( $search ),
350 + $limit,
351 + $offset,
352 + $sort_by, // using whitelist validation in the method.
353 + strtoupper( $order ) // using whitelist validation in the method.
354 + );
333 355
334 - // Get total count.
335 - $total = Donations::get_total_donations_by_status( $status, ! empty( $campaign ) ? absint( $campaign ) : 0 );
356 + // Get total count.
357 + $total = Donations::get_total_donations_by_status( $status, ! empty( $campaign ) ? absint( $campaign ) : 0 );
358 + }
336 359
337 360 // Format donations data.
338 361 $donations = [];
339 362 foreach ( $results as $donation ) {
@@ -411,28 +434,40 @@
411 434 if ( ! empty( $donor_email ) ) {
412 435 $donor_id = Donors::get_or_create( $donor_email, $donor_name, $donor_phone );
413 436 }
414 437
438 + // Build donation data — pro can add subscription fields via filter.
439 + $donation_data = [
440 + 'campaign_id' => $campaign_id,
441 + 'donor_id' => $donor_id ? $donor_id : 0,
442 + 'amount' => $amount,
443 + 'fees_covered' => $fees_covered,
444 + 'currency' => Payment_Helper::get_currency(),
445 + 'gateway' => $gateway,
446 + 'payment_status' => $payment_status,
447 + 'payment_mode' => Payment_Helper::get_payment_mode(),
448 + 'donor_name' => $donor_name,
449 + 'donor_email' => $donor_email,
450 + 'donor_phone' => $donor_phone,
451 + 'is_anonymous' => $is_anonymous ? 1 : 0,
452 + 'donation_type' => $donation_type,
453 + 'donor_comment' => $donor_comment,
454 + 'transaction_id' => $transaction_id,
455 + ];
456 +
457 + /**
458 + * Filter donation data before insertion.
459 + *
460 + * Pro uses this to add subscription_id, subscription_status, parent_subscription_id.
461 + *
462 + * @param array<string, mixed> $donation_data Donation data to insert.
463 + * @param \WP_REST_Request $request The original REST request.
464 + * @since 1.0.0
465 + */
466 + $donation_data = apply_filters( 'suredonation_create_donation_data', $donation_data, $request );
467 +
415 468 // Create the donation in database.
416 - $donation_id = Donations::add(
417 - [
418 - 'campaign_id' => $campaign_id,
419 - 'donor_id' => $donor_id ? $donor_id : 0,
420 - 'amount' => $amount,
421 - 'fees_covered' => $fees_covered,
422 - 'currency' => Payment_Helper::get_currency(),
423 - 'gateway' => $gateway,
424 - 'payment_status' => $payment_status,
425 - 'payment_mode' => Payment_Helper::get_payment_mode(),
426 - 'donor_name' => $donor_name,
427 - 'donor_email' => $donor_email,
428 - 'donor_phone' => $donor_phone,
429 - 'is_anonymous' => $is_anonymous ? 1 : 0,
430 - 'donation_type' => $donation_type,
431 - 'donor_comment' => $donor_comment,
432 - 'transaction_id' => $transaction_id,
433 - ]
434 - );
469 + $donation_id = Donations::add( $donation_data );
435 470
436 471 if ( ! $donation_id ) {
437 472 return new WP_Error(
438 473 'create_failed',
@@ -500,8 +535,20 @@
500 535 }
501 536 }
502 537 }
503 538
539 + /**
540 + * Filter donation update data before saving.
541 + *
542 + * Pro uses this to add subscription fields to the update.
543 + *
544 + * @param array<string, mixed> $update_data Data to update.
545 + * @param \WP_REST_Request $request The REST request.
546 + * @param int $donation_id Donation ID.
547 + * @since 1.0.0
548 + */
549 + $update_data = apply_filters( 'suredonation_update_donation_data', $update_data, $request, $donation_id );
550 +
504 551 if ( ! empty( $update_data ) ) {
505 552 Donations::update( $donation_id, $update_data );
506 553 }
507 554
@@ -674,18 +721,10 @@
674 721 [ 'status' => 400 ]
675 722 );
676 723 }
677 724
678 - // Check if Stripe is connected.
679 - if ( ! Stripe_Helper::is_stripe_connected() ) {
680 - return new WP_Error(
681 - 'stripe_not_connected',
682 - __( 'Stripe is not connected. Please configure Stripe in settings.', 'suredonation' ),
683 - [ 'status' => 400 ]
684 - );
685 - }
686 -
687 725 // Validate refund amount.
726 + $gateway = $donation['gateway'] ?? 'stripe';
688 727 $currency = $donation['currency'] ?? 'USD';
689 728 $total_amount = $this->amount_to_stripe_format( floatval( $donation['amount'] ), $currency );
690 729 $refunded_amount = $this->amount_to_stripe_format( floatval( $donation['refunded_amount'] ?? 0 ), $currency );
691 730 $refundable = $total_amount - $refunded_amount;
@@ -701,10 +740,27 @@
701 740 [ 'status' => 400 ]
702 741 );
703 742 }
704 743
705 - // Process refund through Stripe.
706 - $refund_result = Stripe_Helper::create_refund( $transaction_id, $refund_amount, 'requested_by_customer' );
744 + // Process refund through the appropriate gateway.
745 + if ( 'paypal' === $gateway ) {
746 + $refund_amount_major = $this->amount_from_stripe_format( $refund_amount, $currency );
747 + $refund_result = \SureDonation\Inc\Payments\PayPal\PayPal_Api_Payments::refund_capture(
748 + $transaction_id,
749 + $refund_amount_major,
750 + $currency
751 + );
752 + } else {
753 + // Check if Stripe is connected.
754 + if ( ! Stripe_Helper::is_stripe_connected() ) {
755 + return new WP_Error(
756 + 'stripe_not_connected',
757 + __( 'Stripe is not connected. Please configure Stripe in settings.', 'suredonation' ),
758 + [ 'status' => 400 ]
759 + );
760 + }
761 + $refund_result = Stripe_Helper::create_refund( $transaction_id, $refund_amount, 'requested_by_customer' );
762 + }
707 763
708 764 if ( is_wp_error( $refund_result ) ) {
709 765 return new WP_Error(
710 766 'refund_failed',
@@ -770,8 +826,24 @@
770 826 'currency' => strtoupper( $currency ),
771 827 ]
772 828 );
773 829
830 + // Send refund email notifications.
831 + $campaign_id = isset( $donation['campaign_id'] ) && is_numeric( $donation['campaign_id'] ) ? absint( $donation['campaign_id'] ) : 0;
832 + $form_id = isset( $donation['form_id'] ) && is_numeric( $donation['form_id'] ) ? absint( $donation['form_id'] ) : 0;
833 + $donation_data = [
834 + 'id' => $donation_id,
835 + 'donor_name' => $donation['donor_name'] ?? '',
836 + 'donor_email' => $donation['donor_email'] ?? '',
837 + 'amount' => $donation['amount'] ?? 0,
838 + 'currency' => strtoupper( $currency ),
839 + 'refund_amount' => $this->amount_from_stripe_format( $refund_amount, $currency ),
840 + 'donation_type' => $donation['donation_type'] ?? 'one-time',
841 + 'gateway' => 'stripe',
842 + ];
843 +
844 + Email_Handler::send_refund_processed( $donation_id, $campaign_id, $donation_data, $form_id );
845 +
774 846 // Get updated donation.
775 847 $updated_donation = Donations::get( $donation_id );
776 848
777 849 return new WP_REST_Response(
@@ -784,9 +856,8 @@
784 856 ],
785 857 200
786 858 );
787 859 }
788 -
789 860 /**
790 861 * Check if user has permission to manage donations.
791 862 *
792 863 * @return bool True if user has permission.
@@ -1010,16 +1081,24 @@
1010 1081 'sanitize_callback' => 'sanitize_text_field',
1011 1082 ],
1012 1083 'amount' => [
1013 1084 'required' => $required,
1014 - 'sanitize_callback' => 'floatval',
1085 + 'sanitize_callback' => static function ( $value ) {
1086 + return floatval( $value );
1087 + },
1015 1088 ],
1016 1089 'fees_covered' => [
1017 - 'sanitize_callback' => 'floatval',
1090 + 'sanitize_callback' => static function ( $value ) {
1091 + return floatval( $value );
1092 + },
1018 1093 ],
1019 1094 'donation_type' => [
1020 - 'default' => 'one-time',
1021 - 'enum' => [ 'one-time', 'recurring' ],
1095 + 'default' => 'one-time',
1096 + 'enum' => [ 'one-time', 'recurring', 'renewal' ],
1097 + 'sanitize_callback' => 'sanitize_text_field',
1098 + 'validate_callback' => static function ( $param ) {
1099 + return in_array( $param, [ 'one-time', 'recurring', 'renewal' ], true );
1100 + },
1022 1101 ],
1023 1102 'is_anonymous' => [
1024 1103 'sanitize_callback' => 'rest_sanitize_boolean',
1025 1104 ],
@@ -1026,10 +1105,14 @@
1026 1105 'donor_comment' => [
1027 1106 'sanitize_callback' => 'wp_kses_post',
1028 1107 ],
1029 1108 'payment_status' => [
1030 - 'default' => 'pending',
1031 - 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
1109 + 'default' => 'pending',
1110 + 'enum' => [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ],
1111 + 'sanitize_callback' => 'sanitize_text_field',
1112 + 'validate_callback' => static function ( $param ) {
1113 + return in_array( $param, [ 'pending', 'processing', 'completed', 'failed', 'refunded', 'partially_refunded', 'cancelled' ], true );
1114 + },
1032 1115 ],
1033 1116 'gateway' => [
1034 1117 'sanitize_callback' => 'sanitize_text_field',
1035 1118 ],
@@ -1039,8 +1122,23 @@
1039 1122 ];
1040 1123 }
1041 1124
1042 1125 /**
1126 + * Validate REST date filter parameters.
1127 + *
1128 + * @param mixed $param Date parameter.
1129 + * @return bool Whether the date is valid.
1130 + * @since 0.0.1
1131 + */
1132 + public function validate_date_param( $param ) {
1133 + if ( '' === $param || null === $param ) {
1134 + return true;
1135 + }
1136 +
1137 + return is_string( $param ) && 1 === preg_match( '/^\d{4}-\d{2}-\d{2}$/', $param );
1138 + }
1139 +
1140 + /**
1043 1141 * Convert amount to Stripe's smallest currency unit.
1044 1142 *
1045 1143 * @param float $amount Amount in major currency unit.
1046 1144 * @param string $currency Currency code.
@@ -1078,8 +1176,9 @@
1078 1176 */
1079 1177 private function format_donation( $donation ) {
1080 1178 $campaign_id = isset( $donation['campaign_id'] ) ? Helper::get_integer_value( $donation['campaign_id'] ) : 0;
1081 1179 $donation_id = isset( $donation['id'] ) ? Helper::get_integer_value( $donation['id'] ) : 0;
1180 + $form_id = isset( $donation['form_id'] ) ? Helper::get_integer_value( $donation['form_id'] ) : 0;
1082 1181
1083 1182 // Get payment logs for this donation.
1084 1183 $logs = $donation_id ? Donations::get_log( $donation_id ) : [];
1085 1184
@@ -1085,30 +1184,52 @@
1085 1184
1086 1185 // Get payment mode for Stripe dashboard URL.
1087 1186 $payment_mode = $donation['payment_mode'] ?? 'test';
1088 1187
1188 + $form_edit_url = '';
1189 + if ( $form_id && current_user_can( 'edit_post', $form_id ) ) {
1190 + $form_edit_url = esc_url_raw( get_edit_post_link( $form_id, 'raw' ) );
1191 + }
1192 +
1193 + // Parse donation_data for subscription metadata.
1194 + $donation_data = $donation['donation_data'] ?? [];
1195 + if ( is_string( $donation_data ) && ! empty( $donation_data ) ) {
1196 + $donation_data = json_decode( $donation_data, true );
1197 + }
1198 + if ( ! is_array( $donation_data ) ) {
1199 + $donation_data = [];
1200 + }
1201 +
1089 1202 return [
1090 - 'id' => $donation_id,
1091 - 'campaign_id' => $campaign_id,
1092 - 'campaign_title' => $campaign_id ? wp_kses_post( get_the_title( $campaign_id ) ) : '',
1093 - 'donor_id' => isset( $donation['donor_id'] ) ? Helper::get_integer_value( $donation['donor_id'] ) : 0,
1094 - 'donor_name' => $donation['donor_name'] ?? '',
1095 - 'donor_email' => $donation['donor_email'] ?? '',
1096 - 'donor_phone' => $donation['donor_phone'] ?? '',
1097 - 'amount' => Helper::get_float_value( $donation['amount'] ?? 0 ),
1098 - 'fees_covered' => Helper::get_float_value( $donation['fees_covered'] ?? 0 ),
1099 - 'refunded_amount' => Helper::get_float_value( $donation['refunded_amount'] ?? 0 ),
1100 - 'currency' => $donation['currency'] ?? 'USD',
1101 - 'donation_type' => $donation['donation_type'] ?? 'one-time',
1102 - 'is_anonymous' => ! empty( $donation['is_anonymous'] ),
1103 - 'donor_comment' => $donation['donor_comment'] ?? '',
1104 - 'payment_status' => $donation['payment_status'] ?? 'pending',
1105 - 'payment_mode' => $payment_mode,
1106 - 'gateway' => $donation['gateway'] ?? '',
1107 - 'transaction_id' => $donation['transaction_id'] ?? '',
1108 - 'stripe_customer_id' => $donation['customer_id'] ?? '',
1109 - 'created_at' => $donation['created_at'] ?? '',
1110 - 'updated_at' => $donation['updated_at'] ?? '',
1111 - 'logs' => $logs,
1203 + 'id' => $donation_id,
1204 + 'campaign_id' => $campaign_id,
1205 + 'campaign_title' => $campaign_id ? wp_kses_post( (string) get_the_title( $campaign_id ) ) : '',
1206 + 'form_id' => $form_id,
1207 + 'form_title' => $form_id ? wp_kses_post( (string) get_the_title( $form_id ) ) : '',
1208 + 'form_edit_url' => $form_edit_url,
1209 + 'donor_id' => isset( $donation['donor_id'] ) ? Helper::get_integer_value( $donation['donor_id'] ) : 0,
1210 + 'donor_name' => esc_html( Helper::get_string_value( $donation['donor_name'] ?? '' ) ),
1211 + 'donor_email' => sanitize_email( Helper::get_string_value( $donation['donor_email'] ?? '' ) ),
1212 + 'donor_phone' => esc_html( Helper::get_string_value( $donation['donor_phone'] ?? '' ) ),
1213 + 'amount' => Helper::get_float_value( $donation['amount'] ?? 0 ),
1214 + 'fees_covered' => Helper::get_float_value( $donation['fees_covered'] ?? 0 ),
1215 + 'refunded_amount' => Helper::get_float_value( $donation['refunded_amount'] ?? 0 ),
1216 + 'currency' => esc_html( Helper::get_string_value( $donation['currency'] ?? 'USD' ) ),
1217 + 'donation_type' => esc_html( Helper::get_string_value( $donation['donation_type'] ?? 'one-time' ) ),
1218 + 'is_anonymous' => ! empty( $donation['is_anonymous'] ),
1219 + 'donor_comment' => wp_kses_post( Helper::get_string_value( $donation['donor_comment'] ?? '' ) ),
1220 + 'payment_status' => esc_html( Helper::get_string_value( $donation['payment_status'] ?? 'pending' ) ),
1221 + 'payment_mode' => esc_html( Helper::get_string_value( $payment_mode ) ),
1222 + 'gateway' => esc_html( Helper::get_string_value( $donation['gateway'] ?? '' ) ),
1223 + 'transaction_id' => esc_html( Helper::get_string_value( $donation['transaction_id'] ?? '' ) ),
1224 + 'stripe_customer_id' => esc_html( Helper::get_string_value( $donation['customer_id'] ?? '' ) ),
1225 + 'subscription_id' => esc_html( Helper::get_string_value( $donation['subscription_id'] ?? '' ) ),
1226 + 'subscription_status' => esc_html( Helper::get_string_value( $donation['subscription_status'] ?? '' ) ),
1227 + 'parent_subscription_id' => isset( $donation['parent_subscription_id'] ) ? Helper::get_integer_value( $donation['parent_subscription_id'] ) : 0,
1228 + 'subscription_interval' => esc_html( Helper::get_string_value( $donation_data['subscription_interval'] ?? '' ) ),
1229 + 'billing_cycles' => esc_html( Helper::get_string_value( $donation_data['billing_cycles'] ?? '' ) ),
1230 + 'created_at' => esc_html( Helper::get_string_value( $donation['created_at'] ?? '' ) ),
1231 + 'updated_at' => esc_html( Helper::get_string_value( $donation['updated_at'] ?? '' ) ),
1232 + 'logs' => $logs,
1112 1233 ];
1113 1234 }
1114 1235 }