| 1 |
<?php |
| 2 |
/** |
| 3 |
* Ebook Store - Stripe settings tab. |
| 4 |
* |
| 5 |
* Rendered inside <div class="ebook-settings-panel">, so this file emits |
| 6 |
* <section> blocks only: no table rows, cells or headers. |
| 7 |
* |
| 8 |
* PHP 7.4 compatible. |
| 9 |
* |
| 10 |
* @package EbookStore |
| 11 |
*/ |
| 12 |
|
| 13 |
if ( ! defined( 'ABSPATH' ) ) { |
| 14 |
exit; |
| 15 |
} |
| 16 |
|
| 17 |
settings_fields( 'ebook-settings-group-stripe' ); |
| 18 |
do_settings_sections( 'ebook-settings-group-stripe' ); |
| 19 |
|
| 20 |
$stripe_publishable_key = trim( (string) get_option( 'stripe_publishable_key', '' ) ); |
| 21 |
$stripe_secret_key = trim( (string) get_option( 'stripe_secret_key', '' ) ); |
| 22 |
$stripe_webhook_secret = trim( (string) get_option( 'stripe_webhook_secret', '' ) ); |
| 23 |
$stripe_cancel_url = (string) get_option( 'stripe_cancel_url', home_url() ); |
| 24 |
$stripe_enabled_option = (string) get_option( 'stripe_integration_enabled', '0' ); |
| 25 |
|
| 26 |
$stripe_webhook_endpoint = site_url( 'wp-json/ebook-store/v1/stripe-webhook' ); |
| 27 |
$stripe_general_url = function_exists( 'ebook_store_get_settings_tab_url' ) |
| 28 |
? ebook_store_get_settings_tab_url( 'General' ) |
| 29 |
: admin_url( 'options-general.php?page=ebook_options.php' ); |
| 30 |
$stripe_templates_url = function_exists( 'ebook_store_get_settings_tab_url' ) |
| 31 |
? ebook_store_get_settings_tab_url( 'Templates' ) |
| 32 |
: $stripe_general_url; |
| 33 |
|
| 34 |
/* |
| 35 |
* Which Stripe environment does each key belong to? Stripe keys carry the |
| 36 |
* environment in their prefix, so a test publishable key next to a live secret |
| 37 |
* key can be detected here instead of failing silently at checkout. |
| 38 |
*/ |
| 39 |
$stripe_publishable_mode = ''; |
| 40 |
if ( strpos( $stripe_publishable_key, 'pk_test_' ) === 0 ) { |
| 41 |
$stripe_publishable_mode = 'test'; |
| 42 |
} elseif ( strpos( $stripe_publishable_key, 'pk_live_' ) === 0 ) { |
| 43 |
$stripe_publishable_mode = 'live'; |
| 44 |
} |
| 45 |
|
| 46 |
$stripe_secret_mode = ''; |
| 47 |
if ( strpos( $stripe_secret_key, 'sk_test_' ) === 0 || strpos( $stripe_secret_key, 'rk_test_' ) === 0 ) { |
| 48 |
$stripe_secret_mode = 'test'; |
| 49 |
} elseif ( strpos( $stripe_secret_key, 'sk_live_' ) === 0 || strpos( $stripe_secret_key, 'rk_live_' ) === 0 ) { |
| 50 |
$stripe_secret_mode = 'live'; |
| 51 |
} |
| 52 |
|
| 53 |
$stripe_mode_mismatch = ( $stripe_publishable_mode !== '' && $stripe_secret_mode !== '' && $stripe_publishable_mode !== $stripe_secret_mode ); |
| 54 |
$stripe_mode = $stripe_mode_mismatch ? '' : ( $stripe_publishable_mode !== '' ? $stripe_publishable_mode : $stripe_secret_mode ); |
| 55 |
|
| 56 |
$stripe_is_enabled = ( $stripe_enabled_option === '1' ); |
| 57 |
$stripe_is_ready = ( $stripe_is_enabled && $stripe_publishable_key !== '' && $stripe_secret_key !== '' && ! $stripe_mode_mismatch ); |
| 58 |
|
| 59 |
$stripe_languages = function_exists( 'ebook_store_get_active_languages' ) ? ebook_store_get_active_languages() : array( 0 => __( 'Default', 'ebook-store' ) ); |
| 60 |
$stripe_language_count = count( $stripe_languages ); |
| 61 |
|
| 62 |
$stripe_mismatch_warning = __( 'Your publishable key and your secret key are from different Stripe environments (one test, one live). Stripe rejects every checkout in this state. Copy both keys again from the same place in the Stripe dashboard.', 'ebook-store' ); |
| 63 |
|
| 64 |
/** |
| 65 |
* The status card shown next to the page title. |
| 66 |
*/ |
| 67 |
$stripe_status_card = function () use ( |
| 68 |
$stripe_is_enabled, |
| 69 |
$stripe_is_ready, |
| 70 |
$stripe_mode, |
| 71 |
$stripe_mode_mismatch, |
| 72 |
$stripe_publishable_key, |
| 73 |
$stripe_secret_key, |
| 74 |
$stripe_webhook_secret, |
| 75 |
$stripe_language_count |
| 76 |
) { |
| 77 |
// Payment-method row. |
| 78 |
if ( ! $stripe_is_enabled ) { |
| 79 |
$enabled_state = 'muted'; |
| 80 |
$enabled_detail = __( 'Off. Buyers do not see a Stripe button yet.', 'ebook-store' ); |
| 81 |
} elseif ( $stripe_is_ready ) { |
| 82 |
$enabled_state = 'ok'; |
| 83 |
$enabled_detail = __( 'On. Buyers see a Stripe button on your ebooks.', 'ebook-store' ); |
| 84 |
} else { |
| 85 |
$enabled_state = 'warn'; |
| 86 |
$enabled_detail = __( 'On, but something below is still missing, so checkout will fail.', 'ebook-store' ); |
| 87 |
} |
| 88 |
|
| 89 |
// Test / live row. |
| 90 |
if ( $stripe_mode_mismatch ) { |
| 91 |
$mode_state = 'error'; |
| 92 |
$mode_label = __( 'Test or live mode: mismatched keys', 'ebook-store' ); |
| 93 |
$mode_detail = __( 'One key is a test key and the other is a live key. Stripe will refuse every payment until both come from the same environment.', 'ebook-store' ); |
| 94 |
} elseif ( $stripe_mode === 'test' ) { |
| 95 |
$mode_state = 'warn'; |
| 96 |
$mode_label = __( 'Test mode', 'ebook-store' ); |
| 97 |
$mode_detail = __( 'Test keys are saved. No real money moves. Swap in your live keys when you are ready to sell.', 'ebook-store' ); |
| 98 |
} elseif ( $stripe_mode === 'live' ) { |
| 99 |
$mode_state = 'ok'; |
| 100 |
$mode_label = __( 'Live mode', 'ebook-store' ); |
| 101 |
$mode_detail = __( 'Live keys are saved. Real cards will be charged.', 'ebook-store' ); |
| 102 |
} elseif ( $stripe_publishable_key !== '' || $stripe_secret_key !== '' ) { |
| 103 |
$mode_state = 'warn'; |
| 104 |
$mode_label = __( 'Test or live mode: unknown', 'ebook-store' ); |
| 105 |
$mode_detail = __( 'The saved keys do not start with pk_test_, pk_live_, sk_test_ or sk_live_. Check for a typo or a stray space.', 'ebook-store' ); |
| 106 |
} else { |
| 107 |
$mode_state = 'muted'; |
| 108 |
$mode_label = __( 'Test or live mode: not set', 'ebook-store' ); |
| 109 |
$mode_detail = __( 'No Stripe keys have been saved yet.', 'ebook-store' ); |
| 110 |
} |
| 111 |
?> |
| 112 |
<aside class="ebook-settings-status-card"> |
| 113 |
<h4><?php echo esc_html__( 'Current status', 'ebook-store' ); ?></h4> |
| 114 |
<ul class="ebook-settings-status-list"> |
| 115 |
<?php |
| 116 |
ebook_store_status_row( __( 'Stripe payment button', 'ebook-store' ), $enabled_state, $enabled_detail ); |
| 117 |
ebook_store_status_row( $mode_label, $mode_state, $mode_detail ); |
| 118 |
ebook_store_status_row( |
| 119 |
__( 'Publishable key', 'ebook-store' ), |
| 120 |
$stripe_publishable_key !== '' ? 'ok' : 'warn', |
| 121 |
$stripe_publishable_key !== '' |
| 122 |
? __( 'Saved.', 'ebook-store' ) |
| 123 |
: __( 'Not saved yet. Checkout cannot start without it.', 'ebook-store' ) |
| 124 |
); |
| 125 |
ebook_store_status_row( |
| 126 |
__( 'Secret key', 'ebook-store' ), |
| 127 |
$stripe_secret_key !== '' ? 'ok' : 'warn', |
| 128 |
$stripe_secret_key !== '' |
| 129 |
? __( 'Saved.', 'ebook-store' ) |
| 130 |
: __( 'Not saved yet. Checkout cannot start without it.', 'ebook-store' ) |
| 131 |
); |
| 132 |
ebook_store_status_row( |
| 133 |
__( 'Webhook signing secret', 'ebook-store' ), |
| 134 |
$stripe_webhook_secret !== '' ? 'ok' : 'warn', |
| 135 |
$stripe_webhook_secret !== '' |
| 136 |
? __( 'Saved. Paid orders can be confirmed automatically.', 'ebook-store' ) |
| 137 |
: __( 'Not saved yet. Buyers could pay without your store recording the order.', 'ebook-store' ) |
| 138 |
); |
| 139 |
ebook_store_status_row( |
| 140 |
__( 'Button label languages', 'ebook-store' ), |
| 141 |
'muted', |
| 142 |
sprintf( |
| 143 |
/* translators: %d: number of languages the button label can be written in, including the default. */ |
| 144 |
_n( |
| 145 |
'You can write the button label in %d language.', |
| 146 |
'You can write the button label in %d languages.', |
| 147 |
$stripe_language_count, |
| 148 |
'ebook-store' |
| 149 |
), |
| 150 |
$stripe_language_count |
| 151 |
) |
| 152 |
); |
| 153 |
?> |
| 154 |
</ul> |
| 155 |
</aside> |
| 156 |
<?php |
| 157 |
}; |
| 158 |
|
| 159 |
ebook_store_render_hero( |
| 160 |
array( |
| 161 |
'eyebrow' => __( 'Stripe', 'ebook-store' ), |
| 162 |
'title' => __( 'Take card payments with Stripe checkout', 'ebook-store' ), |
| 163 |
'intro' => __( 'Buyers are sent to a payment page hosted by Stripe, pay there, and come straight back to your store. This tab is where you switch Stripe on, paste the keys that connect your store to your Stripe account, and choose the wording on the payment button.', 'ebook-store' ), |
| 164 |
'aside' => $stripe_status_card, |
| 165 |
) |
| 166 |
); |
| 167 |
|
| 168 |
ebook_store_pro_banner( __( 'Stripe', 'ebook-store' ) ); |
| 169 |
|
| 170 |
if ( $stripe_mode_mismatch ) { |
| 171 |
ebook_store_callout( |
| 172 |
esc_html( $stripe_mismatch_warning ), |
| 173 |
'warning', |
| 174 |
__( 'Your two Stripe keys do not match:', 'ebook-store' ) |
| 175 |
); |
| 176 |
} |
| 177 |
|
| 178 |
ebook_store_section_open( |
| 179 |
array( |
| 180 |
'eyebrow' => __( 'Checkout', 'ebook-store' ), |
| 181 |
'title' => __( 'Turn Stripe on and name the button', 'ebook-store' ), |
| 182 |
'intro' => __( 'Switch Stripe on once your keys and webhook below are filled in. The label is the text buyers click to pay.', 'ebook-store' ), |
| 183 |
'id' => 'ebook-stripe-checkout', |
| 184 |
'pro' => true, |
| 185 |
) |
| 186 |
); |
| 187 |
|
| 188 |
ebook_store_toggle_grid_open(); |
| 189 |
ebook_store_toggle( |
| 190 |
array( |
| 191 |
'name' => 'stripe_integration_enabled', |
| 192 |
'id' => 'stripe_integration_enabled', |
| 193 |
'title' => __( 'Show a Stripe button on my ebooks', 'ebook-store' ), |
| 194 |
'description' => __( 'Leave this off until the publishable key, secret key and webhook secret below are all saved. Switching it on too early means buyers see a button that cannot complete a payment.', 'ebook-store' ), |
| 195 |
'checked' => $stripe_enabled_option, |
| 196 |
'pro' => true, |
| 197 |
) |
| 198 |
); |
| 199 |
ebook_store_toggle_grid_close(); |
| 200 |
|
| 201 |
ebook_store_advanced_open( __( 'Advanced checkout options', 'ebook-store' ) ); |
| 202 |
|
| 203 |
ebook_store_render_gateway_button_text( 'stripe' ); |
| 204 |
|
| 205 |
ebook_store_advanced_close(); |
| 206 |
|
| 207 |
ebook_store_callout( |
| 208 |
sprintf( |
| 209 |
/* translators: 1: URL of the General settings tab, 2: URL of the Templates settings tab. */ |
| 210 |
wp_kses_post( __( 'Stripe charges in the store currency set on the <a href="%1$s">General tab</a>, and returns buyers to the checkout page chosen on the <a href="%2$s">Templates tab</a>. If either of those is wrong, Stripe payments will look wrong too.', 'ebook-store' ) ), |
| 211 |
esc_url( $stripe_general_url ), |
| 212 |
esc_url( $stripe_templates_url ) |
| 213 |
), |
| 214 |
'info', |
| 215 |
__( 'Two settings live on other tabs:', 'ebook-store' ) |
| 216 |
); |
| 217 |
|
| 218 |
ebook_store_section_close(); |
| 219 |
|
| 220 |
ebook_store_section_open( |
| 221 |
array( |
| 222 |
'eyebrow' => __( 'API keys', 'ebook-store' ), |
| 223 |
'title' => __( 'Connect your Stripe account', 'ebook-store' ), |
| 224 |
'intro' => __( 'Both keys come from the same screen in your Stripe dashboard: Developers, then API keys. Use the test pair while you try things out and the live pair when you start selling, but never mix one of each.', 'ebook-store' ), |
| 225 |
'id' => 'ebook-stripe-keys', |
| 226 |
'pro' => true, |
| 227 |
) |
| 228 |
); |
| 229 |
|
| 230 |
ebook_store_grid_open( 2 ); |
| 231 |
|
| 232 |
ebook_store_field( |
| 233 |
array( |
| 234 |
'type' => 'text', |
| 235 |
'name' => 'stripe_publishable_key', |
| 236 |
'id' => 'stripe_publishable_key', |
| 237 |
'label' => __( 'Publishable key', 'ebook-store' ), |
| 238 |
'value' => $stripe_publishable_key, |
| 239 |
'placeholder' => 'pk_live_... or pk_test_...', |
| 240 |
'mono' => true, |
| 241 |
'pro' => true, |
| 242 |
'help' => __( 'Starts with pk_. Copy it from Stripe: Developers, then API keys. This one is not secret, it just tells Stripe which account the payment belongs to. If it is missing or wrong, the payment page never opens.', 'ebook-store' ), |
| 243 |
'note' => $stripe_mode_mismatch ? esc_html( $stripe_mismatch_warning ) : '', |
| 244 |
'note_tone' => 'error', |
| 245 |
) |
| 246 |
); |
| 247 |
|
| 248 |
ebook_store_field( |
| 249 |
array( |
| 250 |
'type' => 'text', |
| 251 |
'name' => 'stripe_secret_key', |
| 252 |
'id' => 'stripe_secret_key', |
| 253 |
'label' => __( 'Secret key', 'ebook-store' ), |
| 254 |
'value' => $stripe_secret_key, |
| 255 |
'placeholder' => 'sk_live_... or sk_test_...', |
| 256 |
'mono' => true, |
| 257 |
'pro' => true, |
| 258 |
'help' => __( 'Starts with sk_. In Stripe you have to click "Reveal" to see it. Treat it like a password: anyone who has it can take payments and issue refunds on your account. It must be the test or live twin of the publishable key above.', 'ebook-store' ), |
| 259 |
'note' => $stripe_mode_mismatch ? esc_html( $stripe_mismatch_warning ) : '', |
| 260 |
'note_tone' => 'error', |
| 261 |
) |
| 262 |
); |
| 263 |
|
| 264 |
ebook_store_grid_close(); |
| 265 |
|
| 266 |
ebook_store_callout( |
| 267 |
esc_html__( 'Test keys let you run a full purchase with Stripe test card 4242 4242 4242 4242 without any money changing hands. Nothing is charged and no real order is created for a customer.', 'ebook-store' ), |
| 268 |
'soft', |
| 269 |
__( 'Trying it out first:', 'ebook-store' ) |
| 270 |
); |
| 271 |
|
| 272 |
ebook_store_section_close(); |
| 273 |
|
| 274 |
ebook_store_section_open( |
| 275 |
array( |
| 276 |
'eyebrow' => __( 'Webhook and return page', 'ebook-store' ), |
| 277 |
'title' => __( 'Let Stripe tell your store about paid orders', 'ebook-store' ), |
| 278 |
'intro' => __( 'A webhook is a message Stripe sends to your site the moment a payment succeeds. Without it your store may never learn that a buyer paid, so no order and no download link are created.', 'ebook-store' ), |
| 279 |
'id' => 'ebook-stripe-webhook', |
| 280 |
'pro' => true, |
| 281 |
) |
| 282 |
); |
| 283 |
|
| 284 |
ebook_store_copy_box( |
| 285 |
$stripe_webhook_endpoint, |
| 286 |
__( 'Your webhook address', 'ebook-store' ), |
| 287 |
esc_html__( 'In Stripe go to Developers, then Webhooks, then Add endpoint, and paste this address. Select the events checkout.session.completed and charge.refunded. Stripe then shows you a signing secret to paste in the field below.', 'ebook-store' ) |
| 288 |
); |
| 289 |
|
| 290 |
ebook_store_advanced_open( __( 'Advanced webhook settings', 'ebook-store' ) ); |
| 291 |
|
| 292 |
ebook_store_grid_open( 2 ); |
| 293 |
|
| 294 |
ebook_store_field( |
| 295 |
array( |
| 296 |
'type' => 'text', |
| 297 |
'name' => 'stripe_webhook_secret', |
| 298 |
'id' => 'stripe_webhook_secret', |
| 299 |
'label' => __( 'Webhook signing secret', 'ebook-store' ), |
| 300 |
'value' => $stripe_webhook_secret, |
| 301 |
'placeholder' => 'whsec_...', |
| 302 |
'mono' => true, |
| 303 |
'pro' => true, |
| 304 |
'help' => __( 'Starts with whsec_. Stripe shows it on the webhook you just created, under "Signing secret". It proves the messages really come from Stripe. If it is missing or wrong, paid orders are ignored and your buyer gets nothing.', 'ebook-store' ), |
| 305 |
) |
| 306 |
); |
| 307 |
|
| 308 |
ebook_store_field( |
| 309 |
array( |
| 310 |
'type' => 'url', |
| 311 |
'name' => 'stripe_cancel_url', |
| 312 |
'id' => 'stripe_cancel_url', |
| 313 |
'label' => __( 'Where to send buyers who change their mind', 'ebook-store' ), |
| 314 |
'value' => $stripe_cancel_url, |
| 315 |
'placeholder' => home_url(), |
| 316 |
'pro' => true, |
| 317 |
'help' => __( 'If someone opens the Stripe payment page and then backs out without paying, this is the page they land on. Your shop page or home page both work well. Leave it as your home page if you are unsure.', 'ebook-store' ), |
| 318 |
) |
| 319 |
); |
| 320 |
|
| 321 |
ebook_store_grid_close(); |
| 322 |
|
| 323 |
ebook_store_advanced_close(); |
| 324 |
|
| 325 |
if ( $stripe_webhook_secret === '' ) { |
| 326 |
ebook_store_callout( |
| 327 |
esc_html__( 'No signing secret is saved yet. Until you add one, a buyer can pay Stripe successfully and still never receive their ebook.', 'ebook-store' ), |
| 328 |
'warning', |
| 329 |
__( 'Webhook not finished:', 'ebook-store' ) |
| 330 |
); |
| 331 |
} |
| 332 |
|
| 333 |
ebook_store_section_close(); |
| 334 |
|
| 335 |
ebook_store_section_open( |
| 336 |
array( |
| 337 |
'eyebrow' => __( 'Setup guide', 'ebook-store' ), |
| 338 |
'title' => __( 'Setting Stripe up from scratch', 'ebook-store' ), |
| 339 |
'intro' => __( 'Work through these in order and checkout, order confirmation and buyer redirects all work the first time.', 'ebook-store' ), |
| 340 |
'id' => 'ebook-stripe-setup', |
| 341 |
) |
| 342 |
); |
| 343 |
|
| 344 |
ebook_store_steps( |
| 345 |
__( 'Six steps, about ten minutes', 'ebook-store' ), |
| 346 |
array( |
| 347 |
esc_html__( 'Create a Stripe account at stripe.com, or sign in to the one you already have.', 'ebook-store' ), |
| 348 |
esc_html__( 'In Stripe open Developers, then API keys, and copy the publishable key and the secret key into the fields above.', 'ebook-store' ), |
| 349 |
esc_html__( 'In Stripe open Developers, then Webhooks, and add an endpoint using the webhook address shown above.', 'ebook-store' ), |
| 350 |
esc_html__( 'Copy the signing secret Stripe gives you for that webhook into the webhook signing secret field.', 'ebook-store' ), |
| 351 |
esc_html__( 'Choose the page buyers return to if they cancel, then save your changes.', 'ebook-store' ), |
| 352 |
esc_html__( 'Only now switch on the Stripe button, and place one test purchase to confirm the order appears in your store.', 'ebook-store' ), |
| 353 |
) |
| 354 |
); |
| 355 |
|
| 356 |
ebook_store_section_close(); |
| 357 |
|