ultimate-jekyll-manager 1.0.22 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/CLAUDE.md +102 -0
- package/dist/assets/js/libs/payment-config.js +44 -0
- package/dist/assets/js/pages/account/index.js +6 -35
- package/dist/assets/js/pages/account/sections/billing.js +7 -7
- package/dist/assets/js/pages/account/sections/connections.js +9 -9
- package/dist/assets/js/pages/admin/dashboard/index.js +3 -8
- package/dist/assets/js/pages/payment/checkout/index.js +15 -22
- package/dist/assets/js/pages/payment/checkout/modules/api.js +0 -11
- package/dist/assets/js/pages/payment/checkout/modules/state.js +1 -2
- package/dist/assets/js/pages/pricing/index.js +15 -9
- package/dist/defaults/dist/_includes/core/foot.html +1 -0
- package/dist/defaults/dist/_layouts/themes/classy/frontend/pages/pricing.html +123 -28
- package/dist/defaults/src/_config.yml +11 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
15
15
|
- `Security` in case of vulnerabilities.
|
|
16
16
|
|
|
17
17
|
---
|
|
18
|
+
## [1.1.0] - 2026-04-06
|
|
19
|
+
### Added
|
|
20
|
+
- `payment-config.js` shared library for reading payment data from build-time config
|
|
21
|
+
- Pricing layout resolves prices and feature limits from `_config.yml` when not set in frontmatter
|
|
22
|
+
- `oauth2` config injected into client-side Configuration object via `foot.html`
|
|
23
|
+
- Pricing page shows "Switch to This Plan" on other paid plans when user has active subscription
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Move `payment` under `web_manager` in default `_config.yml` so it serializes into client-side config
|
|
27
|
+
- Checkout page uses `payment-config.js` instead of fetching `/backend-manager/brand`
|
|
28
|
+
- Account billing section uses config for products/limits/currency instead of brand API
|
|
29
|
+
- Account connections section reads `oauth2` from config instead of brand API
|
|
30
|
+
- Admin dashboard uses config for product list in MRR calculations
|
|
31
|
+
- Remove `/backend-manager/brand` fetch from account page entirely
|
|
32
|
+
- "Everything in [plan]" now uses dynamic previous plan name instead of hardcoded index
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Liquid 4.x compatibility: use loop-based hash lookup instead of bracket notation for config limits
|
|
36
|
+
|
|
18
37
|
## [1.0.22] - 2026-04-05
|
|
19
38
|
### Changed
|
|
20
39
|
- Bump web-manager from ^4.1.36 to ^4.1.37
|
package/CLAUDE.md
CHANGED
|
@@ -1136,6 +1136,108 @@ webManager.auth().listen({ once: true }, async () => {
|
|
|
1136
1136
|
|
|
1137
1137
|
**Reference:** `src/assets/js/libs/authorized-fetch.js`
|
|
1138
1138
|
|
|
1139
|
+
#### Payment Config Library
|
|
1140
|
+
|
|
1141
|
+
Reads payment configuration (products, processors, prices, limits) from `webManager.config.payment` — populated from `_config.yml` at build time. **Do NOT fetch `/backend-manager/brand` to get payment data.** It's already available instantly via this library.
|
|
1142
|
+
|
|
1143
|
+
**Import:**
|
|
1144
|
+
```javascript
|
|
1145
|
+
import { getPaymentConfig, getProcessors, getProducts, getProductById, getProductLimits, getCurrency } from '__main_assets__/js/libs/payment-config.js';
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
**Usage:**
|
|
1149
|
+
```javascript
|
|
1150
|
+
// Get all products
|
|
1151
|
+
const products = getProducts();
|
|
1152
|
+
|
|
1153
|
+
// Find a specific product
|
|
1154
|
+
const product = getProductById('plus');
|
|
1155
|
+
|
|
1156
|
+
// Get product limits
|
|
1157
|
+
const limits = getProductLimits('plus'); // { credits: 500, agents: 3, ... }
|
|
1158
|
+
|
|
1159
|
+
// Get processors (stripe, paypal, etc.)
|
|
1160
|
+
const processors = getProcessors();
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
**Config location in `_config.yml`:**
|
|
1164
|
+
```yaml
|
|
1165
|
+
web_manager:
|
|
1166
|
+
payment:
|
|
1167
|
+
processors:
|
|
1168
|
+
stripe:
|
|
1169
|
+
publishableKey: pk_live_...
|
|
1170
|
+
paypal:
|
|
1171
|
+
clientId: ...
|
|
1172
|
+
products:
|
|
1173
|
+
- id: basic
|
|
1174
|
+
name: Basic
|
|
1175
|
+
limits:
|
|
1176
|
+
credits: 100
|
|
1177
|
+
- id: plus
|
|
1178
|
+
name: Plus
|
|
1179
|
+
limits:
|
|
1180
|
+
credits: 500
|
|
1181
|
+
prices:
|
|
1182
|
+
monthly: 19
|
|
1183
|
+
annually: 190
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
**How it works:** The `foot.html` Configuration injection serializes all `web_manager` properties into `window.Configuration`, which `webManager.initialize()` stores in `webManager.config`. The payment config is available immediately — no API call needed.
|
|
1187
|
+
|
|
1188
|
+
**When to still use the brand API:**
|
|
1189
|
+
- `oauth2` provider configuration (used by the connections section on the account page)
|
|
1190
|
+
- Any data that is NOT in `_config.yml` and only exists server-side
|
|
1191
|
+
|
|
1192
|
+
**Reference:** `src/assets/js/libs/payment-config.js`
|
|
1193
|
+
|
|
1194
|
+
#### Pricing Page: Config-Resolved Values
|
|
1195
|
+
|
|
1196
|
+
The pricing layout automatically resolves prices and feature limits from `_config.yml` when not explicitly set in frontmatter. This means consuming projects can define ONLY display metadata (name, tagline, icon, features list) and let prices/limits come from the single source of truth.
|
|
1197
|
+
|
|
1198
|
+
**Resolution order (frontmatter wins):**
|
|
1199
|
+
1. `plan.pricing.monthly` / `plan.pricing.annually` from page frontmatter
|
|
1200
|
+
2. `site.web_manager.payment.products[matching_id].prices.monthly` / `.annually` from config
|
|
1201
|
+
3. `0` (default)
|
|
1202
|
+
|
|
1203
|
+
**Feature value resolution:**
|
|
1204
|
+
1. `feature.value` from page frontmatter
|
|
1205
|
+
2. `site.web_manager.payment.products[matching_id].limits[feature.id]` from config (with `-1` → `"Unlimited"`)
|
|
1206
|
+
|
|
1207
|
+
**Example: Minimal pricing.md (prices/limits come from config):**
|
|
1208
|
+
```yaml
|
|
1209
|
+
---
|
|
1210
|
+
layout: blueprint/pricing
|
|
1211
|
+
permalink: /pricing
|
|
1212
|
+
|
|
1213
|
+
pricing:
|
|
1214
|
+
plans:
|
|
1215
|
+
- id: "basic"
|
|
1216
|
+
name: "Basic"
|
|
1217
|
+
tagline: "best for getting started"
|
|
1218
|
+
url: "/dashboard"
|
|
1219
|
+
features:
|
|
1220
|
+
- id: "credits"
|
|
1221
|
+
name: "Credits"
|
|
1222
|
+
icon: "sparkles"
|
|
1223
|
+
- id: "agents"
|
|
1224
|
+
name: "Agents"
|
|
1225
|
+
icon: "robot"
|
|
1226
|
+
- id: "plus"
|
|
1227
|
+
name: "Plus"
|
|
1228
|
+
tagline: "best for small websites"
|
|
1229
|
+
features:
|
|
1230
|
+
- id: "credits"
|
|
1231
|
+
name: "Credits"
|
|
1232
|
+
icon: "sparkles"
|
|
1233
|
+
- id: "agents"
|
|
1234
|
+
name: "Agents"
|
|
1235
|
+
icon: "robot"
|
|
1236
|
+
---
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
In this example, `credits` value of 100 and price of $19/mo come from `_config.yml`'s `web_manager.payment.products` — no hardcoding needed.
|
|
1240
|
+
|
|
1139
1241
|
#### FormManager Library
|
|
1140
1242
|
|
|
1141
1243
|
Lightweight form state management library with built-in validation, state machine, and event system.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Config Library
|
|
3
|
+
*
|
|
4
|
+
* Reads payment configuration (products, processors, prices, limits) from
|
|
5
|
+
* webManager.config.payment — which is populated from _config.yml at build time.
|
|
6
|
+
* This eliminates the need to fetch /backend-manager/brand at runtime.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import webManager from 'web-manager';
|
|
10
|
+
|
|
11
|
+
// Get the full payment config object
|
|
12
|
+
export function getPaymentConfig() {
|
|
13
|
+
return webManager.config?.payment || {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Get payment processors
|
|
17
|
+
export function getProcessors() {
|
|
18
|
+
return getPaymentConfig().processors || {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get all products
|
|
22
|
+
export function getProducts() {
|
|
23
|
+
return getPaymentConfig().products || [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Find a product by ID
|
|
27
|
+
export function getProductById(productId) {
|
|
28
|
+
return getProducts().find(p => p.id === productId) || null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Get a product's limits
|
|
32
|
+
export function getProductLimits(productId) {
|
|
33
|
+
return getProductById(productId)?.limits || {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Get a product's prices
|
|
37
|
+
export function getProductPrices(productId) {
|
|
38
|
+
return getProductById(productId)?.prices || {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get payment currency
|
|
42
|
+
export function getCurrency() {
|
|
43
|
+
return getPaymentConfig().currency || 'USD';
|
|
44
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Libraries
|
|
2
|
-
import fetch from 'wonderful-fetch';
|
|
3
2
|
import * as profileSection from './sections/profile.js';
|
|
4
3
|
import * as notificationsSection from './sections/notifications.js';
|
|
5
4
|
import * as securitySection from './sections/security.js';
|
|
@@ -12,6 +11,7 @@ import * as dataRequestSection from './sections/data-request.js';
|
|
|
12
11
|
import * as connectionsSection from './sections/connections.js';
|
|
13
12
|
import * as refundSection from './sections/refund.js';
|
|
14
13
|
import webManager from 'web-manager';
|
|
14
|
+
import { getPaymentConfig } from '__main_assets__/js/libs/payment-config.js';
|
|
15
15
|
|
|
16
16
|
// Module
|
|
17
17
|
export default () => {
|
|
@@ -33,8 +33,9 @@ export default () => {
|
|
|
33
33
|
// Global state
|
|
34
34
|
let $navLinks = null;
|
|
35
35
|
let $sections = null;
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
|
|
37
|
+
// Config from _config.yml (available instantly, no fetch needed)
|
|
38
|
+
const paymentConfig = getPaymentConfig();
|
|
38
39
|
|
|
39
40
|
// Section modules map
|
|
40
41
|
const sectionModules = {
|
|
@@ -85,10 +86,6 @@ async function initializeAccount() {
|
|
|
85
86
|
webManager.auth().listen({}, async (state) => {
|
|
86
87
|
console.log('Auth state with account data:', state);
|
|
87
88
|
|
|
88
|
-
// Load user data with the account information
|
|
89
|
-
// Wait for brand data to be fetched before loading section data
|
|
90
|
-
await fetchBrandData();
|
|
91
|
-
|
|
92
89
|
/* @dev-only:start */
|
|
93
90
|
{
|
|
94
91
|
// Check for test subscription parameter
|
|
@@ -128,32 +125,6 @@ async function initializeAccount() {
|
|
|
128
125
|
});
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
// Fetch brand data to get configuration and OAuth settings
|
|
132
|
-
async function fetchBrandData() {
|
|
133
|
-
if (fetchBrandDataPromise) return fetchBrandDataPromise;
|
|
134
|
-
|
|
135
|
-
fetchBrandDataPromise = (async () => {
|
|
136
|
-
try {
|
|
137
|
-
const serverApiURL = `${webManager.getApiUrl()}/backend-manager/brand`;
|
|
138
|
-
|
|
139
|
-
// Fetch brand data
|
|
140
|
-
const response = await fetch(serverApiURL, {
|
|
141
|
-
response: 'json',
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
console.log('Fetched brand data:', response);
|
|
145
|
-
brandData = response;
|
|
146
|
-
|
|
147
|
-
return response;
|
|
148
|
-
} catch (error) {
|
|
149
|
-
webManager.sentry().captureException(new Error('Failed to fetch brand data', { cause: error }));
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
})();
|
|
153
|
-
|
|
154
|
-
return fetchBrandDataPromise;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
128
|
// Load data for all sections
|
|
158
129
|
function loadAllSectionData(authState) {
|
|
159
130
|
const { user, account } = authState;
|
|
@@ -172,7 +143,7 @@ function loadAllSectionData(authState) {
|
|
|
172
143
|
}
|
|
173
144
|
|
|
174
145
|
if (sectionModules.billing.loadData) {
|
|
175
|
-
sectionModules.billing.loadData(account,
|
|
146
|
+
sectionModules.billing.loadData(account, paymentConfig);
|
|
176
147
|
}
|
|
177
148
|
|
|
178
149
|
if (sectionModules.team && sectionModules.team.loadData) {
|
|
@@ -196,7 +167,7 @@ function loadAllSectionData(authState) {
|
|
|
196
167
|
}
|
|
197
168
|
|
|
198
169
|
if (sectionModules.connections.loadData) {
|
|
199
|
-
sectionModules.connections.loadData(account,
|
|
170
|
+
sectionModules.connections.loadData(account, webManager.config?.oauth2 || {});
|
|
200
171
|
}
|
|
201
172
|
|
|
202
173
|
if (sectionModules.refund.loadData) {
|
|
@@ -7,7 +7,7 @@ import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
|
7
7
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
8
|
import webManager from 'web-manager';
|
|
9
9
|
|
|
10
|
-
let
|
|
10
|
+
let paymentConfig = null;
|
|
11
11
|
let cancelFormManager = null;
|
|
12
12
|
let currentAccount = null;
|
|
13
13
|
|
|
@@ -41,12 +41,12 @@ export async function init() {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Load billing data
|
|
44
|
-
export async function loadData(account,
|
|
44
|
+
export async function loadData(account, sharedPaymentConfig) {
|
|
45
45
|
if (!account) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
paymentConfig = sharedPaymentConfig;
|
|
50
50
|
currentAccount = account;
|
|
51
51
|
|
|
52
52
|
updateUI(account);
|
|
@@ -104,7 +104,7 @@ function buildBillingState(account) {
|
|
|
104
104
|
// Pre-format billing details
|
|
105
105
|
const nextBillingUnix = subscription.expires?.timestampUNIX;
|
|
106
106
|
const amount = subscription.payment?.price;
|
|
107
|
-
const currency =
|
|
107
|
+
const currency = paymentConfig?.currency || 'USD';
|
|
108
108
|
const frequency = subscription.payment?.frequency;
|
|
109
109
|
const hasValidBilling = nextBillingUnix && nextBillingUnix > 0 && amount;
|
|
110
110
|
|
|
@@ -338,7 +338,7 @@ function updateUsageInfo(account) {
|
|
|
338
338
|
|
|
339
339
|
// Use the effective plan for usage limits (basic if cancelled/suspended)
|
|
340
340
|
const resolved = webManager.auth().resolveSubscription(account);
|
|
341
|
-
const product =
|
|
341
|
+
const product = paymentConfig?.products?.find(p => p.id === resolved.plan);
|
|
342
342
|
const limits = product?.limits || {};
|
|
343
343
|
|
|
344
344
|
// Clear container
|
|
@@ -404,9 +404,9 @@ function getDisplayName(subscription) {
|
|
|
404
404
|
return subscription.product.name;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
// Fall back to
|
|
407
|
+
// Fall back to config product name
|
|
408
408
|
const productId = subscription.product?.id || 'basic';
|
|
409
|
-
const product =
|
|
409
|
+
const product = paymentConfig?.products?.find(p => p.id === productId);
|
|
410
410
|
return product?.name || 'Free';
|
|
411
411
|
}
|
|
412
412
|
|
|
@@ -7,7 +7,7 @@ import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
|
7
7
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
8
|
import webManager from 'web-manager';
|
|
9
9
|
|
|
10
|
-
let
|
|
10
|
+
let oauth2Config = null;
|
|
11
11
|
let accountData = null;
|
|
12
12
|
let connectionForms = new Map();
|
|
13
13
|
|
|
@@ -24,13 +24,13 @@ export async function init() {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Load connections data
|
|
27
|
-
export async function loadData(account,
|
|
27
|
+
export async function loadData(account, sharedOAuth2Config) {
|
|
28
28
|
if (!account) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
accountData = account;
|
|
33
|
-
|
|
33
|
+
oauth2Config = sharedOAuth2Config;
|
|
34
34
|
|
|
35
35
|
displayConnections();
|
|
36
36
|
}
|
|
@@ -43,14 +43,14 @@ function displayConnections() {
|
|
|
43
43
|
$loading.classList.add('d-none');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
if (!
|
|
46
|
+
if (!oauth2Config) {
|
|
47
47
|
if ($loading) {
|
|
48
48
|
$loading.classList.remove('d-none');
|
|
49
49
|
}
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const availableProviders =
|
|
53
|
+
const availableProviders = oauth2Config;
|
|
54
54
|
const userConnections = accountData?.oauth2 || {};
|
|
55
55
|
|
|
56
56
|
let hasEnabledProviders = false;
|
|
@@ -237,7 +237,7 @@ function initializeProviderForm(providerId) {
|
|
|
237
237
|
formManager.on('statechange', ({ state }) => {
|
|
238
238
|
if (state === 'ready') {
|
|
239
239
|
const userConnection = accountData?.oauth2?.[providerId];
|
|
240
|
-
const providerSettings =
|
|
240
|
+
const providerSettings = oauth2Config?.[providerId];
|
|
241
241
|
updateProviderStatus(providerId, userConnection, providerSettings);
|
|
242
242
|
}
|
|
243
243
|
});
|
|
@@ -252,7 +252,7 @@ function initializeProviderForm(providerId) {
|
|
|
252
252
|
const success = await handleDisconnect(provider);
|
|
253
253
|
|
|
254
254
|
if (success) {
|
|
255
|
-
const providerSettings =
|
|
255
|
+
const providerSettings = oauth2Config?.[provider];
|
|
256
256
|
updateProviderStatus(provider, null, providerSettings);
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -261,7 +261,7 @@ function initializeProviderForm(providerId) {
|
|
|
261
261
|
|
|
262
262
|
// Handle connect action
|
|
263
263
|
async function handleConnect(providerId) {
|
|
264
|
-
const provider =
|
|
264
|
+
const provider = oauth2Config?.[providerId];
|
|
265
265
|
|
|
266
266
|
if (!provider || provider.enabled === false) {
|
|
267
267
|
throw new Error('This connection service is not available.');
|
|
@@ -322,7 +322,7 @@ async function handleDisconnect(providerId) {
|
|
|
322
322
|
|
|
323
323
|
// Called when section is shown
|
|
324
324
|
export function onShow() {
|
|
325
|
-
if (accountData &&
|
|
325
|
+
if (accountData && oauth2Config) {
|
|
326
326
|
displayConnections();
|
|
327
327
|
}
|
|
328
328
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
// Libraries
|
|
6
6
|
import { getPrerenderedIcon } from '__main_assets__/js/libs/prerendered-icons.js';
|
|
7
|
-
import fetch from 'wonderful-fetch';
|
|
8
7
|
import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
8
|
+
import { getProducts } from '__main_assets__/js/libs/payment-config.js';
|
|
9
9
|
import { formatTimeAgo, capitalize, setStatValue, setStatSubValue } from '__main_assets__/js/libs/admin-helpers.js';
|
|
10
10
|
import { Chart, DoughnutController, BarController, ArcElement, BarElement, CategoryScale, LinearScale, Tooltip, Legend } from 'chart.js';
|
|
11
11
|
import webManager from 'web-manager';
|
|
@@ -80,13 +80,8 @@ async function loadSubscriberData() {
|
|
|
80
80
|
const { collection, query, where, getCountFromServer } = await import('firebase/firestore');
|
|
81
81
|
const db = webManager.firebaseFirestore;
|
|
82
82
|
|
|
83
|
-
//
|
|
84
|
-
const
|
|
85
|
-
response: 'json',
|
|
86
|
-
tries: 2,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const products = (brandConfig?.payment?.products || []).filter((p) => p.id !== 'basic');
|
|
83
|
+
// Get product list from _config.yml (available instantly via webManager.config)
|
|
84
|
+
const products = getProducts().filter((p) => p.id !== 'basic');
|
|
90
85
|
const frequencyIds = [...new Set(products.flatMap((p) => Object.keys(p.prices || {})))];
|
|
91
86
|
|
|
92
87
|
// Run count queries for each product × frequency in parallel
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Payment Checkout Page
|
|
2
2
|
import { FormManager } from '__main_assets__/js/libs/form-manager.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getPaymentConfig, getProcessors, getProductById } from '__main_assets__/js/libs/payment-config.js';
|
|
4
|
+
import { fetchTrialEligibility, warmupServer, createPaymentIntent } from './modules/api.js';
|
|
4
5
|
import { state, buildBindingsState, resolveProcessor, FREQUENCIES, getAvailableFrequencies } from './modules/state.js';
|
|
5
6
|
import { applyDiscountCode } from './modules/discount.js';
|
|
6
7
|
import { initializeRecaptcha } from './modules/recaptcha.js';
|
|
@@ -71,15 +72,24 @@ async function initializeCheckout() {
|
|
|
71
72
|
throw new Error('Product ID is missing from URL.');
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// Read payment config from _config.yml (available instantly via webManager.config)
|
|
76
|
+
state.processors = getProcessors();
|
|
77
|
+
|
|
78
|
+
// Find product
|
|
79
|
+
const product = getProductById(productId);
|
|
80
|
+
if (!product) {
|
|
81
|
+
throw new Error(`Product "${productId}" not found.`);
|
|
82
|
+
}
|
|
83
|
+
state.product = product;
|
|
84
|
+
|
|
74
85
|
// Wait for auth state to settle before any authorized calls
|
|
75
86
|
await new Promise((resolve) => webManager.auth().listen({ once: true }, resolve));
|
|
76
87
|
|
|
77
88
|
// Fire-and-forget server warmup
|
|
78
89
|
warmupServer();
|
|
79
90
|
|
|
80
|
-
// Parallel fetch:
|
|
81
|
-
const [
|
|
82
|
-
fetchBrandConfig(),
|
|
91
|
+
// Parallel fetch: trial eligibility + reCAPTCHA
|
|
92
|
+
const [trialResult, recaptchaResult] = await Promise.allSettled([
|
|
83
93
|
fetchTrialEligibility(),
|
|
84
94
|
initializeRecaptcha(webManager.config?.recaptcha?.['site-key']),
|
|
85
95
|
]);
|
|
@@ -96,23 +106,6 @@ async function initializeCheckout() {
|
|
|
96
106
|
}
|
|
97
107
|
/* @dev-only:end */
|
|
98
108
|
|
|
99
|
-
// Brand config is required
|
|
100
|
-
if (brandConfigResult.status === 'rejected') {
|
|
101
|
-
const reason = brandConfigResult.reason?.message || brandConfigResult.reason || 'Unknown error';
|
|
102
|
-
throw new Error(`Failed to load checkout brand config: ${reason}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const brandConfig = brandConfigResult.value;
|
|
106
|
-
state.brandConfig = brandConfig;
|
|
107
|
-
state.processors = brandConfig.payment?.processors || {};
|
|
108
|
-
|
|
109
|
-
// Find product
|
|
110
|
-
const product = brandConfig.payment?.products?.find(p => p.id === productId);
|
|
111
|
-
if (!product) {
|
|
112
|
-
throw new Error(`Product "${productId}" not found.`);
|
|
113
|
-
}
|
|
114
|
-
state.product = product;
|
|
115
|
-
|
|
116
109
|
// Resolve frequency: URL param if valid, otherwise longest available term
|
|
117
110
|
const available = getAvailableFrequencies(product);
|
|
118
111
|
if (frequencyParam && FREQUENCIES.includes(frequencyParam) && available.includes(frequencyParam)) {
|
|
@@ -286,7 +279,7 @@ function initDevPanel() {
|
|
|
286
279
|
// Show the panel
|
|
287
280
|
$panel.hidden = false;
|
|
288
281
|
|
|
289
|
-
const products =
|
|
282
|
+
const products = getPaymentConfig().products || [];
|
|
290
283
|
const params = new URLSearchParams(window.location.search);
|
|
291
284
|
|
|
292
285
|
// Populate product dropdown
|
|
@@ -4,17 +4,6 @@ import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
|
|
|
4
4
|
import { getRecaptchaToken } from './recaptcha.js';
|
|
5
5
|
import webManager from 'web-manager';
|
|
6
6
|
|
|
7
|
-
// Fetch brand config (products + processors)
|
|
8
|
-
export async function fetchBrandConfig() {
|
|
9
|
-
const response = await fetch(`${webManager.getApiUrl()}/backend-manager/brand`, {
|
|
10
|
-
response: 'json',
|
|
11
|
-
tries: 2,
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
console.log('Fetched brand config:', response);
|
|
15
|
-
return response;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
7
|
// Check trial eligibility via backend endpoint
|
|
19
8
|
export async function fetchTrialEligibility() {
|
|
20
9
|
try {
|
|
@@ -9,8 +9,7 @@ export const FREQUENCIES = ['daily', 'weekly', 'monthly', 'annually'];
|
|
|
9
9
|
|
|
10
10
|
// Minimal mutable state
|
|
11
11
|
export const state = {
|
|
12
|
-
// From
|
|
13
|
-
brandConfig: null,
|
|
12
|
+
// From config (stored once, never transformed)
|
|
14
13
|
product: null,
|
|
15
14
|
processors: null,
|
|
16
15
|
|
|
@@ -319,7 +319,7 @@ function setupPromoCountdown() {
|
|
|
319
319
|
adjustNavbarOffset();
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
//
|
|
322
|
+
// Update buttons based on the user's current active plan
|
|
323
323
|
function setupCurrentPlanIndicator() {
|
|
324
324
|
webManager.auth().listen({ once: true }, (state) => {
|
|
325
325
|
const resolved = webManager.auth().resolveSubscription(state.account);
|
|
@@ -328,16 +328,22 @@ function setupCurrentPlanIndicator() {
|
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
|
|
331
|
+
// Mark current plan button
|
|
332
|
+
const $currentButton = document.querySelector(`button[data-plan-id="${resolved.plan}"]`);
|
|
333
|
+
if ($currentButton) {
|
|
334
|
+
$currentButton.disabled = true;
|
|
335
|
+
$currentButton.textContent = 'Current Plan';
|
|
336
|
+
$currentButton.classList.remove('btn-primary', 'btn-gradient-rainbow', 'gradient-animated');
|
|
337
|
+
$currentButton.classList.add('btn-adaptive');
|
|
335
338
|
}
|
|
336
339
|
|
|
337
|
-
|
|
338
|
-
$button
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
// Update other paid plan buttons to "Switch to this plan"
|
|
341
|
+
document.querySelectorAll('button[data-plan-id]').forEach(($button) => {
|
|
342
|
+
if ($button.dataset.planId === resolved.plan || $button.dataset.planId === 'basic' || $button.dataset.planId === 'enterprise') {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
$button.textContent = 'Switch to This Plan';
|
|
346
|
+
});
|
|
341
347
|
});
|
|
342
348
|
}
|
|
343
349
|
|
|
@@ -328,6 +328,32 @@ faqs:
|
|
|
328
328
|
|
|
329
329
|
<div class="row g-4 mb-5 justify-content-center">
|
|
330
330
|
{% for plan in page.resolved.pricing.plans %}
|
|
331
|
+
{% comment %} Look up matching product in _config.yml payment products {% endcomment %}
|
|
332
|
+
{% assign _config_product = nil %}
|
|
333
|
+
{% for p in site.web_manager.payment.products %}
|
|
334
|
+
{% if p.id == plan.id %}
|
|
335
|
+
{% assign _config_product = p %}
|
|
336
|
+
{% break %}
|
|
337
|
+
{% endif %}
|
|
338
|
+
{% endfor %}
|
|
339
|
+
|
|
340
|
+
{% comment %} Resolve prices: frontmatter pricing takes precedence, then config prices {% endcomment %}
|
|
341
|
+
{% if plan.pricing.monthly or plan.pricing.monthly == 0 %}
|
|
342
|
+
{% assign _plan_monthly = plan.pricing.monthly %}
|
|
343
|
+
{% elsif _config_product.prices.monthly or _config_product.prices.monthly == 0 %}
|
|
344
|
+
{% assign _plan_monthly = _config_product.prices.monthly %}
|
|
345
|
+
{% else %}
|
|
346
|
+
{% assign _plan_monthly = 0 %}
|
|
347
|
+
{% endif %}
|
|
348
|
+
|
|
349
|
+
{% if plan.pricing.annually or plan.pricing.annually == 0 %}
|
|
350
|
+
{% assign _plan_annually = plan.pricing.annually %}
|
|
351
|
+
{% elsif _config_product.prices.annually or _config_product.prices.annually == 0 %}
|
|
352
|
+
{% assign _plan_annually = _config_product.prices.annually %}
|
|
353
|
+
{% else %}
|
|
354
|
+
{% assign _plan_annually = 0 %}
|
|
355
|
+
{% endif %}
|
|
356
|
+
|
|
331
357
|
{% if plan.popular %}
|
|
332
358
|
{% assign border_classes = "border-gradient-rainbow border-3" %}
|
|
333
359
|
{% else %}
|
|
@@ -349,12 +375,12 @@ faqs:
|
|
|
349
375
|
<p class="text-muted small mb-3">{{ plan.tagline }}</p>
|
|
350
376
|
|
|
351
377
|
<!-- Price -->
|
|
352
|
-
{% if
|
|
378
|
+
{% if _plan_monthly == 0 %}
|
|
353
379
|
<p class="display-6 fw-bold mb-0">Free</p>
|
|
354
380
|
{% else %}
|
|
355
381
|
<p class="mb-0">
|
|
356
382
|
<span class="display-6 fw-bold">
|
|
357
|
-
<span class="fs-3">$</span><span class="amount" data-monthly="{{
|
|
383
|
+
<span class="fs-3">$</span><span class="amount" data-monthly="{{ _plan_monthly }}" data-annually="{{ _plan_annually | divided_by: 12 | round }}">{{ _plan_annually | divided_by: 12 | round }}</span>
|
|
358
384
|
</span>
|
|
359
385
|
<small class="text-muted">/month</small>
|
|
360
386
|
</p>
|
|
@@ -363,12 +389,17 @@ faqs:
|
|
|
363
389
|
<!-- Price per unit (only shown when enabled) -->
|
|
364
390
|
{% if page.resolved.pricing.price_per_unit.enabled %}
|
|
365
391
|
<p class="text-muted small mb-4">
|
|
366
|
-
{% if
|
|
392
|
+
{% if _plan_monthly > 0 %}
|
|
367
393
|
{% for feature in plan.features %}
|
|
368
394
|
{% if feature.id == page.resolved.pricing.price_per_unit.feature_id %}
|
|
369
|
-
{%
|
|
370
|
-
{% assign
|
|
371
|
-
{%
|
|
395
|
+
{% comment %} Resolve feature value from config limits if not set in frontmatter {% endcomment %}
|
|
396
|
+
{% assign _ppu_value = feature.value %}
|
|
397
|
+
{% if _config_product and _ppu_value == nil %}
|
|
398
|
+
{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _ppu_value = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
399
|
+
{% endif %}
|
|
400
|
+
{% assign monthly_price_per_unit = _plan_monthly | times: 1.0 | divided_by: _ppu_value | round: 2 %}
|
|
401
|
+
{% assign annual_monthly_price = _plan_annually | divided_by: 12.0 %}
|
|
402
|
+
{% assign annual_price_per_unit = annual_monthly_price | divided_by: _ppu_value | round: 2 %}
|
|
372
403
|
<span class="price-per-unit" data-monthly="${{ monthly_price_per_unit }}" data-annually="${{ annual_price_per_unit }}">${{ annual_price_per_unit }}</span> per {{ page.resolved.pricing.price_per_unit.label }}
|
|
373
404
|
{% endif %}
|
|
374
405
|
{% endfor %}
|
|
@@ -389,8 +420,8 @@ faqs:
|
|
|
389
420
|
{% assign btn_style = "btn-primary" %}
|
|
390
421
|
|
|
391
422
|
{% iftruthy plan.url %}
|
|
392
|
-
<a href="{{ plan.url }}" class="btn {% if
|
|
393
|
-
{% if
|
|
423
|
+
<a href="{{ plan.url }}" class="btn {% if _plan_monthly == 0 %}btn-adaptive{% else %}{{ btn_style }}{% endif %} btn-md fw-semibold px-2 fs-5">
|
|
424
|
+
{% if _plan_monthly == 0 %}
|
|
394
425
|
Get Started
|
|
395
426
|
{% else %}
|
|
396
427
|
Get Free Trial
|
|
@@ -406,11 +437,11 @@ faqs:
|
|
|
406
437
|
|
|
407
438
|
<!-- Billing info -->
|
|
408
439
|
<p class="text-center text-muted small mb-3">
|
|
409
|
-
{% if
|
|
440
|
+
{% if _plan_monthly == 0 %}
|
|
410
441
|
<span>No credit card required</span>
|
|
411
442
|
{% else %}
|
|
412
|
-
<span class="billing-info" data-monthly="Billed ${{
|
|
413
|
-
Billed ${{
|
|
443
|
+
<span class="billing-info" data-monthly="Billed ${{ _plan_monthly | uj_commaify }} monthly" data-annually="Billed ${{ _plan_annually | uj_commaify }} annually">
|
|
444
|
+
Billed ${{ _plan_annually | uj_commaify }} annually
|
|
414
445
|
</span>
|
|
415
446
|
{% endif %}
|
|
416
447
|
</p>
|
|
@@ -423,6 +454,16 @@ faqs:
|
|
|
423
454
|
<ul class="list-unstyled mb-3">
|
|
424
455
|
{% for feature in plan.features %}
|
|
425
456
|
{% if common_feature_ids contains feature.id %}
|
|
457
|
+
{% comment %} Resolve feature value: frontmatter > config limits {% endcomment %}
|
|
458
|
+
{% assign _feature_value = feature.value %}
|
|
459
|
+
{% if _config_product and _feature_value == nil %}
|
|
460
|
+
{% assign _config_limit = nil %}{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _config_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
461
|
+
{% if _config_limit == -1 %}
|
|
462
|
+
{% assign _feature_value = "Unlimited" %}
|
|
463
|
+
{% elsif _config_limit %}
|
|
464
|
+
{% assign _feature_value = _config_limit %}
|
|
465
|
+
{% endif %}
|
|
466
|
+
{% endif %}
|
|
426
467
|
{% assign feature_definition = nil %}
|
|
427
468
|
{% for def in page.resolved.pricing.definitions %}
|
|
428
469
|
{% if def.id == feature.id %}
|
|
@@ -433,10 +474,10 @@ faqs:
|
|
|
433
474
|
<li class="d-flex align-items-start mb-2">
|
|
434
475
|
<span class="me-3">{% uj_icon feature.icon, "fa-md" %}</span>
|
|
435
476
|
<span>
|
|
436
|
-
{% if
|
|
477
|
+
{% if _feature_value == "Unlimited" %}
|
|
437
478
|
Unlimited
|
|
438
479
|
{% else %}
|
|
439
|
-
{{
|
|
480
|
+
{{ _feature_value | uj_commaify }}
|
|
440
481
|
{% endif %}
|
|
441
482
|
{% iftruthy feature_definition %}
|
|
442
483
|
<span class="text-decoration-underline text-decoration-dotted cursor-help" data-bs-toggle="tooltip" data-bs-title="{{ feature_definition }}">{{ feature.name }}</span>
|
|
@@ -467,6 +508,16 @@ faqs:
|
|
|
467
508
|
<ul class="list-unstyled mb-0">
|
|
468
509
|
{% for feature in plan.features %}
|
|
469
510
|
{% unless common_feature_ids contains feature.id %}
|
|
511
|
+
{% comment %} Resolve feature value: frontmatter > config limits {% endcomment %}
|
|
512
|
+
{% assign _feature_value = feature.value %}
|
|
513
|
+
{% if _config_product and _feature_value == nil %}
|
|
514
|
+
{% assign _config_limit = nil %}{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _config_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
515
|
+
{% if _config_limit == -1 %}
|
|
516
|
+
{% assign _feature_value = "Unlimited" %}
|
|
517
|
+
{% elsif _config_limit %}
|
|
518
|
+
{% assign _feature_value = _config_limit %}
|
|
519
|
+
{% endif %}
|
|
520
|
+
{% endif %}
|
|
470
521
|
{% assign feature_definition = nil %}
|
|
471
522
|
{% for def in page.resolved.pricing.definitions %}
|
|
472
523
|
{% if def.id == feature.id %}
|
|
@@ -477,12 +528,12 @@ faqs:
|
|
|
477
528
|
<li class="d-flex align-items-start mb-2 {% if forloop.last %}mb-0{% endif %}">
|
|
478
529
|
<span class="me-3">{% uj_icon feature.icon, "fa-md" %}</span>
|
|
479
530
|
<span>
|
|
480
|
-
{% if
|
|
481
|
-
{{
|
|
482
|
-
{% elsif
|
|
531
|
+
{% if _feature_value == "24/7" %}
|
|
532
|
+
{{ _feature_value }}
|
|
533
|
+
{% elsif _feature_value == "Included" or _feature_value == "Available" or _feature_value == "Full" %}
|
|
483
534
|
<!-- No prefix for these values -->
|
|
484
535
|
{% else %}
|
|
485
|
-
{{
|
|
536
|
+
{{ _feature_value | uj_commaify }}
|
|
486
537
|
{% endif %}
|
|
487
538
|
{% iftruthy feature_definition %}
|
|
488
539
|
<span class="text-decoration-underline text-decoration-dotted cursor-help" data-bs-toggle="tooltip" data-bs-title="{{ feature_definition }}">{{ feature.name }}</span>
|
|
@@ -509,16 +560,12 @@ faqs:
|
|
|
509
560
|
{% endunless %}
|
|
510
561
|
{% endfor %}
|
|
511
562
|
|
|
563
|
+
{% assign _prev_index = forloop.index0 | minus: 1 %}
|
|
564
|
+
{% assign _prev_plan = page.resolved.pricing.plans[_prev_index] %}
|
|
512
565
|
<div class="text-muted small mb-2">
|
|
513
566
|
<em>Everything in</em>
|
|
514
567
|
<em>
|
|
515
|
-
<strong>
|
|
516
|
-
{%- if forloop.index == 1 -%}Free{%- endif -%}
|
|
517
|
-
{%- if forloop.index == 2 -%}Basic{%- endif -%}
|
|
518
|
-
{%- if forloop.index == 3 -%}Starter{%- endif -%}
|
|
519
|
-
{%- if forloop.index == 4 -%}Pro{%- endif -%}
|
|
520
|
-
{%- if forloop.index == 5 -%}Max{% endif -%}
|
|
521
|
-
</strong>
|
|
568
|
+
<strong>{{ _prev_plan.name }}</strong>
|
|
522
569
|
{%- if has_additional -%}, plus:{%- endif -%}
|
|
523
570
|
</em>
|
|
524
571
|
</div>
|
|
@@ -528,6 +575,16 @@ faqs:
|
|
|
528
575
|
<ul class="list-unstyled mb-0">
|
|
529
576
|
{% for feature in plan.features %}
|
|
530
577
|
{% unless common_feature_ids contains feature.id %}
|
|
578
|
+
{% comment %} Resolve feature value: frontmatter > config limits {% endcomment %}
|
|
579
|
+
{% assign _feature_value = feature.value %}
|
|
580
|
+
{% if _config_product and _feature_value == nil %}
|
|
581
|
+
{% assign _config_limit = nil %}{% for _lim in _config_product.limits %}{% if _lim[0] == feature.id %}{% assign _config_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
582
|
+
{% if _config_limit == -1 %}
|
|
583
|
+
{% assign _feature_value = "Unlimited" %}
|
|
584
|
+
{% elsif _config_limit %}
|
|
585
|
+
{% assign _feature_value = _config_limit %}
|
|
586
|
+
{% endif %}
|
|
587
|
+
{% endif %}
|
|
531
588
|
{% assign feature_definition = nil %}
|
|
532
589
|
{% for def in page.resolved.pricing.definitions %}
|
|
533
590
|
{% if def.id == feature.id %}
|
|
@@ -538,12 +595,12 @@ faqs:
|
|
|
538
595
|
<li class="d-flex align-items-start mb-2 {% if forloop.last %}mb-0{% endif %}">
|
|
539
596
|
<span class="me-3">{% uj_icon feature.icon, "fa-md" %}</span>
|
|
540
597
|
<span>
|
|
541
|
-
{% if
|
|
542
|
-
{{
|
|
543
|
-
{% elsif
|
|
598
|
+
{% if _feature_value == "24/7" %}
|
|
599
|
+
{{ _feature_value }}
|
|
600
|
+
{% elsif _feature_value == "Included" or _feature_value == "Available" or _feature_value == "Full" %}
|
|
544
601
|
<!-- No prefix for these values -->
|
|
545
602
|
{% else %}
|
|
546
|
-
{{
|
|
603
|
+
{{ _feature_value | uj_commaify }}
|
|
547
604
|
{% endif %}
|
|
548
605
|
{% iftruthy feature_definition %}
|
|
549
606
|
<span class="text-decoration-underline text-decoration-dotted cursor-help" data-bs-toggle="tooltip" data-bs-title="{{ feature_definition }}">{{ feature.name }}</span>
|
|
@@ -664,6 +721,14 @@ faqs:
|
|
|
664
721
|
{% endiffalsy %}
|
|
665
722
|
</th>
|
|
666
723
|
{% for plan in page.resolved.pricing.plans %}
|
|
724
|
+
{% comment %} Look up config product for this plan {% endcomment %}
|
|
725
|
+
{% assign _tbl_config_product = nil %}
|
|
726
|
+
{% for p in site.web_manager.payment.products %}
|
|
727
|
+
{% if p.id == plan.id %}
|
|
728
|
+
{% assign _tbl_config_product = p %}
|
|
729
|
+
{% break %}
|
|
730
|
+
{% endif %}
|
|
731
|
+
{% endfor %}
|
|
667
732
|
<td>
|
|
668
733
|
{% assign has_feature = false %}
|
|
669
734
|
{% assign feature_value = nil %}
|
|
@@ -678,6 +743,16 @@ faqs:
|
|
|
678
743
|
{% endif %}
|
|
679
744
|
{% endfor %}
|
|
680
745
|
|
|
746
|
+
{% comment %} Resolve feature value from config limits if not set {% endcomment %}
|
|
747
|
+
{% if has_feature and feature_value == nil and _tbl_config_product %}
|
|
748
|
+
{% assign _tbl_limit = nil %}{% for _lim in _tbl_config_product.limits %}{% if _lim[0] == feature_def.id %}{% assign _tbl_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
749
|
+
{% if _tbl_limit == -1 %}
|
|
750
|
+
{% assign feature_value = "Unlimited" %}
|
|
751
|
+
{% elsif _tbl_limit %}
|
|
752
|
+
{% assign feature_value = _tbl_limit %}
|
|
753
|
+
{% endif %}
|
|
754
|
+
{% endif %}
|
|
755
|
+
|
|
681
756
|
{% unless has_feature %}
|
|
682
757
|
{% for check_plan in page.resolved.pricing.plans %}
|
|
683
758
|
{% if check_plan.id == plan.id %}
|
|
@@ -690,6 +765,26 @@ faqs:
|
|
|
690
765
|
{% break %}
|
|
691
766
|
{% endif %}
|
|
692
767
|
{% endfor %}
|
|
768
|
+
|
|
769
|
+
{% comment %} Resolve inherited value from config limits if not set {% endcomment %}
|
|
770
|
+
{% if inherited_feature and inherited_value == nil %}
|
|
771
|
+
{% assign _inh_config_product = nil %}
|
|
772
|
+
{% for p in site.web_manager.payment.products %}
|
|
773
|
+
{% if p.id == check_plan.id %}
|
|
774
|
+
{% assign _inh_config_product = p %}
|
|
775
|
+
{% break %}
|
|
776
|
+
{% endif %}
|
|
777
|
+
{% endfor %}
|
|
778
|
+
{% if _inh_config_product %}
|
|
779
|
+
{% assign _inh_limit = nil %}{% for _lim in _inh_config_product.limits %}{% if _lim[0] == feature_def.id %}{% assign _inh_limit = _lim[1] %}{% break %}{% endif %}{% endfor %}
|
|
780
|
+
{% if _inh_limit == -1 %}
|
|
781
|
+
{% assign inherited_value = "Unlimited" %}
|
|
782
|
+
{% elsif _inh_limit %}
|
|
783
|
+
{% assign inherited_value = _inh_limit %}
|
|
784
|
+
{% endif %}
|
|
785
|
+
{% endif %}
|
|
786
|
+
{% endif %}
|
|
787
|
+
|
|
693
788
|
{% if inherited_feature %}
|
|
694
789
|
{% break %}
|
|
695
790
|
{% endif %}
|
|
@@ -134,18 +134,17 @@ web_manager:
|
|
|
134
134
|
config:
|
|
135
135
|
autoRequest: 1000 * 60
|
|
136
136
|
validRedirectHosts: []
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
enabled: false
|
|
137
|
+
payment:
|
|
138
|
+
processors:
|
|
139
|
+
stripe:
|
|
140
|
+
publishableKey: null
|
|
141
|
+
paypal:
|
|
142
|
+
clientId: null
|
|
143
|
+
chargebee:
|
|
144
|
+
site: null
|
|
145
|
+
coinbase:
|
|
146
|
+
enabled: false
|
|
147
|
+
products: []
|
|
149
148
|
|
|
150
149
|
# OAuth2
|
|
151
150
|
oauth2:
|