Plugin Directory

Changeset 3484181


Ignore:
Timestamp:
03/16/2026 06:38:43 PM (3 weeks ago)
Author:
ugoltsev
Message:

release 1.0.0 payments tab

Location:
ask-my-content
Files:
1 added
9 edited

Legend:

Unmodified
Added
Removed
  • ask-my-content/trunk/ask-my-content.php

    r3465382 r3484181  
    44 * Plugin Name:       Ask My Content - AI Q&A Chatbot
    55 * Description:       AI-powered Q&A chatbot, allowing users to ask questions and receive answers sourced from the site’s own posts and pages.
    6  * Version:           0.9.0
     6 * Version:           1.0.0
    77 * Requires at least: 5.8
    88 * Requires PHP:      7.4
     
    9292    $settings_path = $base_dir . 'assets/js/askmyco-settings.js';
    9393
    94     $core_ver = file_exists($core_path) ? filemtime($core_path) : '0.9.0';
    95     $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '0.9.0';
    96     $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '0.9.0';
    97     $style_ver = file_exists($style_path) ? filemtime($style_path) : '0.9.0';
    98     $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '0.9.0';
     94    $core_ver = file_exists($core_path) ? filemtime($core_path) : '1.0.0';
     95    $frontend_ver = file_exists($frontend_path) ? filemtime($frontend_path) : '1.0.0';
     96    $admin_init_ver = file_exists($admin_init_path) ? filemtime($admin_init_path) : '1.0.0';
     97    $style_ver = file_exists($style_path) ? filemtime($style_path) : '1.0.0';
     98    $settings_ver = file_exists($settings_path) ? filemtime($settings_path) : '1.0.0';
    9999
    100100    $payments_path = $base_dir . 'assets/js/amc-payments.js';
    101     $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '0.9.0';
     101    $payments_ver = file_exists($payments_path) ? filemtime($payments_path) : '1.0.0';
    102102
    103103    if (! wp_script_is($core_handle, 'registered')) {
     
    261261
    262262    $floating_path = plugin_dir_path(__FILE__) . 'assets/js/amc-floating.js';
    263     $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '0.9.0';
     263    $floating_ver = file_exists($floating_path) ? filemtime($floating_path) : '1.0.0';
    264264
    265265    wp_enqueue_script(
     
    417417    $current_tab = 'settings';
    418418    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab routing, no data mutation
    419     if (defined('ASKMYCO_PAYPAL_TAB') && isset($_GET['tab']) && $_GET['tab'] === 'payments') {
     419    if (isset($_GET['tab']) && $_GET['tab'] === 'payments') {
    420420        $current_tab = 'payments';
    421421    }
  • ask-my-content/trunk/assets/js/amc-payments.js

    r3464679 r3484181  
    7070        var refreshBtn = document.getElementById('amc-pay-refresh');
    7171        var refreshStatus = document.getElementById('amc-pay-refresh-status');
     72        var stripePayBtn = document.getElementById('amc-stripe-pay-btn');
     73        var stripeAmountInput = document.getElementById('amc-stripe-amount-usd');
     74        var stripeStatusEl = document.getElementById('amc-stripe-status');
    7275
    7376        // Also bind the counters refresh button on payments tab (prefixed IDs)
     
    9497        }
    9598
     99        function setStripeStatus(msg, isError) {
     100            if (!stripeStatusEl) return;
     101            stripeStatusEl.textContent = msg || '';
     102            stripeStatusEl.style.color = isError ? '#b32d2e' : '#1d2327';
     103        }
     104
     105        function updateEnvNotice(data) {
     106            var notice = document.getElementById('amc-pay-env-notice');
     107            if (!notice) return;
     108            var p = notice.querySelector('p');
     109
     110            var stripeEnv = (data && data.stripe_env ? String(data.stripe_env) : '').toLowerCase().trim();
     111            var paypalEnv = (data && data.paypal_env ? String(data.paypal_env) : '').toLowerCase().trim();
     112
     113            var msg = '';
     114            var type = '';
     115            if (stripeEnv && paypalEnv) {
     116                if (stripeEnv !== paypalEnv) {
     117                    type = 'error';
     118                    msg = 'Environment mismatch: Stripe is ' + stripeEnv + ' while PayPal is ' + paypalEnv + '.';
     119                } else if (stripeEnv === 'sandbox') {
     120                    type = 'warning';
     121                    msg = 'Sandbox mode: Stripe and PayPal are configured for sandbox/testing.';
     122                }
     123            }
     124
     125            notice.className = 'notice inline' + (type ? ' notice-' + type : '');
     126            if (p) p.textContent = msg;
     127            notice.style.display = msg ? '' : 'none';
     128        }
     129
    96130        /**
    97131         * Update counter elements (payments tab has prefixed IDs).
     
    126160            if (el && data.free_tier_usd !== undefined) el.textContent = formatUSD(data.free_tier_usd);
    127161
     162            updateEnvNotice(data);
     163
    128164            el = document.getElementById('amc-pay-balance');
    129165            if (el && data.balance_due_usd !== undefined) {
    130                 var bal = parseFloat(data.balance_due_usd);
     166                var rawBal = parseFloat(data.balance_due_usd);
     167                var freeTier = parseFloat(data.free_tier_usd);
     168                if (isNaN(freeTier)) freeTier = 0;
     169                if (isNaN(rawBal)) return;
     170                var bal = rawBal - freeTier;
    131171                var color = bal > 0 ? '#d63638' : '#00a32a';
    132172                var label = bal > 0 ? 'Amount due' : 'Credit remaining / within free tier';
     
    178218            // Sort newest first
    179219            payments.sort(function (a, b) {
    180                 var ta = a.paypal_payment_ts || a.received_at || '';
    181                 var tb = b.paypal_payment_ts || b.received_at || '';
     220                var ta = a.paid_at || '';
     221                var tb = b.paid_at || '';
    182222                return tb.localeCompare(ta);
    183223            });
     
    192232            if (payments.length === 0) {
    193233                var row = document.createElement('tr');
    194                 row.innerHTML = '<td colspan="7" style="text-align:center;">No payments recorded yet.</td>';
     234                row.innerHTML = '<td colspan="8" style="text-align:center;">No payments recorded yet.</td>';
    195235                tbody.appendChild(row);
    196236                return;
     
    198238
    199239            payments.forEach(function (p) {
    200                 var ipn = p.raw_ipn || {};
    201                 var txnId = p.txn_id || ipn.txn_id || '—';
    202                 var status = p.payment_status || ipn.payment_status || '—';
    203                 var gross = p.gross_amount !== undefined ? p.gross_amount : (ipn.mc_gross !== undefined ? ipn.mc_gross : '—');
    204                 var fee = p.fee_amount !== undefined ? p.fee_amount : (ipn.mc_fee !== undefined ? ipn.mc_fee : '—');
    205                 var currency = p.currency || ipn.mc_currency || 'USD';
     240                var provider = (p.provider || '').toLowerCase();
     241                var txnId = p.txn_id || '—';
     242                var status = p.payment_status || '—';
     243                var gross = p.gross_amount !== undefined ? p.gross_amount : '—';
     244                var fee = p.fee_amount !== undefined ? p.fee_amount : '—';
     245                var currency = p.currency || 'USD';
    206246                var net;
    207247                if (p.net_amount !== undefined) {
     
    213253                }
    214254
    215                 var dateStr = formatDate(p.paypal_payment_ts || p.received_at || '');
     255                var dateStr = formatDate(p.paid_at || '');
    216256                var grossDisplay = (!isNaN(parseFloat(gross))) ? formatUSD(gross) : gross;
    217257                var feeDisplay = (!isNaN(parseFloat(fee))) ? formatUSD(fee) : fee;
     
    221261                row.innerHTML =
    222262                    '<td>' + escHtml(dateStr) + '</td>' +
     263                    '<td>' + escHtml(provider ? (provider.charAt(0).toUpperCase() + provider.slice(1)) : '—') + '</td>' +
    223264                    '<td>' + escHtml(txnId) + '</td>' +
    224265                    '<td>' + escHtml(status) + '</td>' +
     
    282323        }
    283324
     325        async function createStripeCheckout() {
     326            if (!stripePayBtn || !stripeAmountInput) {
     327                return;
     328            }
     329            var amountUsd = parseFloat(stripeAmountInput.value);
     330            if (isNaN(amountUsd) || amountUsd <= 0) {
     331                setStripeStatus('Amount must be a valid dollar value.', true);
     332                return;
     333            }
     334
     335            var amountCents = Math.round(amountUsd * 100);
     336            if (amountCents < 100 || amountCents > 50000) {
     337                setStripeStatus('Amount must be between US $1.00 and US $500.00.', true);
     338                return;
     339            }
     340
     341            stripePayBtn.disabled = true;
     342            setStripeStatus('Creating Stripe checkout session…', false);
     343
     344            var params = buildParams('askmyco_create_stripe_checkout');
     345            params.set('amount_cents', String(amountCents));
     346            var url = buildUrl(ajaxBase, 'askmyco_create_stripe_checkout');
     347
     348            try {
     349                var response = await fetch(url.toString(), {
     350                    method: 'POST',
     351                    credentials: 'same-origin',
     352                    headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
     353                    body: params.toString()
     354                });
     355                var json = await response.json();
     356
     357                if (json && json.success && json.data && json.data.checkout_url) {
     358                    setStripeStatus('Redirecting to Stripe checkout…', false);
     359                    window.location.assign(json.data.checkout_url);
     360                    return;
     361                }
     362
     363                var msg = (json && json.data && json.data.message) || 'Unable to create Stripe checkout session.';
     364                console.error('[ASKMYCO] Stripe checkout error payload:', json);
     365                setStripeStatus(msg, true);
     366            } catch (e) {
     367                console.error('[ASKMYCO] Stripe checkout request failed:', e);
     368                setStripeStatus(e && e.message ? e.message : 'Network error', true);
     369            } finally {
     370                stripePayBtn.disabled = false;
     371            }
     372        }
     373
    284374        if (refreshBtn) {
    285375            refreshBtn.addEventListener('click', doRefresh);
     
    288378            counterRefreshBtn.addEventListener('click', doRefresh);
    289379        }
     380        if (stripePayBtn) {
     381            stripePayBtn.addEventListener('click', createStripeCheckout);
     382        }
    290383    });
    291384})();
  • ask-my-content/trunk/build/ask-my-content/block.json

    r3465382 r3484181  
    33  "apiVersion": 3,
    44  "name": "amc/ask-my-content",
    5   "version": "0.9.0",
     5  "version": "1.0.0",
    66  "title": "Ask My Content",
    77  "category": "widgets",
  • ask-my-content/trunk/build/blocks-manifest.php

    r3465382 r3484181  
    66        'apiVersion' => 3,
    77        'name' => 'amc/ask-my-content',
    8         'version' => '0.9.0',
     8        'version' => '1.0.0',
    99        'title' => 'Ask My Content',
    1010        'category' => 'widgets',
  • ask-my-content/trunk/includes/api.php

    r3465382 r3484181  
    100100
    101101/**
     102 * Create a Stripe Checkout Session via backend proxy.
     103 * Sends a raw JSON payload expected by backend:
     104 * { website_id, site_url, amount }
     105 *
     106 * @param string $website_id
     107 * @param string $site_url
     108 * @param int    $amount_cents
     109 * @param string &$error
     110 * @return array|null
     111 */
     112function askmyco_create_stripe_checkout_session(string $website_id, string $site_url, int $amount_cents, string &$error = ''): ?array
     113{
     114    $url     = trim((string) get_option('askmyco_api_url', ''));
     115    $api_key = (string) get_option('askmyco_api_key', '');
     116
     117    if ($url === '') {
     118        $error = 'API URL is not configured';
     119        return null;
     120    }
     121
     122    $default_timeout = 20;
     123    $timeout_option  = get_option('askmyco_api_timeout');
     124    if ($timeout_option !== false && is_numeric($timeout_option)) {
     125        $default_timeout = max(1, (int) $timeout_option);
     126    }
     127    $timeout = (int) apply_filters('askmyco_backend_timeout', $default_timeout);
     128    if ($timeout <= 0) {
     129        $timeout = 20;
     130    }
     131
     132    $endpoint = askmyco_build_api_endpoint('stripe/create-checkout-session');
     133    $payload = [
     134        'website_id' => $website_id,
     135        'site_url'   => $site_url,
     136        'amount'     => $amount_cents,
     137    ];
     138
     139    askmyco_debug_log(sprintf('[AMC] Stripe checkout create start endpoint=%s website_id=%s amount_cents=%d', $endpoint, $website_id, $amount_cents), 'info');
     140
     141    $response = wp_remote_post($endpoint, [
     142        'headers' => [
     143            'Content-Type'  => 'application/json',
     144            'Authorization' => 'Bearer ' . $api_key,
     145        ],
     146        'body'     => wp_json_encode($payload),
     147        'blocking' => true,
     148        'timeout'  => $timeout,
     149    ]);
     150
     151    if (is_wp_error($response)) {
     152        $error = askmyco_backend_error_summary_from_response($response);
     153        askmyco_debug_log('[AMC] Stripe checkout create failed (WP_Error): ' . $error, 'error');
     154        return null;
     155    }
     156
     157    $code = (int) wp_remote_retrieve_response_code($response);
     158    if ($code < 200 || $code >= 300) {
     159        $error = askmyco_backend_error_summary_from_response($response);
     160        askmyco_debug_log(sprintf('[AMC] Stripe checkout create failed HTTP %d: %s', $code, $error), 'error');
     161        return null;
     162    }
     163
     164    $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
     165    if (! is_array($decoded)) {
     166        $error = 'Invalid Stripe create-checkout-session response (not JSON object)';
     167        askmyco_debug_log('[AMC] Stripe checkout create failed: ' . $error, 'error');
     168        return null;
     169    }
     170    if (empty($decoded['checkout_url']) || ! is_string($decoded['checkout_url'])) {
     171        $error = 'Stripe checkout URL missing in backend response';
     172        askmyco_debug_log('[AMC] Stripe checkout create failed: missing checkout_url', 'error');
     173        return null;
     174    }
     175
     176    return $decoded;
     177}
     178
     179/**
    102180 * Shared handler for chat query AJAX endpoints.
    103181 * Validates nonce and (optionally) admin capability, then proxies to backend.
     
    327405
    328406/**
     407 * Normalize one payment record from backend into a customer-facing shape.
     408 * Keeps the plugin display logic independent from provider-specific raw payloads.
     409 */
     410function askmyco_normalize_payment_record(array $payment): array
     411{
     412    $provider = strtolower((string) ($payment['provider'] ?? ''));
     413    $txn_id = '—';
     414
     415    if ($provider === 'stripe') {
     416        $txn_id = (string) ($payment['stripe_payment_intent_id']
     417            ?? $payment['stripe_checkout_session_id']
     418            ?? $payment['stripe_event_id']
     419            ?? '—');
     420    } elseif ($provider === 'paypal') {
     421        $txn_id = (string) ($payment['paypal_txn_id']
     422            ?? $payment['txn_id']
     423            ?? '—');
     424    } elseif (! empty($payment['txn_id'])) {
     425        $txn_id = (string) $payment['txn_id'];
     426    }
     427
     428    return [
     429        'provider'                   => $provider,
     430        'provider_env'               => (string) ($payment['provider_env'] ?? ''),
     431        'paid_at'                    => (string) ($payment['paypal_payment_ts'] ?? $payment['received_at'] ?? ''),
     432        'txn_id'                     => $txn_id,
     433        'paypal_txn_id'              => (string) ($payment['paypal_txn_id'] ?? ''),
     434        'payment_status'             => (string) ($payment['payment_status'] ?? ''),
     435        'gross_amount'               => $payment['gross_amount'] ?? '',
     436        'fee_amount'                 => $payment['fee_amount'] ?? '',
     437        'net_amount'                 => $payment['net_amount'] ?? '',
     438        'currency'                   => (string) ($payment['currency'] ?? 'USD'),
     439        'stripe_payment_intent_id'   => (string) ($payment['stripe_payment_intent_id'] ?? ''),
     440        'stripe_checkout_session_id' => (string) ($payment['stripe_checkout_session_id'] ?? ''),
     441        'stripe_event_id'            => (string) ($payment['stripe_event_id'] ?? ''),
     442    ];
     443}
     444
     445/**
    329446 * Sanitize a payments array for client-side consumption.
    330  * Strips raw IPN, payer email, and internal IDs.
     447 * Converts backend records to the normalized customer-facing shape.
    331448 */
    332449function askmyco_sanitize_payments_for_client(array $payments): array
     
    335452    foreach ($payments as $payment) {
    336453        if (! is_array($payment)) continue;
    337         $ipn = isset($payment['raw_ipn']) && is_array($payment['raw_ipn']) ? $payment['raw_ipn'] : [];
    338         $sanitized[] = [
    339             'paypal_payment_ts' => $payment['paypal_payment_ts'] ?? ($payment['received_at'] ?? ''),
    340             'txn_id'            => $payment['txn_id'] ?? ($ipn['txn_id'] ?? ''),
    341             'payment_status'    => $payment['payment_status'] ?? ($ipn['payment_status'] ?? ''),
    342             'gross_amount'      => $payment['gross_amount'] ?? ($ipn['mc_gross'] ?? ''),
    343             'fee_amount'        => $payment['fee_amount'] ?? ($ipn['mc_fee'] ?? ''),
    344             'net_amount'        => $payment['net_amount'] ?? '',
    345             'currency'          => $payment['currency'] ?? ($ipn['mc_currency'] ?? 'USD'),
    346         ];
     454        $sanitized[] = askmyco_normalize_payment_record($payment);
    347455    }
    348456    return $sanitized;
     
    396504
    397505    wp_send_json_success(askmyco_sanitize_payments_for_client($data));
     506}
     507
     508/**
     509 * AJAX: Create Stripe checkout session URL.
     510 * Admin-only endpoint.
     511 */
     512add_action('wp_ajax_askmyco_create_stripe_checkout', 'askmyco_ajax_create_stripe_checkout');
     513function askmyco_ajax_create_stripe_checkout()
     514{
     515    if (! current_user_can('manage_options')) {
     516        wp_send_json_error(['message' => 'Unauthorized'], 403);
     517    }
     518    check_ajax_referer('askmyco_admin_query', 'security');
     519
     520    $amount_cents = isset($_POST['amount_cents']) ? (int) sanitize_text_field(wp_unslash($_POST['amount_cents'])) : 0;
     521    if ($amount_cents < 100 || $amount_cents > 50000) {
     522        wp_send_json_error(['message' => 'Amount must be between 100 and 50000 cents', 'code' => 'invalid_amount'], 400);
     523    }
     524
     525    $website_id = (string) get_option('askmyco_site_uuid', '');
     526    if ($website_id === '') {
     527        wp_send_json_error(['message' => 'Missing site UUID', 'code' => 'missing_site_uuid'], 500);
     528    }
     529    $site_url = (string) admin_url('admin.php?page=ask-my-content&tab=payments');
     530    askmyco_debug_log('[AMC] Stripe checkout callback base URL: ' . $site_url, 'debug');
     531
     532    $error = '';
     533    $data = askmyco_create_stripe_checkout_session($website_id, $site_url, $amount_cents, $error);
     534    if ($data === null) {
     535        wp_send_json_error(['message' => $error ?: 'Unable to create Stripe checkout session', 'code' => 'stripe_create_failed'], 500);
     536    }
     537    if (empty($data['checkout_url'])) {
     538        askmyco_debug_log('[AMC] Stripe checkout create failed: checkout_url missing in decoded response', 'error');
     539        wp_send_json_error(['message' => 'Checkout URL missing in backend response', 'code' => 'missing_checkout_url'], 502);
     540    }
     541
     542    wp_send_json_success(['checkout_url' => (string) $data['checkout_url']]);
    398543}
    399544
  • ask-my-content/trunk/includes/settings.php

    r3464679 r3484181  
    2525function askmyco_settings_page()
    2626{
    27     $has_payments_tab = defined('ASKMYCO_PAYPAL_TAB');
     27    $has_payments_tab = true;
    2828    $current_tab = 'settings';
    2929    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only tab routing, no data mutation
  • ask-my-content/trunk/includes/settings_payments.php

    r3464679 r3484181  
    55}
    66
    7 // Payments Tab UI — only rendered when ASKMYCO_PAYPAL_TAB is defined.
     7// Payments Tab UI.
    88// Shared fetch helpers live in api.php: askmyco_fetch_counters(), askmyco_fetch_payments().
     9
     10// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only UI status message from redirect query parameter
     11$askmyco_payment_redirect = isset($_GET['ask-my-content-payment']) ? sanitize_text_field(wp_unslash($_GET['ask-my-content-payment'])) : '';
     12$askmyco_paypal_enabled   = defined('ASKMYCO_PAYPAL') ? (bool) constant('ASKMYCO_PAYPAL') : false;
    913
    1014// Fetch data
     
    2327$askmyco_free_tier        = $askmyco_billing['free_tier_usd'] ?? null;
    2428$askmyco_balance_due      = $askmyco_billing['balance_due_usd'] ?? null;
     29$askmyco_stripe_env       = $askmyco_billing['stripe_env'] ?? '';
    2530$askmyco_paypal_env       = $askmyco_billing['paypal_env'] ?? 'live';
    2631$askmyco_paypal_email     = $askmyco_billing['paypal_business_email'] ?? '';
     
    3035// Counter options are already persisted by askmyco_fetch_counters().
    3136
    32 // Balance color
    33 $askmyco_balance_float = $askmyco_balance_due !== null ? (float) $askmyco_balance_due : 0.0;
    34 $askmyco_balance_color = $askmyco_balance_float > 0 ? '#d63638' : '#00a32a';
    35 $askmyco_balance_label = $askmyco_balance_float > 0 ? 'Amount due' : 'Credit remaining / within free tier';
     37// Displayed balance applies current free tier locally:
     38// displayed_balance = balance_due_usd - free_tier_usd
     39$askmyco_balance_raw_float = $askmyco_balance_due !== null ? (float) $askmyco_balance_due : 0.0;
     40$askmyco_free_tier_float = $askmyco_free_tier !== null ? (float) $askmyco_free_tier : 0.0;
     41$askmyco_display_balance_float = $askmyco_balance_raw_float - $askmyco_free_tier_float;
     42$askmyco_balance_color = $askmyco_display_balance_float > 0 ? '#d63638' : '#00a32a';
     43$askmyco_balance_label = $askmyco_display_balance_float > 0 ? 'Amount due' : 'Credit remaining / within free tier';
     44
     45// Stripe/PayPal environment notice
     46$askmyco_env_notice_type = '';
     47$askmyco_env_notice_text = '';
     48if (! $askmyco_billing_error) {
     49    $askmyco_stripe_env_norm = strtolower(trim((string) $askmyco_stripe_env));
     50    $askmyco_paypal_env_norm = strtolower(trim((string) $askmyco_paypal_env));
     51    if ($askmyco_stripe_env_norm !== '' && $askmyco_paypal_env_norm !== '') {
     52        if ($askmyco_stripe_env_norm !== $askmyco_paypal_env_norm) {
     53            $askmyco_env_notice_type = 'error';
     54            $askmyco_env_notice_text = sprintf('Environment mismatch: Stripe is %s while PayPal is %s.', $askmyco_stripe_env_norm, $askmyco_paypal_env_norm);
     55        } elseif ($askmyco_stripe_env_norm === 'sandbox') {
     56            $askmyco_env_notice_type = 'warning';
     57            $askmyco_env_notice_text = 'Sandbox mode: Stripe and PayPal are configured for sandbox/testing.';
     58        }
     59    }
     60}
    3661
    3762// PayPal form action URL
    3863$askmyco_paypal_url = ($askmyco_paypal_env === 'sandbox')
    39     ? 'https://www.sandbox.paypal.com/cgi-bin/webscr'
    40     : 'https://www.paypal.com/cgi-bin/webscr';
     64    ? 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr'
     65    : 'https://ipnpb.paypal.com/cgi-bin/webscr';
    4166
    4267// Suggested payment amount: max(free_tier, balance_due), minimum $1
     
    4570    $askmyco_suggested_amount = number_format(max((float) $askmyco_free_tier, (float) $askmyco_balance_due, 1.00), 2, '.', '');
    4671}
     72$askmyco_stripe_default_amount_usd = '5.00';
    4773
    4874// Format last payment date
     
    7197<?php askmyco_render_token_counters('amc-pay'); ?>
    7298
     99<?php if ($askmyco_payment_redirect === 'success') : ?>
     100    <div class="notice notice-success inline">
     101        <p>Payment redirect SUCCESS. Credits will appear after webhook confirmation.</p>
     102    </div>
     103<?php elseif ($askmyco_payment_redirect === 'cancel') : ?>
     104    <div class="notice notice-warning inline">
     105        <p>Payment cancelled.</p>
     106    </div>
     107<?php endif; ?>
     108
    73109<hr>
    74110
     
    105141        </tr>
    106142        <tr>
    107             <th scope="row" id="amc-pay-balance-label"><?php echo $askmyco_balance_float > 0 ? 'Balance due' : 'Credit remaining'; ?></th>
     143            <th scope="row" id="amc-pay-balance-label"><?php echo $askmyco_display_balance_float > 0 ? 'Balance due' : 'Credit remaining'; ?></th>
    108144            <td id="amc-pay-balance" style="color: <?php echo esc_attr($askmyco_balance_color); ?>; font-weight: 600;">
    109                 US $<?php echo esc_html(number_format(abs($askmyco_balance_float), 2)); ?>
     145                US $<?php echo esc_html(number_format(abs($askmyco_display_balance_float), 2)); ?>
    110146                <span style="font-weight: normal; margin-left: 0.5em;">(<?php echo esc_html($askmyco_balance_label); ?>)</span>
    111147            </td>
     
    119155<h2>Add Credit</h2>
    120156
    121 <?php if ($askmyco_billing_error || empty($askmyco_paypal_email)) : ?>
    122     <p class="description">Payment form is unavailable. Billing data could not be loaded.</p>
    123 <?php else : ?>
    124     <?php if ($askmyco_paypal_env === 'sandbox') : ?>
    125         <div class="notice notice-warning inline" style="margin-bottom: 12px;">
    126             <p><strong>Sandbox mode:</strong> Payments are directed to the PayPal Sandbox environment for testing.</p>
    127         </div>
     157<div
     158    id="amc-pay-env-notice"
     159    class="notice inline<?php echo $askmyco_env_notice_type ? ' notice-' . esc_attr($askmyco_env_notice_type) : ''; ?>"
     160    style="<?php echo $askmyco_env_notice_text === '' ? 'display:none;' : ''; ?>margin-bottom:12px;">
     161    <p><?php echo esc_html($askmyco_env_notice_text); ?></p>
     162</div>
     163
     164<h3 style="margin-top:0;">Stripe</h3>
     165<p class="description">Create a Stripe Checkout session and continue payment securely on Stripe.</p>
     166<p>
     167    <label for="amc-stripe-amount-usd"><strong>Amount (USD):</strong></label>
     168    <input
     169        type="number"
     170        min="1"
     171        max="500"
     172        step="0.01"
     173        id="amc-stripe-amount-usd"
     174        value="<?php echo esc_attr($askmyco_stripe_default_amount_usd); ?>"
     175        style="width:120px; margin-left:8px;" />
     176    <button type="button" class="button button-primary" id="amc-stripe-pay-btn" style="margin-left:8px;">Pay with Stripe</button>
     177</p>
     178<p id="amc-stripe-status" class="description" style="margin-top:4px;"></p>
     179
     180<?php if ($askmyco_paypal_enabled) : ?>
     181    <h3>PayPal</h3>
     182
     183    <?php if ($askmyco_billing_error || empty($askmyco_paypal_email)) : ?>
     184        <p class="description">Payment form is unavailable. Billing data could not be loaded.</p>
     185    <?php else : ?>
     186        <?php if ($askmyco_paypal_env === 'sandbox') : ?>
     187            <div class="notice notice-warning inline" style="margin-bottom: 12px;">
     188                <p><strong>Sandbox mode:</strong> Payments are directed to the PayPal Sandbox environment for testing.</p>
     189            </div>
     190        <?php endif; ?>
     191
     192        <?php
     193        askmyco_debug_log(sprintf(
     194            'PayPal form rendered: env=%s action=%s business=%s custom=%s notify_url=%s amount=%s',
     195            $askmyco_paypal_env,
     196            $askmyco_paypal_url,
     197            $askmyco_paypal_email,
     198            $askmyco_website_id,
     199            $askmyco_paypal_notify,
     200            $askmyco_suggested_amount ?: '(user-entered)'
     201        ), 'info');
     202        ?>
     203        <form action="<?php echo esc_url($askmyco_paypal_url); ?>" method="post" target="_blank" style="margin-top: 8px;">
     204            <input type="hidden" name="cmd" value="_xclick">
     205            <input type="hidden" name="business" value="<?php echo esc_attr($askmyco_paypal_email); ?>">
     206            <input type="hidden" name="item_name" value="Ask My Content Credit">
     207            <?php if ($askmyco_suggested_amount !== '') : ?>
     208                <input type="hidden" name="amount" value="<?php echo esc_attr($askmyco_suggested_amount); ?>">
     209            <?php endif; ?>
     210            <input type="hidden" name="currency_code" value="USD">
     211            <input type="hidden" name="custom" value="<?php echo esc_attr($askmyco_website_id); ?>">
     212            <input type="hidden" name="notify_url" value="<?php echo esc_url($askmyco_paypal_notify); ?>">
     213            <input type="hidden" name="return" value="<?php echo esc_url(admin_url('admin.php?page=ask-my-content&tab=payments')); ?>">
     214            <input type="hidden" name="cancel_return" value="<?php echo esc_url(admin_url('admin.php?page=ask-my-content&tab=payments')); ?>">
     215            <?php if ($askmyco_suggested_amount !== '') : ?>
     216                <p class="description" style="margin-bottom: 8px;">
     217                    Suggested amount: <strong>US $<?php echo esc_html($askmyco_suggested_amount); ?></strong>
     218                </p>
     219            <?php endif; ?>
     220            <input type="submit" class="button button-primary" value="Add Credit via PayPal">
     221        </form>
    128222    <?php endif; ?>
    129 
    130     <?php
    131     askmyco_debug_log(sprintf(
    132         'PayPal form rendered: env=%s action=%s business=%s custom=%s notify_url=%s amount=%s',
    133         $askmyco_paypal_env,
    134         $askmyco_paypal_url,
    135         $askmyco_paypal_email,
    136         $askmyco_website_id,
    137         $askmyco_paypal_notify,
    138         $askmyco_suggested_amount ?: '(user-entered)'
    139     ), 'info');
    140     ?>
    141     <form action="<?php echo esc_url($askmyco_paypal_url); ?>" method="post" target="_blank" style="margin-top: 8px;">
    142         <input type="hidden" name="cmd" value="_xclick">
    143         <input type="hidden" name="business" value="<?php echo esc_attr($askmyco_paypal_email); ?>">
    144         <input type="hidden" name="item_name" value="Ask My Content Credit">
    145         <?php if ($askmyco_suggested_amount !== '') : ?>
    146             <input type="hidden" name="amount" value="<?php echo esc_attr($askmyco_suggested_amount); ?>">
    147         <?php endif; ?>
    148         <input type="hidden" name="currency_code" value="USD">
    149         <input type="hidden" name="custom" value="<?php echo esc_attr($askmyco_website_id); ?>">
    150         <input type="hidden" name="notify_url" value="<?php echo esc_url($askmyco_paypal_notify); ?>">
    151         <input type="hidden" name="return" value="<?php echo esc_url(admin_url('admin.php?page=ask-my-content&tab=payments')); ?>">
    152         <input type="hidden" name="cancel_return" value="<?php echo esc_url(admin_url('admin.php?page=ask-my-content&tab=payments')); ?>">
    153         <?php if ($askmyco_suggested_amount !== '') : ?>
    154             <p class="description" style="margin-bottom: 8px;">
    155                 Suggested amount: <strong>US $<?php echo esc_html($askmyco_suggested_amount); ?></strong>
    156             </p>
    157         <?php endif; ?>
    158         <input type="submit" class="button button-primary" value="Add Credit via PayPal">
    159     </form>
    160223<?php endif; ?>
    161224
     
    181244            <tr>
    182245                <th>Date</th>
     246                <th>Provider</th>
    183247                <th>Transaction ID</th>
    184248                <th>Status</th>
     
    199263
    200264            foreach ($askmyco_payments as $askmyco_payment) :
     265                $askmyco_payment = askmyco_normalize_payment_record($askmyco_payment);
    201266                $askmyco_pay_date = '';
    202                 $askmyco_raw_ts = $askmyco_payment['paypal_payment_ts'] ?? $askmyco_payment['received_at'] ?? '';
     267                $askmyco_raw_ts = $askmyco_payment['paid_at'] ?? '';
    203268                if ($askmyco_raw_ts) {
    204269                    try {
     
    212277                }
    213278
    214                 $askmyco_txn_id   = $askmyco_payment['txn_id'] ?? ($askmyco_payment['raw_ipn']['txn_id'] ?? '—');
    215                 $askmyco_status   = $askmyco_payment['payment_status'] ?? ($askmyco_payment['raw_ipn']['payment_status'] ?? '—');
    216                 $askmyco_gross    = $askmyco_payment['gross_amount'] ?? ($askmyco_payment['raw_ipn']['mc_gross'] ?? '—');
    217                 $askmyco_fee      = $askmyco_payment['fee_amount'] ?? ($askmyco_payment['raw_ipn']['mc_fee'] ?? '—');
    218                 $askmyco_currency = $askmyco_payment['currency'] ?? ($askmyco_payment['raw_ipn']['mc_currency'] ?? 'USD');
     279                $askmyco_provider = strtolower((string) ($askmyco_payment['provider'] ?? ''));
     280                $askmyco_txn_id   = $askmyco_payment['txn_id'] ?? '—';
     281                $askmyco_status   = $askmyco_payment['payment_status'] ?? '—';
     282                $askmyco_gross    = $askmyco_payment['gross_amount'] ?? '—';
     283                $askmyco_fee      = $askmyco_payment['fee_amount'] ?? '—';
     284                $askmyco_currency = $askmyco_payment['currency'] ?? 'USD';
    219285
    220286                // Calculate net
     
    234300                <tr>
    235301                    <td><?php echo esc_html($askmyco_pay_date); ?></td>
     302                    <td><?php echo esc_html($askmyco_provider !== '' ? ucfirst($askmyco_provider) : '—'); ?></td>
    236303                    <td><?php echo esc_html($askmyco_txn_id); ?></td>
    237304                    <td><?php echo esc_html($askmyco_status); ?></td>
  • ask-my-content/trunk/readme.txt

    r3465382 r3484181  
    55Requires PHP: 7.4
    66Tested up to: 6.9
    7 Stable tag: 0.9.0
     7Stable tag: 1.0.0
    88License: GPL-2.0-or-later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1531531. Chatbot on a page, answering a question using site content.
    154154
    155 2. Dashboard - Ask My Content admin page.
    156 
    157 3. Block Editor - Ask My Content block with align, size and colors params.
     1552. Dashboard - `Ask My Content` admin page.
     156
     1573. Block Editor - `Ask My Content` block with align, size and colors params.
     158
     1594. Dashboard - `Ask My Content` - `Payments` tab.
    158160
    159161== Changelog ==
     162
     163= 1.0.0 =
     164Added `Payments` tab to `Ask My Content Settings`, where you can check your usage and pay the balance (currently with Stripe)
    160165
    161166= 0.9.0 =
     
    203208== Upgrade Notice ==
    204209
    205 = 0.8.0 =
    206 Added a new optional floating chat launcher. Enable it in Dashboard -> Ask My Content after updating.
     210= 1.0.0 =
     211Added `Payments` tab to `Ask My Content Settings`, where you can check your usage and pay the balance (currently with Stripe)
  • ask-my-content/trunk/src/ask-my-content/block.json

    r3465382 r3484181  
    33    "apiVersion": 3,
    44    "name": "amc/ask-my-content",
    5     "version": "0.9.0",
     5    "version": "1.0.0",
    66    "title": "Ask My Content",
    77    "category": "widgets",
Note: See TracChangeset for help on using the changeset viewer.