'United States', 'GB' => 'United Kingdom', 'CA' => 'Canada', 'AU' => 'Australia', 'AT' => 'Austria', 'BE' => 'Belgium', 'BR' => 'Brazil', 'BG' => 'Bulgaria', 'HR' => 'Croatia', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic', 'DK' => 'Denmark', 'EE' => 'Estonia', 'FI' => 'Finland', 'FR' => 'France', 'DE' => 'Germany', 'GR' => 'Greece', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IE' => 'Ireland', 'IT' => 'Italy', 'JP' => 'Japan', 'LV' => 'Latvia', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MY' => 'Malaysia', 'MT' => 'Malta', 'MX' => 'Mexico', 'NL' => 'Netherlands', 'NZ' => 'New Zealand', 'NO' => 'Norway', 'PL' => 'Poland', 'PT' => 'Portugal', 'RO' => 'Romania', 'SG' => 'Singapore', 'SK' => 'Slovakia', 'SI' => 'Slovenia', 'ES' => 'Spain', 'SE' => 'Sweden', 'CH' => 'Switzerland', 'TH' => 'Thailand', 'AE' => 'United Arab Emirates', ); } function acta_get_supported_currencies() { return array( 'usd' => 'USD - US Dollar', 'eur' => 'EUR - Euro', 'gbp' => 'GBP - British Pound', 'cad' => 'CAD - Canadian Dollar', 'aud' => 'AUD - Australian Dollar', 'jpy' => 'JPY - Japanese Yen', 'chf' => 'CHF - Swiss Franc', 'nzd' => 'NZD - New Zealand Dollar', 'sek' => 'SEK - Swedish Krona', 'nok' => 'NOK - Norwegian Krone', 'dkk' => 'DKK - Danish Krone', 'sgd' => 'SGD - Singapore Dollar', 'hkd' => 'HKD - Hong Kong Dollar', 'mxn' => 'MXN - Mexican Peso', 'brl' => 'BRL - Brazilian Real', 'pln' => 'PLN - Polish Zloty', 'czk' => 'CZK - Czech Koruna', 'huf' => 'HUF - Hungarian Forint', 'ron' => 'RON - Romanian Leu', 'bgn' => 'BGN - Bulgarian Lev', 'thb' => 'THB - Thai Baht', 'myr' => 'MYR - Malaysian Ringgit', 'aed' => 'AED - UAE Dirham', ); } // ─── Activation: generate a secret key ─────────────────────────────────────── register_activation_hook( __FILE__, 'acta_activate' ); function acta_activate() { if ( ! get_option( ACTA_OPTION_KEY ) ) { update_option( ACTA_OPTION_KEY, bin2hex( random_bytes( 32 ) ) ); } // Initialize connection status if not set if ( ! get_option( ACTA_CONNECTION_STATUS ) ) { update_option( ACTA_CONNECTION_STATUS, 'not_registered' ); } // Redirect to the Acta settings page after activation. update_option( ACTA_ACTIVATION_REDIRECT_OPTION, 1 ); } // Also redirect after plugin update (upload zip) — WordPress shows "Go to Plugin Installer" link otherwise. add_action( 'upgrader_process_complete', 'acta_maybe_set_redirect_after_update', 10, 2 ); function acta_maybe_set_redirect_after_update( $upgrader_object, $options ) { if ( empty( $options['action'] ) || $options['action'] !== 'update' ) { return; } if ( empty( $options['type'] ) || $options['type'] !== 'plugin' ) { return; } $our_plugin = plugin_basename( __FILE__ ); $updated = array(); if ( ! empty( $options['plugins'] ) && is_array( $options['plugins'] ) ) { $updated = $options['plugins']; } elseif ( ! empty( $options['plugin'] ) ) { $updated = array( $options['plugin'] ); } if ( in_array( $our_plugin, $updated, true ) ) { update_option( ACTA_ACTIVATION_REDIRECT_OPTION, 1 ); } } add_action( 'admin_init', 'acta_maybe_redirect_after_activation' ); function acta_maybe_redirect_after_activation() { if ( ! get_option( ACTA_ACTIVATION_REDIRECT_OPTION ) ) { return; } delete_option( ACTA_ACTIVATION_REDIRECT_OPTION ); if ( ! is_admin() || ! current_user_can( 'manage_options' ) || wp_doing_ajax() ) { return; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return; } if ( isset( $_GET['activate-multi'] ) ) { return; } wp_safe_redirect( admin_url( 'admin.php?page=' . ACTA_ADMIN_PAGE_SLUG ) ); exit; } // ─── Admin settings page ───────────────────────────────────────────────────── add_action( 'admin_menu', 'acta_add_admin_menu' ); add_action( 'admin_enqueue_scripts', 'acta_enqueue_admin_scripts' ); function acta_enqueue_admin_scripts( $hook ) { if ( 'toplevel_page_' . ACTA_ADMIN_PAGE_SLUG !== $hook ) { return; } $asset_url = plugin_dir_url( __FILE__ ) . 'assets/js/admin-copy-price.js'; wp_enqueue_script( 'acta-admin-copy-price', $asset_url, array(), ACTA_PLUGIN_VERSION, true ); } function acta_add_admin_menu() { add_menu_page( 'Acta Settings', 'Acta', 'manage_options', ACTA_ADMIN_PAGE_SLUG, 'acta_settings_page', ACTA_MENU_ICON, 30 ); } add_action( 'admin_init', 'acta_settings_init' ); function acta_settings_init() { register_setting( 'acta_settings', ACTA_OPTION_KEY, array( 'sanitize_callback' => 'sanitize_text_field', ) ); register_setting( 'acta_settings', ACTA_PUBLISHER_ID_KEY, array( 'sanitize_callback' => 'sanitize_text_field', ) ); } // ─── Backend API functions ─────────────────────────────────────────────────── /** * Self-service onboarding: create a new publisher on the Acta backend. * Called when publisher clicks "Get Started with Acta". * * @return array { success: bool, message: string, publisherId?: string, stripeOnboardingUrl?: string } */ function acta_onboard_to_backend( $email, $first_name, $last_name, $country, $currency, $article_price ) { $backend_url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/onboard-wordpress'; $secret = get_option( ACTA_OPTION_KEY, '' ); $endpoint = rest_url( 'acta/v1/content' ); $response = wp_remote_post( $backend_url, array( 'timeout' => 30, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'email' => $email, 'firstName' => $first_name, 'lastName' => $last_name, 'website' => home_url(), 'name' => get_bloginfo( 'name' ), 'country' => $country, 'currency' => $currency, 'articlePrice' => (float) $article_price, 'pluginEndpoint' => $endpoint, 'pluginSecretKey'=> $secret, ) ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => 'Connection failed: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 201 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => 'Publisher created successfully.', 'publisherId' => $body['publisherId'] ?? '', 'stripeOnboardingUrl'=> $body['stripeOnboardingUrl'] ?? '', ); } $error_msg = $body['error'] ?? ( 'HTTP ' . $code ); return array( 'success' => false, 'message' => $error_msg, ); } /** * Reconnect the plugin to an existing publisher on the Acta backend. * Used for plugin reinstall or v1 → v2 migration. */ function acta_connect_to_backend( $publisher_id, $plugin_endpoint, $plugin_secret_key ) { $backend_url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/connect-wordpress'; $response = wp_remote_post( $backend_url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'publisherId' => $publisher_id, 'siteUrl' => home_url(), 'pluginEndpoint' => $plugin_endpoint, 'pluginSecretKey' => $plugin_secret_key, ) ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => 'Connection failed: ' . $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => $body['message'] ?? 'Connected successfully.', ); } $error_msg = $body['error'] ?? ( 'HTTP ' . $code ); return array( 'success' => false, 'message' => $error_msg, ); } /** * Fetch publisher info (article price, rate limit) from Acta backend. * * @return array{ articlePrice: float, changesRemaining: int, revShare?: int }|null */ function acta_fetch_publisher_info( $publisher_id, $secret, $site_url ) { if ( empty( $publisher_id ) || empty( $secret ) ) { return null; } $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-publisher-info'; $response = wp_remote_post( $url, array( 'timeout' => 10, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, ) ), ) ); if ( is_wp_error( $response ) ) { return null; } $code = wp_remote_retrieve_response_code( $response ); if ( $code !== 200 ) { return null; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( ! is_array( $body ) || ! isset( $body['articlePrice'] ) ) { return null; } return array( 'articlePrice' => (float) $body['articlePrice'], 'changesRemaining' => isset( $body['changesRemaining'] ) ? (int) $body['changesRemaining'] : 3, 'revShare' => isset( $body['revShare'] ) ? (int) $body['revShare'] : 15, ); } /** * Update default article price via Acta backend. * * @return array{ success: bool, message: string, articlePrice?: float, changesRemaining?: int } */ function acta_update_default_price( $publisher_id, $secret, $site_url, $article_price ) { $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-update-article-price'; $body = array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, 'articlePrice' => (float) $article_price, ); $response = wp_remote_post( $url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true, 'message' => 'Default price updated.', 'articlePrice' => $body['articlePrice'] ?? $article_price, 'changesRemaining' => $body['changesRemaining'] ?? 0, ); } return array( 'success' => false, 'message' => $body['error'] ?? ( 'HTTP ' . $code ), ); } /** * Push the paywall mode (pay-per-article only vs. alongside subscriptions) to * the Acta backend. Stored on the publisher record and delivered to the embed * script at runtime — so the flag never lives in (cacheable) page HTML and a * toggle takes effect on the next page load without a cache purge. */ function acta_update_paywall_mode( $publisher_id, $secret, $site_url, $subscribe_override ) { $url = rtrim( ACTA_BACKEND_URL, '/' ) . '/api/v1/public/wordpress-update-paywall-mode'; $body = array( 'publisherId' => $publisher_id, 'key' => $secret, 'siteUrl' => $site_url, 'subscribeOverride' => (bool) $subscribe_override, ); $response = wp_remote_post( $url, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'message' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code === 200 && ! empty( $body['success'] ) ) { return array( 'success' => true ); } return array( 'success' => false, 'message' => $body['error'] ?? ( 'HTTP ' . $code ), ); } // ─── Settings page renderer ───────────────────────────────────────────────── function acta_settings_page() { $secret = get_option( ACTA_OPTION_KEY, '' ); $publisher_id = get_option( ACTA_PUBLISHER_ID_KEY, '' ); $stripe_url = get_option( ACTA_STRIPE_URL_KEY, '' ); $conn_status = get_option( ACTA_CONNECTION_STATUS, 'not_registered' ); $endpoint = rest_url( 'acta/v1/content' ); // ── Handle form actions ────────────────────────────────────────────────── // Handle "Get Started with Acta" (self-service onboarding) if ( isset( $_POST['acta_action'] ) && $_POST['acta_action'] === 'onboard' && check_admin_referer( 'acta_onboard' ) ) { $email = sanitize_email( wp_unslash( $_POST['acta_email'] ?? '' ) ); $first_name = sanitize_text_field( wp_unslash( $_POST['acta_first_name'] ?? '' ) ); $last_name = sanitize_text_field( wp_unslash( $_POST['acta_last_name'] ?? '' ) ); $country = sanitize_text_field( wp_unslash( $_POST['acta_country'] ?? '' ) ); $currency = sanitize_text_field( wp_unslash( $_POST['acta_currency'] ?? '' ) ); $article_price = floatval( wp_unslash( $_POST['acta_article_price'] ?? 0 ) ); if ( empty( $email ) || empty( $first_name ) || empty( $last_name ) || empty( $country ) || empty( $currency ) || $article_price <= 0 ) { echo '
' . esc_html__( 'All fields are required. Please fill in your email, name, country, currency, and a price greater than 0.', 'acta-pay-per-article' ) . '
' . esc_html__( 'Successfully registered! Complete your Stripe setup below to start accepting payments.', 'acta-pay-per-article' ) . '
' . esc_html( $result['message'] ) . '
' . esc_html__( 'Status updated. If your Stripe setup is complete, you\'re live!', 'acta-pay-per-article' ) . '
' . esc_html__( 'Default price updated to ', 'acta-pay-per-article' ) . esc_html( number_format( $result['articlePrice'], 2 ) ) . '.
' . esc_html( $result['message'] ) . '
' . ( '1' === $override ? esc_html__( 'Saved. Acta will now replace your subscribe paywall with a pay-per-article button.', 'acta-pay-per-article' ) : esc_html__( 'Saved. Acta will mount alongside your existing subscription paywall.', 'acta-pay-per-article' ) ) . '
' . esc_html( $result['message'] ) . '
Acta is a pay-per-content solution for publishers and creators.
Version
Publisher ID:
You need to set up your Stripe account to receive payments from readers. This takes about 5 minutes.
✓ Stripe setup complete — readers can now purchase your articles.
Publisher ID:
Acta adds a “Read instantly” button to the paywall you already use, giving readers the option to buy a single article. Your subscription offer stays exactly as it is — Acta finds your paywall and adds the button automatically.
Works with: Jetpack, MemberPress, Paid Memberships Pro, Restrict Content Pro, and more. If something looks off, just contact us and we’ll sort it out.
No paywall yet? Install the free Jetpack plugin and add a Paywall block where the article should lock — Acta integrates right within it.
Your price: Set any price per article (see currency minimums).
Our fee: No setup or monthly fee; % rev share including processor fees.
Questions or need setup help? Reach us at contact@readwithacta.com.
Current default price:
By default, all articles use your default price. To set a different price on a specific article, add a Custom HTML block in the post editor (before the paywall break) with this snippet:
<div id="acta-price" data-price="ENTER_PRICE_HERE"></div>
Replace ENTER_PRICE_HERE with the price (e.g. 2.99). This overrides the default price for that article only.
Contact us at contact@readwithacta.com or visit readwithacta.com