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/fields/payment-markup.php +218 -16 0.0.1 → 1.1.0 View file →
@@ -8,10 +8,12 @@
8 8
9 9 namespace SureDonation\Inc\Fields;
10 10
11 11 use SureDonation\Inc\Helper;
12 +use SureDonation\Inc\Payments\Offline\Offline_Helper;
12 13 use SureDonation\Inc\Payments\Payment_Helper;
13 14 use SureDonation\Inc\Payments\Stripe\Stripe_Helper;
15 +use SureDonation\Inc\Post_Types\Donation_Form;
14 16
15 17 if ( ! defined( 'ABSPATH' ) ) {
16 18 exit; // Exit if accessed directly.
17 19 }
@@ -126,8 +128,16 @@
126 128 */
127 129 protected $variable_amount_field;
128 130
129 131 /**
132 + * Payment methods enabled for this block.
133 + *
134 + * @var array<string>
135 + * @since 1.0.0
136 + */
137 + protected $payment_methods = [];
138 +
139 + /**
130 140 * Aria-describedby attribute value.
131 141 *
132 142 * @var string
133 143 * @since 0.0.1
@@ -164,9 +174,11 @@
164 174
165 175 // Get appropriate Stripe publishable key based on mode.
166 176 $this->stripe_publishable_key = Stripe_Helper::get_stripe_publishable_key();
167 177
168 - $this->payment_type = $attributes['paymentType'] ?? 'one-time';
178 + // Fall back to one-time if subscription is configured but pro is not active.
179 + $configured_type = $attributes['paymentType'] ?? 'one-time';
180 + $this->payment_type = 'subscription' === $configured_type && ! defined( 'SUREDONATION_PRO_VER' ) ? 'one-time' : $configured_type;
169 181 $this->subscription_plan = $attributes['subscriptionPlan'] ?? [];
170 182 $this->amount_type = $attributes['amountType'] ?? 'fixed';
171 183 $this->fixed_amount = $attributes['fixedAmount'] ?? 10;
172 184 $this->minimum_amount = $attributes['minimumAmount'] ?? 0;
@@ -177,8 +189,39 @@
177 189
178 190 // Set variable amount field mapping.
179 191 $this->variable_amount_field = $attributes['variableAmountField'] ?? '';
180 192
193 + // Parse payment methods from block attributes (with backward compat fallback).
194 + $block_payment_methods = $attributes['paymentMethods'] ?? null;
195 + if ( ! is_array( $block_payment_methods ) || empty( $block_payment_methods ) ) {
196 + $gateway = $attributes['gateway'] ?? 'stripe';
197 + $block_payment_methods = [ is_string( $gateway ) ? $gateway : 'stripe' ];
198 + }
199 +
200 + // Filter to only globally-available gateways.
201 + $available = [];
202 + foreach ( $block_payment_methods as $method ) {
203 + if ( 'stripe' === $method && $this->stripe_connected && ! empty( $this->stripe_publishable_key ) ) {
204 + $available[] = 'stripe';
205 + } elseif ( 'offline' === $method && Offline_Helper::is_offline_enabled() ) {
206 + $available[] = 'offline';
207 + }
208 + }
209 +
210 + /**
211 + * Filter the list of available payment methods for this block.
212 + *
213 + * Allows extensions (e.g., PayPal) to add themselves when connected.
214 + *
215 + * @param array<string> $available Currently available method IDs.
216 + * @param array<string> $block_payment_methods Methods selected in the block.
217 + * @param array<string,mixed> $attributes Block attributes.
218 + * @since 1.0.0
219 + */
220 + $available = apply_filters( 'suredonation_available_payment_methods', $available, $block_payment_methods, $attributes );
221 +
222 + $this->payment_methods = ! empty( $available ) ? $available : $block_payment_methods;
223 +
181 224 // BACKWARD COMPATIBILITY: Migrate customer fields from subscriptionPlan.
182 225 if ( empty( $this->customer_name_field ) && ! empty( $this->subscription_plan['customer_name'] ) ) {
183 226 $this->customer_name_field = $this->subscription_plan['customer_name'];
184 227 }
@@ -194,13 +237,21 @@
194 237 * @return string
195 238 * @since 0.0.1
196 239 */
197 240 public function markup() {
198 - // Check if Stripe is connected.
199 - if ( ! $this->stripe_connected || empty( $this->stripe_publishable_key ) ) {
241 + $has_stripe = in_array( 'stripe', $this->payment_methods, true );
242 + $has_offline = in_array( 'offline', $this->payment_methods, true );
243 +
244 + // If only Stripe is selected, it must be connected.
245 + if ( $has_stripe && ! $has_offline && ( ! $this->stripe_connected || empty( $this->stripe_publishable_key ) ) ) {
200 246 return '';
201 247 }
202 248
249 + // If no gateway is available at all, hide the block.
250 + if ( empty( $this->payment_methods ) ) {
251 + return '';
252 + }
253 +
203 254 // Validate payment field requirements.
204 255 $is_valid = $this->validate_payment_requirements();
205 256 if ( ! $is_valid ) {
206 257 return '';
@@ -205,9 +256,11 @@
205 256 if ( ! $is_valid ) {
206 257 return '';
207 258 }
208 259
209 - $field_classes = $this->get_field_classes();
260 + $field_classes = $this->get_field_classes();
261 + $payment_methods_csv = implode( ',', $this->payment_methods );
262 + $default_gateway = $this->payment_methods[0];
210 263
211 264 ob_start();
212 265 ?>
213 266 <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>"
@@ -212,11 +265,15 @@
212 265 ?>
213 266 <div data-block-id="<?php echo esc_attr( $this->block_id ); ?>"
214 267 data-form-id="<?php echo esc_attr( $this->form_id ); ?>"
215 268 class="<?php echo esc_attr( $field_classes ); ?>"
216 - data-gateway="stripe"
217 - data-stripe-key="<?php echo esc_attr( $this->stripe_publishable_key ); ?>"
269 + data-payment-methods="<?php echo esc_attr( $payment_methods_csv ); ?>"
270 + data-gateway="<?php echo esc_attr( $default_gateway ); ?>"
271 + <?php if ( $has_stripe && ! empty( $this->stripe_publishable_key ) ) { ?>
272 + data-stripe-key="<?php echo esc_attr( $this->stripe_publishable_key ); ?>"
273 + <?php } ?>
218 274 data-currency="<?php echo esc_attr( strtolower( $this->currency ) ); ?>"
275 + data-currency-symbol="<?php echo esc_attr( Payment_Helper::get_currency_symbol( $this->currency ) ); ?>"
219 276 data-payment-mode="<?php echo esc_attr( $this->payment_mode ); ?>"
220 277 data-amount-type="<?php echo esc_attr( $this->amount_type ); ?>"
221 278 data-fixed-amount="<?php echo esc_attr( (string) $this->fixed_amount ); ?>"
222 279 data-payment-type="<?php echo esc_attr( $this->payment_type ); ?>"
@@ -238,22 +295,22 @@
238 295 <?php echo wp_kses_post( $this->label_markup ); ?>
239 296 <?php echo wp_kses_post( $this->help_markup ); ?>
240 297 <div class="sd-payment-field-wrapper">
241 298 <?php
242 - // Amount display.
243 - echo wp_kses_post( $this->render_amount_display() );
299 + // Amount display — uses wp_kses with data attributes allowed
300 + // because wp_kses_post strips data-message-format, data-currency-symbol etc.
301 + echo wp_kses( $this->render_amount_display(), Helper::get_allowed_form_html() );
244 302
245 303 // Test mode notice.
246 - if ( 'test' === $this->payment_mode ) {
304 + if ( 'test' === $this->payment_mode && $has_stripe ) {
247 305 echo wp_kses_post( $this->get_test_mode_notice() );
248 306 }
307 +
308 + // Payment methods accordion.
309 + $methods = $this->get_registered_payment_methods();
310 + echo $this->render_payment_methods_accordion( $methods ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Method builds markup with esc_attr/esc_html on all dynamic values and wp_kses_post on content.
249 311 ?>
250 312
251 - <!-- Stripe Elements Container -->
252 - <div id="sd-payment-element-<?php echo esc_attr( $this->block_id ); ?>" class="sd-payment-element">
253 - <!-- Stripe Elements will be inserted here -->
254 - </div>
255 -
256 313 <!-- Payment error display -->
257 314 <div class="sd-payment-error" style="display: none;"></div>
258 315 </div>
259 316 </div>
@@ -262,8 +319,152 @@
262 319 return false !== $output ? $output : '';
263 320 }
264 321
265 322 /**
323 + * Get registered payment methods for display.
324 + *
325 + * @return array<mixed> Array of payment method configurations.
326 + * @since 1.0.0
327 + */
328 + private function get_registered_payment_methods() {
329 + $available_methods = [
330 + 'stripe' => [
331 + 'id' => 'stripe',
332 + 'label' => __( 'Credit / Debit Card', 'suredonation' ),
333 + 'enabled' => $this->stripe_connected && ! empty( $this->stripe_publishable_key ),
334 + 'container_class' => 'sd-payment-element',
335 + 'container_id' => 'sd-payment-element-' . $this->block_id,
336 + ],
337 + 'offline' => [
338 + 'id' => 'offline',
339 + 'label' => __( 'Offline Payment', 'suredonation' ),
340 + 'enabled' => Offline_Helper::is_offline_enabled(),
341 + 'container_class' => 'sd-offline-instructions',
342 + 'content' => Offline_Helper::get_offline_instructions( $this->get_campaign_name() ),
343 + ],
344 + ];
345 +
346 + $methods = [];
347 + foreach ( $this->payment_methods as $method_id ) {
348 + if ( isset( $available_methods[ $method_id ] ) && $available_methods[ $method_id ]['enabled'] ) {
349 + $methods[ $method_id ] = $available_methods[ $method_id ];
350 + }
351 + }
352 +
353 + /**
354 + * Filter the registered payment methods and their display config.
355 + *
356 + * Allows extensions to add payment methods (e.g., PayPal) with their
357 + * label, container class, and enabled state for frontend rendering.
358 + *
359 + * @param array<string, array<string,mixed>> $methods Registered methods keyed by ID.
360 + * @param array<string> $payment_methods Methods selected in this block.
361 + * @param string $block_id The block ID.
362 + * @since 1.0.0
363 + */
364 + return apply_filters( 'suredonation_registered_payment_methods', $methods, $this->payment_methods, $this->block_id );
365 + }
366 +
367 + /**
368 + * Get the campaign name for this form.
369 + *
370 + * @return string Campaign name or empty string.
371 + * @since 1.0.0
372 + */
373 + private function get_campaign_name() {
374 + if ( empty( $this->form_id ) ) {
375 + return '';
376 + }
377 +
378 + $campaign_id = Donation_Form::get_form_campaign_id( absint( $this->form_id ) );
379 +
380 + if ( empty( $campaign_id ) ) {
381 + return '';
382 + }
383 +
384 + return get_the_title( $campaign_id );
385 + }
386 +
387 + /**
388 + * Render payment methods as accordion.
389 + * Each payment method is an accordion item with header and collapsible content.
390 + *
391 + * @param array<mixed> $methods Array of payment methods.
392 + * @return string Payment methods accordion markup.
393 + * @since 1.0.0
394 + */
395 + private function render_payment_methods_accordion( $methods ) {
396 + if ( empty( $methods ) || ! is_array( $methods ) ) {
397 + return '';
398 + }
399 +
400 + $is_single_method = count( $methods ) === 1;
401 + $is_first = true;
402 +
403 + ob_start();
404 + ?>
405 + <div class="sd-payment-methods-accordion <?php echo esc_attr( $is_single_method ? 'sd-single-payment-method' : '' ); ?>">
406 + <?php foreach ( $methods as $method ) { ?>
407 + <div
408 + class="sd-accordion-item <?php echo esc_attr( $is_first ? 'sd-payment-active' : '' ); ?>"
409 + data-method="<?php echo esc_attr( $method['id'] ); ?>"
410 + >
411 + <div
412 + class="sd-accordion-header"
413 + role="button"
414 + tabindex="0"
415 + aria-expanded="<?php echo esc_attr( $is_first ? 'true' : 'false' ); ?>"
416 + aria-controls="sd-accordion-content-<?php echo esc_attr( $method['id'] ); ?>-<?php echo esc_attr( $this->block_id ); ?>"
417 + >
418 + <div class="sd-payment-input-wrapper">
419 + <input
420 + type="radio"
421 + name="sd-gateway-choice-<?php echo esc_attr( $this->block_id ); ?>"
422 + value="<?php echo esc_attr( $method['id'] ); ?>"
423 + class="sd-payment-method-radio"
424 + data-method="<?php echo esc_attr( $method['id'] ); ?>"
425 + <?php checked( $is_first ); ?>
426 + aria-label="<?php echo esc_attr( $method['label'] ); ?>"
427 + />
428 + <span class="sd-accordion-title sd-block-label">
429 + <?php echo esc_html( $method['label'] ); ?>
430 + </span>
431 + </div>
432 + </div>
433 + <div
434 + id="sd-accordion-content-<?php echo esc_attr( $method['id'] ); ?>-<?php echo esc_attr( $this->block_id ); ?>"
435 + class="sd-accordion-content"
436 + role="region"
437 + >
438 + <div
439 + class="sd-payment-method-content"
440 + data-method="<?php echo esc_attr( $method['id'] ); ?>"
441 + >
442 + <div
443 + <?php if ( ! empty( $method['container_id'] ) ) { ?>
444 + id="<?php echo esc_attr( $method['container_id'] ); ?>"
445 + <?php } ?>
446 + class="<?php echo esc_attr( $method['container_class'] ); ?>"
447 + >
448 + <?php
449 + if ( ! empty( $method['content'] ) ) {
450 + echo wp_kses_post( $method['content'] );
451 + }
452 + ?>
453 + </div>
454 + </div>
455 + </div>
456 + </div>
457 + <?php $is_first = false; ?>
458 + <?php } ?>
459 + </div>
460 + <?php
461 + $template = ob_get_clean();
462 +
463 + return is_string( $template ) ? $template : '';
464 + }
465 +
466 + /**
266 467 * Render amount display section.
267 468 *
268 469 * @return string Amount display HTML.
269 470 * @since 0.0.1
@@ -345,8 +546,9 @@
345 546 }
346 547 }
347 548 ?>
348 549 <span class="sd-payment-value" data-currency="<?php echo esc_attr( strtolower( $this->currency ) ); ?>" data-currency-symbol="<?php echo esc_attr( Payment_Helper::get_currency_symbol( $this->currency ) ); ?>" data-message-format="<?php echo esc_attr( $message_format ); ?>">
550 + <?php esc_html_e( 'Complete the form to view the amount.', 'suredonation' ); ?>
349 551 </span>
350 552 </div>
351 553 <?php if ( $this->minimum_amount > 0 ) { ?>
352 554 <span class="sd-help">
@@ -450,11 +652,11 @@
450 652 $settings_url = admin_url( 'admin.php?page=suredonation_settings&tab=payments' );
451 653
452 654 ob_start();
453 655 ?>
454 - <div class="sd-test-mode-notice" style="background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 12px; margin-bottom: 16px; color: #856404;">
656 + <div class="sd-test-mode-notice">
455 657 <strong><?php esc_html_e( 'Test mode is enabled:', 'suredonation' ); ?></strong>
456 - <a href="<?php echo esc_url( $settings_url ); ?>" style="color: #856404;" target="_blank" rel="noopener noreferrer">
658 + <a href="<?php echo esc_url( $settings_url ); ?>" target="_blank" rel="noopener noreferrer">
457 659 <?php esc_html_e( 'Click here to enable live mode and accept payment', 'suredonation' ); ?>
458 660 </a>
459 661 </div>
460 662 <?php