├── .gitignore ├── src ├── templates │ ├── index.twig │ ├── settings.twig │ ├── _layouts │ │ ├── settingscp.twig │ │ └── settings.twig │ ├── _charges │ │ ├── partials │ │ │ ├── data.twig │ │ │ ├── metadata.twig │ │ │ ├── refunds.twig │ │ │ ├── paymentDetails.twig │ │ │ ├── outcome.twig │ │ │ └── source.twig │ │ ├── index.twig │ │ └── charge.twig │ ├── _macros │ │ └── settings.twig │ └── _settings │ │ ├── webhooks │ │ └── index.twig │ │ ├── general │ │ └── index.twig │ │ └── credentials │ │ └── index.twig ├── icon-mask.svg ├── events │ └── ChargeEvent.php ├── icon.svg ├── assetbundles │ ├── ChargeBundle.php │ └── css │ │ └── charge.css ├── twigextensions │ └── StripeCheckoutTwigExtension.php ├── variables │ └── StripeCheckoutVariable.php ├── models │ └── Settings.php ├── controllers │ ├── WebhooksController.php │ ├── SettingsController.php │ ├── CredentialsController.php │ ├── WebhookController.php │ ├── ChargesController.php │ └── ChargeController.php ├── services │ ├── WebhookService.php │ ├── SettingsService.php │ ├── CheckoutService.php │ └── ChargeService.php ├── migrations │ ├── Install.php │ └── m180531_090631_v2_upgrade.php ├── elements │ ├── db │ │ └── ChargeQuery.php │ └── Charge.php └── StripeCheckout.php ├── docs ├── charges.md ├── available-variables.md ├── default-templates.md ├── installation.md ├── general-config.md ├── craft-stripecheckout-charges.md ├── charge-model.md └── creating-charges.md ├── resources └── screenshots │ └── charges.png ├── templates └── checkout │ ├── _layouts │ └── main.twig │ ├── charge.twig │ ├── index.twig │ ├── confirmation.twig │ ├── charges.twig │ └── create.twig ├── README.md ├── LICENSE.md ├── CHANGELOG.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /src/templates/index.twig: -------------------------------------------------------------------------------- 1 | {% redirect 'stripe-checkout/charges' %} 2 | -------------------------------------------------------------------------------- /src/templates/settings.twig: -------------------------------------------------------------------------------- 1 | {% redirect 'stripe-checkout/settings/general' %} 2 | -------------------------------------------------------------------------------- /docs/charges.md: -------------------------------------------------------------------------------- 1 | # Charges 2 | 3 | Charges are [charge models](charge-model.md). 4 | -------------------------------------------------------------------------------- /resources/screenshots/charges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jalendport/craft-stripecheckout/HEAD/resources/screenshots/charges.png -------------------------------------------------------------------------------- /src/templates/_layouts/settingscp.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | {% requireAdmin %} 3 | {% set selectedSubnavItem = 'settings' %} 4 | -------------------------------------------------------------------------------- /src/templates/_charges/partials/data.twig: -------------------------------------------------------------------------------- 1 |
2 |
{{ charge.data|json_encode(constant('JSON_PRETTY_PRINT')) }}
3 |
4 | -------------------------------------------------------------------------------- /docs/available-variables.md: -------------------------------------------------------------------------------- 1 | # Available Variables 2 | 3 | The following are common methods you will want to call in your front end templates: 4 | 5 | ### craft.stripecheckout.charges 6 | 7 | See [craft.stripecheckout.charges](craft-stripecheckout-charges.md) 8 | -------------------------------------------------------------------------------- /src/templates/_charges/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/elementindex" %} 2 | 3 | {% set elementType = 'jalendport\\stripecheckout\\elements\\Charge' %} 4 | 5 | {% block actionButton %} 6 |
7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/templates/_macros/settings.twig: -------------------------------------------------------------------------------- 1 | {% macro configWarning(setting) -%} 2 | {% set setting = '' ~ setting ~ '' %} 3 | {{ 'This is being overridden by the {setting} config setting in your {file} config file.'|t('stripe-checkout', { 4 | setting: setting, 5 | file: 'stripe-checkout.php' 6 | })|raw }} 7 | {%- endmacro %} 8 | -------------------------------------------------------------------------------- /docs/default-templates.md: -------------------------------------------------------------------------------- 1 | # Default Templates 2 | 3 | This plugin comes with a set of default templates. These templates are intended to help you understand how to create your own frontend templates and forms. Simply copy across the contents of the `templates` folder into your own templates to get started. 4 | 5 | The default templates [can be found here](/templates/checkout). 6 | -------------------------------------------------------------------------------- /src/templates/_settings/webhooks/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "stripe-checkout/_layouts/settings" %} 2 | 3 | {% set fullPageForm = true %} 4 | 5 | {% import "_includes/forms" as forms %} 6 | 7 | {% block content %} 8 | 9 |

Webhooks

10 | 11 | {{ forms.textField({ 12 | first: true, 13 | label: 'Endpoint', 14 | instructions: 'Use this URL when setting up your Stripe Webhook.', 15 | value: webhookUrl, 16 | })}} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/icon-mask.svg: -------------------------------------------------------------------------------- 1 | 2 | stripecheckout-icon-mask 3 | -------------------------------------------------------------------------------- /templates/checkout/_layouts/main.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Stripe Checkout 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | {% block content %}{% endblock %} 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/checkout/charge.twig: -------------------------------------------------------------------------------- 1 | {% extends "checkout/_layouts/main" %} 2 | 3 | {% set id = craft.app.request.getQueryParam('id') %} 4 | {% set charge = craft.stripecheckout.charges.id(id).one() %} 5 | 6 | {% block content %} 7 | 8 | {% if charge %} 9 |
10 |
{{ charge|json_encode(constant('JSON_PRETTY_PRINT')) }}
11 |
12 | {% else %} 13 |
14 |
Charge not found.
15 |
16 | {% endif %} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/events/ChargeEvent.php: -------------------------------------------------------------------------------- 1 | 17 | {% for link in links %} 18 | 19 | {{ link.label }} 20 | 21 | {% endfor %} 22 | 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | stripecheckout-icon 3 | -------------------------------------------------------------------------------- /src/templates/_charges/partials/metadata.twig: -------------------------------------------------------------------------------- 1 | {% set metadata = charge.data.metadata ?: null %} 2 | 3 | {% if metadata %} 4 | 5 |
6 |
7 | Metadata 8 |
9 | 10 | {% for key, val in metadata %} 11 |
12 |
13 | {{ key }} 14 |
15 |
16 | {{ val|default('N/A') }} 17 |
18 |
19 | {% endfor %} 20 | 21 |
22 | {% endif %} 23 | -------------------------------------------------------------------------------- /templates/checkout/confirmation.twig: -------------------------------------------------------------------------------- 1 | {% extends "checkout/_layouts/main" %} 2 | 3 | {% set charge = craft.app.session.getFlash('charge') %} 4 | {% set errors = craft.app.session.getFlash('errors') %} 5 | 6 | {% block content %} 7 | 8 | {% if charge %} 9 |
10 |
{{ charge|json_encode(constant('JSON_PRETTY_PRINT')) }}
11 |
12 | {% elseif errors %} 13 |
14 |
{{ errors|json_encode(constant('JSON_PRETTY_PRINT')) }}
15 |
16 | {% else %} 17 |
18 |
Charge data not available.
19 |
20 | {% endif %} 21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /src/assetbundles/ChargeBundle.php: -------------------------------------------------------------------------------- 1 | sourcePath = '@jalendport/stripecheckout/assetbundles'; 22 | 23 | // define the dependencies 24 | $this->depends = [ 25 | CpAsset::class, 26 | ]; 27 | 28 | $this->css = [ 29 | 'css/charge.css', 30 | ]; 31 | 32 | parent::init(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/twigextensions/StripeCheckoutTwigExtension.php: -------------------------------------------------------------------------------- 1 | checkoutService->getCheckoutHtml($options);; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /templates/checkout/charges.twig: -------------------------------------------------------------------------------- 1 | {% extends "checkout/_layouts/main" %} 2 | 3 | {% set charges = craft.stripecheckout.charges.all() %} 4 | 5 | {% block content %} 6 | 7 | {% if charges %} 8 |
9 |

Charges

10 | 11 | {% for charge in charges %} 12 |
13 | 16 |
17 | {{ charge.formattedAmount }} 18 |
19 |
20 | {% endfor %} 21 |
22 | {% else %} 23 |
24 |
No charges found.
25 |
26 | {% endif %} 27 | 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | icon 2 | 3 | # Stripe Checkout plugin for Craft CMS 3 4 | 5 | Bringing the power of Stripe Checkout to your Craft templates. 6 | 7 | screenshot 8 | 9 | ## Documentation 10 | 11 | ### Installation 12 | 13 | - [Installation](docs/installation.md) 14 | 15 | ### Configuration 16 | 17 | - [General Config](docs/general-config.md) 18 | 19 | ### Core Concepts 20 | 21 | - [Charges](docs/charges.md) 22 | 23 | ### Getting Elements 24 | 25 | - [craft.stripecheckout.charges](docs/craft-stripecheckout-charges.md) 26 | 27 | ### Template Guides 28 | 29 | - [Available Variables](docs/available-variables.md) 30 | - [Creating Charges](docs/creating-charges.md) 31 | - [Default Templates](docs/default-templates.md) 32 | 33 | ## Stripe Checkout Roadmap 34 | 35 | Some things to do, and ideas for potential features: 36 | 37 | - Reconcile tools 38 | 39 | Brought to you by [Luke Youell](https://github.com/lukeyouell/craft-stripecheckout) 40 | -------------------------------------------------------------------------------- /docs/general-config.md: -------------------------------------------------------------------------------- 1 | # General Config 2 | 3 | The config items below can be placed into a `stripe-checkout.php` file in your `craft/config` directory, this is a handy way to have different settings across multiple environments. 4 | 5 | ### `pluginNameOverride` 6 | 7 | The plugin name as you’d like it to be displayed in the CP. 8 | 9 | ### `accountMode` 10 | 11 | Whether you want to use the `test` or `live` Stripe credentials. 12 | 13 | ### `testPublishableKey` 14 | 15 | Your Stripe test publishable key. See [API Keys](https://stripe.com/docs/keys). 16 | 17 | ### `testSecretKey` 18 | 19 | Your Stripe test secret key. See [API Keys](https://stripe.com/docs/keys). 20 | 21 | ### `livePublishableKey` 22 | 23 | Your Stripe live publishable key. See [API Keys](https://stripe.com/docs/keys). 24 | 25 | ### `liveSecretKey` 26 | 27 | Your Stripe live secret key. See [API Keys](https://stripe.com/docs/keys). 28 | 29 | ### `defaultCurrency` 30 | 31 | Three-letter ISO currency code. Must be a [supported currency](https://stripe.com/docs/currencies). 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jalen Davenport 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/templates/_charges/partials/refunds.twig: -------------------------------------------------------------------------------- 1 | {% set refunds = charge.data.refunds.data ?: null %} 2 | 3 |
4 |
5 |
6 | Refunds 7 |
8 | {% if refunds %} 9 | {% for refund in refunds %} 10 |
11 |
12 | {{ refund.created|datetime('short') }} 13 |
14 |
15 | {{ (refund.amount / 100)|number_format(2, '.', ',') }} 16 | {{ refund.currency|upper }} 17 | ({{ ((refund.reason|replace({'_': ' '}))|title)|default('N/A') }}) 18 |
19 |
20 | {% endfor %} 21 | {% else %} 22 |
23 |
24 | No refunds exist for this charge. 25 |
26 |
27 | {% endif %} 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /src/variables/StripeCheckoutVariable.php: -------------------------------------------------------------------------------- 1 | checkoutService->getCheckoutHtml($options); 27 | } 28 | 29 | public function charges(array $criteria = []): ChargeQuery 30 | { 31 | $query = Charge::find(); 32 | Craft::configure($query, $criteria); 33 | 34 | return $query; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/templates/_layouts/settings.twig: -------------------------------------------------------------------------------- 1 | {% extends "stripe-checkout/_layouts/settingscp" %} 2 | 3 | {% set title = 'Stripe Checkout Settings' %} 4 | 5 | {% set navItems = { 6 | 'general': { title: 'General Settings' }, 7 | 'credentials': { title: 'Credentials' }, 8 | 'webhooks': { title: 'Webhooks' }, 9 | } %} 10 | 11 | {% if selectedItem is not defined %} 12 | {% set selectedItem = craft.app.request.getSegment(3) %} 13 | {% endif %} 14 | 15 | {% set docTitle = navItems[selectedItem].title ~ ' - ' ~ title %} 16 | 17 | {% block sidebar %} 18 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /src/assetbundles/css/charge.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{box-sizing:border-box;font-family:sans-serif}*,::after,::before{box-sizing:inherit}pre{margin:0}*,::after,::before{border-width:0;border-style:solid;border-color:#dae1e7}.bg-grey-lightest{background-color:#f8fafc}.bg-red-lightest{background-color:#fcebea}.bg-orange-lightest{background-color:#fff5eb}.bg-green-lightest{background-color:#e3fcec}.border-grey-light{border-color:#dae1e7}.rounded{border-radius:.25rem}.border-solid{border-style:solid}.border{border-width:1px}.border-b{border-bottom-width:1px}.hidden{display:none}.flex{display:flex}.mb-4{margin-bottom:1rem}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.p-4{padding:1rem}.pb-2{padding-bottom:.5rem}.text-grey-dark{color:#8795a1}.text-sm{font-size:.875rem}.w-full{width:100%}@media (min-width:576px){.sm\:w-1\/3{width:33.33333%}} 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Stripe Checkout Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## 2.1.0 - 2019-11-16 8 | 9 | Transfer of ownership 👀 10 | 11 | ## 2.0.2 - 2018-07-24 12 | 13 | ### Fixed 14 | 15 | - Wrong value being displayed for the live secret key in the settings page ([#11](https://github.com/jalendport/craft-stripecheckout/issues/11)) 16 | 17 | ## 2.0.1 - 2018-05-31 18 | 19 | ### Fixed 20 | 21 | - Migration table schema 22 | 23 | ## 2.0.0 - 2018-05-31 24 | 25 | ### Added 26 | 27 | - Default templates 28 | - The ability to filter, search, sort and delete charges 29 | - The ability to override the plugin name 30 | - View raw charge data 31 | - Additional parameters can be inserted into the stripe charge request via the `BEFORE_CHARGE` event 32 | 33 | ### Changed 34 | 35 | - Updated Stripe to `^6.7` 36 | - Charges are now elements 37 | - Improved error handling 38 | - Improved settings templates 39 | - Event names changed to `EVENT_BEFORE_CHARGE` and `EVENT_AFTER_CHARGE` 40 | - Updated icons 41 | - More charge data made available 42 | -------------------------------------------------------------------------------- /src/models/Settings.php: -------------------------------------------------------------------------------- 1 | settings = StripeCheckout::$plugin->getSettings(); 35 | if (!$this->settings->validate()) { 36 | throw new InvalidConfigException('Stripe Checkout settings don’t validate.'); 37 | } 38 | } 39 | 40 | public function actionIndex() 41 | { 42 | $webhookUrl = StripeCheckout::getInstance()->settingsService->getWebhookUrl(); 43 | 44 | $variables = [ 45 | 'webhookUrl' => $webhookUrl, 46 | ]; 47 | 48 | return $this->renderTemplate('stripe-checkout/_settings/webhooks/index', $variables); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/templates/_settings/general/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "stripe-checkout/_layouts/settings" %} 2 | 3 | {% set fullPageForm = true %} 4 | 5 | {% import "_includes/forms" as forms %} 6 | {% import "stripe-checkout/_macros/settings" as macros %} 7 | 8 | {% block content %} 9 | 10 | {{ forms.hidden({ 11 | name: 'action', 12 | value: 'plugins/save-plugin-settings' 13 | })}} 14 | 15 | {{ forms.hidden({ 16 | name: 'pluginHandle', 17 | value: plugin.handle 18 | })}} 19 | 20 |

General Settings

21 | 22 | {{ forms.textField({ 23 | first: true, 24 | label: 'Plugin Name Override', 25 | instructions: 'The plugin name as you’d like it to be displayed in the CP.', 26 | id: 'pluginNameOverride', 27 | name: 'settings[pluginNameOverride]', 28 | placeholder: plugin.name, 29 | value: settings.pluginNameOverride, 30 | disabled: 'pluginNameOverride' in overrides, 31 | warning: 'pluginNameOverride' in overrides ? macros.configWarning('pluginNameOverride'), 32 | })}} 33 | 34 | 35 | {{ forms.selectField({ 36 | label: 'Default Currency', 37 | id: 'defaultCurrency', 38 | name: 'settings[defaultCurrency]', 39 | options: currencyOptions, 40 | value: settings.defaultCurrency, 41 | disabled: 'defaultCurrency' in overrides, 42 | warning: 'defaultCurrency' in overrides ? macros.configWarning('defaultCurrency'), 43 | })}} 44 | 45 | {% endblock %} 46 | -------------------------------------------------------------------------------- /src/templates/_charges/charge.twig: -------------------------------------------------------------------------------- 1 | {% extends "_layouts/cp" %} 2 | 3 | {% do view.registerAssetBundle("jalendport\\stripecheckout\\assetbundles\\ChargeBundle") %} 4 | 5 | {% block actionButton %} 6 |
7 | 8 | View on Stripe 9 |
10 | {% endblock %} 11 | 12 | {% block content %} 13 | 14 |
15 |
16 | {% include "stripe-checkout/_charges/partials/outcome" %} 17 | 18 | {% include "stripe-checkout/_charges/partials/paymentDetails" %} 19 | 20 | {% include "stripe-checkout/_charges/partials/metadata" %} 21 | 22 | {% include "stripe-checkout/_charges/partials/source" %} 23 |
24 | 25 | 28 | 29 | 32 |
33 | 34 | {% endblock %} 35 | 36 | {% block details %} 37 | 38 |
39 |
40 |
41 | Created 42 |
43 |
44 | {{ charge.dateCreated|datetime('short') }} 45 |
46 |
47 | 48 |
49 |
50 | Updated 51 |
52 |
53 | {{ charge.dateCreated|datetime('short') }} 54 |
55 |
56 |
57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /src/templates/_charges/partials/paymentDetails.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | Payment Details 4 |
5 | 6 |
7 |
8 | ID 9 |
10 |
11 | {{ charge.stripeId|default('N/A') }} 12 |
13 |
14 | 15 |
20 |
21 | Status 22 |
23 |
24 | {{ (charge.chargeStatus|title)|default('N/A') }} 25 |
26 |
27 | 28 |
29 |
30 | Amount 31 |
32 |
33 | {{ charge.formattedAmount|default('N/A') }} 34 |
35 |
36 | 37 |
38 |
39 | Email 40 |
41 |
42 | {{ charge.email|default('N/A') }} 43 |
44 |
45 | 46 |
47 |
48 | Description 49 |
50 |
51 | {{ charge.description|default('N/A') }} 52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /src/controllers/SettingsController.php: -------------------------------------------------------------------------------- 1 | settings = StripeCheckout::$plugin->getSettings(); 35 | if (!$this->settings->validate()) { 36 | throw new InvalidConfigException('Stripe Checkout settings don’t validate.'); 37 | } 38 | } 39 | 40 | public function actionIndex() 41 | { 42 | $plugin = StripeCheckout::$plugin; 43 | $settings = $this->settings; 44 | $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($plugin->handle)); 45 | $currencyOptions = StripeCheckout::getInstance()->settingsService->getCurrencyOptions(); 46 | 47 | $variables = [ 48 | 'plugin' => $plugin, 49 | 'settings' => $settings, 50 | 'overrides' => $overrides, 51 | 'currencyOptions' => $currencyOptions, 52 | ]; 53 | 54 | return $this->renderTemplate('stripe-checkout/_settings/general/index', $variables); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/controllers/CredentialsController.php: -------------------------------------------------------------------------------- 1 | settings = StripeCheckout::$plugin->getSettings(); 35 | if (!$this->settings->validate()) { 36 | throw new InvalidConfigException('Stripe Checkout settings don’t validate.'); 37 | } 38 | } 39 | 40 | public function actionIndex() 41 | { 42 | $plugin = StripeCheckout::$plugin; 43 | $settings = $this->settings; 44 | $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($plugin->handle)); 45 | $accountModeOptions = StripeCheckout::getInstance()->settingsService->getAccountModeOptions(); 46 | 47 | $variables = [ 48 | 'plugin' => $plugin, 49 | 'settings' => $settings, 50 | 'overrides' => $overrides, 51 | 'accountModeOptions' => $accountModeOptions, 52 | ]; 53 | 54 | return $this->renderTemplate('stripe-checkout/_settings/credentials/index', $variables); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /templates/checkout/create.twig: -------------------------------------------------------------------------------- 1 | {% extends "checkout/_layouts/main" %} 2 | 3 | {% set options = { 4 | amount: 500, 5 | label: 'Proceed Payment', 6 | name: 'Stripe Checkout', 7 | metadata: ['orderComments'], 8 | } %} 9 | 10 | {% block content %} 11 | 12 |
13 | {{ csrfInput() }} 14 | {{ redirectInput('checkout/confirmation') }} 15 | 16 | 17 |
18 |
19 |

Checkout

20 |

Praesent facilisis placerat libero, non varius mauris tempor sit amet. Ut lobortis sed nisi vitae blandit.

21 |
22 | 23 |
24 |
25 | Coffee Mug 26 |
27 |
28 | £{{ (options.amount / 100)|number_format(2, '.', ',') }} 29 |
30 |
31 | 32 |
33 |
34 | Final Price 35 |
36 |
37 | £{{ (options.amount / 100)|number_format(2, '.', ',') }} 38 |
39 |
40 | 41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 | {{ checkout(options) }} 49 |
50 |
51 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /src/controllers/WebhookController.php: -------------------------------------------------------------------------------- 1 | enableCsrfValidation = false; 37 | 38 | $secretKey = StripeCheckout::getInstance()->settingsService->getSecretKey(); 39 | \Stripe\Stripe::setApiKey($secretKey); 40 | } 41 | 42 | public function actionIndex() 43 | { 44 | $this->requirePostRequest(); 45 | 46 | $request = @file_get_contents('php://input'); 47 | $webhook = Json::decode($request, false); 48 | 49 | $event = StripeCheckout::getInstance()->webhookService->verifyEvent($webhook->id); 50 | 51 | if (!$event) { 52 | Craft::$app->response->setStatusCode(400); 53 | return $this->asJson(['success' => false, 'reason' => 'Event couldn’t be verified.']); 54 | } 55 | 56 | $handled = StripeCheckout::getInstance()->webhookService->handleEvent($webhook); 57 | 58 | if (!$handled) { 59 | Craft::$app->response->setStatusCode(400); 60 | return $this->asJson(['success' => false, 'reason' => 'Event couldn’t be handled.']); 61 | } 62 | 63 | Craft::$app->response->setStatusCode(200); 64 | return $this->asJson(['success' => true]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/controllers/ChargesController.php: -------------------------------------------------------------------------------- 1 | 'Charges', 30 | ]; 31 | 32 | return $this->renderTemplate('stripe-checkout/_charges/index', $variables); 33 | } 34 | 35 | public function actionView($id = null) 36 | { 37 | $charge = StripeCheckout::getInstance()->chargeService->getChargeById($id); 38 | 39 | if (!$charge) { 40 | throw new NotFoundHttpException('Charge not found (#'.$id.')'); 41 | } 42 | 43 | $crumbs = [ 44 | [ 45 | 'label' => StripeCheckout::getInstance()->settingsService->getName(), 46 | 'url' => UrlHelper::cpUrl('stripe-checkout'), 47 | ], 48 | ]; 49 | 50 | $tabs = [ 51 | 'overview' => [ 52 | 'url' => '#overview', 53 | 'label' => 'Overview', 54 | ], 55 | 'refunds' => [ 56 | 'url' => '#refunds', 57 | 'label' => 'Refunds ('.count($charge->data['refunds']['data']).')', 58 | ], 59 | 'data' => [ 60 | 'url' => '#data', 61 | 'label' => 'Data', 62 | ], 63 | ]; 64 | 65 | $variables = [ 66 | 'title' => $charge->formattedAmount, 67 | 'crumbs' => $crumbs, 68 | 'tabs' => $tabs, 69 | 'charge' => $charge, 70 | ]; 71 | 72 | return $this->renderTemplate('stripe-checkout/_charges/charge', $variables); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/templates/_charges/partials/outcome.twig: -------------------------------------------------------------------------------- 1 | {% set outcome = charge.data.outcome ?: null %} 2 | 3 | {% if outcome %} 4 | 5 |
6 |
7 | Outcome 8 |
9 | 10 |
15 |
16 | Type 17 |
18 |
19 | {{ ((outcome.type|replace({'_': ' '}))|title)|default('N/A') }} 20 |
21 |
22 | 23 |
28 |
29 | Risk Level 30 |
31 |
32 | {{ ((outcome.risk_level|replace({'_': ' '}))|title)|default('N/A') }} 33 |
34 |
35 | 36 |
37 |
38 | Message 39 |
40 |
41 | {{ outcome.seller_message|default('N/A') }} 42 |
43 |
44 | 45 |
49 |
50 | Network Status 51 |
52 |
53 | {{ ((outcome.network_status|replace({'_': ' '}))|title)|default('N/A') }} 54 |
55 |
56 | 57 |
58 |
59 | Reason 60 |
61 |
62 | {{ ((outcome.reason|replace({'_': ' '}))|title)|default('N/A') }} 63 |
64 |
65 | 66 |
67 | {% endif %} 68 | -------------------------------------------------------------------------------- /src/services/WebhookService.php: -------------------------------------------------------------------------------- 1 | getErrorHandler()->logException($e); 31 | } catch (\Stripe\Error\RateLimit $e) { 32 | Craft::$app->getErrorHandler()->logException($e); 33 | } catch (\Stripe\Error\InvalidRequest $e) { 34 | Craft::$app->getErrorHandler()->logException($e); 35 | } catch (\Stripe\Error\Authentication $e) { 36 | Craft::$app->getErrorHandler()->logException($e); 37 | } catch (\Stripe\Error\ApiConnection $e) { 38 | Craft::$app->getErrorHandler()->logException($e); 39 | } catch (\Stripe\Error\Base $e) { 40 | Craft::$app->getErrorHandler()->logException($e); 41 | } catch (Exception $e) { 42 | Craft::$app->getErrorHandler()->logException($e); 43 | } 44 | 45 | return false; 46 | } 47 | 48 | public function handleEvent($event = null) 49 | { 50 | if ($event) { 51 | $charge = $event->data->object; 52 | 53 | switch ($event->type) { 54 | case 'charge.updated': 55 | case 'charge.captured': 56 | case 'charge.refunded': 57 | case 'charge.succeeded': 58 | return StripeCheckout::getInstance()->chargeService->updateCharge($charge); 59 | break; 60 | 61 | case 'charge.failed': 62 | return StripeCheckout::getInstance()->chargeService->insertCharge($charge); 63 | break; 64 | } 65 | } 66 | 67 | return null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/templates/_settings/credentials/index.twig: -------------------------------------------------------------------------------- 1 | {% extends "stripe-checkout/_layouts/settings" %} 2 | 3 | {% set fullPageForm = true %} 4 | 5 | {% import "_includes/forms" as forms %} 6 | {% import "stripe-checkout/_macros/settings" as macros %} 7 | 8 | {% block content %} 9 | 10 | {{ forms.hidden({ 11 | name: 'action', 12 | value: 'plugins/save-plugin-settings' 13 | })}} 14 | 15 | {{ forms.hidden({ 16 | name: 'pluginHandle', 17 | value: plugin.handle 18 | })}} 19 | 20 |

Credentials

21 | 22 | {{ forms.selectField({ 23 | first: true, 24 | label: 'Account Mode', 25 | id: 'accountMode', 26 | name: 'settings[accountMode]', 27 | options: accountModeOptions, 28 | value: settings.accountMode, 29 | disabled: 'accountMode' in overrides, 30 | warning: 'accountMode' in overrides ? macros.configWarning('accountMode'), 31 | })}} 32 | 33 |

Test Credentials

34 | 35 | {{ forms.textField({ 36 | first: true, 37 | label: 'Publishable Key', 38 | id: 'testPublishableKey', 39 | name: 'settings[testPublishableKey]', 40 | placeholder: 'pk_test_Hoz3tEXcZaO50jahb9xzhn7b', 41 | value: settings.testPublishableKey, 42 | disabled: 'testPublishableKey' in overrides, 43 | warning: 'testPublishableKey' in overrides ? macros.configWarning('testPublishableKey'), 44 | })}} 45 | 46 | {{ forms.textField({ 47 | label: 'Secret Key', 48 | id: 'testSecretKey', 49 | name: 'settings[testSecretKey]', 50 | placeholder: 'sk_test_Hoz3tEXcZaO50jahb9xzhn7b', 51 | value: settings.testSecretKey, 52 | disabled: 'testSecretKey' in overrides, 53 | warning: 'testSecretKey' in overrides ? macros.configWarning('testSecretKey'), 54 | })}} 55 | 56 |

Live Credentials

57 | 58 | {{ forms.textField({ 59 | first: true, 60 | label: 'Publishable Key', 61 | id: 'livePublishableKey', 62 | name: 'settings[livePublishableKey]', 63 | placeholder: 'pk_Hoz3tEXcZaO50jahb9xzhn7b', 64 | value: settings.livePublishableKey, 65 | disabled: 'livePublishableKey' in overrides, 66 | warning: 'livePublishableKey' in overrides ? macros.configWarning('livePublishableKey'), 67 | })}} 68 | 69 | {{ forms.textField({ 70 | label: 'Secret Key', 71 | id: 'liveSecretKey', 72 | name: 'settings[liveSecretKey]', 73 | placeholder: 'sk_Hoz3tEXcZaO50jahb9xzhn7b', 74 | value: settings.liveSecretKey, 75 | disabled: 'liveSecretKey' in overrides, 76 | warning: 'liveSecretKey' in overrides ? macros.configWarning('liveSecretKey'), 77 | })}} 78 | 79 | {% endblock %} 80 | -------------------------------------------------------------------------------- /docs/charge-model.md: -------------------------------------------------------------------------------- 1 | # ChargeModel 2 | 3 | Whenever you're dealing with a charge in your template, you're actually working with a ChargeModel object. 4 | 5 | ## Simple Output 6 | 7 | Outputting a ChargeModel object without attaching a property or method will return the charge's formatted amount. 8 | 9 | ```twig 10 |

{{ charge }}

11 | ``` 12 | 13 | Would output: 14 | 15 | ``` 16 | 5.00 GBP 17 | ``` 18 | 19 | ## Properties 20 | 21 | ### `amount` 22 | 23 | A positive integer in the [smallest currency unit](https://stripe.com/docs/currencies#zero-decimal) (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency) representing how much to charge. The minimum amount is $0.50 US or [equivalent in charge currency](https://support.stripe.com/questions/what-is-the-minimum-amount-i-can-charge-with-stripe). 24 | 25 | ### `amountRefunded` 26 | 27 | Amount in pence refunded (can be less than the amount attribute on the charge if a partial refund was issued). 28 | 29 | ### `chargeStatus` 30 | 31 | The status of the payment is either `succeeded`, `pending`, or `failed`. 32 | 33 | ### `cpEditUrl` 34 | 35 | Returns the URL to the charge within the control panel. 36 | 37 | ### `currency` 38 | 39 | Three-letter ISO currency code. 40 | 41 | ### `data` 42 | 43 | The full [charge object](https://stripe.com/docs/api#charge_object) provided by Stripe. 44 | 45 | ### `dateCreated` 46 | 47 | A [DateTime](http://php.net/manual/en/class.datetime.php) object of the date the charge was created. 48 | 49 | ### `dateUpdated` 50 | 51 | A [DateTime](http://php.net/manual/en/class.datetime.php) object of the date the charge was last updated. 52 | 53 | ### `description` 54 | 55 | An arbitrary string attached to the object. Often useful for displaying to users. 56 | 57 | ### `failureCode` 58 | 59 | Error code explaining reason for charge failure if available (see [the errors section](https://stripe.com/docs/api#errors) for a list of codes). 60 | 61 | ### `failureMessage` 62 | 63 | Message to user further explaining reason for charge failure if available. 64 | 65 | ### `formattedAmount` 66 | 67 | The amount followed by the currency. 68 | 69 | ### `email` 70 | 71 | This is the email address that the receipt for this charge was sent to. 72 | 73 | ### `id` 74 | 75 | The charge record ID. 76 | 77 | ### `live` 78 | 79 | Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode. 80 | 81 | ### `paid` 82 | 83 | `true` if the charge succeeded, or was successfully authorised for later capture. 84 | 85 | ### `refunded` 86 | 87 | Whether the charge has been fully refunded. If the charge is only partially refunded, this attribute will still be `false`. 88 | 89 | ### `stripeId` 90 | 91 | The charge ID supplied by Stripe. 92 | 93 | ### `stripeUrl` 94 | 95 | Returns the URL to the charge in the Stripe dashboard. 96 | -------------------------------------------------------------------------------- /src/controllers/ChargeController.php: -------------------------------------------------------------------------------- 1 | requirePostRequest(); 32 | 33 | $request = Craft::$app->getRequest(); 34 | $stripeOptions = unserialize(StringHelper::decdec($request->getRequiredBodyParam('stripeOptions'))); 35 | 36 | $stripeRequest = []; 37 | $stripeRequest['token'] = $request->getRequiredBodyParam('stripeToken'); 38 | $stripeRequest['email'] = $request->getRequiredBodyParam('stripeEmail'); 39 | $stripeRequest['options'] = $stripeOptions; 40 | $stripeRequest['shipping'] = [ 41 | 'name' => $request->post('stripeShippingName'), 42 | 'address' => [ 43 | 'line1' => $request->post('stripeShippingAddressLine1'), 44 | 'city' => $request->post('stripeShippingAddressCity'), 45 | 'state' => $request->post('stripeShippingAddressState'), 46 | 'country' => $request->post('stripeShippingAddressCountry'), 47 | 'postal_code' => $request->post('stripeShippingAddressZip') 48 | ] 49 | ]; 50 | 51 | $stripeRequest['metadata'] = []; 52 | $metadata = isset($stripeRequest['options']['metadata']) ? $stripeRequest['options']['metadata'] : []; 53 | 54 | foreach ($metadata as $key) { 55 | $stripeRequest['metadata'][$key] = is_array($request->post($key)) ? StringHelper::toString($request->post($key)) : $request->post($key); 56 | } 57 | 58 | $response = StripeCheckout::getInstance()->chargeService->createCharge($stripeRequest); 59 | 60 | if (!isset($response->id)) { 61 | if ($request->getAcceptsJson()) { 62 | return $this->asJson($response); 63 | } 64 | 65 | Craft::$app->session->setFlash('errors', $response); 66 | Craft::$app->getSession()->setError('Couldn’t create the charge.'); 67 | } else { 68 | if ($request->getAcceptsJson()) { 69 | return $this->asJson($response); 70 | } 71 | 72 | Craft::$app->session->setFlash('charge', $response); 73 | Craft::$app->getSession()->setNotice('Charge created.'); 74 | } 75 | 76 | return $this->redirectToPostedUrl(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/services/SettingsService.php: -------------------------------------------------------------------------------- 1 | settings = StripeCheckout::$plugin->getSettings(); 34 | if (!$this->settings->validate()) { 35 | throw new InvalidConfigException('Stripe Checkout settings don’t validate.'); 36 | } 37 | } 38 | 39 | public function getName() 40 | { 41 | $settings = $this->settings; 42 | 43 | $name = $settings->pluginNameOverride ?: StripeCheckout::$plugin->name; 44 | 45 | return $name; 46 | } 47 | 48 | public function getPublishableKey() 49 | { 50 | $settings = $this->settings; 51 | 52 | $key = $settings->accountMode == 'live' ? $settings->livePublishableKey : $settings->testPublishableKey; 53 | 54 | return $key; 55 | } 56 | 57 | public function getSecretKey() 58 | { 59 | $settings = $this->settings; 60 | 61 | $key = $settings->accountMode == 'live' ? $settings->liveSecretKey : $settings->testSecretKey; 62 | 63 | return $key; 64 | } 65 | 66 | public function getDefaultCurrency() 67 | { 68 | $currency = $this->settings->defaultCurrency; 69 | 70 | return $currency; 71 | } 72 | 73 | public function getAccountModeOptions() 74 | { 75 | $options = [ 76 | 'test' => 'Test', 77 | 'live' => 'Live', 78 | ]; 79 | 80 | return $options; 81 | } 82 | 83 | public function getCurrencyOptions() 84 | { 85 | $options = [ 86 | 'AUD' => 'AUD - Australian Dollars', 87 | 'CAD' => 'CAD - Canadian Dollars', 88 | 'CHF' => 'CHF - Swiss Franc', 89 | 'DKK' => 'DAK - Danish Krone', 90 | 'EUR' => 'EUR - Euro', 91 | 'GBP' => 'GBP - British Pound Sterling', 92 | 'HKD' => 'HKD - Hong Kong Dollar', 93 | 'JPY' => 'JPY - Japanese Yen', 94 | 'NOK' => 'NOK - Norwegian Krone', 95 | 'NZD' => 'NZD - New Zealand Dollar', 96 | 'SEK' => 'SEK - Swedish Krona', 97 | 'USD' => 'USD - American Dollar', 98 | ]; 99 | 100 | return $options; 101 | } 102 | 103 | public function getWebhookUrl() 104 | { 105 | $url = UrlHelper::siteUrl('actions/stripe-checkout/webhook'); 106 | 107 | return $url; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/services/CheckoutService.php: -------------------------------------------------------------------------------- 1 | getCheckoutOptions($options); 28 | 29 | $html = $this->buildHtml($checkoutOptions); 30 | 31 | return $html; 32 | } 33 | 34 | public function getCheckoutOptions($options = []) 35 | { 36 | $checkout = []; 37 | $key = StripeCheckout::getInstance()->settingsService->getPublishableKey(); 38 | $defaultCurrency = StripeCheckout::getInstance()->settingsService->getDefaultCurrency(); 39 | 40 | $checkout['key'] = $key; 41 | $checkout['amount'] = isset($options['amount']) ? floor($options['amount']) : null; 42 | $checkout['locale'] = isset($options['locale']) ? $options['locale'] : null; 43 | $checkout['name'] = isset($options['name']) ? $options['name'] : null; 44 | $checkout['description'] = isset($options['description']) ? $options['description'] : null; 45 | $checkout['image'] = isset($options['image']) ? $options['image'] : null; 46 | $checkout['currency'] = isset($options['currency']) ? $options['currency'] : $defaultCurrency; 47 | $checkout['email'] = isset($options['email']) ? $options['email'] : null; 48 | $checkout['label'] = isset($options['label']) ? $options['label'] : null; 49 | $checkout['panel-label'] = isset($options['panelLabel']) ? $options['panelLabel'] : null; 50 | $checkout['zip-code'] = (isset($options['zipCode']) and $options['zipCode']) ? 'true' : 'false'; 51 | $checkout['billing-address'] = (isset($options['billingAddress']) and $options['billingAddress']) ? 'true' : 'false'; 52 | $checkout['shipping-address'] = (isset($options['shippingAddress']) and $options['shippingAddress']) ? 'true' : 'false'; 53 | $checkout['allow-remember-me'] = (isset($options['allowRememberMe']) and $options['allowRememberMe']) ? 'true' : 'false'; 54 | $checkout['metadata'] = isset($options['metadata']) ? $options['metadata'] : null; 55 | 56 | return $checkout; 57 | } 58 | 59 | public function buildHtml($checkoutOptions = []) 60 | { 61 | $encryptedOptions = StringHelper::encenc(serialize($checkoutOptions)); 62 | unset($checkoutOptions['metadata']); 63 | 64 | // Build data string 65 | $dataString = []; 66 | 67 | foreach ($checkoutOptions as $key => $val) { 68 | if ($val) { 69 | $dataString[] = 'data-'.$key.'="'.$val.'"'; 70 | } 71 | } 72 | 73 | $dataString = StringHelper::toString($dataString, ' '); 74 | 75 | // Put everything together 76 | $html = '
'; 77 | $html .= ''; 78 | $html .= ''; 79 | $html .= '
'; 80 | 81 | return Template::raw($html); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/migrations/Install.php: -------------------------------------------------------------------------------- 1 | driver = Craft::$app->getConfig()->getDb()->driver; 33 | if ($this->createTables()) { 34 | $this->addForeignKeys(); 35 | // Refresh the db schema caches 36 | Craft::$app->db->schema->refresh(); 37 | } 38 | 39 | return true; 40 | } 41 | 42 | public function safeDown() 43 | { 44 | $this->driver = Craft::$app->getConfig()->getDb()->driver; 45 | $this->dropForeignKeys(); 46 | $this->dropTables(); 47 | 48 | return true; 49 | } 50 | 51 | // Protected Methods 52 | // ========================================================================= 53 | 54 | protected function createTables() 55 | { 56 | $tablesCreated = false; 57 | 58 | // support_tickets table 59 | $tableSchema = Craft::$app->db->schema->getTableSchema('{{%checkout_charges}}'); 60 | if ($tableSchema === null) { 61 | $tablesCreated = true; 62 | 63 | $this->createTable( 64 | '{{%checkout_charges}}', 65 | [ 66 | 'id' => $this->primaryKey(), 67 | 'dateCreated' => $this->dateTime()->notNull(), 68 | 'dateUpdated' => $this->dateTime()->notNull(), 69 | 'uid' => $this->uid(), 70 | // Custom columns in the table 71 | 'stripeId' => $this->string(), 72 | 'email' => $this->string(), 73 | 'live' => $this->boolean(), 74 | 'chargeStatus' => $this->string(), 75 | 'paid' => $this->boolean(), 76 | 'refunded' => $this->boolean(), 77 | 'amount' => $this->integer(), 78 | 'amountRefunded' => $this->integer(), 79 | 'currency' => $this->string(), 80 | 'description' => $this->string(), 81 | 'failureCode' => $this->string(), 82 | 'failureMessage' => $this->string(), 83 | 'data' => $this->text(), 84 | ] 85 | ); 86 | } 87 | 88 | return $tablesCreated; 89 | } 90 | 91 | protected function addForeignKeys() 92 | { 93 | $this->addForeignKey(null, '{{%checkout_charges}}', ['id'], '{{%elements}}', ['id'], 'CASCADE'); 94 | } 95 | 96 | protected function dropForeignKeys() 97 | { 98 | MigrationHelper::dropAllForeignKeysOnTable('{{%checkout_charges}}', $this); 99 | } 100 | 101 | protected function dropTables() 102 | { 103 | $this->dropTable('{{%checkout_charges}}'); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /docs/creating-charges.md: -------------------------------------------------------------------------------- 1 | # Creating Charges 2 | 3 | Creating a charge requires Stripe Checkout to be inserted into your templates. 4 | 5 | ### Basic example 6 | 7 | ```twig 8 |
9 | {{ csrfInput() }} 10 | {{ redirectInput('checkout/confirmation') }} 11 | 12 | 13 | {{ checkout({ 14 | amount: 500 15 | }) }} 16 |
17 | ``` 18 | 19 | `checkout()` creates a hidden encrypted input that contains all of your Stripe parameters along with the Stripe Checkout button and javascript. 20 | 21 | ### Checkout Options 22 | 23 | All of the standard Stripe Checkout parameters are supported, simply add them to your `checkout()` request. 24 | 25 | | Parameter | Expected value | 26 | | ----------------- | --------------------- | 27 | | `amount` | Amount (in pence) | 28 | | `locale` | [User's preferred language](https://support.stripe.com/questions/what-languages-does-stripe-checkout-support) | 29 | | `name` | Name of your company or website | 30 | | `description` | Description of what is being purchased | 31 | | `image` | URL pointing to a square image. | 32 | | `currency` | 3-letter currency ISO code | 33 | | `email` | Email address of your user | 34 | | `label` | Text to be shown on the blue button | 35 | | `panelLabel` | Label of the payment button in the Checkout form | 36 | | `zipCode` | Whether Checkout should validate the billing postal code (true or false) | 37 | | `billingAddress` | Whether Checkout should collect the user's billing address (true or false) | 38 | | `shippingAddress` | Whether Checkout should collect the user's shipping address (true or false) | 39 | | `allowRememberMe` | Whether to include the option to "Remember Me" for future purchases (true or false) | 40 | | `metadata` | An array of additional input field names to pass as charge metadata | 41 | 42 | ### Metadata 43 | 44 | It's possible to pass additional data as charge metadata, simply provide the input field names in an array using the `metadata` checkout parameter. 45 | 46 | ```twig 47 | 48 | 49 | {{ checkout({ 50 | amount: 500, 51 | metadata: ['orderComments'] 52 | }) }} 53 | ``` 54 | 55 | ### Redirecting 56 | 57 | The user will be redirected to the location defined in your redirect input, with the full [charge model](charge-model.md) available as flash data. 58 | 59 | ```twig 60 | {% set charge = craft.app.session.getFlash('charge') %} 61 | ``` 62 | 63 | Any errors will be made available too: 64 | 65 | ```twig 66 | {% set errors = craft.app.session.getFlash('errors') %} 67 | ``` 68 | 69 | ### Full Example 70 | 71 | ```twig 72 | {% set options = { 73 | amount: 10000, 74 | locale: 'auto', 75 | name: 'Demo', 76 | description: 'This is a demo', 77 | image: 'https://www.yourwebsite.com/images/checkout.png', 78 | currency: 'gbp', 79 | email: 'joe.bloggs@yourwebsite.com', 80 | label: 'Pay with Card', 81 | panelLabel: 'Pay', 82 | zipCode: true, 83 | billingAddress: true, 84 | shippingAddress: true, 85 | allowRememberMe: true, 86 | metadata: ['sizes', 'orderComments'] 87 | } %} 88 | 89 |
90 | {{ csrfInput() }} 91 | {{ redirectInput('checkout/confirmation') }} 92 | 93 | 94 | 95 | 100 | 101 | 102 | 103 | 104 | {{ checkout(options) }} 105 | 106 |
107 | ``` 108 | -------------------------------------------------------------------------------- /src/templates/_charges/partials/source.twig: -------------------------------------------------------------------------------- 1 | {% set source = charge.data.source ?: null %} 2 | 3 | {% if source %} 4 | 5 |
6 |
7 | Card 8 |
9 | 10 |
11 |
12 | ID 13 |
14 |
15 | {{ source.id|default('N/A') }} 16 |
17 |
18 | 19 |
20 |
21 | Name 22 |
23 |
24 | {{ source.name|default('N/A') }} 25 |
26 |
27 | 28 |
29 |
30 | Type 31 |
32 |
33 | {{ source.brand|default('N/A') }} 34 |
35 |
36 | 37 |
38 |
39 | Country 40 |
41 |
42 | {{ source.country|default('N/A') }} 43 |
44 |
45 | 46 |
47 |
48 | Funding 49 |
50 |
51 | {{ (source.funding|title)|default('N/A') }} 52 |
53 |
54 | 55 |
56 |
57 | Number 58 |
59 |
60 | •••• •••• •••• {{ source.last4|default('N/A') }} 61 |
62 |
63 | 64 |
65 |
66 | Expires 67 |
68 |
69 | {{ source.exp_month|default('N/A') }}/{{ source.exp_year|default('N/A') }} 70 |
71 |
72 | 73 |
78 |
79 | CVC Check 80 |
81 |
82 | {{ (source.cvc_check|title)|default('N/A') }} 83 |
84 |
85 | 86 |
87 |
88 | Address 89 |
90 |
91 | {% if source.address_line1 %} 92 | {{ source.address_line1 ? (source.address_line1 ~ '
')|raw }} 93 | {{ source.address_line2 ? (source.address_line2 ~ '
')|raw }} 94 | {{ source.address_city ? (source.address_city ~ '
')|raw }} 95 | {{ source.address_zip ? (source.address_zip ~ '
')|raw }} 96 | {{ source.address_country ? source.address_country }} 97 | {% else %} 98 | N/A 99 | {% endif %} 100 |
101 |
102 | 103 |
108 |
109 | Street Check 110 |
111 |
112 | {{ (source.address_line1_check|title)|default('N/A') }} 113 |
114 |
115 | 116 |
121 |
122 | Zip Check 123 |
124 |
125 | {{ (source.address_zip_check|title)|default('N/A') }} 126 |
127 |
128 | 129 |
130 | {% endif %} 131 | -------------------------------------------------------------------------------- /src/elements/db/ChargeQuery.php: -------------------------------------------------------------------------------- 1 | stripeId = $value; 47 | 48 | return $this; 49 | } 50 | 51 | public function email($value) 52 | { 53 | $this->email = $value; 54 | 55 | return $this; 56 | } 57 | 58 | public function live($value) 59 | { 60 | $this->live = $value; 61 | 62 | return $this; 63 | } 64 | 65 | public function chargeStatus($value) 66 | { 67 | $this->chargeStatus = $value; 68 | 69 | return $this; 70 | } 71 | 72 | public function paid($value) 73 | { 74 | $this->paid = $value; 75 | 76 | return $this; 77 | } 78 | 79 | public function refunded($value) 80 | { 81 | $this->refunded = $value; 82 | 83 | return $this; 84 | } 85 | 86 | public function amount($value) 87 | { 88 | $this->amount = $value; 89 | 90 | return $this; 91 | } 92 | 93 | public function amountRefunded($value) 94 | { 95 | $this->amountRefunded = $value; 96 | 97 | return $this; 98 | } 99 | 100 | public function currency($value) 101 | { 102 | $this->currency = $value; 103 | 104 | return $this; 105 | } 106 | 107 | public function description($value) 108 | { 109 | $this->description = $value; 110 | 111 | return $this; 112 | } 113 | 114 | public function failureCode($value) 115 | { 116 | $this->failureCode = $value; 117 | 118 | return $this; 119 | } 120 | 121 | public function failureMessage($value) 122 | { 123 | $this->failureMessage = $value; 124 | 125 | return $this; 126 | } 127 | 128 | public function data($value) 129 | { 130 | $this->data = $value; 131 | 132 | return $this; 133 | } 134 | 135 | protected function beforePrepare(): bool 136 | { 137 | // join in the products table 138 | $this->joinElementTable('checkout_charges'); 139 | 140 | // select the columns 141 | $this->query->select([ 142 | 'checkout_charges.stripeId', 143 | 'checkout_charges.email', 144 | 'checkout_charges.live', 145 | 'checkout_charges.chargeStatus', 146 | 'checkout_charges.paid', 147 | 'checkout_charges.refunded', 148 | 'checkout_charges.amount', 149 | 'checkout_charges.amountRefunded', 150 | 'checkout_charges.currency', 151 | 'checkout_charges.description', 152 | 'checkout_charges.failureMessage', 153 | 'checkout_charges.failureCode', 154 | 'checkout_charges.data', 155 | ]); 156 | 157 | if ($this->stripeId) { 158 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.stripeId', $this->stripeId)); 159 | } 160 | 161 | if ($this->email) { 162 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.email', $this->email)); 163 | } 164 | 165 | if ($this->live) { 166 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.live', $this->live)); 167 | } 168 | 169 | if ($this->chargeStatus) { 170 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.chargeStatus', $this->chargeStatus)); 171 | } 172 | 173 | if ($this->paid) { 174 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.paid', $this->paid)); 175 | } 176 | 177 | if ($this->refunded) { 178 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.refunded', $this->refunded)); 179 | } 180 | 181 | if ($this->amount) { 182 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.amount', $this->amount)); 183 | } 184 | 185 | if ($this->amountRefunded) { 186 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.amountRefunded', $this->amountRefunded)); 187 | } 188 | 189 | if ($this->currency) { 190 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.currency', $this->currency)); 191 | } 192 | 193 | if ($this->description) { 194 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.description', $this->description)); 195 | } 196 | 197 | if ($this->failureCode) { 198 | $this->subQuery->andWhere(Db::parseParam('checkout_charges.failureCode', $this->failureCode)); 199 | } 200 | 201 | return parent::beforePrepare(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/StripeCheckout.php: -------------------------------------------------------------------------------- 1 | view->registerTwigExtension(new StripeCheckoutTwigExtension()); 62 | 63 | // Register CP routes 64 | Event::on( 65 | UrlManager::class, 66 | UrlManager::EVENT_REGISTER_CP_URL_RULES, 67 | function (RegisterUrlRulesEvent $event) { 68 | $event->rules['stripe-checkout/charges'] = 'stripe-checkout/charges/index'; 69 | $event->rules['stripe-checkout/charges/'] = 'stripe-checkout/charges/view'; 70 | 71 | $event->rules['stripe-checkout/settings/general'] = 'stripe-checkout/settings/index'; 72 | $event->rules['stripe-checkout/settings/credentials'] = 'stripe-checkout/credentials/index'; 73 | $event->rules['stripe-checkout/settings/webhooks'] = 'stripe-checkout/webhooks/index'; 74 | } 75 | ); 76 | 77 | // Register elements 78 | Event::on( 79 | Elements::class, 80 | Elements::EVENT_REGISTER_ELEMENT_TYPES, 81 | function (RegisterComponentTypesEvent $event) { 82 | $event->types[] = ChargeElement::class; 83 | } 84 | ); 85 | 86 | // Register variables 87 | Event::on( 88 | CraftVariable::class, 89 | CraftVariable::EVENT_INIT, 90 | function (Event $event) { 91 | $variable = $event->sender; 92 | $variable->set('stripecheckout', StripeCheckoutVariable::class); 93 | } 94 | ); 95 | 96 | // Redirect to settings after installation 97 | Event::on( 98 | Plugins::class, 99 | Plugins::EVENT_AFTER_INSTALL_PLUGIN, 100 | function (PluginEvent $event) { 101 | if ($event->plugin === $this) { 102 | Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('settings/plugins/stripe-checkout'))->send(); 103 | } 104 | } 105 | ); 106 | 107 | // Register components 108 | $this->setComponents([ 109 | 'chargeService' => \jalendport\stripecheckout\services\ChargeService::class, 110 | 'checkoutService' => \jalendport\stripecheckout\services\CheckoutService::class, 111 | 'settingsService' => \jalendport\stripecheckout\services\SettingsService::class, 112 | 'webhookService' => \jalendport\stripecheckout\services\WebhookService::class, 113 | ]); 114 | 115 | Craft::info( 116 | Craft::t( 117 | 'stripe-checkout', 118 | '{name} plugin loaded', 119 | ['name' => $this->name] 120 | ), 121 | __METHOD__ 122 | ); 123 | } 124 | 125 | public function getCpNavItem() 126 | { 127 | $ret = parent::getCpNavItem(); 128 | 129 | $ret['label'] = $this->getSettings()->pluginNameOverride ?: $this->name; 130 | 131 | $ret['subnav']['tickets'] = [ 132 | 'label' => 'Charges', 133 | 'url' => 'stripe-checkout/charges', 134 | ]; 135 | 136 | if (Craft::$app->getUser()->getIsAdmin()) { 137 | $ret['subnav']['settings'] = [ 138 | 'label' => 'Settings', 139 | 'url' => 'stripe-checkout/settings/general', 140 | ]; 141 | } 142 | 143 | return $ret; 144 | } 145 | 146 | // Protected Methods 147 | // ========================================================================= 148 | 149 | /** 150 | * @inheritdoc 151 | */ 152 | protected function createSettingsModel() 153 | { 154 | return new Settings(); 155 | } 156 | 157 | /** 158 | * @inheritdoc 159 | */ 160 | protected function settingsHtml(): string 161 | { 162 | // Get and pre-validate the settings 163 | $settings = $this->getSettings(); 164 | $settings->validate(); 165 | 166 | // Get the settings that are being defined by the config file 167 | $overrides = Craft::$app->getConfig()->getConfigFromFile(strtolower($this->handle)); 168 | 169 | return Craft::$app->view->renderTemplate( 170 | 'stripe-checkout/settings', 171 | [ 172 | 'settings' => $settings, 173 | 'overrides' => array_keys($overrides) 174 | ] 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/migrations/m180531_090631_v2_upgrade.php: -------------------------------------------------------------------------------- 1 | driver = Craft::$app->getConfig()->getDb()->driver; 28 | if ($this->createTables()) { 29 | $this->addForeignKeys(); 30 | // Refresh the db schema caches 31 | Craft::$app->db->schema->refresh(); 32 | $this->transferData(); 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public function safeDown() 39 | { 40 | echo "m180531_090631_v2_upgrade cannot be reverted\n"; 41 | 42 | return false; 43 | } 44 | 45 | // Protected Methods 46 | // ========================================================================= 47 | 48 | protected function createTables() 49 | { 50 | $tablesCreated = false; 51 | 52 | // support_tickets table 53 | $tableSchema = Craft::$app->db->schema->getTableSchema('{{%checkout_charges}}'); 54 | if ($tableSchema === null) { 55 | $tablesCreated = true; 56 | 57 | $this->createTable( 58 | '{{%checkout_charges}}', 59 | [ 60 | 'id' => $this->primaryKey(), 61 | 'dateCreated' => $this->dateTime()->notNull(), 62 | 'dateUpdated' => $this->dateTime()->notNull(), 63 | 'uid' => $this->uid(), 64 | // Custom columns in the table 65 | 'stripeId' => $this->string(), 66 | 'email' => $this->string(), 67 | 'live' => $this->boolean(), 68 | 'chargeStatus' => $this->string(), 69 | 'paid' => $this->boolean(), 70 | 'refunded' => $this->boolean(), 71 | 'amount' => $this->integer(), 72 | 'amountRefunded' => $this->integer(), 73 | 'currency' => $this->string(), 74 | 'description' => $this->string(), 75 | 'failureCode' => $this->string(), 76 | 'failureMessage' => $this->string(), 77 | 'data' => $this->text(), 78 | ] 79 | ); 80 | } 81 | 82 | return $tablesCreated; 83 | } 84 | 85 | protected function addForeignKeys() 86 | { 87 | $this->addForeignKey(null, '{{%checkout_charges}}', ['id'], '{{%elements}}', ['id'], 'CASCADE'); 88 | } 89 | 90 | protected function transferData() 91 | { 92 | // Fetch existing charges 93 | echo " > Starting data transfer\n"; 94 | echo " > Fetching existing charge records\n"; 95 | $records = $this->getCharges(); 96 | 97 | if ($records) { 98 | // Insert found charges into new table 99 | echo " > Starting transfer of charge records\n"; 100 | $inserted = $this->insertCharges($records); 101 | 102 | if (!$inserted) { 103 | echo " > Unable to transfer charge records\n"; 104 | } else { 105 | echo " > Charge records transferred\n"; 106 | } 107 | } 108 | 109 | echo " > Data transfer finished\n"; 110 | } 111 | 112 | protected function getCharges() 113 | { 114 | return \Craft::$app->db->createCommand('SELECT * FROM {{%stripecheckout_charges}}')->queryAll(); 115 | } 116 | 117 | protected function insertCharges($records = null) 118 | { 119 | if ($records) { 120 | foreach ($records as $record) { 121 | // Ignore if charge already exists for some reason 122 | $exists = StripeCheckout::getInstance()->chargeService->getChargeByStripeId($record['stripeId']); 123 | 124 | if (!$exists) { 125 | $charge = new Charge(); 126 | 127 | $charge->stripeId = $record['stripeId']; 128 | $charge->dateCreated = $record['dateCreated']; 129 | 130 | $res = Craft::$app->getElements()->saveElement($charge, true, false); 131 | 132 | if (!$res) { 133 | echo " > {$charge->stripeId} not inserted\n"; 134 | } else { 135 | echo " > {$charge->stripeId} inserted\n"; 136 | 137 | // Reconcile charge 138 | $reconciled = $this->reconcileCharge($charge->stripeId); 139 | 140 | if (!$reconciled) { 141 | echo " > {$charge->stripeId} not reconciled\n"; 142 | } else { 143 | echo " > {$charge->stripeId} reconciled\n"; 144 | } 145 | } 146 | } 147 | } 148 | 149 | return true; 150 | } 151 | 152 | return false; 153 | } 154 | 155 | protected function reconcileCharge($id = null) 156 | { 157 | $secretKey = StripeCheckout::getInstance()->settingsService->getSecretKey(); 158 | 159 | \Stripe\Stripe::setApiKey($secretKey); 160 | 161 | $charge = \Stripe\Charge::retrieve($id); 162 | 163 | if ($charge) { 164 | $inserted = StripeCheckout::getInstance()->chargeService->insertCharge($charge); 165 | 166 | if ($inserted) { 167 | return true; 168 | } 169 | } 170 | 171 | return false; 172 | } 173 | 174 | protected function dropTables() 175 | { 176 | $this->dropTable('{{%stripecheckout_charges}}'); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/services/ChargeService.php: -------------------------------------------------------------------------------- 1 | $request, 45 | ]); 46 | $self = new static; 47 | $self->trigger(self::EVENT_BEFORE_CHARGE, $event); 48 | 49 | $response = $this->createStripeCharge($request, $additional); 50 | 51 | if ((!isset($response['charge'])) or (isset($response['message']))) { 52 | return $response; 53 | } 54 | 55 | $charge = $this->insertCharge($response['charge']); 56 | 57 | if (!$charge) { 58 | throw new Exception('Couldn’t create the charge element.'); 59 | } 60 | 61 | // Trigger afterCharge event 62 | $event = new ChargeEvent([ 63 | 'request' => $request, 64 | 'charge' => $charge, 65 | ]); 66 | $self = new static; 67 | $self->trigger(self::EVENT_AFTER_CHARGE, $event); 68 | 69 | return $charge; 70 | } 71 | 72 | public function createStripeCharge($request, $additional = []) 73 | { 74 | $secretKey = StripeCheckout::getInstance()->settingsService->getSecretKey(); 75 | 76 | \Stripe\Stripe::setApiKey($secretKey); 77 | 78 | $response = []; 79 | 80 | try { 81 | $response['charge'] = \Stripe\Charge::create([ 82 | 'source' => $request['token'], 83 | 'receipt_email' => $request['email'], 84 | 'amount' => $request['options']['amount'], 85 | 'currency' => $request['options']['currency'], 86 | 'description' => $request['options']['description'], 87 | 'shipping' => $request['shipping'], 88 | 'metadata' => $request['metadata'], 89 | ], $additional); 90 | } catch (\Stripe\Error\Base $e) { 91 | Craft::$app->getErrorHandler()->logException($e); 92 | 93 | $body = $e->getJsonBody(); 94 | $response = $body['error']; 95 | 96 | $response['message'] = $e->getMessage(); 97 | } catch (Exception $e) { 98 | Craft::$app->getErrorHandler()->logException($e); 99 | $response['message'] = $e->getMessage(); 100 | } 101 | 102 | return $response; 103 | } 104 | 105 | public function insertCharge($stripeCharge = null) 106 | { 107 | if ($stripeCharge) { 108 | // Update charge if it already exists 109 | $exists = $this->getChargeByStripeId($stripeCharge->id); 110 | 111 | if ($exists) { 112 | $res = $this->updateCharge($stripeCharge); 113 | 114 | if ($res) { 115 | return true; 116 | } 117 | } else { 118 | $charge = new Charge(); 119 | 120 | $charge->stripeId = $stripeCharge->id ?? null; 121 | $charge->email = $stripeCharge->receipt_email ?? null; 122 | $charge->live = $stripeCharge->livemode ?? null; 123 | $charge->chargeStatus = $stripeCharge->status ?? null; 124 | $charge->paid = $stripeCharge->paid ?? null; 125 | $charge->refunded = $stripeCharge->refunded ?? null; 126 | $charge->amount = $stripeCharge->amount ?? null; 127 | $charge->amountRefunded = $stripeCharge->amount_refunded ?? null; 128 | $charge->currency = $stripeCharge->currency ?? null; 129 | $charge->description = $stripeCharge->description ?? null; 130 | $charge->failureCode = $stripeCharge->failure_code ?? null; 131 | $charge->failureMessage = $stripeCharge->failure_message ?? null; 132 | $charge->data = $stripeCharge ? Json::encode($stripeCharge) : null; 133 | 134 | $res = Craft::$app->getElements()->saveElement($charge, true, false); 135 | 136 | if ($res) { 137 | return $charge; 138 | } 139 | } 140 | } 141 | 142 | return null; 143 | } 144 | 145 | public function updateCharge($stripeCharge = null) 146 | { 147 | if ($stripeCharge) { 148 | $charge = $this->getChargeByStripeId($stripeCharge->id); 149 | 150 | if (!$charge) { 151 | return null; 152 | } 153 | 154 | $charge->email = $stripeCharge->receipt_email ?? null; 155 | $charge->live = $stripeCharge->livemode ?? null; 156 | $charge->chargeStatus = $stripeCharge->status ?? null; 157 | $charge->paid = $stripeCharge->paid ?? null; 158 | $charge->refunded = $stripeCharge->refunded ?? null; 159 | $charge->amount = $stripeCharge->amount ?? null; 160 | $charge->amountRefunded = $stripeCharge->amount_refunded ?? null; 161 | $charge->currency = $stripeCharge->currency ?? null; 162 | $charge->description = $stripeCharge->description ?? null; 163 | $charge->failureCode = $stripeCharge->failure_code ?? null; 164 | $charge->failureMessage = $stripeCharge->failure_message ?? null; 165 | $charge->data = $stripeCharge ? Json::encode($stripeCharge) : null; 166 | 167 | $res = Craft::$app->getElements()->saveElement($charge, true, false); 168 | 169 | if ($res) { 170 | return $charge; 171 | } 172 | } 173 | 174 | return null; 175 | } 176 | 177 | public function getChargeById($id = null) 178 | { 179 | if ($id) { 180 | $query = new ChargeQuery(Charge::class); 181 | $query->id = $id; 182 | 183 | return $query->one(); 184 | } 185 | 186 | return null; 187 | } 188 | 189 | public function getChargeByStripeId($id = null) 190 | { 191 | if ($id) { 192 | $query = new ChargeQuery(Charge::class); 193 | $query->stripeId = $id; 194 | 195 | return $query->one(); 196 | } 197 | 198 | return null; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/elements/Charge.php: -------------------------------------------------------------------------------- 1 | 'Succeeded', 89 | 'orange' => 'Pending', 90 | 'red' => 'Failed', 91 | ]; 92 | } 93 | 94 | public static function find(): ElementQueryInterface 95 | { 96 | return new ChargeQuery(static::class); 97 | } 98 | 99 | protected static function defineSources(string $context = null): array 100 | { 101 | $sources = [ 102 | '*' => [ 103 | 'key' => '*', 104 | 'label' => 'All Charges', 105 | 'criteria' => [], 106 | 'defaultSort' => ['dateCreated', 'desc'], 107 | ], 108 | ]; 109 | 110 | $sources[] = ['heading' => 'Account Mode']; 111 | 112 | $sources[] = [ 113 | 'key' => 'live', 114 | 'status' => 'green', 115 | 'label' => 'Live', 116 | 'criteria' => [ 117 | 'live' => true, 118 | ], 119 | 'defaultSort' => ['dateCreated', 'desc'], 120 | ]; 121 | 122 | $sources[] = [ 123 | 'key' => 'test', 124 | 'status' => 'orange', 125 | 'label' => 'Test', 126 | 'criteria' => [ 127 | 'live' => 'not 1', 128 | ], 129 | 'defaultSort' => ['dateCreated', 'desc'], 130 | ]; 131 | 132 | $sources[] = ['heading' => 'Payment']; 133 | 134 | $sources[] = [ 135 | 'key' => 'paid', 136 | 'status' => 'green', 137 | 'label' => 'Paid', 138 | 'criteria' => [ 139 | 'paid' => true, 140 | ], 141 | 'defaultSort' => ['dateCreated', 'desc'], 142 | ]; 143 | 144 | $sources[] = [ 145 | 'key' => 'paymentPending', 146 | 'status' => 'orange', 147 | 'label' => 'Pending', 148 | 'criteria' => [ 149 | 'paid' => 'not 1', 150 | ], 151 | 'defaultSort' => ['dateCreated', 'desc'], 152 | ]; 153 | 154 | $sources[] = ['heading' => 'Refund']; 155 | 156 | $sources[] = [ 157 | 'key' => 'fullRefund', 158 | 'status' => 'green', 159 | 'label' => 'Full', 160 | 'criteria' => [ 161 | 'refunded' => true, 162 | ], 163 | 'defaultSort' => ['dateCreated', 'desc'], 164 | ]; 165 | 166 | $sources[] = [ 167 | 'key' => 'partialRefund', 168 | 'status' => 'orange', 169 | 'label' => 'Partial', 170 | 'criteria' => [ 171 | 'refunded' => 'not 1', 172 | 'amountRefunded' => '> 0', 173 | ], 174 | 'defaultSort' => ['dateCreated', 'desc'], 175 | ]; 176 | 177 | $sources[] = [ 178 | 'key' => 'noRefund', 179 | 'status' => 'light', 180 | 'label' => 'None', 181 | 'criteria' => [ 182 | 'refunded' => 'not 1', 183 | 'amountRefunded' => '< 1', 184 | ], 185 | 'defaultSort' => ['dateCreated', 'desc'], 186 | ]; 187 | 188 | return $sources; 189 | } 190 | 191 | protected static function defineSearchableAttributes(): array 192 | { 193 | return ['id', 'amount', 'stripeId', 'email', 'description']; 194 | } 195 | 196 | protected static function defineActions(string $source = null): array 197 | { 198 | $actions[] = Craft::$app->getElements()->createAction([ 199 | 'type' => Delete::class, 200 | 'confirmationMessage' => Craft::t('stripe-checkout', 'Are you sure you want to delete the selected charges?'), 201 | 'successMessage' => Craft::t('stripe-checkout', 'Charges deleted.'), 202 | ]); 203 | 204 | return $actions; 205 | } 206 | 207 | protected static function defineTableAttributes(): array 208 | { 209 | $attributes = [ 210 | 'amount' => ['label' => Craft::t('stripe-checkout', 'Amount')], 211 | 'email' => ['label' => Craft::t('stripe-checkout', 'Email')], 212 | 'dateCreated' => ['label' => Craft::t('stripe-checkout', 'Date Created')], 213 | 'dateUpdated' => ['label' => Craft::t('stripe-checkout', 'Date Updated')], 214 | 'paid' => ['label' => Craft::t('stripe-checkout', 'Paid'), 'icon' => 'tag'], 215 | 'refunded' => ['label' => Craft::t('stripe-checkout', 'Refunded'), 'icon' => 'refresh'], 216 | 'live' => ['label' => Craft::t('stripe-checkout', 'Mode'), 'icon' => 'tool'], 217 | 'stripe' => ['label' => Craft::t('stripe-checkout', 'Stripe'), 'icon' => 'share'], 218 | ]; 219 | 220 | return $attributes; 221 | } 222 | 223 | protected static function defineDefaultTableAttributes(string $source): array 224 | { 225 | $attributes = ['amount', 'email', 'dateCreated', 'dateUpdated', 'paid', 'refunded', 'live', 'stripe']; 226 | 227 | return $attributes; 228 | } 229 | 230 | // Public Methods 231 | // ========================================================================= 232 | 233 | public function init() 234 | { 235 | parent::init(); 236 | 237 | $this->data = Json::decode($this->data); 238 | } 239 | 240 | public function __toString() 241 | { 242 | return $this->getFormattedAmount(); 243 | } 244 | 245 | public function getTableAttributeHtml(string $attribute): string 246 | { 247 | switch ($attribute) { 248 | case 'amount': 249 | return $this->getFormattedAmount(); 250 | 251 | case 'email': 252 | return $this->email; 253 | 254 | case 'paid': 255 | return $this->getPaidLabelHtml(); 256 | 257 | case 'refunded': 258 | return $this->getRefundedLabelHtml(); 259 | 260 | case 'live': 261 | return $this->getLiveLabelHtml(); 262 | 263 | case 'stripe': 264 | return $this->getStripeLabelHtml(); 265 | 266 | default: 267 | { 268 | return parent::tableAttributeHtml($attribute); 269 | } 270 | } 271 | } 272 | 273 | public function getCpEditUrl() 274 | { 275 | return UrlHelper::cpUrl('stripe-checkout/charges/'.$this->id); 276 | } 277 | 278 | public function getStripeUrl() 279 | { 280 | return 'https://dashboard.stripe.com/payments/'.$this->stripeId; 281 | } 282 | 283 | public function getStatus() 284 | { 285 | switch ($this->chargeStatus) { 286 | case 'succeeded': 287 | return self::CHARGE_SUCCEEDED; 288 | 289 | case 'pending': 290 | return self::CHARGE_PENDING; 291 | 292 | case 'failed': 293 | return self::CHARGE_FAILED; 294 | 295 | default: 296 | return self::CHARGE_PENDING; 297 | } 298 | } 299 | 300 | public function getFormattedAmount() 301 | { 302 | $amount = ($this->amount / 100); 303 | $amount = number_format($amount, 2); 304 | $currency = strtoupper($this->currency); 305 | 306 | return $amount . ' ' . $currency; 307 | } 308 | 309 | public function getPaidLabelHtml() 310 | { 311 | if ($this->paid) { 312 | $colour = 'green'; 313 | $title = 'Paid'; 314 | } else { 315 | $colour = 'orange'; 316 | $title = 'Pending payment'; 317 | } 318 | 319 | $html = ''; 320 | 321 | return $html; 322 | } 323 | 324 | public function getRefundedLabelHtml() 325 | { 326 | if ($this->refunded) { 327 | $colour = 'green'; 328 | $title = 'Refunded'; 329 | } else if ($this->amountRefunded > 0) { 330 | $colour = 'orange'; 331 | $title = 'Partially refunded'; 332 | } else { 333 | $colour = 'light'; 334 | $title = 'Not refunded'; 335 | } 336 | 337 | $html = ''; 338 | 339 | return $html; 340 | } 341 | 342 | public function getLiveLabelHtml() 343 | { 344 | if ($this->live) { 345 | $colour = 'green'; 346 | $title = 'Live'; 347 | } else { 348 | $colour = 'orange'; 349 | $title = 'Test'; 350 | } 351 | 352 | $html = ''; 353 | 354 | return $html; 355 | } 356 | 357 | public function getStripeLabelHtml() 358 | { 359 | return ''; 360 | } 361 | 362 | // Indexes, etc. 363 | // ------------------------------------------------------------------------- 364 | 365 | protected static function defineSortOptions(): array 366 | { 367 | $sortOptions = [ 368 | 'checkout_charges.dateCreated' => 'Date Created', 369 | 'checkout_charges.dateUpdated' => 'Date Updated', 370 | ]; 371 | 372 | return $sortOptions; 373 | } 374 | 375 | // Events 376 | // ------------------------------------------------------------------------- 377 | public function afterSave(bool $isNew) 378 | { 379 | if ($isNew) { 380 | Craft::$app->db->createCommand() 381 | ->insert('{{%checkout_charges}}', [ 382 | 'id' => $this->id, 383 | 'stripeId' => $this->stripeId, 384 | 'email' => $this->email, 385 | 'live' => $this->live, 386 | 'chargeStatus' => $this->chargeStatus, 387 | 'paid' => $this->paid, 388 | 'refunded' => $this->refunded, 389 | 'amount' => $this->amount, 390 | 'amountRefunded' => $this->amountRefunded, 391 | 'currency' => $this->currency, 392 | 'description' => $this->description, 393 | 'failureCode' => $this->failureCode, 394 | 'failureMessage' => $this->failureMessage, 395 | 'data' => $this->data, 396 | ]) 397 | ->execute(); 398 | } else { 399 | Craft::$app->db->createCommand() 400 | ->update('{{%checkout_charges}}', [ 401 | 'stripeId' => $this->stripeId, 402 | 'email' => $this->email, 403 | 'live' => $this->live, 404 | 'chargeStatus' => $this->chargeStatus, 405 | 'paid' => $this->paid, 406 | 'refunded' => $this->refunded, 407 | 'amount' => $this->amount, 408 | 'amountRefunded' => $this->amountRefunded, 409 | 'currency' => $this->currency, 410 | 'description' => $this->description, 411 | 'failureCode' => $this->failureCode, 412 | 'failureMessage' => $this->failureMessage, 413 | 'data' => $this->data, 414 | ], ['id' => $this->id]) 415 | ->execute(); 416 | } 417 | parent::afterSave($isNew); 418 | } 419 | } 420 | --------------------------------------------------------------------------------