PluginProbe
Subscriptions for WooCommerce with Stripe Recurring Payments / 2.0.0
Subscriptions for WooCommerce with Stripe Recurring Payments v2.0.0
2.0.0 1.11.2 1.11.1 1.11.0 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.6 1.9.5 trunk 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 All 61 releases
← All changes | includes/Admin/views/integrations.php +482 -125 1.9.62.0.0 View file →
@@ -1,159 +1,516 @@
1 1 <?php
2 2 /**
3 - * Payment Gateways Admin Page
3 + * Integrations Admin Page
4 4 *
5 - * Displays available payment gateway options as cards.
5 + * Displays payment gateways and third-party integrations as cards.
6 6 *
7 - * @package SpringDevs\Subscription
7 + * @package SpringDevs\Subscription\Admin
8 + *
9 + * @var array $integrations Filtered integrations list.
8 10 */
9 11
10 12 // Exit if accessed directly.
11 13 defined( 'ABSPATH' ) || exit;
14 +
15 +// Split by type.
16 +$payment_gateways = array_filter(
17 + $integrations,
18 + function ( $i ) {
19 + return ( $i['type'] ?? 'payment_gateway' ) === 'payment_gateway';
20 + }
21 +);
22 +$third_party = array_filter(
23 + $integrations,
24 + function ( $i ) {
25 + return ( $i['type'] ?? '' ) === 'third_party';
26 + }
27 +);
28 +
29 +// Category config. The `section` key is the full sub-section heading; `label` is the short card badge.
30 +$category_config = [
31 + 'lms' => [
32 + 'label' => __( 'LMS', 'subscription' ),
33 + 'section' => __( 'Learning Management System', 'subscription' ),
34 + 'bg' => '#ede9fe',
35 + 'color' => '#6d28d9',
36 + ],
37 + 'crm' => [
38 + 'label' => __( 'CRM', 'subscription' ),
39 + 'section' => __( 'Customer Relationship Management', 'subscription' ),
40 + 'bg' => '#fce7f3',
41 + 'color' => '#9d174d',
42 + ],
43 + 'automation' => [
44 + 'label' => __( 'Automation', 'subscription' ),
45 + 'section' => __( 'Automation', 'subscription' ),
46 + 'bg' => '#e0f2fe',
47 + 'color' => '#0369a1',
48 + ],
49 + 'email' => [
50 + 'label' => __( 'Email', 'subscription' ),
51 + 'section' => __( 'Email Marketing', 'subscription' ),
52 + 'bg' => '#dcfce7',
53 + 'color' => '#166534',
54 + ],
55 + 'license' => [
56 + 'label' => __( 'License', 'subscription' ),
57 + 'section' => __( 'License Management', 'subscription' ),
58 + 'bg' => '#fef3c7',
59 + 'color' => '#92400e',
60 + ],
61 +];
62 +
63 +// Group third-party integrations by category, preserving the config order above
64 +// and appending any uncategorised ones under "Other".
65 +$third_party_grouped = [];
66 +foreach ( array_keys( $category_config ) as $cat_key ) {
67 + $third_party_grouped[ $cat_key ] = [];
68 +}
69 +foreach ( $third_party as $integration ) {
70 + $cat_key = $integration['category'] ?? '';
71 + $cat_key = isset( $category_config[ $cat_key ] ) ? $cat_key : 'other';
72 + $third_party_grouped[ $cat_key ][] = $integration;
73 +}
74 +$third_party_grouped = array_filter( $third_party_grouped );
75 +
76 +/**
77 + * Facet counts for the filter bar.
78 + *
79 + * Derived from the same array the cards render from, so a count can never
80 + * disagree with what is on screen — including when filter_integration_actions()
81 + * has removed something, or when a future integration is added and nobody
82 + * remembers to update a hardcoded number.
83 + */
84 +$subscrpt_status_of = static function ( array $integration ): string {
85 + if ( ! empty( $integration['is_active'] ) ) {
86 + return 'active';
87 + }
88 + return ! empty( $integration['is_installed'] ) ? 'inactive' : 'not-installed';
89 +};
90 +
91 +$subscrpt_facets = [
92 + 'category' => [],
93 + 'status' => [
94 + 'active' => 0,
95 + 'inactive' => 0,
96 + 'not-installed' => 0,
97 + ],
98 + // No `recurring` facet: every integration that supports automatic
99 + // recurring is a payment gateway, so the chip selected exactly the same
100 + // six cards as the Payment Gateways category. The card badge still shows
101 + // it, where it says something about that one integration.
102 + 'tag' => [
103 + 'free' => 0,
104 + 'pro' => 0,
105 + 'beta' => 0,
106 + ],
107 +];
108 +
109 +foreach ( $integrations as $integration ) {
110 + $cat_key = ( ( $integration['type'] ?? '' ) === 'payment_gateway' )
111 + ? 'payment_gateway'
112 + : ( isset( $category_config[ $integration['category'] ?? '' ] ) ? $integration['category'] : 'other' );
113 +
114 + $subscrpt_facets['category'][ $cat_key ] = ( $subscrpt_facets['category'][ $cat_key ] ?? 0 ) + 1;
115 + ++$subscrpt_facets['status'][ $subscrpt_status_of( $integration ) ];
116 +
117 + if ( ! empty( $integration['is_pro'] ) ) {
118 + ++$subscrpt_facets['tag']['pro'];
119 + } else {
120 + ++$subscrpt_facets['tag']['free'];
121 + }
122 + if ( ! empty( $integration['is_beta'] ) ) {
123 + ++$subscrpt_facets['tag']['beta'];
124 + }
125 +}
126 +
127 +$subscrpt_total = count( $integrations );
128 +
129 +// Labels for the category chips. Payment gateways are a `type`, not a
130 +// `category`, but on this page they read as one more group.
131 +//
132 +// The short `label` rather than the full `section`: in a 232px rail
133 +// "Customer Relationship Management" wraps to two lines and the group of
134 +// chips ends up taller than the viewport. The section headings above the
135 +// cards still spell it out in full.
136 +$subscrpt_category_labels = [ 'payment_gateway' => __( 'Payment Gateways', 'subscription' ) ];
137 +foreach ( $category_config as $key => $cfg ) {
138 + $subscrpt_category_labels[ $key ] = $cfg['label'] ?? $cfg['section'];
139 +}
140 +$subscrpt_category_labels['other'] = __( 'Other', 'subscription' );
141 +
142 +$subscrpt_status_labels = [
143 + 'active' => __( 'Active', 'subscription' ),
144 + 'inactive' => __( 'Inactive', 'subscription' ),
145 + 'not-installed' => __( 'Not installed', 'subscription' ),
146 +];
147 +
148 +$subscrpt_tag_labels = [
149 + 'free' => __( 'Free', 'subscription' ),
150 + 'pro' => __( 'Pro', 'subscription' ),
151 + 'beta' => __( 'Beta', 'subscription' ),
152 +];
12 153 ?>
13 154
155 +<div class="wp-subscription-admin-content list-page">
14 156
15 -<div class="wrap">
157 + <?php
158 + wpsubs_render_page_header(
159 + array(
160 + 'title' => __( 'Integrations', 'subscription' ),
161 + 'description' => __( 'Connect your subscriptions with payment gateways and third-party plugins.', 'subscription' ),
162 + )
163 + );
164 + ?>
16 165
17 - <div class="wp-subscription-admin-content" style="max-width:1240px;margin:32px auto 0 auto">
18 - <h2><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
19 -
20 - <p style="font-size:14px;line-height:1.7;margin:0 0 24px 0;">
21 - <?php esc_html_e( 'Configure your store\'s payment gateways for subscription products. Enable, disable, and manage available payment methods that support recurring billing.', 'subscription' ); ?>
22 - </p>
166 + <?php
167 + /**
168 + * Filter bar.
169 + *
170 + * Filtering is done in the browser against cards that are all already in the
171 + * DOM — there are fifteen of them, so a round trip per keystroke would be
172 + * slower and would lose the page's scroll position for nothing.
173 + *
174 + * Every control degrades to "everything visible" without JavaScript, which
175 + * is the state the page is in today.
176 + */
177 + ?>
178 + <?php
179 + /*
180 + * Two columns: filters in a sticky rail, results beside them. There are
181 + * three facet groups and a dozen chips — as a horizontal bar above the
182 + * grid that pushed the first card most of the way down the viewport,
183 + * and scrolled out of reach exactly when a long list needed narrowing.
184 + */
185 + ?>
186 + <div class="subscrpt-int-layout">
23 187
24 - <!-- Payment Gateway Cards -->
25 - <div class="wp-subscription-payment-gateways" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:24px;margin-bottom:32px;">
188 + <aside class="subscrpt-int-sidebar">
189 + <div class="subscrpt-int-filters" data-subscrpt-integration-filters hidden>
190 +
26 191 <?php
27 - foreach ( $integrations as $integration ) :
192 + $subscrpt_chip_groups = [
193 + [
194 + 'facet' => 'category',
195 + 'legend' => __( 'Category', 'subscription' ),
196 + 'counts' => $subscrpt_facets['category'],
197 + 'labels' => $subscrpt_category_labels,
198 + ],
199 + [
200 + 'facet' => 'status',
201 + 'legend' => __( 'Status', 'subscription' ),
202 + 'counts' => $subscrpt_facets['status'],
203 + 'labels' => $subscrpt_status_labels,
204 + ],
205 + [
206 + 'facet' => 'tag',
207 + 'legend' => __( 'Tags', 'subscription' ),
208 + 'counts' => $subscrpt_facets['tag'],
209 + 'labels' => $subscrpt_tag_labels,
210 + ],
211 + ];
212 +
213 + foreach ( $subscrpt_chip_groups as $subscrpt_group ) :
214 + // A facet nothing has is noise, not information.
215 + $subscrpt_shown = array_filter( $subscrpt_group['counts'] );
216 + if ( empty( $subscrpt_shown ) ) {
217 + continue;
218 + }
219 + $subscrpt_is_single = in_array( $subscrpt_group['facet'], array( 'category', 'status' ), true );
28 220 ?>
29 - <div style="background-color:white;border-radius:8px;padding:18px;box-shadow:0 2px 8px #e0e7ef;">
30 - <div style="display: flex; gap: 14px;">
31 - <div>
32 - <img
33 - style="width: 40px; height: 40px; border-radius: 8px;"
34 - src=<?php echo esc_url( $integration['icon_url'] ?? '' ); ?>
35 - alt=""
36 - />
37 - </div>
38 - <div style="width: 100%;">
39 - <div style="display: flex; align-items: center; gap: 4px;">
40 - <span style="font-size:1.2em;font-weight:600;">
41 - <?php echo esc_html( $integration['title'] ?? '' ); ?>
42 - </span>
221 + <nav class="wpsubs-vnav" aria-label="<?php echo esc_attr( $subscrpt_group['legend'] ); ?>">
222 + <p class="wpsubs-vnav__title"><?php echo esc_html( $subscrpt_group['legend'] ); ?></p>
223 + <?php
224 + /*
225 + * Category and status are single-select — a card has exactly
226 + * one of each, so picking two could only ever return nothing.
227 + * Both therefore need an explicit "All" to come back to.
228 + */
229 + if ( $subscrpt_is_single ) :
230 + ?>
231 + <button type="button" class="wpsubs-vnav__item is-active" data-subscrpt-int-chip data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>" data-value="">
232 + <span class="wpsubs-vnav__label"><?php esc_html_e( 'All', 'subscription' ); ?></span>
233 + <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_total ) ); ?></span>
234 + </button>
235 + <?php
236 + endif;
237 + ?>
238 + <?php foreach ( $subscrpt_shown as $subscrpt_key => $subscrpt_count ) : ?>
239 + <button
240 + type="button"
241 + class="wpsubs-vnav__item"
242 + data-subscrpt-int-chip
243 + data-facet="<?php echo esc_attr( $subscrpt_group['facet'] ); ?>"
244 + data-value="<?php echo esc_attr( $subscrpt_key ); ?>"
245 + aria-pressed="false"
246 + >
247 + <span class="wpsubs-vnav__label"><?php echo esc_html( $subscrpt_group['labels'][ $subscrpt_key ] ?? $subscrpt_key ); ?></span>
248 + <span class="subscrpt-int-chip__count"><?php echo esc_html( number_format_i18n( $subscrpt_count ) ); ?></span>
249 + </button>
250 + <?php endforeach; ?>
251 + </nav>
252 + <?php
253 + endforeach;
254 + ?>
255 + <?php // Last in the rail: it only appears once a filter is on, and it undoes what is above it. ?>
256 + <button type="button" class="wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm" data-subscrpt-int-reset hidden>
257 + <?php esc_html_e( 'Reset', 'subscription' ); ?>
258 + </button>
259 + </div>
260 + </aside>
43 261
44 - <div style="margin-left: auto; display: flex; gap: 6px; align-items: center;">
45 - <!-- Integration Status Badge -->
46 - <?php if ( ! empty( $integration['is_installed'] ) && ! empty( $integration['is_active'] ) ) : ?>
47 - <span style="font-size:0.85em;padding:4px 8px;border-radius:12px;white-space:nowrap;text-transform: uppercase;background:#e8f5e9;color:#1b5e20;">
48 - <?php esc_html_e( 'Active', 'subscription' ); ?>
262 + <div class="subscrpt-int-results">
263 + <p class="subscrpt-int-empty" data-subscrpt-int-empty hidden>
264 + <?php esc_html_e( 'No integrations match these filters.', 'subscription' ); ?>
265 + </p>
266 +
267 + <?php if ( ! empty( $_GET['subscrpt_installed'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
268 + <div class="notice notice-success is-dismissible" style="margin:0 0 16px;"><p><?php esc_html_e( 'Plugin installed and activated successfully.', 'subscription' ); ?></p></div>
269 + <?php endif; ?>
270 +
271 + <!-- Payment Gateways -->
272 + <div style="margin-bottom:32px;" data-subscrpt-int-section>
273 + <div style="margin-bottom:12px;">
274 + <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php esc_html_e( 'Payment Gateways', 'subscription' ); ?></h2>
275 + </div>
276 + <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
277 + <?php foreach ( $payment_gateways as $integration ) : ?>
278 + <?php
279 + $is_installed = ! empty( $integration['is_installed'] );
280 + $is_active = ! empty( $integration['is_active'] );
281 + $is_beta = ! empty( $integration['is_beta'] );
282 + $is_pro = ! empty( $integration['is_pro'] );
283 + $icon_url = $integration['icon_url'] ?? '';
284 + $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
285 + $icon_color = $integration['icon_color'] ?? '#64748b';
286 +
287 + if ( $is_active ) {
288 + $status_dot = '#16a34a';
289 + $status_text = __( 'Active', 'subscription' );
290 + } elseif ( $is_installed ) {
291 + $status_dot = '#d97706';
292 + $status_text = __( 'Inactive', 'subscription' );
293 + } else {
294 + $status_dot = '#9ca3af';
295 + $status_text = __( 'Not Installed', 'subscription' );
296 + }
297 +
298 + // Filtering reads these rather than scraping the rendered markup,
299 + // which would break the moment a label is translated.
300 + $subscrpt_card_status = $subscrpt_status_of( $integration );
301 + $subscrpt_card_tags = array_keys(
302 + array_filter(
303 + [
304 + 'free' => ! $is_pro,
305 + 'pro' => $is_pro,
306 + 'beta' => $is_beta,
307 + 'recurring' => ! empty( $integration['supports_recurring'] ),
308 + ]
309 + )
310 + );
311 + ?>
312 + <div
313 + class="wpsubs-table-card subscrpt-int-card"
314 + style="padding:16px;display:flex;flex-direction:column;gap:12px;"
315 + data-subscrpt-int-card
316 + data-name="<?php echo esc_attr( $integration['title'] ); ?>"
317 + data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
318 + data-category="payment_gateway"
319 + data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
320 + data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
321 + >
322 +
323 + <!-- Header: icon + name + status -->
324 + <div style="display:flex;gap:10px;align-items:flex-start;">
325 + <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
326 + <?php if ( $icon_url ) : ?>
327 + <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
328 + <?php else : ?>
329 + <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
330 + <?php endif; ?>
331 + </div>
332 + <div style="flex:1;min-width:0;">
333 + <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
334 + <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
335 + <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
336 + <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
337 + <?php echo esc_html( $status_text ); ?>
338 + </span>
339 + </div>
340 + <div style="display:flex;flex-wrap:wrap;gap:4px;">
341 + <?php if ( $is_pro ) : ?>
342 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
343 + <?php endif; ?>
344 + <?php if ( $is_beta ) : ?>
345 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff7ed;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Beta', 'subscription' ); ?></span>
346 + <?php endif; ?>
347 + <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
348 + <span style="display:inline-flex;align-items:center;gap:3px;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#dcfce7;color:#166534;line-height:1.6;">
349 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#166534"/></svg>
350 + <?php esc_html_e( 'Automatic recurring', 'subscription' ); ?>
49 351 </span>
50 - <?php elseif ( ! empty( $integration['is_installed'] ) && empty( $integration['is_active'] ) ) : ?>
51 - <span style="font-size:0.85em;padding:4px 8px;border-radius:12px;white-space:nowrap;text-transform: uppercase;background:#e3f2fd;color:#0d47a1;">
52 - <?php esc_html_e( 'Inactive', 'subscription' ); ?>
53 - </span>
54 352 <?php else : ?>
55 - <span style="font-size:0.85em;padding:4px 8px;border-radius:12px;white-space:nowrap;text-transform: uppercase;background:#ffebee;color:#b71c1c;">
56 - <?php esc_html_e( 'Not Available', 'subscription' ); ?>
57 - </span>
353 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:#fff3e0;color:#c2410c;line-height:1.6;"><?php esc_html_e( 'Manual renewals only', 'subscription' ); ?></span>
58 354 <?php endif; ?>
355 + </div>
356 + </div>
357 + </div>
59 358
60 - <!-- Beta Badge -->
61 - <?php if ( ! empty( $integration['is_beta'] ) && $integration['is_beta'] ) : ?>
62 - <span style="font-size:0.85em;padding:4px 8px;border-radius:12px;white-space:nowrap;text-transform: uppercase;background:#ffebee;color:#b71c1c;font-weight: 500;">
63 - <?php esc_html_e( 'Beta', 'subscription' ); ?>
64 - </span>
359 + <!-- Description -->
360 + <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
361 +
362 + <!-- Separator -->
363 + <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
364 +
365 + <!-- Actions -->
366 + <div style="display:flex;gap:6px;flex-wrap:wrap;">
367 + <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
368 + <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
369 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
370 + <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
371 + </div>
372 + <?php else : ?>
373 + <?php
374 + foreach ( $integration['actions'] as $action ) :
375 + $is_primary = ( 'function' === $action['type'] );
376 + $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
377 + ?>
378 + <?php if ( 'link' === $action['type'] ) : ?>
379 + <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
380 + <?php elseif ( 'external_link' === $action['type'] ) : ?>
381 + <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
382 + <?php echo esc_html( $action['label'] ); ?>
383 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
384 + </a>
385 + <?php elseif ( 'function' === $action['type'] ) : ?>
386 + <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
65 387 <?php endif; ?>
388 + <?php endforeach; ?>
389 + <?php endif; ?>
390 + </div>
391 +
392 + </div>
393 + <?php endforeach; ?>
394 + </div>
395 + </div>
396 +
397 + <!-- 3rd Party Integrations -->
398 + <?php foreach ( $third_party_grouped as $cat_key => $cat_integrations ) : ?>
399 + <div style="margin-bottom:32px;" data-subscrpt-int-section>
400 + <div style="margin-bottom:12px;">
401 + <h2 style="font-size:12px;font-weight:600;color:var(--wpsubs-text-muted);text-transform:uppercase;letter-spacing:0.06em;margin:0;line-height:1.4;margin-left:1px;"><?php echo esc_html( $category_config[ $cat_key ]['section'] ?? __( 'Other', 'subscription' ) ); ?></h2>
402 + </div>
403 + <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;">
404 + <?php foreach ( $cat_integrations as $integration ) : ?>
405 + <?php
406 + $is_active = ! empty( $integration['is_active'] );
407 + $is_pro = ! empty( $integration['is_pro'] );
408 + $icon_url = $integration['icon_url'] ?? '';
409 + $icon_initial = $integration['icon_initial'] ?? strtoupper( substr( $integration['title'], 0, 2 ) );
410 + $icon_color = $integration['icon_color'] ?? '#64748b';
411 + $category = $integration['category'] ?? '';
412 + $cat = $category_config[ $category ] ?? [
413 + 'label' => $category,
414 + 'bg' => '#f1f5f9',
415 + 'color' => '#475569',
416 + ];
417 +
418 + $status_dot = $is_active ? '#16a34a' : '#9ca3af';
419 + $status_text = $is_active ? __( 'Active', 'subscription' ) : __( 'Not Installed', 'subscription' );
420 +
421 + $subscrpt_card_status = $subscrpt_status_of( $integration );
422 + $subscrpt_card_tags = array_keys(
423 + array_filter(
424 + [
425 + 'free' => ! $is_pro,
426 + 'pro' => $is_pro,
427 + 'beta' => ! empty( $integration['is_beta'] ),
428 + 'recurring' => ! empty( $integration['supports_recurring'] ),
429 + ]
430 + )
431 + );
432 + ?>
433 + <div
434 + class="wpsubs-table-card subscrpt-int-card"
435 + style="padding:16px;display:flex;flex-direction:column;gap:12px;"
436 + data-subscrpt-int-card
437 + data-name="<?php echo esc_attr( $integration['title'] ); ?>"
438 + data-search="<?php echo esc_attr( strtolower( $integration['title'] . ' ' . ( $integration['description'] ?? '' ) ) ); ?>"
439 + data-category="<?php echo esc_attr( isset( $category_config[ $category ] ) ? $category : 'other' ); ?>"
440 + data-status="<?php echo esc_attr( $subscrpt_card_status ); ?>"
441 + data-tags="<?php echo esc_attr( implode( ' ', $subscrpt_card_tags ) ); ?>"
442 + >
443 +
444 + <!-- Header: icon + name + status -->
445 + <div style="display:flex;gap:10px;align-items:flex-start;">
446 + <div style="width:40px;height:40px;flex-shrink:0;border-radius:8px;overflow:hidden;border:1px solid var(--wpsubs-border);background:var(--wpsubs-surface-muted);display:flex;align-items:center;justify-content:center;">
447 + <?php if ( $icon_url ) : ?>
448 + <img src="<?php echo esc_url( $icon_url ); ?>" style="width:100%;height:100%;object-fit:contain;" alt="" />
449 + <?php else : ?>
450 + <span style="font-size:11px;font-weight:700;color:#fff;background:<?php echo esc_attr( $icon_color ); ?>;width:100%;height:100%;display:flex;align-items:center;justify-content:center;"><?php echo esc_html( $icon_initial ); ?></span>
451 + <?php endif; ?>
452 + </div>
453 + <div style="flex:1;min-width:0;">
454 + <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:6px;margin-bottom:5px;">
455 + <span style="font-size:13px;font-weight:600;color:var(--wpsubs-text);line-height:1.3;"><?php echo esc_html( $integration['title'] ); ?></span>
456 + <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:500;color:<?php echo esc_attr( $status_dot ); ?>;white-space:nowrap;flex-shrink:0;">
457 + <span style="width:7px;height:7px;border-radius:50%;background:<?php echo esc_attr( $status_dot ); ?>;flex-shrink:0;"></span>
458 + <?php echo esc_html( $status_text ); ?>
459 + </span>
66 460 </div>
461 + <?php if ( $cat['label'] || $is_pro ) : ?>
462 + <div style="display:flex;flex-wrap:wrap;gap:4px;">
463 + <?php if ( $is_pro ) : ?>
464 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--wpsubs-brand-light,#fff1eb);color:var(--wpsubs-brand-dark,#d93f00);line-height:1.6;"><?php esc_html_e( 'Pro', 'subscription' ); ?></span>
465 + <?php endif; ?>
466 + <?php if ( $cat['label'] ) : ?>
467 + <span style="display:inline-flex;align-items:center;font-size:10px;font-weight:500;padding:2px 7px;border-radius:10px;background:<?php echo esc_attr( $cat['bg'] ); ?>;color:<?php echo esc_attr( $cat['color'] ); ?>;line-height:1.6;"><?php echo esc_html( $cat['label'] ); ?></span>
468 + <?php endif; ?>
469 + </div>
470 + <?php endif; ?>
67 471 </div>
68 - <p style="font-size:12px;line-height:1.5;margin:8px 0 0 0;color:#555;">
69 - <?php echo esc_html( $integration['description'] ?? '' ); ?>
70 - </p>
71 472 </div>
72 - </div>
73 473
74 - <!-- Recurring Support -->
75 - <?php if ( ! empty( $integration['supports_recurring'] ) ) : ?>
76 - <div style="background:#e8f5e9;color:#1b5e20;padding:6px 10px;margin:20px 0;border-radius:4px;font-size:13px;margin-bottom:16px;">
77 - <span style="display:flex;align-items:center;gap:6px;">
78 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
79 - <path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z" fill="#1b5e20"/>
80 - </svg>
81 - <?php esc_html_e( 'Supports automatic recurring payments.', 'subscription' ); ?>
82 - </span>
474 + <!-- Description -->
475 + <p style="font-size:13px;color:var(--wpsubs-text-muted);margin:0;line-height:1.5;"><?php echo esc_html( $integration['description'] ?? '' ); ?></p>
476 +
477 + <!-- Separator -->
478 + <div style="border-top:1px solid var(--wpsubs-border);margin:auto -16px 0;"></div>
479 +
480 + <!-- Actions -->
481 + <div style="display:flex;gap:6px;flex-wrap:wrap;">
482 + <?php if ( $is_pro && ! defined( 'SUBSCRIPT_PRO_VERSION' ) ) : ?>
483 + <div style="width:100%;display:flex;align-items:center;gap:6px;background:var(--wpsubs-brand-light,#fff1eb);border-radius:6px;padding:7px 10px;font-size:12px;font-weight:500;color:var(--wpsubs-brand-dark,#d93f00);line-height:1.4;">
484 + <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex-shrink:0;"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
485 + <?php esc_html_e( 'WPSubscription Pro required', 'subscription' ); ?>
486 + </div>
487 + <?php else : ?>
488 + <?php
489 + foreach ( $integration['actions'] as $action ) :
490 + $is_primary = ( 'function' === $action['type'] );
491 + $btn_class = $is_primary ? 'wpsubs-btn wpsubs-btn--primary wpsubs-btn--sm' : 'wpsubs-btn wpsubs-btn--outline wpsubs-btn--sm';
492 + ?>
493 + <?php if ( 'link' === $action['type'] ) : ?>
494 + <a href="<?php echo esc_url( $action['url'] ); ?>" class="<?php echo esc_attr( $btn_class ); ?>"><?php echo esc_html( $action['label'] ); ?></a>
495 + <?php elseif ( 'external_link' === $action['type'] ) : ?>
496 + <a href="<?php echo esc_url( $action['url'] ); ?>" target="_blank" rel="noopener noreferrer" class="<?php echo esc_attr( $btn_class ); ?>">
497 + <?php echo esc_html( $action['label'] ); ?>
498 + <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
499 + </a>
500 + <?php elseif ( 'function' === $action['type'] ) : ?>
501 + <button class="<?php echo esc_attr( $btn_class ); ?>" onclick="<?php echo esc_attr( $action['function'] ); ?>"><?php echo esc_html( $action['label'] ); ?></button>
502 + <?php endif; ?>
503 + <?php endforeach; ?>
504 + <?php endif; ?>
83 505 </div>
84 - <?php else : ?>
85 - <div style="background:#fff3e0;color:#e65100;padding:6px 10px;margin:20px 0;border-radius:4px;font-size:13px;margin-bottom:16px;">
86 - <span style="display:flex;align-items:center;gap:6px;">
87 - <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
88 - <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" fill="#e65100"/>
89 - </svg>
90 - <?php esc_html_e( 'Manual renewals only.', 'subscription' ); ?>
91 - </span>
92 - </div>
93 - <?php endif; ?>
94 506
95 - <!-- Actions -->
96 - <div style="width: 100%; display: flex; gap: 8px; flex-wrap: wrap; align-items: end;">
97 - <?php foreach ( $integration['actions'] as $integration_action ) : ?>
98 - <?php if ( 'link' === $integration_action['type'] ) : ?>
99 - <a href="<?php echo esc_url( $integration_action['url'] ); ?>" class="button button-primary" style="flex:1;text-align:center;">
100 - <?php echo esc_html( $integration_action['label'] ); ?>
101 - </a>
102 - <?php elseif ( 'external_link' === $integration_action['type'] ) : ?>
103 - <a href="<?php echo esc_url( $integration_action['url'] ); ?>" target="_blank" class="button button-primary" style="flex:1;text-align:center;">
104 - <?php echo esc_html( $integration_action['label'] ); ?>
105 - </a>
106 - <?php elseif ( 'function' === $integration_action['type'] ) : ?>
107 - <button
108 - class="<?php echo esc_attr( $integration_action['class'] ?? 'button button-primary' ); ?>"
109 - style="flex:1;text-align:center;"
110 - onclick="<?php echo esc_attr( $integration_action['function'] ); ?>"
111 - >
112 - <?php echo esc_html( $integration_action['label'] ); ?>
113 - </button>
114 - <?php endif; ?>
115 - <?php endforeach; ?>
116 507 </div>
117 - </div>
118 - <?php
119 - endforeach;
120 - ?>
508 + <?php endforeach; ?>
509 + </div>
121 510 </div>
511 + <?php endforeach; ?>
122 512
123 - <!-- Information box -->
124 - <div class="wp-subscription-admin-box" style="margin-bottom:24px;">
125 - <h3><?php esc_html_e( 'About Payment Gateways', 'subscription' ); ?></h3>
126 - <p style="font-size:14px;line-height:1.7;margin:0 0 10px 0;">
127 - <?php esc_html_e( 'For subscription products to work properly, you need to use payment gateways that support recurring payments. Some payment methods only support manual renewals, which requires customers to manually pay for each renewal period.', 'subscription' ); ?>
128 - </p>
129 - <ul style="font-size:14px;line-height:1.6;margin:0 0 0 18px;padding:0;list-style:disc;">
130 - <li><?php esc_html_e( 'Automatic recurring billing requires a compatible payment gateway', 'subscription' ); ?></li>
131 - <li><?php esc_html_e( 'Manual renewal methods work with any payment gateway', 'subscription' ); ?></li>
132 - <li><?php esc_html_e( 'Some gateways may require additional configuration for subscriptions', 'subscription' ); ?></li>
133 - </ul>
134 513 </div>
135 514
136 - <!-- Documentation box -->
137 - <div class="wp-subscription-support-resources" style="display:grid;grid-template-columns:repeat(2,1fr);gap:24px;margin-bottom:24px;">
138 - <div class="wp-subscription-admin-box">
139 - <h3><?php esc_html_e( 'Payment Gateway Documentation', 'subscription' ); ?></h3>
140 - <p style="font-size:14px;margin:0 0 8px 0;">
141 - <?php esc_html_e( 'Learn how to set up and configure payment gateways for subscription products.', 'subscription' ); ?>
142 - </p>
143 - <a href="https://docs.converslabs.com/en" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">
144 - <?php esc_html_e( 'View Documentation', 'subscription' ); ?>
145 - </a>
146 - </div>
147 - <div class="wp-subscription-admin-box">
148 - <h3><?php esc_html_e( 'Need Help?', 'subscription' ); ?></h3>
149 - <p style="font-size:14px;margin:0 0 8px 0;">
150 - <?php esc_html_e( 'If you\'re having trouble with a payment gateway, our support team can help.', 'subscription' ); ?>
151 - </p>
152 - <a href="https://wpsubscription.co/contact" target="_blank" class="button button-small" style="font-size:13px;padding:5px 14px;">
153 - <?php esc_html_e( 'Get Support', 'subscription' ); ?>
154 - </a>
155 - </div>
156 - </div>
157 515 </div>
158 -
159 516 </div>