mirror of
https://github.com/vr-payment/shopware-6.git
synced 2026-06-05 11:36:37 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57246e23ce | |||
| 68592a9409 | |||
| 1393f4ff7c | |||
| d714cf2f84 | |||
| 2f9a30ebd3 | |||
| 2f4d38b4b2 | |||
| 2cba2a8f3e | |||
| 3f291ef7ea | |||
| 350057e327 |
+35
-1
@@ -1,3 +1,37 @@
|
||||
# 6.2.0
|
||||
- Renamed database table to avoid a naming conflict with legacy plugins
|
||||
- Fixed issue with refunds failing for payments using Invoice
|
||||
|
||||
# 6.1.17
|
||||
- Sales channels now support different spaces
|
||||
- Upgraded SDK to include latest fallback CA Bundle
|
||||
- Fixed error screen when returning from portal on failed payment
|
||||
|
||||
# 6.1.16
|
||||
- Fixed issue with pending orders remaining open
|
||||
|
||||
# 6.1.15
|
||||
- Fixed issue with shipping costs not being processed correctly
|
||||
|
||||
# 6.1.14
|
||||
- Disable Recreate Cart for Headless Storefront Order
|
||||
- Added the correct Exception Type to the finalize method
|
||||
|
||||
# 6.1.13
|
||||
- Updated English documentation
|
||||
- Added French, German and Italian documentation
|
||||
|
||||
# 6.1.12
|
||||
- Compatibility with 6.6.10.x
|
||||
- Prevent duplicate transactions being created when the first times out
|
||||
- Fix for error when changing space credentials
|
||||
- Payment status now shows refunded/partially refunded
|
||||
- Order delivery status now shows 'open' instead of 'hold'
|
||||
|
||||
# 6.1.11
|
||||
- Implement payment page integration.
|
||||
- Fixed bug with duplicate payment methods appearing
|
||||
|
||||
# 6.1.10
|
||||
- Multiple/bulk refund for line item.
|
||||
- Partial line item refund.
|
||||
@@ -137,7 +171,7 @@
|
||||
- Added settings to control update of webhooks and payment methods
|
||||
|
||||
# 4.0.15
|
||||
- Adjust VRPay/SW6 documentation - how to do refunds
|
||||
- Adjust VR Payment/SW6 documentation - how to do refunds
|
||||
|
||||
# 4.0.14
|
||||
- Support for Shopware 6.4.6
|
||||
|
||||
+35
-1
@@ -1,3 +1,37 @@
|
||||
# 6.2.0
|
||||
- Datenbanktabelle umbenannt, um Namenskonflikte mit älteren Plugins zu vermeiden.
|
||||
- Problem mit fehlgeschlagenen Rückerstattungen bei Zahlungen mit Rechnungen behoben.
|
||||
|
||||
# 6.1.17
|
||||
- Vertriebskanäle unterstützen jetzt verschiedene Bereiche
|
||||
- SDK aktualisiert und enthält nun das neueste CA-Fallback-Bundle
|
||||
- Fehlerbildschirm beim Zurückkehren vom Portal nach fehlgeschlagener Zahlung behoben
|
||||
|
||||
# 6.1.16
|
||||
- Problem behoben, bei dem die Versandkosten nicht korrekt verarbeitet wurden
|
||||
|
||||
# 6.1.15
|
||||
- Problem behoben, bei dem ausstehende Bestellungen offen blieben
|
||||
|
||||
# 6.1.14
|
||||
– Warenkorb neu erstellen für Headless Storefront Order deaktivieren
|
||||
– Der korrekte Ausnahmetyp wurde zur Finalisierungsmethode hinzugefügt
|
||||
|
||||
# 6.1.13
|
||||
– Englische Dokumentation aktualisiert
|
||||
– Französische, deutsche und italienische Dokumentation hinzugefügt
|
||||
|
||||
# 6.1.12
|
||||
- Kompatibilität mit 6.6.10.x
|
||||
- Verhindern Sie, dass beim ersten Timeout doppelte Transaktionen erstellt werden
|
||||
- Fehler beim Ändern der Space-Anmeldeinformationen behoben
|
||||
- Der Zahlungsstatus zeigt jetzt „erstattet/teilweise erstattet“ an
|
||||
- Der Lieferstatus der Bestellung wird jetzt „Offen“ statt „Halten“ angezeigt.
|
||||
|
||||
# 6.1.11
|
||||
- Integration der Zahlungsseite implementieren.
|
||||
- Fehler mit doppelten angezeigten Zahlungsmethoden behoben
|
||||
|
||||
# 6.1.10
|
||||
- Mehrfache/gesammelte Rückerstattung für Einzelposten.
|
||||
- Teilweise Rückerstattung von Einzelposten.
|
||||
@@ -135,7 +169,7 @@
|
||||
- Einstellungen zur Steuerung der Aktualisierung von Webhooks und Zahlungsmethoden hinzugefügt
|
||||
|
||||
# 4.0.15
|
||||
- VRPay/SW6-Dokumentation anpassen – wie man Rückerstattungen durchführt
|
||||
- VR Payment/SW6-Dokumentation anpassen – wie man Rückerstattungen durchführt
|
||||
|
||||
# 4.0.14
|
||||
- Unterstützung für Shopware 6.4.6
|
||||
|
||||
+1
-1
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2025 VR Payment GmbH
|
||||
Copyright 2026 VR Payment GmbH
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,17 +1,83 @@
|
||||
|
||||
|
||||
VRPayment Payment for Shopware 6
|
||||
VR Payment Integration for Shopware 6
|
||||
=============================
|
||||
|
||||
The VRPayment Payment plugin wraps around the VRPayment API. This library facilitates your interaction with various services such as transactions.
|
||||
Please note that this plugin is for versions 6.5 and 6.6. For the 6.4 plugin please visit [our Shopware 6.4 plugin](https://github.com/vr-payment/shopware-6-4).
|
||||
## **Overview**
|
||||
The VR Payment Payment Plugin integrates modern payment processing into Shopware 6, offering features like iFrame-based payments, refunds, captures, and PCI compliance. It supports seamless integration with the [VR Payment Portal](https://gateway.vr-payment.de/) for managing transactions and payment methods.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Shopware 6.5.x or Shopware 6.6.x. See table below.
|
||||
- PHP minimum version supported by the each shop version.
|
||||
- **Shopware Version:** 6.5.x or 6.6.x (see [compatibility table](#compatibility)).
|
||||
- **PHP:** Minimum version as required by your Shopware installation (e.g., 7.4+).
|
||||
- **VR Payment Account:** Obtain `Space ID`, `User ID`, and `API Key` from the [VR Payment Dashboard](https://gateway.vr-payment.de/).
|
||||
|
||||
## Supported versions
|
||||
## Documentation
|
||||
- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/6.2.0/docs/en/documentation.html)
|
||||
- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/6.2.0/docs/de/documentation.html)
|
||||
- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/6.2.0/docs/fr/documentation.html)
|
||||
- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/6.2.0/docs/it/documentation.html)
|
||||
|
||||
## Installation
|
||||
|
||||
### **Via Composer (Recommended)**
|
||||
1. Navigate to your Shopware root directory.
|
||||
2. Run:
|
||||
|
||||
```bash
|
||||
Copy
|
||||
composer require vrpayment/shopware-6
|
||||
php bin/console plugin:refresh
|
||||
php bin/console plugin:install --activate --clearCache VRPaymentPayment
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
1. Download the latest [Release](../../releases)
|
||||
2. Extract the ZIP to custom/plugins/VRPaymentPayment.
|
||||
|
||||
```bash
|
||||
Copy
|
||||
bin/console plugin:refresh
|
||||
bin/console plugin:install --activate --clearCache VRPaymentPayment
|
||||
```
|
||||
|
||||
## Update
|
||||
|
||||
### Via Administration
|
||||
1. Go to Shopware Admin > Extensions > My extensions.
|
||||
2. Find VRPaymentPayment.
|
||||
3. Click Update.
|
||||
|
||||
### Via CLI
|
||||
1. Deploy the new plugin files (replace the folder in custom/plugins/VRPaymentPayment or upload/install a new ZIP).
|
||||
2. Run:
|
||||
```bash
|
||||
bin/console plugin:refresh
|
||||
bin/console plugin:update --clearCache VRPaymentPayment
|
||||
bin/console cache:clear
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### API Credentials
|
||||
|
||||
1. Navigate to Shopware Admin > Settings > VRPayment Payment.
|
||||
2. Enter your Space ID, User ID, and API Key (obtained from the [VR Payment Portal](https://gateway.vr-payment.de/)).
|
||||
|
||||
### Payment Methods
|
||||
|
||||
Configure supported methods (e.g., credit cards, Apple Pay) via the [VR Payment Portal](https://gateway.vr-payment.de/).
|
||||
|
||||
### Key Features
|
||||
**iFrame Integration**: Embed payment forms directly into your checkout.
|
||||
|
||||
**Refunds & Captures**: Trigger full/partial refunds and captures from Shopware or the [VR Payment Portal](https://gateway.vr-payment.de/).
|
||||
|
||||
**Multi-Store Support**: Manage configurations across multiple stores.
|
||||
|
||||
**Automatic Updates**: Payment methods sync dynamically via the VRPayment API.
|
||||
|
||||
## Compatibiliity
|
||||
|
||||
___________________________________________________________________________________
|
||||
| Shopware 6 version | Plugin major version | Supported until |
|
||||
@@ -20,60 +86,35 @@ ________________________________________________________________________________
|
||||
| Shopware 6.5.x | 5.x | October 2024 |
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
## Installation
|
||||
|
||||
You can use **Composer** or **install manually**
|
||||
|
||||
### Composer
|
||||
|
||||
The preferred method is via [composer](https://getcomposer.org). Follow the
|
||||
[installation instructions](https://getcomposer.org/doc/00-intro.md) if you do not already have
|
||||
composer installed.
|
||||
|
||||
Once composer is installed, execute the following command from the shop root to install the plugin:
|
||||
### Troubleshooting
|
||||
**Logs**: Check payment logs with:
|
||||
|
||||
```bash
|
||||
composer require vrpayment/shopware-6
|
||||
php bin/console plugin:refresh
|
||||
php bin/console plugin:install --activate --clearCache VRPaymentPayment
|
||||
```
|
||||
|
||||
#### Update via composer
|
||||
```bash
|
||||
composer update vrpayment/shopware-6
|
||||
php bin/console plugin:refresh
|
||||
php bin/console plugin:install --activate --clearCache VRPaymentPayment
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
Alternatively you can download the package in its entirety. The [Releases](../../releases) page lists all stable versions.
|
||||
|
||||
Uncompress the zip file you download, and include the autoloader in your project:
|
||||
|
||||
```bash
|
||||
# unzip to ShopwareInstallDir/custom/plugins/VRPaymentPayment
|
||||
# For versions 6.1.10 and older, the SDK is installed automatically when installing the plugin in the shop, so you don't need to
|
||||
# run the following command.
|
||||
composer require vrpayment/sdk 4.6.0
|
||||
php bin/console plugin:refresh
|
||||
php bin/console plugin:install --activate --clearCache VRPaymentPayment
|
||||
```
|
||||
|
||||
## Usage
|
||||
The library needs to be configured with your account's space id, user id, and application key which are available in your VRPayment
|
||||
account dashboard.
|
||||
|
||||
### Logs and debugging
|
||||
To view the logs please run the command below:
|
||||
```bash
|
||||
cd shopware/install/dir
|
||||
Copy
|
||||
tail -f var/log/vrpayment_payment*.log
|
||||
```
|
||||
### Common Issues:
|
||||
|
||||
## Documentation
|
||||
Ensure composer update vrpayment/shopware-6 is run after updates.
|
||||
|
||||
[Documentation](https://gateway.vr-payment.de/doc/shopware-6/6.1.10/docs/en/documentation.html)
|
||||
Verify API credentials match your VRPayment account.
|
||||
|
||||
## FAQs
|
||||
**Q: Does this plugin support one-click payments?**
|
||||
A: Yes, via tokenization in the VRPayment Portal.
|
||||
|
||||
**Q: How do I handle PCI compliance?**
|
||||
A: The plugin uses iFrame integration, reducing PCI requirements to SAQ-A.
|
||||
|
||||
### Changelog
|
||||
For version-specific updates, see the [GitHub Releases](https://github.com/vr-payment/shopware-6/releases).
|
||||
|
||||
### Contributing
|
||||
Report issues via GitHub Issues.
|
||||
|
||||
Follow the Shopware Plugin Base Guide for development.
|
||||
|
||||
This template combines technical clarity with user-friendly guidance. For advanced customization (e.g., overriding templates or payment handlers), refer to the Shopware Documentation.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+62
-62
@@ -1,63 +1,63 @@
|
||||
{
|
||||
"authors": [
|
||||
{
|
||||
"homepage": "https://www.vr-payment.de/",
|
||||
"name": "VRPay"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VRPaymentPayment\\": "src/"
|
||||
}
|
||||
},
|
||||
"description": "VRPayment integration for Shopware 6",
|
||||
"extra": {
|
||||
"copyright": "(c) by VRPay",
|
||||
"description": {
|
||||
"de-DE": "VRPayment integration f\u00fcr Shopware 6",
|
||||
"en-GB": "VRPayment integration for Shopware 6",
|
||||
"fr-FR": "Int\u00e9gration de VRPayment pour Shopware 6",
|
||||
"it-IT": "Integrazione VRPayment per Shopware"
|
||||
},
|
||||
"label": {
|
||||
"de-DE": "VRPayment Produkte f\u00fcr Shopware 6",
|
||||
"en-GB": "VRPayment Products for Shopware 6",
|
||||
"fr-FR": "VRPayment Produits for Shopware 6",
|
||||
"it-IT": "VRPayment Prodotti per Shopware 6"
|
||||
},
|
||||
"manufacturerLink": {
|
||||
"de-DE": "https://www.vr-payment.de/",
|
||||
"en-GB": "https://www.vr-payment.de/",
|
||||
"fr-FR": "https://www.vr-payment.de/",
|
||||
"it-IT": "https://www.vr-payment.de/"
|
||||
},
|
||||
"supportLink": {
|
||||
"de-DE": "https://www.vr-payment.de/hotline",
|
||||
"en-GB": "https://www.vr-payment.de/hotline",
|
||||
"fr-FR": "https://www.vr-payment.de/hotline",
|
||||
"it-IT": "https://www.vr-payment.de/hotline"
|
||||
},
|
||||
"shopware-plugin-class": "VRPaymentPayment\\VRPaymentPayment"
|
||||
},
|
||||
"homepage": "https://www.vr-payment.de//",
|
||||
"keywords": [
|
||||
"VRPay",
|
||||
"payment",
|
||||
"php",
|
||||
"shopware"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"name": "vrpayment/shopware-6",
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=8.2",
|
||||
"shopware/core": "6.6.*",
|
||||
"shopware/administration": "~6.6.0",
|
||||
"shopware/storefront": "6.6.*",
|
||||
"vrpayment/sdk": "4.6.0"
|
||||
},
|
||||
"type": "shopware-platform-plugin",
|
||||
"version": "6.1.10"
|
||||
}
|
||||
"authors": [
|
||||
{
|
||||
"homepage": "https://www.vr-payment.de/",
|
||||
"name": "VR Payment"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"VRPaymentPayment\\": "src/"
|
||||
}
|
||||
},
|
||||
"description": "VRPayment integration for Shopware 6",
|
||||
"extra": {
|
||||
"copyright": "(c) by VR Payment",
|
||||
"description": {
|
||||
"de-DE": "VRPayment integration für Shopware 6",
|
||||
"en-GB": "VRPayment integration for Shopware 6",
|
||||
"fr-FR": "Intégration de VRPayment pour Shopware 6",
|
||||
"it-IT": "Integrazione VRPayment per Shopware"
|
||||
},
|
||||
"label": {
|
||||
"de-DE": "VRPayment Produkte für Shopware 6",
|
||||
"en-GB": "VRPayment Products for Shopware 6",
|
||||
"fr-FR": "VRPayment Produits for Shopware 6",
|
||||
"it-IT": "VRPayment Prodotti per Shopware 6"
|
||||
},
|
||||
"manufacturerLink": {
|
||||
"de-DE": "https://www.vr-payment.de/",
|
||||
"en-GB": "https://www.vr-payment.de/",
|
||||
"fr-FR": "https://www.vr-payment.de/",
|
||||
"it-IT": "https://www.vr-payment.de/"
|
||||
},
|
||||
"supportLink": {
|
||||
"de-DE": "https://www.vr-payment.de/hotline",
|
||||
"en-GB": "https://www.vr-payment.de/hotline",
|
||||
"fr-FR": "https://www.vr-payment.de/hotline",
|
||||
"it-IT": "https://www.vr-payment.de/hotline"
|
||||
},
|
||||
"shopware-plugin-class": "VRPaymentPayment\\VRPaymentPayment"
|
||||
},
|
||||
"homepage": "https://www.vr-payment.de//",
|
||||
"keywords": [
|
||||
"VR Payment",
|
||||
"payment",
|
||||
"php",
|
||||
"shopware"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"name": "vrpayment/shopware-6",
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=8.2",
|
||||
"shopware/core": "~6.6.0",
|
||||
"shopware/administration": "~6.6.0",
|
||||
"shopware/storefront":"~6.6.0",
|
||||
"vrpayment/sdk": "^4.0.0"
|
||||
},
|
||||
"type": "shopware-platform-plugin",
|
||||
"version": "6.2.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*:before, *:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
padding-right: 0 !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html,body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 200;
|
||||
margin-bottom: 1.875rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.625rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 300;
|
||||
margin-top: 1.3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title], abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol, ul, dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol, ul ul, ol ul, ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre, code, kbd, samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 90%;
|
||||
padding: 2px 4px 2px 4px;
|
||||
color: #c7254e;
|
||||
background-color: #f9f2f4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #a7a7a7;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table col[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-column;
|
||||
}
|
||||
|
||||
table td[class*="col-"],table th[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
ol.glossary {
|
||||
counter-reset: glossary-counter;
|
||||
list-style: none;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
ol.glossary li {
|
||||
counter-increment: glossary-counter;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ol.glossary li::before {
|
||||
content: counter(glossary-counter);
|
||||
position: absolute;
|
||||
background-color: #73EAA9;
|
||||
color: #fff;
|
||||
border-radius: 100px;
|
||||
width: 24px;
|
||||
left: -40px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.layout-title {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layout-title h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.layout-navigation .nav {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a {
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 100px;
|
||||
padding: 6px 12px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus {
|
||||
border: 1px solid #0056b3;
|
||||
color: #0056b3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-content:before, .layout-content:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.layout-content .col-right-wrapper {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.layout-content .col-body:before, .layout-content .col-body:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content .col-body:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-body-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
padding: 1.25rem 0;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title {
|
||||
color: #212529;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title:hover {
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav {
|
||||
display: none;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li.active > .nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin: 0 0 6rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1 {
|
||||
border-bottom: 2px solid #eeeeee;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.chapter-title .title-number, .section-title .title-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
.paragraph + .paragraph {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.dlist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.dlist dl dt {
|
||||
float: left;
|
||||
width: 160px;
|
||||
clear: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dlist dl dd {
|
||||
margin-left: 180px;
|
||||
}
|
||||
|
||||
.ulist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.imageblock {
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.imageblock .content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.imageblock .title {
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.exampleblock, .quoteblock, .literalblock {
|
||||
background: #f5f4f4;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.exampleblock .title, .quoteblock .title, .literalblock .title {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75em;
|
||||
font-weight: 400;
|
||||
color: #979797;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.quoteblock blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.literalblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.listingblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock pre code {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.admonitionblock {
|
||||
line-height: 1.8em;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.admonitionblock .icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admonitionblock.important {
|
||||
background: #fce1e1;
|
||||
border-left: 5px solid #ff6060;
|
||||
}
|
||||
|
||||
.admonitionblock.note, .admonitionblock.tip {
|
||||
background: #e0f2fc;
|
||||
border-left: 5px solid #88d5ff;
|
||||
}
|
||||
|
||||
.admonitionblock.caution, .admonitionblock.warning {
|
||||
background: #fdf3d8;
|
||||
border-left: 5px solid #f1c654;
|
||||
}
|
||||
|
||||
table.tableblock {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td {
|
||||
padding: 8px;
|
||||
line-height: 1.42857143;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
table.tableblock > tbody + tbody {
|
||||
border-top: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock .table {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
table.tableblock > tbody > tr:nth-of-type(odd) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.loaded .table-of-contents .nav .nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
html {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
color: #000;
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
(function($){
|
||||
|
||||
hljs.initHighlightingOnLoad();
|
||||
|
||||
$(document).ready(function(){
|
||||
$('.col-right-wrapper').stick_in_parent({
|
||||
parent: '.layout-content'
|
||||
});
|
||||
$('body').scrollspy({
|
||||
target: '.table-of-contents'
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
|
||||
Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #23241f;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-tag,
|
||||
.hljs-subst {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-strong,
|
||||
.hljs-emphasis {
|
||||
color: #a8a8a2;
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-quote,
|
||||
.hljs-number,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-link {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-selector-class {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-name,
|
||||
.hljs-attr {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-attribute {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.hljs-params,
|
||||
.hljs-class .hljs-title {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #75715e;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: scrollspy.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#scrollspy
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
!function(r){"use strict";function o(t,s){this.$body=r(document.body),this.$scrollElement=r(t).is(document.body)?r(window):r(t),this.options=r.extend({},o.DEFAULTS,s),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s<o[0])return this.activeTarget=null,this.clear();for(t=o.length;t--;)l!=r[t]&&s>=o[t]&&(void 0===o[t+1]||s<o[t+1])&&this.activate(r[t])},o.prototype.activate=function(t){this.activeTarget=t,this.clear();var s=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',e=r(s).parents("li").addClass("active");e.parent(".dropdown-menu").length&&(e=e.closest("li.dropdown").addClass("active")),e.trigger("activate.bs.scrollspy")},o.prototype.clear=function(){r(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var t=r.fn.scrollspy;r.fn.scrollspy=s,r.fn.scrollspy.Constructor=o,r.fn.scrollspy.noConflict=function(){return r.fn.scrollspy=t,this},r(window).on("load.bs.scrollspy.data-api",function(){r('[data-spy="scroll"]').each(function(){var t=r(this);s.call(t,t.data())})})}(jQuery);
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
|
||||
*/
|
||||
|
||||
(function(){var M,Q;M=this.jQuery||window.jQuery,Q=M(window),M.fn.stick_in_parent=function(t){var x,o,C,i,e,P,s,V,F,z,r,I,A,j;for(null==t&&(t={}),j=t.sticky_class,P=t.inner_scrolling,A=t.recalc_every,I=t.parent,F=t.offset_top,z=t.offset_bottom,V=t.spacer,C=t.bottoming,null==F&&(F=0),null==z&&(z=0),null==I&&(I=void 0),null==P&&(P=!0),null==j&&(j="is_stuck"),x=M(document),null==C&&(C=!0),r=function(t){var o,i;return window.getComputedStyle?(t[0],o=window.getComputedStyle(t[0]),i=parseFloat(o.getPropertyValue("width"))+parseFloat(o.getPropertyValue("margin-left"))+parseFloat(o.getPropertyValue("margin-right")),"border-box"!==o.getPropertyValue("box-sizing")&&(i+=parseFloat(o.getPropertyValue("border-left-width"))+parseFloat(o.getPropertyValue("border-right-width"))+parseFloat(o.getPropertyValue("padding-left"))+parseFloat(o.getPropertyValue("padding-right"))),i):t.outerWidth(!0)},i=function(n,l,a,c,p,d,u,f){var h,t,g,m,k,y,b,v,o,_,w,e;if(!n.data("sticky_kit")){if(n.data("sticky_kit",!0),k=x.height(),b=n.parent(),null!=I&&(b=b.closest(I)),!b.length)throw"failed to find stick parent";return h=g=!1,(w=null!=V?V&&n.closest(V):M('<div class="sticky-kit-manual-spacer" />'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a<e+d+y+z,h&&!s&&(h=!1,n.css({position:"fixed",bottom:"",top:y}).removeClass("is_bottomed").trigger("sticky_kit:unbottom"))),e<p&&(g=!1,y=F,null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.detach()),t={position:"",width:"",top:""},n.css(t).removeClass(j).trigger("sticky_kit:unstick")),P&&(r=Q.height())<d+F&&(h||(y-=o,y=Math.max(r-d,y),y=Math.min(F,y),g&&n.css({top:y+"px"})))):p<e&&(g=!0,(t={position:"fixed",top:y}).width="border-box"===n.css("box-sizing")?n.outerWidth()+"px":n.width()+"px",n.css(t).addClass(j),null==V&&(n.after(w),"left"!==u&&"right"!==u||w.append(n)),n.trigger("sticky_kit:stick")),g&&C&&(null==s&&(s=c+a<e+d+y+z),!h&&s)?(h=!0,"static"===b.css("position")&&b.css({position:"relative"}),n.css({position:"absolute",bottom:l+z,top:"auto"}).addClass("is_bottomed").trigger("sticky_kit:bottom")):void 0},o=function(){return v(),e()},t=function(){if(f=!0,Q.off("touchmove",e),Q.off("scroll",e),Q.off("resize",o),M(document.body).off("sticky_kit:recalc",o),n.off("sticky_kit:detach",t),n.removeData("sticky_kit"),n.css({position:"",bottom:"",top:"",width:""}),b.position("position",""),g)return null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.remove()),n.removeClass(j)},Q.on("touchmove",e),Q.on("scroll",e),Q.on("resize",o),M(document.body).on("sticky_kit:recalc",o),n.on("sticky_kit:detach",t),setTimeout(e,0)}},e=0,s=this.length;e<s;e++)o=this[e],i(M(o));return this}}).call(this);
|
||||
File diff suppressed because it is too large
Load Diff
+662
-275
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,692 @@
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*:before, *:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
padding-right: 0 !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html,body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 200;
|
||||
margin-bottom: 1.875rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.625rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 300;
|
||||
margin-top: 1.3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title], abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol, ul, dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol, ul ul, ol ul, ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre, code, kbd, samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 90%;
|
||||
padding: 2px 4px 2px 4px;
|
||||
color: #c7254e;
|
||||
background-color: #f9f2f4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #a7a7a7;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table col[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-column;
|
||||
}
|
||||
|
||||
table td[class*="col-"],table th[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
ol.glossary {
|
||||
counter-reset: glossary-counter;
|
||||
list-style: none;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
ol.glossary li {
|
||||
counter-increment: glossary-counter;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ol.glossary li::before {
|
||||
content: counter(glossary-counter);
|
||||
position: absolute;
|
||||
background-color: #73EAA9;
|
||||
color: #fff;
|
||||
border-radius: 100px;
|
||||
width: 24px;
|
||||
left: -40px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.layout-title {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layout-title h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.layout-navigation .nav {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a {
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 100px;
|
||||
padding: 6px 12px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus {
|
||||
border: 1px solid #0056b3;
|
||||
color: #0056b3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-content:before, .layout-content:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.layout-content .col-right-wrapper {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.layout-content .col-body:before, .layout-content .col-body:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content .col-body:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-body-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
padding: 1.25rem 0;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title {
|
||||
color: #212529;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title:hover {
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav {
|
||||
display: none;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li.active > .nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin: 0 0 6rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1 {
|
||||
border-bottom: 2px solid #eeeeee;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.chapter-title .title-number, .section-title .title-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
.paragraph + .paragraph {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.dlist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.dlist dl dt {
|
||||
float: left;
|
||||
width: 160px;
|
||||
clear: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dlist dl dd {
|
||||
margin-left: 180px;
|
||||
}
|
||||
|
||||
.ulist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.imageblock {
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.imageblock .content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.imageblock .title {
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.exampleblock, .quoteblock, .literalblock {
|
||||
background: #f5f4f4;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.exampleblock .title, .quoteblock .title, .literalblock .title {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75em;
|
||||
font-weight: 400;
|
||||
color: #979797;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.quoteblock blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.literalblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.listingblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock pre code {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.admonitionblock {
|
||||
line-height: 1.8em;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.admonitionblock .icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admonitionblock.important {
|
||||
background: #fce1e1;
|
||||
border-left: 5px solid #ff6060;
|
||||
}
|
||||
|
||||
.admonitionblock.note, .admonitionblock.tip {
|
||||
background: #e0f2fc;
|
||||
border-left: 5px solid #88d5ff;
|
||||
}
|
||||
|
||||
.admonitionblock.caution, .admonitionblock.warning {
|
||||
background: #fdf3d8;
|
||||
border-left: 5px solid #f1c654;
|
||||
}
|
||||
|
||||
table.tableblock {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td {
|
||||
padding: 8px;
|
||||
line-height: 1.42857143;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
table.tableblock > tbody + tbody {
|
||||
border-top: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock .table {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
table.tableblock > tbody > tr:nth-of-type(odd) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.loaded .table-of-contents .nav .nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
html {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
color: #000;
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
(function($){
|
||||
|
||||
hljs.initHighlightingOnLoad();
|
||||
|
||||
$(document).ready(function(){
|
||||
$('.col-right-wrapper').stick_in_parent({
|
||||
parent: '.layout-content'
|
||||
});
|
||||
$('body').scrollspy({
|
||||
target: '.table-of-contents'
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
|
||||
Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #23241f;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-tag,
|
||||
.hljs-subst {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-strong,
|
||||
.hljs-emphasis {
|
||||
color: #a8a8a2;
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-quote,
|
||||
.hljs-number,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-link {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-selector-class {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-name,
|
||||
.hljs-attr {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-attribute {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.hljs-params,
|
||||
.hljs-class .hljs-title {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #75715e;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: scrollspy.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#scrollspy
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
!function(r){"use strict";function o(t,s){this.$body=r(document.body),this.$scrollElement=r(t).is(document.body)?r(window):r(t),this.options=r.extend({},o.DEFAULTS,s),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s<o[0])return this.activeTarget=null,this.clear();for(t=o.length;t--;)l!=r[t]&&s>=o[t]&&(void 0===o[t+1]||s<o[t+1])&&this.activate(r[t])},o.prototype.activate=function(t){this.activeTarget=t,this.clear();var s=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',e=r(s).parents("li").addClass("active");e.parent(".dropdown-menu").length&&(e=e.closest("li.dropdown").addClass("active")),e.trigger("activate.bs.scrollspy")},o.prototype.clear=function(){r(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var t=r.fn.scrollspy;r.fn.scrollspy=s,r.fn.scrollspy.Constructor=o,r.fn.scrollspy.noConflict=function(){return r.fn.scrollspy=t,this},r(window).on("load.bs.scrollspy.data-api",function(){r('[data-spy="scroll"]').each(function(){var t=r(this);s.call(t,t.data())})})}(jQuery);
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
|
||||
*/
|
||||
|
||||
(function(){var M,Q;M=this.jQuery||window.jQuery,Q=M(window),M.fn.stick_in_parent=function(t){var x,o,C,i,e,P,s,V,F,z,r,I,A,j;for(null==t&&(t={}),j=t.sticky_class,P=t.inner_scrolling,A=t.recalc_every,I=t.parent,F=t.offset_top,z=t.offset_bottom,V=t.spacer,C=t.bottoming,null==F&&(F=0),null==z&&(z=0),null==I&&(I=void 0),null==P&&(P=!0),null==j&&(j="is_stuck"),x=M(document),null==C&&(C=!0),r=function(t){var o,i;return window.getComputedStyle?(t[0],o=window.getComputedStyle(t[0]),i=parseFloat(o.getPropertyValue("width"))+parseFloat(o.getPropertyValue("margin-left"))+parseFloat(o.getPropertyValue("margin-right")),"border-box"!==o.getPropertyValue("box-sizing")&&(i+=parseFloat(o.getPropertyValue("border-left-width"))+parseFloat(o.getPropertyValue("border-right-width"))+parseFloat(o.getPropertyValue("padding-left"))+parseFloat(o.getPropertyValue("padding-right"))),i):t.outerWidth(!0)},i=function(n,l,a,c,p,d,u,f){var h,t,g,m,k,y,b,v,o,_,w,e;if(!n.data("sticky_kit")){if(n.data("sticky_kit",!0),k=x.height(),b=n.parent(),null!=I&&(b=b.closest(I)),!b.length)throw"failed to find stick parent";return h=g=!1,(w=null!=V?V&&n.closest(V):M('<div class="sticky-kit-manual-spacer" />'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a<e+d+y+z,h&&!s&&(h=!1,n.css({position:"fixed",bottom:"",top:y}).removeClass("is_bottomed").trigger("sticky_kit:unbottom"))),e<p&&(g=!1,y=F,null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.detach()),t={position:"",width:"",top:""},n.css(t).removeClass(j).trigger("sticky_kit:unstick")),P&&(r=Q.height())<d+F&&(h||(y-=o,y=Math.max(r-d,y),y=Math.min(F,y),g&&n.css({top:y+"px"})))):p<e&&(g=!0,(t={position:"fixed",top:y}).width="border-box"===n.css("box-sizing")?n.outerWidth()+"px":n.width()+"px",n.css(t).addClass(j),null==V&&(n.after(w),"left"!==u&&"right"!==u||w.append(n)),n.trigger("sticky_kit:stick")),g&&C&&(null==s&&(s=c+a<e+d+y+z),!h&&s)?(h=!0,"static"===b.css("position")&&b.css({position:"relative"}),n.css({position:"absolute",bottom:l+z,top:"auto"}).addClass("is_bottomed").trigger("sticky_kit:bottom")):void 0},o=function(){return v(),e()},t=function(){if(f=!0,Q.off("touchmove",e),Q.off("scroll",e),Q.off("resize",o),M(document.body).off("sticky_kit:recalc",o),n.off("sticky_kit:detach",t),n.removeData("sticky_kit"),n.css({position:"",bottom:"",top:"",width:""}),b.position("position",""),g)return null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.remove()),n.removeClass(j)},Q.on("touchmove",e),Q.on("scroll",e),Q.on("resize",o),M(document.body).on("sticky_kit:recalc",o),n.on("sticky_kit:detach",t),setTimeout(e,0)}},e=0,s=this.length;e<s;e++)o=this[e],i(M(o));return this}}).call(this);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,692 @@
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*:before, *:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
padding-right: 0 !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
html,body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 200;
|
||||
margin-bottom: 1.875rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.625rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 300;
|
||||
margin-top: 1.3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 400;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
margin-top: 1.875rem;
|
||||
margin-bottom: 1.3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title], abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol, ul, dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol, ul ul, ol ul, ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre, code, kbd, samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 90%;
|
||||
padding: 2px 4px 2px 4px;
|
||||
color: #c7254e;
|
||||
background-color: #f9f2f4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
-ms-overflow-style: scrollbar;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: #a7a7a7;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table col[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-column;
|
||||
}
|
||||
|
||||
table td[class*="col-"],table th[class*="col-"] {
|
||||
position: static;
|
||||
float: none;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
ol.glossary {
|
||||
counter-reset: glossary-counter;
|
||||
list-style: none;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
ol.glossary li {
|
||||
counter-increment: glossary-counter;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ol.glossary li::before {
|
||||
content: counter(glossary-counter);
|
||||
position: absolute;
|
||||
background-color: #73EAA9;
|
||||
color: #fff;
|
||||
border-radius: 100px;
|
||||
width: 24px;
|
||||
left: -40px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.layout-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.layout-title {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.layout-title h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: 200;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.layout-navigation .nav {
|
||||
padding: 1.875rem 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a {
|
||||
border: 1px solid #007bff;
|
||||
border-radius: 100px;
|
||||
padding: 6px 12px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.layout-navigation .nav > li > a:hover, .layout-navigation .nav > li > a:active, .layout-navigation .nav > li > a:focus {
|
||||
border: 1px solid #0056b3;
|
||||
color: #0056b3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-content:before, .layout-content:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
width: 25%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.layout-content .col-right-wrapper {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 75%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.layout-content .col-body:before, .layout-content .col-body:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.layout-content .col-body:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.layout-content .col-body-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 2.5rem 2rem 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.table-of-contents {
|
||||
padding: 1.25rem 0;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title {
|
||||
color: #212529;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li > a .item-title:hover {
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.table-of-contents .nav > li.extended > a .item-title, .table-of-contents .nav > li.active > a .item-title, .table-of-contents .nav > li.extended > a .item-title:hover, .table-of-contents .nav > li.active > a .item-title:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav {
|
||||
display: none;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li > .nav > li > .nav > li > a .item-title {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.table-of-contents > .nav > li.active > .nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin: 0 0 6rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1, .chapter > .chapter-title h2, .chapter > .chapter-title h3, .chapter > .chapter-title h4, .chapter > .chapter-title h5, .chapter > .chapter-title h6, .section > .section-title h1, .section > .section-title h2, .section > .section-title h3, .section > .section-title h4, .section > .section-title h5, .section > .section-title h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.chapter > .chapter-title h1 {
|
||||
border-bottom: 2px solid #eeeeee;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.chapter-title .title-number, .section-title .title-number {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
line-height: 1.75em;
|
||||
}
|
||||
|
||||
.paragraph + .paragraph {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.dlist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.dlist dl dt {
|
||||
float: left;
|
||||
width: 160px;
|
||||
clear: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dlist dl dd {
|
||||
margin-left: 180px;
|
||||
}
|
||||
|
||||
.ulist {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.imageblock {
|
||||
margin: 30px auto;
|
||||
}
|
||||
|
||||
.imageblock .content img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.imageblock .title {
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.exampleblock, .quoteblock, .literalblock {
|
||||
background: #f5f4f4;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.exampleblock .title, .quoteblock .title, .literalblock .title {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75em;
|
||||
font-weight: 400;
|
||||
color: #979797;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.quoteblock blockquote {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.quoteblock blockquote p:last-child, .quoteblock blockquote ul:last-child, .quoteblock blockquote ol:last-child {
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.literalblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock {
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.listingblock pre {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.listingblock pre code {
|
||||
display: block;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.admonitionblock {
|
||||
line-height: 1.8em;
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.admonitionblock .icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admonitionblock.important {
|
||||
background: #fce1e1;
|
||||
border-left: 5px solid #ff6060;
|
||||
}
|
||||
|
||||
.admonitionblock.note, .admonitionblock.tip {
|
||||
background: #e0f2fc;
|
||||
border-left: 5px solid #88d5ff;
|
||||
}
|
||||
|
||||
.admonitionblock.caution, .admonitionblock.warning {
|
||||
background: #fdf3d8;
|
||||
border-left: 5px solid #f1c654;
|
||||
}
|
||||
|
||||
table.tableblock {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 18px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th, table.tableblock > tbody > tr > th, table.tableblock > tfoot > tr > th, table.tableblock > thead > tr > td, table.tableblock > tbody > tr > td, table.tableblock > tfoot > tr > td {
|
||||
padding: 8px;
|
||||
line-height: 1.42857143;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
border-bottom: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock > caption + thead > tr:first-child > th, table.tableblock > colgroup + thead > tr:first-child > th, table.tableblock > thead:first-child > tr:first-child > th, table.tableblock > caption + thead > tr:first-child > td, table.tableblock > colgroup + thead > tr:first-child > td, table.tableblock > thead:first-child > tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
table.tableblock > tbody + tbody {
|
||||
border-top: 2px solid #eee;
|
||||
}
|
||||
|
||||
table.tableblock .table {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
table.tableblock > tbody > tr:nth-of-type(odd) {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
table.tableblock > thead > tr > th p:last-child, table.tableblock > tbody > tr > th p:last-child, table.tableblock > tfoot > tr > th p:last-child, table.tableblock > thead > tr > td p:last-child, table.tableblock > tbody > tr > td p:last-child, table.tableblock > tfoot > tr > td p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.loaded .table-of-contents .nav .nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.layout-wrapper .layout-title, .layout-wrapper .layout-navigation, .layout-wrapper .layout-content {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
html {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
color: #000;
|
||||
font-family: Georgia, "Times New Roman", Times, serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.layout-title h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.layout-content .col-right {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-content .col-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
(function($){
|
||||
|
||||
hljs.initHighlightingOnLoad();
|
||||
|
||||
$(document).ready(function(){
|
||||
$('.col-right-wrapper').stick_in_parent({
|
||||
parent: '.layout-content'
|
||||
});
|
||||
$('body').scrollspy({
|
||||
target: '.table-of-contents'
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
|
||||
Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #23241f;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-tag,
|
||||
.hljs-subst {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-strong,
|
||||
.hljs-emphasis {
|
||||
color: #a8a8a2;
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-quote,
|
||||
.hljs-number,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-link {
|
||||
color: #ae81ff;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-selector-class {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-name,
|
||||
.hljs-attr {
|
||||
color: #f92672;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-attribute {
|
||||
color: #66d9ef;
|
||||
}
|
||||
|
||||
.hljs-params,
|
||||
.hljs-class .hljs-title {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-addition,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #75715e;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* ========================================================================
|
||||
* Bootstrap: scrollspy.js v3.3.7
|
||||
* http://getbootstrap.com/javascript/#scrollspy
|
||||
* ========================================================================
|
||||
* Copyright 2011-2016 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* ======================================================================== */
|
||||
|
||||
!function(r){"use strict";function o(t,s){this.$body=r(document.body),this.$scrollElement=r(t).is(document.body)?r(window):r(t),this.options=r.extend({},o.DEFAULTS,s),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",r.proxy(this.process,this)),this.refresh(),this.process()}function s(i){return this.each(function(){var t=r(this),s=t.data("bs.scrollspy"),e="object"==typeof i&&i;s||t.data("bs.scrollspy",s=new o(this,e)),"string"==typeof i&&s[i]()})}o.VERSION="3.3.7",o.DEFAULTS={offset:10},o.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},o.prototype.refresh=function(){var t=this,i="offset",o=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),r.isWindow(this.$scrollElement[0])||(i="position",o=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=r(this),s=t.data("target")||t.attr("href"),e=/^#./.test(s)&&r(s);return e&&e.length&&e.is(":visible")&&[[e[i]().top+o,s]]||null}).sort(function(t,s){return t[0]-s[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},o.prototype.process=function(){var t,s=this.$scrollElement.scrollTop()+this.options.offset,e=this.getScrollHeight(),i=this.options.offset+e-this.$scrollElement.height(),o=this.offsets,r=this.targets,l=this.activeTarget;if(this.scrollHeight!=e&&this.refresh(),i<=s)return l!=(t=r[r.length-1])&&this.activate(t);if(l&&s<o[0])return this.activeTarget=null,this.clear();for(t=o.length;t--;)l!=r[t]&&s>=o[t]&&(void 0===o[t+1]||s<o[t+1])&&this.activate(r[t])},o.prototype.activate=function(t){this.activeTarget=t,this.clear();var s=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',e=r(s).parents("li").addClass("active");e.parent(".dropdown-menu").length&&(e=e.closest("li.dropdown").addClass("active")),e.trigger("activate.bs.scrollspy")},o.prototype.clear=function(){r(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var t=r.fn.scrollspy;r.fn.scrollspy=s,r.fn.scrollspy.Constructor=o,r.fn.scrollspy.noConflict=function(){return r.fn.scrollspy=t,this},r(window).on("load.bs.scrollspy.data-api",function(){r('[data-spy="scroll"]').each(function(){var t=r(this);s.call(t,t.data())})})}(jQuery);
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
@license Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | http://leafo.net
|
||||
*/
|
||||
|
||||
(function(){var M,Q;M=this.jQuery||window.jQuery,Q=M(window),M.fn.stick_in_parent=function(t){var x,o,C,i,e,P,s,V,F,z,r,I,A,j;for(null==t&&(t={}),j=t.sticky_class,P=t.inner_scrolling,A=t.recalc_every,I=t.parent,F=t.offset_top,z=t.offset_bottom,V=t.spacer,C=t.bottoming,null==F&&(F=0),null==z&&(z=0),null==I&&(I=void 0),null==P&&(P=!0),null==j&&(j="is_stuck"),x=M(document),null==C&&(C=!0),r=function(t){var o,i;return window.getComputedStyle?(t[0],o=window.getComputedStyle(t[0]),i=parseFloat(o.getPropertyValue("width"))+parseFloat(o.getPropertyValue("margin-left"))+parseFloat(o.getPropertyValue("margin-right")),"border-box"!==o.getPropertyValue("box-sizing")&&(i+=parseFloat(o.getPropertyValue("border-left-width"))+parseFloat(o.getPropertyValue("border-right-width"))+parseFloat(o.getPropertyValue("padding-left"))+parseFloat(o.getPropertyValue("padding-right"))),i):t.outerWidth(!0)},i=function(n,l,a,c,p,d,u,f){var h,t,g,m,k,y,b,v,o,_,w,e;if(!n.data("sticky_kit")){if(n.data("sticky_kit",!0),k=x.height(),b=n.parent(),null!=I&&(b=b.closest(I)),!b.length)throw"failed to find stick parent";return h=g=!1,(w=null!=V?V&&n.closest(V):M('<div class="sticky-kit-manual-spacer" />'))&&w.css("position",n.css("position")),(v=function(){var t,o,i;if(!f)return k=x.height(),t=parseInt(b.css("border-top-width"),10),o=parseInt(b.css("padding-top"),10),l=parseInt(b.css("padding-bottom"),10),a=b.offset().top+t+o,c=b.height(),g&&(h=g=!1,null==V&&(n.insertAfter(w),w.detach()),n.css({position:"",top:"",width:"",bottom:""}).removeClass(j),i=!0),p=n.offset().top-(parseInt(n.css("margin-top"),10)||0)-F,d=n.outerHeight(!0),u=n.css("float"),w&&w.css({width:r(n),height:d,display:n.css("display"),"vertical-align":n.css("vertical-align"),float:u}),i?e():void 0})(),m=void 0,y=F,_=A,e=function(){var t,o,i,e,s,r;if(d!==c&&!f)return i=!1,null!=_&&(_-=1)<=0&&(_=A,v(),i=!0),i||x.height()===k||(v(),i=!0),e=Q.scrollTop(),null!=m&&(o=e-m),m=e,g?(C&&(s=c+a<e+d+y+z,h&&!s&&(h=!1,n.css({position:"fixed",bottom:"",top:y}).removeClass("is_bottomed").trigger("sticky_kit:unbottom"))),e<p&&(g=!1,y=F,null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.detach()),t={position:"",width:"",top:""},n.css(t).removeClass(j).trigger("sticky_kit:unstick")),P&&(r=Q.height())<d+F&&(h||(y-=o,y=Math.max(r-d,y),y=Math.min(F,y),g&&n.css({top:y+"px"})))):p<e&&(g=!0,(t={position:"fixed",top:y}).width="border-box"===n.css("box-sizing")?n.outerWidth()+"px":n.width()+"px",n.css(t).addClass(j),null==V&&(n.after(w),"left"!==u&&"right"!==u||w.append(n)),n.trigger("sticky_kit:stick")),g&&C&&(null==s&&(s=c+a<e+d+y+z),!h&&s)?(h=!0,"static"===b.css("position")&&b.css({position:"relative"}),n.css({position:"absolute",bottom:l+z,top:"auto"}).addClass("is_bottomed").trigger("sticky_kit:bottom")):void 0},o=function(){return v(),e()},t=function(){if(f=!0,Q.off("touchmove",e),Q.off("scroll",e),Q.off("resize",o),M(document.body).off("sticky_kit:recalc",o),n.off("sticky_kit:detach",t),n.removeData("sticky_kit"),n.css({position:"",bottom:"",top:"",width:""}),b.position("position",""),g)return null==V&&("left"!==u&&"right"!==u||n.insertAfter(w),w.remove()),n.removeClass(j)},Q.on("touchmove",e),Q.on("scroll",e),Q.on("resize",o),M(document.body).on("sticky_kit:recalc",o),n.on("sticky_kit:detach",t),setTimeout(e,0)}},e=0,s=this.length;e<s;e++)o=this[e],i(M(o));return this}}).call(this);
|
||||
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
+200
-95
@@ -10,7 +10,9 @@ use Shopware\Core\{
|
||||
Content\ImportExport\Struct\Config,
|
||||
Content\Media\MediaDefinition,
|
||||
Framework\Context,
|
||||
Framework\DataAbstractionLayer\EntityRepository,
|
||||
Framework\DataAbstractionLayer\Search\Criteria,
|
||||
Framework\DataAbstractionLayer\Search\EntitySearchResult,
|
||||
Framework\DataAbstractionLayer\Search\Filter\EqualsFilter,
|
||||
Framework\Plugin\Util\PluginIdProvider,
|
||||
Framework\Uuid\Uuid};
|
||||
@@ -32,7 +34,6 @@ use VRPaymentPayment\Core\{
|
||||
Util\LocaleCodeProvider};
|
||||
use VRPaymentPayment\VRPaymentPayment;
|
||||
|
||||
|
||||
/**
|
||||
* Class PaymentMethodConfigurationService
|
||||
*
|
||||
@@ -102,6 +103,31 @@ class PaymentMethodConfigurationService {
|
||||
*/
|
||||
private $paymentMethodRepository;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface
|
||||
*/
|
||||
private $salesChannelPaymentRepository;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface
|
||||
*/
|
||||
private $mediaRepository;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface
|
||||
*/
|
||||
private $mediaFolderRepository;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface
|
||||
*/
|
||||
private $mediaDefaultFolderRepository;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface
|
||||
*/
|
||||
private $vRPaymentPaymentMethodConfigurationRepository;
|
||||
|
||||
/**
|
||||
* PaymentMethodConfigurationService constructor.
|
||||
*
|
||||
@@ -109,21 +135,39 @@ class PaymentMethodConfigurationService {
|
||||
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
|
||||
* @param \Shopware\Core\Content\ImportExport\DataAbstractionLayer\Serializer\Entity\MediaSerializer $mediaSerializer
|
||||
* @param \Shopware\Core\Content\ImportExport\DataAbstractionLayer\Serializer\SerializerRegistry $serializerRegistry
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $salesChannelPaymentRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $paymentMethodRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $mediaRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $mediaFolderRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $mediaDefaultFolderRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $ruleRepository
|
||||
* @param \Shopware\Core\Framework\DataAbstractionLayer\EntityRepository $vRPaymentPaymentMethodConfigurationRepository
|
||||
*/
|
||||
public function __construct(
|
||||
SettingsService $settingsService,
|
||||
ContainerInterface $container,
|
||||
MediaSerializer $mediaSerializer,
|
||||
SerializerRegistry $serializerRegistry
|
||||
)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->ruleRepository = $this->container->get('rule.repository');
|
||||
SerializerRegistry $serializerRegistry,
|
||||
EntityRepository $salesChannelPaymentRepository,
|
||||
EntityRepository $paymentMethodRepository,
|
||||
EntityRepository $mediaRepository,
|
||||
EntityRepository $mediaFolderRepository,
|
||||
EntityRepository $mediaDefaultFolderRepository,
|
||||
EntityRepository $ruleRepository,
|
||||
EntityRepository $vRPaymentPaymentMethodConfigurationRepository,
|
||||
) {
|
||||
$this->settingsService = $settingsService;
|
||||
$this->container = $container;
|
||||
$this->mediaSerializer = $mediaSerializer;
|
||||
$this->serializerRegistry = $serializerRegistry;
|
||||
$this->salesChannelPaymentRepository = $salesChannelPaymentRepository;
|
||||
$this->paymentMethodRepository = $paymentMethodRepository;
|
||||
$this->mediaRepository = $mediaRepository;
|
||||
$this->mediaFolderRepository = $mediaFolderRepository;
|
||||
$this->mediaDefaultFolderRepository = $mediaDefaultFolderRepository;
|
||||
$this->ruleRepository = $ruleRepository;
|
||||
$this->vRPaymentPaymentMethodConfigurationRepository = $vRPaymentPaymentMethodConfigurationRepository;
|
||||
$this->localeCodeProvider = $this->container->get(LocaleCodeProvider::class);
|
||||
$this->paymentMethodRepository = $this->container->get('payment_method.repository');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,8 +213,7 @@ class PaymentMethodConfigurationService {
|
||||
{
|
||||
// Configuration
|
||||
$settings = $this->settingsService->getSettings($this->getSalesChannelId());
|
||||
$this->setSpaceId($settings->getSpaceId())
|
||||
->setApiClient($settings->getApiClient());
|
||||
$this->setSpaceId($settings->getSpaceId())->setApiClient($settings->getApiClient());
|
||||
|
||||
$this->disablePaymentMethodConfigurations($context);
|
||||
$this->enablePaymentMethodConfigurations($context);
|
||||
@@ -208,18 +251,15 @@ class PaymentMethodConfigurationService {
|
||||
{
|
||||
$data = [];
|
||||
$paymentMethodData = [];
|
||||
$salesChannelPaymentMethodData = [];
|
||||
|
||||
$criteria = (new Criteria())->addFilter(new EqualsFilter('spaceId', $this->getSpaceId()));
|
||||
$criteria = (new Criteria())->addFilter(new EqualsFilter('state', 'ACTIVE'))
|
||||
->addFilter(new EqualsFilter('spaceId', $this->getSpaceId()));
|
||||
|
||||
/**
|
||||
* @var $vRPaymentPMConfigurationRepository
|
||||
*/
|
||||
$vRPaymentPMConfigurationRepository = $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository');
|
||||
|
||||
/** @var EntityRepositoryInterface $salesChannelPaymentRepository */
|
||||
$salesChannelPaymentRepository = $this->container->get('sales_channel_payment_method.repository');
|
||||
|
||||
$paymentMethodConfigurationEntities = $vRPaymentPMConfigurationRepository
|
||||
->search($criteria, $context)
|
||||
->getEntities();
|
||||
@@ -236,19 +276,14 @@ class PaymentMethodConfigurationService {
|
||||
];
|
||||
|
||||
$paymentMethodData[] = [
|
||||
'id' => $paymentMethodConfigurationEntity->getId(),
|
||||
'id' => $paymentMethodConfigurationEntity->getPaymentMethodId(),
|
||||
'active' => false,
|
||||
];
|
||||
|
||||
$salesChannelPaymentMethodData[] = [
|
||||
'paymentMethodId' => $paymentMethodConfigurationEntity->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$vRPaymentPMConfigurationRepository->update($data, $context);
|
||||
$this->vRPaymentPaymentMethodConfigurationRepository->update($data, $context);
|
||||
$this->paymentMethodRepository->update($paymentMethodData, $context);
|
||||
$salesChannelPaymentRepository->delete($salesChannelPaymentMethodData, $context);
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical($exception->getMessage());
|
||||
}
|
||||
@@ -314,33 +349,68 @@ class PaymentMethodConfigurationService {
|
||||
*/
|
||||
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
||||
|
||||
$paymentMethodConfigurationEntity = $this->getPaymentMethodConfigurationEntity(
|
||||
$paymentMethodConfiguration->getSpaceId(),
|
||||
$paymentMethodConfiguration->getId(),
|
||||
$context
|
||||
$entity = $this->getPaymentMethodConfigurationEntity(
|
||||
$paymentMethodConfiguration->getSpaceId(),
|
||||
$paymentMethodConfiguration->getId(),
|
||||
$context
|
||||
);
|
||||
|
||||
$id = is_null($paymentMethodConfigurationEntity) ? Uuid::randomHex() : $paymentMethodConfigurationEntity->getId();
|
||||
|
||||
$configId = $entity ? $entity->getId() : Uuid::randomHex();
|
||||
$technicalName = $paymentMethodConfiguration->getName();
|
||||
|
||||
$paymentMethodId = $this->getOrCreatePaymentMethodId(
|
||||
$technicalName,
|
||||
VRPaymentPaymentHandler::class,
|
||||
$context
|
||||
);
|
||||
$data = [
|
||||
'id' => $id,
|
||||
'paymentMethodConfigurationId' => $paymentMethodConfiguration->getId(),
|
||||
'paymentMethodId' => $id,
|
||||
'data' => json_decode(strval($paymentMethodConfiguration), true),
|
||||
'sortOrder' => $paymentMethodConfiguration->getSortOrder(),
|
||||
'spaceId' => $paymentMethodConfiguration->getSpaceId(),
|
||||
'state' => CreationEntityState::ACTIVE,
|
||||
'id' => $configId,
|
||||
'paymentMethodConfigurationId' => $paymentMethodConfiguration->getId(),
|
||||
'paymentMethodId' => $paymentMethodId,
|
||||
'data' => json_decode(strval($paymentMethodConfiguration), true),
|
||||
'sortOrder' => $paymentMethodConfiguration->getSortOrder(),
|
||||
'spaceId' => $paymentMethodConfiguration->getSpaceId(),
|
||||
'state' => CreationEntityState::ACTIVE,
|
||||
];
|
||||
|
||||
$this->upsertPaymentMethod($id, $paymentMethodConfiguration, $context);
|
||||
|
||||
|
||||
$this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')
|
||||
->upsert([$data], $context);
|
||||
|
||||
try {
|
||||
$this->upsertPaymentMethod($paymentMethodId, $paymentMethodConfiguration, $context);
|
||||
$this->container
|
||||
->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')
|
||||
->upsert([$data], $context);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage(), [$e->getTraceAsString()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function getOrCreatePaymentMethodId(string $technicalName, string $handlerIdentifier, Context $context): string
|
||||
{
|
||||
$criteria = new Criteria();
|
||||
$criteria->addFilter(new EqualsFilter('technicalName', $technicalName));
|
||||
$criteria->setLimit(1);
|
||||
|
||||
$existing = $this->paymentMethodRepository->search($criteria, $context)->first();
|
||||
if ($existing !== null) {
|
||||
return $existing->getId();
|
||||
}
|
||||
|
||||
$paymentMethodId = Uuid::randomHex();
|
||||
|
||||
$this->paymentMethodRepository->upsert([[
|
||||
'id' => $paymentMethodId,
|
||||
'handlerIdentifier' => $handlerIdentifier,
|
||||
'technicalName' => $technicalName,
|
||||
'name' => $technicalName,
|
||||
'active' => false,
|
||||
]], $context);
|
||||
|
||||
return $paymentMethodId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch active merchant payment methods from VRPayment API
|
||||
*
|
||||
@@ -408,27 +478,27 @@ class PaymentMethodConfigurationService {
|
||||
new EqualsFilter('paymentMethodConfigurationId', $paymentMethodConfigurationId)
|
||||
);
|
||||
|
||||
return $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')
|
||||
return $this->vRPaymentPaymentMethodConfigurationRepository
|
||||
->search($criteria, $context)
|
||||
->getEntities()
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $spaceId
|
||||
* @param Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function getAllPaymentMethodConfigurations(int $spaceId, Context $context): array
|
||||
{
|
||||
$criteria = (new Criteria())->addFilter(new EqualsFilter('spaceId', $spaceId));
|
||||
/**
|
||||
* @param int $spaceId
|
||||
* @param Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function getAllPaymentMethodConfigurations(int $spaceId, Context $context): array
|
||||
{
|
||||
$criteria = (new Criteria())->addFilter(new EqualsFilter('spaceId', $spaceId));
|
||||
|
||||
$configurations = $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')
|
||||
->search($criteria, $context)
|
||||
->getEntities();
|
||||
$configurations = $this->vRPaymentPaymentMethodConfigurationRepository
|
||||
->search($criteria, $context)
|
||||
->getEntities();
|
||||
|
||||
return $configurations->getElements();
|
||||
}
|
||||
return $configurations->getElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or insert Payment Method
|
||||
@@ -442,33 +512,38 @@ class PaymentMethodConfigurationService {
|
||||
* @throws \VRPayment\Sdk\VersioningException
|
||||
*/
|
||||
protected function upsertPaymentMethod(
|
||||
string $id,
|
||||
PaymentMethodConfiguration $paymentMethodConfiguration,
|
||||
Context $context
|
||||
): void
|
||||
{
|
||||
string $id,
|
||||
PaymentMethodConfiguration $paymentMethodConfiguration,
|
||||
Context $context
|
||||
): void {
|
||||
/** @var PluginIdProvider $pluginIdProvider */
|
||||
$pluginIdProvider = $this->container->get(PluginIdProvider::class);
|
||||
$pluginId = $pluginIdProvider->getPluginIdByBaseClass(
|
||||
VRPaymentPayment::class,
|
||||
$context
|
||||
$pluginId = $pluginIdProvider->getPluginIdByBaseClass(
|
||||
VRPaymentPayment::class,
|
||||
$context
|
||||
);
|
||||
|
||||
$data = [
|
||||
'id' => $id,
|
||||
'handlerIdentifier' => VRPaymentPaymentHandler::class,
|
||||
'pluginId' => $pluginId,
|
||||
'position' => $paymentMethodConfiguration->getSortOrder() - 100,
|
||||
'afterOrderEnabled' => true,
|
||||
'active' => true,
|
||||
'translations' => $this->getPaymentMethodConfigurationTranslation($paymentMethodConfiguration, $context),
|
||||
'id' => $id,
|
||||
'handlerIdentifier' => VRPaymentPaymentHandler::class,
|
||||
'pluginId' => $pluginId,
|
||||
'position' => $paymentMethodConfiguration->getSortOrder() - 100,
|
||||
'afterOrderEnabled' => true,
|
||||
'active' => true,
|
||||
'translations' => $this->getPaymentMethodConfigurationTranslation($paymentMethodConfiguration, $context),
|
||||
'technicalName' => $paymentMethodConfiguration->getName(),
|
||||
];
|
||||
|
||||
$data['mediaId'] = $this->upsertMedia($id, $paymentMethodConfiguration, $context);
|
||||
$mediaId = $this->upsertMedia($id, $paymentMethodConfiguration, $context);
|
||||
if ($mediaId) {
|
||||
$data['mediaId'] = $mediaId;
|
||||
}
|
||||
|
||||
$data = array_filter($data);
|
||||
|
||||
$this->paymentMethodRepository->upsert([$data], $context);
|
||||
try {
|
||||
$this->paymentMethodRepository->upsert([$data], $context);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage(), [$e->getTraceAsString()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,42 +645,58 @@ class PaymentMethodConfigurationService {
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
/**
|
||||
* Upload or update Payment Method icons
|
||||
*/
|
||||
protected function upsertMedia(string $id, PaymentMethodConfiguration $paymentMethodConfiguration, Context $context): ?string
|
||||
{
|
||||
try {
|
||||
$mediaDefaultFolderRepository = $this->container->get('media_default_folder.repository');
|
||||
$mediaDefaultFolderRepository->upsert([
|
||||
[
|
||||
'id' => $id,
|
||||
'associationFields' => [],
|
||||
'entity' => 'payment_method_' . $paymentMethodConfiguration->getId(),
|
||||
],
|
||||
$folderKey = 'payment_method_' . $paymentMethodConfiguration->getId();
|
||||
|
||||
// Check existing default folder
|
||||
$criteria = new Criteria();
|
||||
$criteria->addFilter(new EqualsFilter('entity', $folderKey));
|
||||
$existingFolder = $this->mediaDefaultFolderRepository->search($criteria, $context);
|
||||
|
||||
$folderId = $id;
|
||||
if ($existingFolder->count() > 0) {
|
||||
$folderId = $existingFolder->first()->getId();
|
||||
}
|
||||
|
||||
// Ensure default folder
|
||||
$this->mediaDefaultFolderRepository->upsert([
|
||||
[
|
||||
'id' => $folderId,
|
||||
'associationFields' => [],
|
||||
'entity' => $folderKey,
|
||||
],
|
||||
], $context);
|
||||
|
||||
$mediaFolderRepository = $this->container->get('media_folder.repository');
|
||||
$mediaFolderRepository->upsert([
|
||||
[
|
||||
'id' => $id,
|
||||
'defaultFolderId' => $id,
|
||||
'name' => $paymentMethodConfiguration->getName(),
|
||||
'useParentConfiguration' => false,
|
||||
'configuration' => [],
|
||||
],
|
||||
// Ensure media folder
|
||||
$this->mediaFolderRepository->upsert([
|
||||
[
|
||||
'id' => $folderId,
|
||||
'defaultFolderId' => $folderId,
|
||||
'name' => $paymentMethodConfiguration->getName(),
|
||||
'useParentConfiguration' => false,
|
||||
'configuration' => [],
|
||||
],
|
||||
], $context);
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Content\Media\MediaDefinition
|
||||
*/
|
||||
// Media insert/update
|
||||
$mediaDefinition = $this->container->get(MediaDefinition::class);
|
||||
$this->mediaSerializer->setRegistry($this->serializerRegistry);
|
||||
|
||||
$data = [
|
||||
'id' => $id,
|
||||
'title' => $paymentMethodConfiguration->getName(),
|
||||
'url' => $paymentMethodConfiguration->getResolvedImageUrl(),
|
||||
'mediaFolderId' => $id,
|
||||
'id' => $id,
|
||||
'title' => $paymentMethodConfiguration->getName(),
|
||||
'url' => $paymentMethodConfiguration->getResolvedImageUrl(),
|
||||
'mediaFolderId' => $folderId,
|
||||
];
|
||||
|
||||
$data = $this->mediaSerializer->deserialize(new Config([], [], []), $mediaDefinition, $data);
|
||||
$this->container->get('media.repository')->upsert([$data], $context);
|
||||
$this->mediaRepository->upsert([$data], $context);
|
||||
|
||||
return $id;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->critical($e->getMessage(), [$e->getTraceAsString()]);
|
||||
@@ -613,5 +704,19 @@ class PaymentMethodConfigurationService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves media default folder for a given payment method configuration.
|
||||
*
|
||||
* @param PaymentMethodConfiguration $paymentMethodConfiguration The payment method configuration to check.
|
||||
* @param Context $context The current context.
|
||||
*
|
||||
* @return EntitySearchResult The search result for the media default folder.
|
||||
*/
|
||||
private function getMediaDefaultFolderForPaymentMethod(PaymentMethodConfiguration $paymentMethodConfiguration, Context $context): ?EntitySearchResult
|
||||
{
|
||||
$criteria = new Criteria();
|
||||
$criteria->addFilter(new EqualsFilter('entity', 'payment_method_' . $paymentMethodConfiguration->getId()));
|
||||
return $this->mediaDefaultFolderRepository->search($criteria, $context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ use Symfony\Component\{
|
||||
};
|
||||
use VRPaymentPayment\Core\{
|
||||
Api\Refund\Service\RefundService,
|
||||
Settings\Service\SettingsService
|
||||
Api\Transaction\Service\TransactionService,
|
||||
Settings\Service\SettingsService,
|
||||
Util\Exception\RefundNotSupportedException
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,17 +42,24 @@ class RefundController extends AbstractController
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
|
||||
/**
|
||||
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
|
||||
*/
|
||||
protected $transactionService;
|
||||
|
||||
/**
|
||||
* RefundController constructor.
|
||||
*
|
||||
* @param \VRPaymentPayment\Core\Api\Refund\Service\RefundService $refundService
|
||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
||||
*/
|
||||
public function __construct(RefundService $refundService, SettingsService $settingsService)
|
||||
public function __construct(RefundService $refundService, SettingsService $settingsService, TransactionService $transactionService)
|
||||
{
|
||||
$this->settingsService = $settingsService;
|
||||
$this->refundService = $refundService;
|
||||
$this->transactionService = $transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,12 +90,29 @@ class RefundController extends AbstractController
|
||||
$transactionId = $request->request->get('transactionId');
|
||||
$quantity = (int)$request->request->get('quantity');
|
||||
$lineItemId = $request->request->get('lineItemId');
|
||||
|
||||
|
||||
if ($quantity === null || $quantity <= 0) {
|
||||
return new Response('refundQuantityZero', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$settings = $this->settingsService->getSettings($salesChannelId);
|
||||
$apiClient = $settings->getApiClient();
|
||||
|
||||
$transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId);
|
||||
$refund = $this->refundService->create($transaction, $context, $lineItemId, $quantity);
|
||||
|
||||
$maxQuantity = $this->refundService->getMaxRefundableQuantity($transaction, $context, $lineItemId);
|
||||
|
||||
if ($quantity > $maxQuantity) {
|
||||
return new Response('refundExceedsQuantity', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$refund = $this->refundService->create($transaction, $context, $lineItemId, $quantity);
|
||||
} catch (RefundNotSupportedException $exception) {
|
||||
$this->logger->info('Payment method does not support online refunds for transaction: ' . $transactionId);
|
||||
return new Response('methodDoesNotSupportRefund', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ($refund === null) {
|
||||
return new Response('Refund was not created. Please check the refund amound or if the item was not refunded before', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
@@ -111,11 +137,33 @@ class RefundController extends AbstractController
|
||||
$transactionId = $request->request->get('transactionId');
|
||||
$refundableAmount = $request->request->get('refundableAmount');
|
||||
|
||||
if ($refundableAmount === null || $refundableAmount <= 0.0) {
|
||||
return new Response('refundAmountZero', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$settings = $this->settingsService->getSettings($salesChannelId);
|
||||
$apiClient = $settings->getApiClient();
|
||||
|
||||
$transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId);
|
||||
$this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
|
||||
|
||||
$completed = (float) $transaction->getCompletedAmount();
|
||||
$refunded = (float) $transaction->getRefundedAmount();
|
||||
$maxRefund = round($completed - $refunded, 2);
|
||||
|
||||
if ($refundableAmount > $maxRefund) {
|
||||
return new Response('refundExceedsAmount', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$refund = $this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
|
||||
} catch (RefundNotSupportedException $exception) {
|
||||
$this->logger->info('Payment method does not support online refunds for transaction: ' . $transactionId);
|
||||
return new Response('methodDoesNotSupportRefund', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if ($refund === null) {
|
||||
return new Response(null, Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
return new Response(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
@@ -142,7 +190,13 @@ class RefundController extends AbstractController
|
||||
$apiClient = $settings->getApiClient();
|
||||
|
||||
$transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId);
|
||||
$this->refundService->createPartialRefund($transaction, $context, $lineItemId, $refundableAmount);
|
||||
|
||||
try {
|
||||
$refund = $this->refundService->createPartialRefund($transaction, $context, $lineItemId, $refundableAmount);
|
||||
} catch (RefundNotSupportedException $exception) {
|
||||
$this->logger->info('Payment method does not support online refunds for transaction: ' . $transactionId);
|
||||
return new Response('methodDoesNotSupportRefund', Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
return new Response(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
@@ -12,14 +12,20 @@ use Shopware\Core\{
|
||||
};
|
||||
use VRPayment\Sdk\{
|
||||
Model\Refund,
|
||||
Model\Transaction
|
||||
Model\Transaction,
|
||||
Model\CriteriaOperator,
|
||||
Model\EntityQueryFilter,
|
||||
Model\EntityQueryFilterType,
|
||||
Model\EntityQuery,
|
||||
ApiException
|
||||
};
|
||||
use VRPaymentPayment\Core\{
|
||||
Api\Refund\Entity\RefundEntity,
|
||||
Api\Transaction\Entity\TransactionEntity,
|
||||
Api\Transaction\Entity\TransactionEntityDefinition,
|
||||
Settings\Service\SettingsService,
|
||||
Util\Payload\RefundPayload
|
||||
Util\Payload\RefundPayload,
|
||||
Util\Exception\RefundNotSupportedException
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -99,6 +105,12 @@ class RefundService
|
||||
$this->upsert($refund, $context);
|
||||
return $refund;
|
||||
}
|
||||
} catch (ApiException $exception) {
|
||||
$message = $exception->getMessage();
|
||||
$this->logger->critical($message);
|
||||
if ($exception->getCode() === 442 && str_contains($message, 'does not support online refunds')) {
|
||||
throw new RefundNotSupportedException($message, 0, $exception);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical($exception->getMessage());
|
||||
}
|
||||
@@ -134,6 +146,12 @@ class RefundService
|
||||
$this->upsert($refund, $context);
|
||||
return $refund;
|
||||
}
|
||||
} catch (ApiException $exception) {
|
||||
$message = $exception->getMessage();
|
||||
$this->logger->critical($message);
|
||||
if ($exception->getCode() === 442 && str_contains($message, 'does not support online refunds')) {
|
||||
throw new RefundNotSupportedException($message, 0, $exception);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical($exception->getMessage());
|
||||
}
|
||||
@@ -170,6 +188,12 @@ class RefundService
|
||||
$this->upsert($refund, $context);
|
||||
return $refund;
|
||||
}
|
||||
} catch (ApiException $exception) {
|
||||
$message = $exception->getMessage();
|
||||
$this->logger->critical($message);
|
||||
if ($exception->getCode() === 442 && str_contains($message, 'does not support online refunds')) {
|
||||
throw new RefundNotSupportedException($message, 0, $exception);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical($exception->getMessage());
|
||||
}
|
||||
@@ -240,5 +264,68 @@ class RefundService
|
||||
)
|
||||
->first();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get total refunded quantity for transaction's line item by lineItemId.
|
||||
*
|
||||
* @param \VRPayment\Sdk\Model\Transaction $transaction
|
||||
* @param \Shopware\Core\Framework\Context $context
|
||||
* @param string $lineItemId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRefundedQuantity(Transaction $transaction, Context $context, string $lineItemId): int {
|
||||
$transactionEntity = $this->getTransactionEntityByTransactionId($transaction->getId(), $context);
|
||||
$settings = $this->settingsService->getSettings($transactionEntity->getSalesChannel()->getId());
|
||||
$apiClient = $settings->getApiClient();
|
||||
|
||||
$entityQueryFilter = (new EntityQueryFilter())
|
||||
->setType(EntityQueryFilterType::LEAF)
|
||||
->setOperator(CriteriaOperator::EQUALS)
|
||||
->setFieldName('transaction.id')
|
||||
->setValue($transaction->getId());
|
||||
|
||||
$query = (new EntityQuery())->setFilter($entityQueryFilter);
|
||||
|
||||
$refunds = $apiClient->getRefundService()->search($settings->getSpaceId(), $query);
|
||||
|
||||
$refundedQuantity = 0;
|
||||
|
||||
foreach ($refunds as $refund) {
|
||||
foreach ($refund->getReductions() as $reduction) {
|
||||
if ($reduction->getLineItemUniqueId() === $lineItemId) {
|
||||
$refundedQuantity += (int) $reduction->getQuantityReduction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $refundedQuantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum quantity of available items to refund for line item.
|
||||
*
|
||||
* @param \VRPayment\Sdk\Model\Transaction $transaction
|
||||
* @param \Shopware\Core\Framework\Context $context
|
||||
* @param string $lineItemId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxRefundableQuantity(Transaction $transaction, Context $context, string $lineItemId): int {
|
||||
|
||||
$originalQuantity = 0;
|
||||
|
||||
foreach ($transaction->getLineItems() as $lineItem) {
|
||||
if ($lineItem->getUniqueId() === $lineItemId) {
|
||||
$originalQuantity = (int) $lineItem->getQuantity();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$refundedQuantity = $this->getRefundedQuantity($transaction, $context, $lineItemId);
|
||||
|
||||
$maxQuantity = $originalQuantity - $refundedQuantity;
|
||||
|
||||
return $maxQuantity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use VRPaymentPayment\Core\Api\Refund\Entity\RefundEntityDefinition;
|
||||
*/
|
||||
class TransactionEntityDefinition extends EntityDefinition {
|
||||
|
||||
public const ENTITY_NAME = 'vrpayment_transaction';
|
||||
public const ENTITY_NAME = 'vrpayment_transaction_data';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
|
||||
@@ -15,6 +15,7 @@ use Shopware\Core\{
|
||||
System\SalesChannel\SalesChannelContext
|
||||
};
|
||||
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
|
||||
use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use VRPayment\Sdk\{
|
||||
Model\AddressCreate,
|
||||
@@ -46,6 +47,9 @@ use VRPaymentPayment\Core\{
|
||||
Util\Payload\TransactionPayload
|
||||
};
|
||||
|
||||
use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity;
|
||||
use Shopware\Core\Framework\Struct\ArrayEntity;
|
||||
|
||||
/**
|
||||
* Class TransactionService
|
||||
*
|
||||
@@ -132,13 +136,12 @@ class TransactionService
|
||||
$settings = $this->settingsService->getSettings($salesChannelId);
|
||||
$apiClient = $settings->getApiClient();
|
||||
|
||||
$failedStates = [
|
||||
TransactionState::DECLINE,
|
||||
TransactionState::FAILED,
|
||||
TransactionState::VOIDED,
|
||||
];
|
||||
$pendingTransaction = $this->read($_SESSION['transactionId'], $salesChannelId);
|
||||
if (in_array($pendingTransaction->getState(), $failedStates)) {
|
||||
$transactionId = $_SESSION['transactionId'] ?? null;
|
||||
if ($transactionId !== null) {
|
||||
$pendingTransaction = $this->read($_SESSION['transactionId'], $salesChannelId);
|
||||
}
|
||||
|
||||
if ($transactionId === null || $pendingTransaction === null || $pendingTransaction->getState() !== TransactionState::PENDING) {
|
||||
unset($_SESSION['transactionId']);
|
||||
$pendingTransactionId = $this->createPendingTransaction($salesChannelContext);
|
||||
$pendingTransaction = $this->read($pendingTransactionId, $salesChannelId);
|
||||
@@ -181,10 +184,19 @@ class TransactionService
|
||||
$transaction->getOrderTransaction()->getPaymentMethodId(),
|
||||
$transaction->getOrder()->getSalesChannelId()
|
||||
);
|
||||
$_SESSION['transactionId'] = null;
|
||||
$_SESSION['arrayOfPossibleMethods'] = null;
|
||||
$_SESSION['addressCheck'] = null;
|
||||
$_SESSION['currencyCheck'] = null;
|
||||
$salesChannelContext->getContext()->addExtension(
|
||||
'checkoutState',
|
||||
new ArrayEntity([
|
||||
'transactionId' => null,
|
||||
'addressHash' => null,
|
||||
'currency' => null,
|
||||
])
|
||||
);
|
||||
|
||||
$salesChannelContext->getContext()->addExtension(
|
||||
'possibleMethods',
|
||||
new ArrayEntity(['ids' => []])
|
||||
);
|
||||
|
||||
|
||||
$this->holdDelivery($transaction->getOrder()->getId(), $salesChannelContext->getContext());
|
||||
@@ -479,14 +491,18 @@ class TransactionService
|
||||
|
||||
/**
|
||||
* @param SalesChannelContext $salesChannelContext
|
||||
* @param CheckoutConfirmPageLoadedEvent|null $event
|
||||
* @param $event
|
||||
* @return int
|
||||
*/
|
||||
public function createPendingTransaction(SalesChannelContext $salesChannelContext, ?CheckoutConfirmPageLoadedEvent $event = null): int
|
||||
{
|
||||
|
||||
public function createPendingTransaction(SalesChannelContext $salesChannelContext, $event = null): int
|
||||
{
|
||||
$expiredTransaction = true;
|
||||
$transactionId = $_SESSION['transactionId'] ?? null;
|
||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||
if (!$settings) {
|
||||
throw new \Exception('Space settings not configured');
|
||||
}
|
||||
|
||||
if ($transactionId) {
|
||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||
@@ -495,6 +511,7 @@ class TransactionService
|
||||
TransactionState::DECLINE,
|
||||
TransactionState::FAILED,
|
||||
TransactionState::VOIDED,
|
||||
null
|
||||
];
|
||||
if (!in_array($pendingTransaction->getState(), $failedStates)) {
|
||||
$expiredTransaction = false;
|
||||
@@ -568,13 +585,20 @@ class TransactionService
|
||||
|
||||
$lineItems = [];
|
||||
if ($event) {
|
||||
$cartLineItems = $event->getPage()->getCart()->getLineItems()->getElements();
|
||||
foreach ($cartLineItems as $cartLineItem) {
|
||||
if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||
continue;
|
||||
}
|
||||
$lineItems[] = $this->createTempLineItem($cartLineItem);
|
||||
}
|
||||
if ($event instanceof CheckoutConfirmPageLoadedEvent) {
|
||||
$cartLineItems = $event->getPage()->getCart()->getLineItems()->getElements();
|
||||
foreach ($cartLineItems as $cartLineItem) {
|
||||
if ($cartLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_CUSTOMIZED_PRODUCTS) {
|
||||
continue;
|
||||
}
|
||||
$lineItems[] = $this->createTempLineItem($cartLineItem);
|
||||
}
|
||||
} elseif ($event instanceof AccountEditOrderPageLoadedEvent) {
|
||||
$order = $event->getPage()->getOrder();
|
||||
foreach ($order->getLineItems() as $orderLineItem) {
|
||||
$lineItems[] = $this->createTempLineItem($orderLineItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customerId = "";
|
||||
@@ -732,16 +756,28 @@ class TransactionService
|
||||
* @param LineItem $productData
|
||||
* @return LineItemCreate
|
||||
*/
|
||||
private function createTempLineItem(LineItem $productData): LineItemCreate
|
||||
{
|
||||
$lineItem = new LineItemCreate();
|
||||
$lineItem->setName($productData->getLabel());
|
||||
$lineItem->setUniqueId($productData->getId());
|
||||
$lineItem->setSku($productData->getId());
|
||||
$lineItem->setQuantity($productData->getQuantity());
|
||||
$lineItem->setAmountIncludingTax($productData->getPrice()->getUnitPrice());
|
||||
$lineItem->setType(LineItemType::PRODUCT);
|
||||
private function createTempLineItem($productData): LineItemCreate
|
||||
{
|
||||
$lineItem = new LineItemCreate();
|
||||
|
||||
return $lineItem;
|
||||
}
|
||||
if ($productData instanceof LineItem) {
|
||||
$lineItem->setName($productData->getLabel());
|
||||
$lineItem->setUniqueId($productData->getId());
|
||||
$lineItem->setSku($productData->getReferencedId() ?? $productData->getId());
|
||||
$lineItem->setQuantity($productData->getQuantity());
|
||||
$lineItem->setAmountIncludingTax($productData->getPrice()->getUnitPrice());
|
||||
} elseif ($productData instanceof OrderLineItemEntity) {
|
||||
$lineItem->setName($productData->getLabel());
|
||||
$lineItem->setUniqueId($productData->getId());
|
||||
$lineItem->setSku($productData->getProductId() ?? $productData->getIdentifier() ?? $productData->getId());
|
||||
$lineItem->setQuantity($productData->getQuantity());
|
||||
$lineItem->setAmountIncludingTax($productData->getUnitPrice());
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Unsupported line item type: ' . get_class($productData));
|
||||
}
|
||||
|
||||
$lineItem->setType(LineItemType::PRODUCT);
|
||||
|
||||
return $lineItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +226,14 @@ class WebHookController extends AbstractController {
|
||||
$this->settings = $this->settingsService->getSettings($salesChannelId);
|
||||
$signature = $request->server->get('HTTP_X_SIGNATURE');
|
||||
$requestJson = json_decode($request->getContent(), true);
|
||||
|
||||
if ($requestJson['eventId'] == null && $requestJson['entityId'] == null && $requestJson['listenerEntityId'] == null && $requestJson['listenerEntityId'] == null && $requestJson['listenerEntityTechnicalName'] == null && $requestJson['spaceId'] == null) {
|
||||
throw new \InvalidArgumentException('Empty webhook');
|
||||
}
|
||||
|
||||
if (!$this->settings->getSpaceId() || !$this->settings->getUserId() || !$this->settings->getApplicationKey()) {
|
||||
throw new \InvalidArgumentException('Not correct webhook configuration for salesChannelId: ' . $salesChannelId . ' Debug: ' . var_dump($requestJson));
|
||||
}
|
||||
$apiClient = $this->settings->getApiClient();
|
||||
$callBackData->assign($requestJson);
|
||||
|
||||
@@ -638,25 +646,46 @@ class WebHookController extends AbstractController {
|
||||
private function unholdDelivery(string $orderId, Context $context): void
|
||||
{
|
||||
try {
|
||||
/**
|
||||
* @var OrderDeliveryStateHandler $orderDeliveryStateHandler
|
||||
*/
|
||||
$order = $this->getOrderEntity($orderId, $context);
|
||||
/**
|
||||
* @var OrderDeliveryEntity $orderDelivery
|
||||
*/
|
||||
$criteria = new Criteria([$orderId]);
|
||||
$criteria->addAssociation('deliveries.stateMachineState');
|
||||
$order = $this->container->get('order.repository')
|
||||
->search($criteria, $context)
|
||||
->first();
|
||||
|
||||
if (!$order) {
|
||||
$this->logger->info('Order not found: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var OrderDeliveryEntity|null $orderDelivery */
|
||||
$orderDelivery = $order->getDeliveries()?->last();
|
||||
|
||||
|
||||
if (null === $orderDelivery) {
|
||||
$this->logger->info('No deliveries found for order: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
if ($orderDelivery->getStateMachineState()?->getTechnicalName() !== OrderDeliveryStateHandler::STATE_HOLD){
|
||||
|
||||
$orderDeliveryState = $orderDelivery->getStateMachineState();
|
||||
if (!$orderDeliveryState) {
|
||||
$this->logger->info('Order delivery state is null for order: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
$technicalName = $orderDeliveryState->getTechnicalName();
|
||||
$this->logger->info('Order delivery state: ' . $technicalName);
|
||||
|
||||
if ($technicalName !== OrderDeliveryStateHandler::STATE_HOLD) {
|
||||
$this->logger->info('Order delivery is not on hold, skipping unhold process.');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var OrderDeliveryStateHandler $orderDeliveryStateHandler */
|
||||
$orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class);
|
||||
$orderDeliveryStateHandler->unhold($orderDelivery->getId(), $context);
|
||||
|
||||
$this->logger->info('Successfully unheld order delivery for order: ' . $orderId);
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->info($exception->getMessage(), $exception->getTrace());
|
||||
$this->logger->error('Error unholding order delivery: ' . $exception->getMessage(), $exception->getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,7 +718,7 @@ class WebHookController extends AbstractController {
|
||||
* @var OrderDeliveryEntity $orderDelivery
|
||||
*/
|
||||
$orderDelivery = $order->getDeliveries()?->last();
|
||||
|
||||
|
||||
if (null === $orderDelivery) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,41 +125,21 @@ class WebHookRefundStrategy extends WebHookStrategyBase implements WebhookStrate
|
||||
$orderId = $this->getOrderIdByTransaction($refund);
|
||||
|
||||
if(!empty($orderId)) {
|
||||
|
||||
$this->executeLocked($orderId, $context, function () use ($orderId, $refund, $context, $request) {
|
||||
if ($request->getListenerEntityTechnicalName() == WebHookRequest::REFUND && $request->getState() == RefundState::SUCCESSFUL) {
|
||||
$this->refundService->upsert($refund, $context);
|
||||
$orderTransactionId = $refund->getTransaction()->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID];
|
||||
$orderTransaction = $this->getOrderTransaction($orderId, $context);
|
||||
|
||||
$this->refundService->upsert($refund, $context);
|
||||
|
||||
$orderTransactionId = $refund->getTransaction()->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID];
|
||||
$orderTransaction = $this->getOrderTransaction($orderId, $context);
|
||||
if (
|
||||
in_array(
|
||||
$orderTransaction->getStateMachineState()?->getTechnicalName(),
|
||||
[
|
||||
OrderTransactionStates::STATE_PAID,
|
||||
OrderTransactionStates::STATE_PARTIALLY_PAID,
|
||||
]
|
||||
) &&
|
||||
($request->getState() == RefundState::SUCCESSFUL)
|
||||
) {
|
||||
if ($refund->getAmount() == $orderTransaction->getAmount()->getTotalPrice()) {
|
||||
$this->orderTransactionStateHandler->refund($orderTransactionId, $context);
|
||||
} else {
|
||||
if ($refund->getAmount() < $orderTransaction->getAmount()->getTotalPrice()) {
|
||||
$this->orderTransactionStateHandler->refundPartially($orderTransactionId, $context);
|
||||
}
|
||||
}
|
||||
} elseif ($orderTransaction->getStateMachineState()?->getTechnicalName() ===
|
||||
OrderTransactionStates::STATE_PARTIALLY_REFUNDED &&
|
||||
($request->getState() == RefundState::SUCCESSFUL)
|
||||
) {
|
||||
$transactionByOrderTransactionId = $this->transactionService->getByOrderTransactionId($orderTransactionId, $context);
|
||||
$totalRefundedAmount = $this->getTotalRefundedAmount($transactionByOrderTransactionId->getTransactionId(), $context);
|
||||
if (floatval($orderTransaction->getAmount()->getTotalPrice()) - $totalRefundedAmount <= 0) {
|
||||
$leftToRefund = floatval($orderTransaction->getAmount()->getTotalPrice()) - $totalRefundedAmount;
|
||||
if ($leftToRefund > 0) {
|
||||
$this->orderTransactionStateHandler->refundPartially($orderTransactionId, $context);
|
||||
} elseif ($leftToRefund === floatval(0)) { // This trick is used, because it's float type and 0 is int
|
||||
$this->orderTransactionStateHandler->refund($orderTransactionId, $context);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +306,14 @@ abstract class WebHookStrategyBase implements WebHookStrategyInterface {
|
||||
return $this->getOrderEntity($orderId, $context)->getTransactions()->last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unholds the delivery of an order.
|
||||
*
|
||||
* This method changes the state of an order's last delivery from 'held' to 'released', allowing further processing like shipping.
|
||||
*
|
||||
* @param string $orderId The unique identifier of the order.
|
||||
* @param Context $context The context of the current operation, including scope and permissions.
|
||||
*/
|
||||
/**
|
||||
* Unholds the delivery of an order.
|
||||
*
|
||||
@@ -317,22 +325,46 @@ abstract class WebHookStrategyBase implements WebHookStrategyInterface {
|
||||
protected function unholdDelivery(string $orderId, Context $context): void
|
||||
{
|
||||
try {
|
||||
$order = $this->getOrderEntity($orderId, $context);
|
||||
/** @var OrderDeliveryEntity $orderDelivery */
|
||||
$orderDelivery = $order->getDeliveries()?->last();
|
||||
|
||||
if (null === $orderDelivery) {
|
||||
$criteria = new Criteria([$orderId]);
|
||||
$criteria->addAssociation('deliveries.stateMachineState');
|
||||
$order = $this->container->get('order.repository')
|
||||
->search($criteria, $context)
|
||||
->first();
|
||||
|
||||
if (!$order) {
|
||||
$this->logger->info('Order not found: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($orderDelivery->getStateMachineState()?->getTechnicalName() !== OrderDeliveryStateHandler::STATE_HOLD){
|
||||
/** @var OrderDeliveryEntity|null $orderDelivery */
|
||||
$orderDelivery = $order->getDeliveries()?->last();
|
||||
|
||||
if (null === $orderDelivery) {
|
||||
$this->logger->info('No deliveries found for order: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
$orderDeliveryState = $orderDelivery->getStateMachineState();
|
||||
if (!$orderDeliveryState) {
|
||||
$this->logger->info('Order delivery state is null for order: ' . $orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
$technicalName = $orderDeliveryState->getTechnicalName();
|
||||
$this->logger->info('Order delivery state: ' . $technicalName);
|
||||
|
||||
if ($technicalName !== OrderDeliveryStateHandler::STATE_HOLD) {
|
||||
$this->logger->info('Order delivery is not on hold, skipping unhold process.');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var OrderDeliveryStateHandler $orderDeliveryStateHandler */
|
||||
$orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class);
|
||||
$orderDeliveryStateHandler->unhold($orderDelivery->getId(), $context);
|
||||
|
||||
$this->logger->info('Successfully unheld order delivery for order: ' . $orderId);
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->info($exception->getMessage(), $exception->getTrace());
|
||||
$this->logger->error('Error unholding order delivery: ' . $exception->getMessage(), $exception->getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +394,7 @@ abstract class WebHookStrategyBase implements WebHookStrategyInterface {
|
||||
$orderDeliveryStateHandler = $this->container->get(OrderDeliveryStateHandler::class);
|
||||
/** @var OrderDeliveryEntity $orderDelivery */
|
||||
$orderDelivery = $order->getDeliveries()?->last();
|
||||
|
||||
|
||||
if (null === $orderDelivery) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Shopware\Core\{
|
||||
Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface,
|
||||
Checkout\Payment\Exception\AsyncPaymentFinalizeException,
|
||||
Checkout\Payment\Exception\AsyncPaymentProcessException,
|
||||
Checkout\Payment\PaymentException,
|
||||
Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException,
|
||||
Framework\Validation\DataBag\RequestDataBag,
|
||||
System\SalesChannel\SalesChannelContext
|
||||
@@ -140,7 +141,7 @@ class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
|
||||
]);
|
||||
unset($_SESSION['transactionId']);
|
||||
$this->logger->info($errorMessage);
|
||||
throw new \Exception($transaction->getOrder()->getId());
|
||||
throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage);
|
||||
}
|
||||
} else {
|
||||
$this->orderTransactionStateHandler->paid($transaction->getOrderTransaction()->getId(), $salesChannelContext->getContext());
|
||||
|
||||
@@ -95,7 +95,7 @@ class SettingsCommand extends Command {
|
||||
SettingsService::CONFIG_INTEGRATION,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
SettingsService::CONFIG_INTEGRATION,
|
||||
Integration::IFRAME
|
||||
Integration::PAYMENT_PAGE
|
||||
)
|
||||
->addOption(
|
||||
SettingsService::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED,
|
||||
|
||||
@@ -29,6 +29,31 @@ class SettingsService {
|
||||
public const CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED = 'storefrontWebhooksUpdateEnabled';
|
||||
public const CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED = 'storefrontPaymentsUpdateEnabled';
|
||||
|
||||
/**
|
||||
* List of config properties whose values allowed to be empty without triggering a warning in logger.
|
||||
*
|
||||
* This list is derived from testing of all config properties. The plugin fails only when either spaceId, userId, applicationKey and/or integration is empty.
|
||||
* On top of that, spaceId, userId, applicationKey are marked as "required" input fields in admin interface.
|
||||
*
|
||||
* It is worth considering updating this list whenever a new config is introduced in settings.
|
||||
* If new config is optional, left empty by design and not required for transactions to work, this list should be updated to avoid false-positive warnings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const ALLOWED_EMPTY_CONFIGS = [
|
||||
// Options
|
||||
self::CONFIG_SPACE_VIEW_ID,
|
||||
self::CONFIG_LINE_ITEM_CONSISTENCY_ENABLED,
|
||||
self::CONFIG_EMAIL_ENABLED,
|
||||
|
||||
// Storefront Options
|
||||
self::CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED,
|
||||
|
||||
// Advanced Options
|
||||
self::CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED,
|
||||
self::CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\System\SystemConfig\SystemConfigService
|
||||
*/
|
||||
@@ -132,7 +157,13 @@ class SettingsService {
|
||||
if ($property === '') {
|
||||
continue;
|
||||
}
|
||||
if (!is_numeric($value) && empty($value)) {
|
||||
// Space view id is only numeric setting which can be 0. If it is, rest of the loop is skipped.
|
||||
if ($property === self::CONFIG_SPACE_VIEW_ID && $value === 0) {
|
||||
$propertyValuePairs[$property] = $value;
|
||||
continue;
|
||||
}
|
||||
// Check if $value is empty and is not in the list of configs which are allowed to be empty
|
||||
if (empty($value) && !in_array($property, self::ALLOWED_EMPTY_CONFIGS, true)) {
|
||||
$this->logger->warning(strtr('Empty value :value for settings :property.', [':property' => $property, ':value' => $value]));
|
||||
}
|
||||
$propertyValuePairs[$property] = $value;
|
||||
|
||||
@@ -2,15 +2,21 @@
|
||||
|
||||
namespace VRPaymentPayment\Core\Storefront\Checkout\Controller;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\{
|
||||
Log\LoggerInterface,
|
||||
Cache\CacheItemPoolInterface
|
||||
};
|
||||
use Shopware\Core\{
|
||||
Checkout\Payment\PaymentException,
|
||||
Checkout\Cart\Cart,
|
||||
Checkout\Cart\CartException,
|
||||
Checkout\Cart\LineItemFactoryRegistry,
|
||||
Checkout\Cart\SalesChannel\CartService,
|
||||
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection,
|
||||
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
|
||||
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler,
|
||||
Checkout\Order\OrderEntity,
|
||||
Checkout\Order\OrderDefinition,
|
||||
Checkout\Order\SalesChannel\AbstractOrderRoute,
|
||||
Framework\Context,
|
||||
Framework\DataAbstractionLayer\Search\Criteria,
|
||||
@@ -21,7 +27,9 @@ use Shopware\Core\{
|
||||
Framework\Uuid\Uuid,
|
||||
Framework\Uuid\Exception\InvalidUuidException,
|
||||
Framework\Validation\DataBag\RequestDataBag,
|
||||
System\SalesChannel\SalesChannelContext
|
||||
System\SalesChannel\SalesChannelContext,
|
||||
System\StateMachine\StateMachineRegistry,
|
||||
System\StateMachine\Transition,
|
||||
};
|
||||
use Shopware\Storefront\{
|
||||
Controller\StorefrontController,
|
||||
@@ -31,9 +39,13 @@ use Shopware\Storefront\{
|
||||
use Symfony\Component\{
|
||||
HttpFoundation\Request,
|
||||
HttpFoundation\Response,
|
||||
HttpFoundation\RedirectResponse,
|
||||
Routing\Attribute\Route,
|
||||
Routing\Generator\UrlGeneratorInterface
|
||||
Routing\Generator\UrlGeneratorInterface,
|
||||
Cache\Adapter\FilesystemAdapter,
|
||||
DependencyInjection\ParameterBag\ParameterBagInterface
|
||||
};
|
||||
use Symfony\Contracts\Cache\ItemInterface;
|
||||
use VRPayment\Sdk\{
|
||||
Model\Transaction,
|
||||
Model\TransactionState
|
||||
@@ -43,10 +55,10 @@ use VRPaymentPayment\Core\{
|
||||
Settings\Options\Integration,
|
||||
Settings\Service\SettingsService,
|
||||
Storefront\Checkout\Struct\CheckoutPageData,
|
||||
Util\Payload\CustomProducts\CustomProductsLineItemTypes
|
||||
Util\Payload\CustomProducts\CustomProductsLineItemTypes,
|
||||
Util\Payload\TransactionPayload
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Class CheckoutController
|
||||
*
|
||||
@@ -57,6 +69,18 @@ use VRPaymentPayment\Core\{
|
||||
#[Route(defaults: ['_routeScope' => ['storefront']])]
|
||||
class CheckoutController extends StorefrontController {
|
||||
|
||||
public const ORDER_STATE_CANCEL = 'cancel';
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\System\StateMachine\StateMachineRegistry
|
||||
*/
|
||||
private $stateMachineRegistry;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler
|
||||
*/
|
||||
protected $orderTransactionStateHandler;
|
||||
|
||||
/**
|
||||
* @var \Shopware\Storefront\Page\GenericPageLoader
|
||||
*/
|
||||
@@ -97,6 +121,11 @@ class CheckoutController extends StorefrontController {
|
||||
*/
|
||||
private $orderRoute;
|
||||
|
||||
/**
|
||||
* @var \Psr\Cache\CacheItemPoolInterface
|
||||
*/
|
||||
private CacheItemPoolInterface $cache;
|
||||
|
||||
/**
|
||||
* PaymentController constructor.
|
||||
*
|
||||
@@ -106,6 +135,9 @@ class CheckoutController extends StorefrontController {
|
||||
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
|
||||
* @param \Shopware\Storefront\Page\GenericPageLoaderInterface $genericLoader
|
||||
* @param \Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute $orderRoute
|
||||
* @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler $orderTransactionStateHandler
|
||||
* @param \Shopware\Core\System\StateMachine\StateMachineRegistry $stateMachineRegistry
|
||||
* @param Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface $params
|
||||
*/
|
||||
public function __construct(
|
||||
LineItemFactoryRegistry $lineItemFactoryRegistry,
|
||||
@@ -113,7 +145,10 @@ class CheckoutController extends StorefrontController {
|
||||
SettingsService $settingsService,
|
||||
TransactionService $transactionService,
|
||||
GenericPageLoaderInterface $genericLoader,
|
||||
AbstractOrderRoute $orderRoute
|
||||
AbstractOrderRoute $orderRoute,
|
||||
OrderTransactionStateHandler $orderTransactionStateHandler,
|
||||
StateMachineRegistry $stateMachineRegistry,
|
||||
ParameterBagInterface $params
|
||||
)
|
||||
{
|
||||
$this->cartService = $cartService;
|
||||
@@ -122,6 +157,9 @@ class CheckoutController extends StorefrontController {
|
||||
$this->transactionService = $transactionService;
|
||||
$this->lineItemFactoryRegistry = $lineItemFactoryRegistry;
|
||||
$this->orderRoute = $orderRoute;
|
||||
$this->orderTransactionStateHandler = $orderTransactionStateHandler;
|
||||
$this->stateMachineRegistry = $stateMachineRegistry;
|
||||
$this->cache = new FilesystemAdapter('vrpayment', 0, rtrim($params->get('kernel.cache_dir'), '/') . '/vrpayment-cache');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,6 +392,45 @@ class CheckoutController extends StorefrontController {
|
||||
throw new MissingRequestParameterException('orderId');
|
||||
}
|
||||
|
||||
// Adoption for Headless Storefronts
|
||||
$orderRepo = $this->container->get('order.repository');
|
||||
$criteria = new Criteria([$orderId]);
|
||||
|
||||
$orderEntity = $orderRepo->search($criteria, $salesChannelContext->getContext())->first();
|
||||
|
||||
if($orderEntity->getSalesChannelId() !== $salesChannelContext->getSalesChannelId()) {
|
||||
$this->settings = $this->settingsService->getSettings($orderEntity->getSalesChannelId());
|
||||
$trans = $this->getTransaction($orderId, $salesChannelContext->getContext());
|
||||
|
||||
// Adoption in case of duplicate requests
|
||||
// Get order specific value from cache
|
||||
$cacheKey = 'vrpayment_recreate_order_' . $orderId;
|
||||
$isFound = $this->cache->get($cacheKey, function (ItemInterface $item) {
|
||||
$item->expiresAfter(10);
|
||||
return false;
|
||||
});
|
||||
|
||||
// If value is found in cache - send user directly to successful checkout confirmation page for unpaid transactions
|
||||
if ($isFound === true && in_array($trans->getState(), [TransactionState::FAILED])) {
|
||||
$unpaidUrl = $this->getUnpaidUrlFromToken($trans->getSuccessUrl())
|
||||
?? $this->buildUnpaidUrl($orderEntity->getSalesChannelId(), $salesChannelContext, $orderId);
|
||||
if ($unpaidUrl) {
|
||||
return new RedirectResponse(
|
||||
$unpaidUrl . (parse_url($unpaidUrl, \PHP_URL_QUERY) ? '&' : '?') . 'error-code=' . PaymentException::PAYMENT_CUSTOMER_CANCELED_EXTERNAL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache order specific value for some time on first request
|
||||
$this->cache->delete($cacheKey);
|
||||
$this->cache->get($cacheKey, function (ItemInterface $item) {
|
||||
$item->expiresAfter(10);
|
||||
return true;
|
||||
});
|
||||
return $this->redirect($trans->getSuccessUrl());
|
||||
}
|
||||
// End Adoption for Headless Storefronts
|
||||
|
||||
try {
|
||||
$this->cartService->deleteCart($salesChannelContext);
|
||||
$cart = $this->cartService->createNew($salesChannelContext->getToken());
|
||||
@@ -367,6 +444,7 @@ class CheckoutController extends StorefrontController {
|
||||
}
|
||||
|
||||
$transaction = $this->getTransaction($orderId, $salesChannelContext->getContext());
|
||||
$orderTransactionId = $transaction->getMetaData()[TransactionPayload::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID];
|
||||
if (!empty($transaction->getUserFailureMessage())) {
|
||||
$this->addFlash('danger', $transaction->getUserFailureMessage());
|
||||
}
|
||||
@@ -401,6 +479,18 @@ class CheckoutController extends StorefrontController {
|
||||
|
||||
}
|
||||
|
||||
// Close the old, existing order to prevent confusion for the customer
|
||||
$this->orderTransactionStateHandler->cancel($orderTransactionId, $salesChannelContext->getContext());
|
||||
$this->stateMachineRegistry->transition(
|
||||
new Transition(
|
||||
OrderDefinition::ENTITY_NAME,
|
||||
$orderId,
|
||||
self::ORDER_STATE_CANCEL,
|
||||
'stateId'
|
||||
),
|
||||
$salesChannelContext->getContext()
|
||||
);
|
||||
|
||||
} catch (\Exception $exception) {
|
||||
$this->addFlash('danger', $this->trans('error.addToCartError'));
|
||||
$this->logger->critical($exception->getMessage());
|
||||
@@ -410,6 +500,74 @@ class CheckoutController extends StorefrontController {
|
||||
return $this->redirectToRoute('frontend.checkout.confirm.page');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return successful checkout confirmation url for unpaid transactions.
|
||||
*
|
||||
* It achieves that by getting payment token from successUrl, parsing and decoding
|
||||
* it, and finally reading the claims.
|
||||
*
|
||||
* @param string $successUrl
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function getUnpaidUrlFromToken(string $successUrl): ?string {
|
||||
$query = [];
|
||||
parse_str((string) parse_url($successUrl, PHP_URL_QUERY), $query);
|
||||
$jwt = $query['_sw_payment_token'] ?? null;
|
||||
|
||||
if (!$jwt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = explode('.', $jwt, 3);
|
||||
if (count($data) !== 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[, $c, ] = $data;
|
||||
|
||||
try {
|
||||
$urlSafeData = strtr($c, '-_', '+/');
|
||||
$paddedData = str_pad($urlSafeData, \strlen($urlSafeData) % 4, '=');
|
||||
$decoded = base64_decode($paddedData, true);
|
||||
if (!$decoded) {
|
||||
return null;
|
||||
}
|
||||
$claims = json_decode(json: $decoded, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$unpaidUrl = $claims['eul'] ?? null;
|
||||
return $unpaidUrl;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning("CheckoutController::getUnpaidUrlFromToken - JWT parse failed: {errorMessage}", [
|
||||
'errorMessage' => $e->getMessage(),
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return successful checkout confirmation url for unpaid transactions.
|
||||
*
|
||||
* It achieves that by fetching headless storefront's base url,
|
||||
* and building custom url.
|
||||
*
|
||||
* @param string $salesChannelId
|
||||
* @param SalesChannelContext $salesChannelContext
|
||||
* @param string $orderId
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function buildUnpaidUrl(string $salesChannelId, SalesChannelContext $salesChannelContext, string $orderId): ?string {
|
||||
$salesChannelDomainRepo = $this->container->get('sales_channel_domain.repository');
|
||||
$criteria = new Criteria();
|
||||
$criteria->addFilter(new EqualsFilter('salesChannelId', $salesChannelId))->setLimit(10);
|
||||
$domain = $salesChannelDomainRepo->search($criteria, $salesChannelContext->getContext())->first();
|
||||
if(!$domain) {
|
||||
return null;
|
||||
}
|
||||
$baseUrl = rtrim($domain->getUrl(), '/');
|
||||
return sprintf('%s/checkout/success/%s/unpaid', $baseUrl, $orderId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OrderLineItemCollection $orderItems
|
||||
*
|
||||
|
||||
@@ -4,33 +4,42 @@ namespace VRPaymentPayment\Core\Storefront\Checkout\Subscriber;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shopware\Core\{Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection,
|
||||
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates,
|
||||
Checkout\Order\OrderEntity,
|
||||
Content\MailTemplate\Service\Event\MailBeforeValidateEvent};
|
||||
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates,
|
||||
Checkout\Order\OrderEntity,
|
||||
Content\MailTemplate\Service\Event\MailBeforeValidateEvent};
|
||||
use Shopware\Core\Checkout\Payment\PaymentMethodCollection;
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
|
||||
use Shopware\Core\System\SalesChannel\SalesChannelContext;
|
||||
use Shopware\Storefront\Page\Account\Order\AccountEditOrderPageLoadedEvent;
|
||||
use Shopware\Storefront\Page\Account\PaymentMethod\AccountPaymentMethodPageLoadedEvent;
|
||||
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
|
||||
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use VRPaymentPayment\Core\{Api\Transaction\Service\OrderMailService,
|
||||
Api\Transaction\Service\TransactionService,
|
||||
Checkout\PaymentHandler\VRPaymentPaymentHandler,
|
||||
Settings\Service\SettingsService,
|
||||
Settings\Struct\Settings,
|
||||
Util\PaymentMethodUtil};
|
||||
use VRPaymentPayment\Core\{Api\Transaction\Service\TransactionService,
|
||||
Checkout\PaymentHandler\VRPaymentPaymentHandler,
|
||||
Settings\Service\SettingsService,
|
||||
Settings\Struct\Settings,
|
||||
Util\PaymentMethodUtil};
|
||||
use VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Service\PaymentMethodConfigurationService;
|
||||
use VRPaymentPayment\Sdk\{Model\AddressCreate,
|
||||
Model\ChargeAttempt,
|
||||
Model\CreationEntityState,
|
||||
Model\CriteriaOperator,
|
||||
Model\EntityQuery,
|
||||
Model\EntityQueryFilter,
|
||||
Model\EntityQueryFilterType,
|
||||
Model\LineItemAttributeCreate,
|
||||
Model\LineItemCreate,
|
||||
Model\LineItemType,
|
||||
Model\TaxCreate,
|
||||
Model\Transaction,
|
||||
Model\TransactionCreate,
|
||||
Model\TransactionPending};
|
||||
Model\ChargeAttempt,
|
||||
Model\CreationEntityState,
|
||||
Model\CriteriaOperator,
|
||||
Model\EntityQuery,
|
||||
Model\EntityQueryFilter,
|
||||
Model\EntityQueryFilterType,
|
||||
Model\LineItemAttributeCreate,
|
||||
Model\LineItemCreate,
|
||||
Model\LineItemType,
|
||||
Model\TaxCreate,
|
||||
Model\Transaction,
|
||||
Model\TransactionCreate,
|
||||
Model\TransactionPending};
|
||||
use Shopware\Core\Framework\Struct\ArrayEntity;
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
|
||||
|
||||
/**
|
||||
* Class CheckoutSubscriber
|
||||
@@ -65,6 +74,9 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
||||
*/
|
||||
private $paymentMethodUtil;
|
||||
|
||||
/** @var EntityRepository */
|
||||
private EntityRepository $paymentMethodRepository;
|
||||
|
||||
/**
|
||||
* CheckoutSubscriber constructor.
|
||||
*
|
||||
@@ -73,12 +85,13 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
||||
* @param \VRPaymentPayment\Core\Settings\Service\SettingsService $settingsService
|
||||
* @param \VRPaymentPayment\Core\Util\PaymentMethodUtil $paymentMethodUtil
|
||||
*/
|
||||
public function __construct(PaymentMethodConfigurationService $paymentMethodConfigurationService, TransactionService $transactionService, SettingsService $settingsService, PaymentMethodUtil $paymentMethodUtil)
|
||||
public function __construct(PaymentMethodConfigurationService $paymentMethodConfigurationService, TransactionService $transactionService, SettingsService $settingsService, PaymentMethodUtil $paymentMethodUtil, EntityRepository $paymentMethodRepository)
|
||||
{
|
||||
$this->paymentMethodConfigurationService = $paymentMethodConfigurationService;
|
||||
$this->transactionService = $transactionService;
|
||||
$this->settingsService = $settingsService;
|
||||
$this->paymentMethodUtil = $paymentMethodUtil;
|
||||
$this->paymentMethodConfigurationService = $paymentMethodConfigurationService;
|
||||
$this->transactionService = $transactionService;
|
||||
$this->settingsService = $settingsService;
|
||||
$this->paymentMethodUtil = $paymentMethodUtil;
|
||||
$this->paymentMethodRepository = $paymentMethodRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,8 +112,10 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
CheckoutConfirmPageLoadedEvent::class => ['onConfirmPageLoaded', 1],
|
||||
MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1],
|
||||
CheckoutConfirmPageLoadedEvent::class => 'onCheckoutConfirmLoaded',
|
||||
AccountEditOrderPageLoadedEvent::class => 'onAccountOrderEditLoaded',
|
||||
AccountPaymentMethodPageLoadedEvent::class => 'onAccountPaymentMethodLoaded',
|
||||
MailBeforeValidateEvent::class => ['onMailBeforeValidate', 1],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -152,105 +167,232 @@ class CheckoutSubscriber implements EventSubscriberInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event
|
||||
*/
|
||||
public function onConfirmPageLoaded(CheckoutConfirmPageLoadedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$salesChannelContext = $event->getSalesChannelContext();
|
||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||
if (is_null($settings)) {
|
||||
$this->logger->notice('Removing payment methods because settings are invalid');
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
/**
|
||||
* @param CheckoutConfirmPageLoadedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function onCheckoutConfirmLoaded(CheckoutConfirmPageLoadedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$salesChannelContext = $event->getSalesChannelContext();
|
||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||
if (is_null($settings)) {
|
||||
$this->logger->notice('Removing payment methods because settings are invalid');
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
|
||||
$createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
||||
$this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId);
|
||||
$createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
||||
$this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId);
|
||||
|
||||
$this->getAvailablePaymentMethods($settings, $createdTransactionId);
|
||||
$this->setPossiblePaymentMethods($settings->getSpaceId(), $event);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
}
|
||||
$this->getAvailablePaymentMethods($settings, $createdTransactionId, $salesChannelContext);
|
||||
$this->setPossiblePaymentMethods($settings->getSpaceId(), $event);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event
|
||||
*/
|
||||
private function removeVRPaymentPaymentMethodFromConfirmPage(CheckoutConfirmPageLoadedEvent $event): void
|
||||
{
|
||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
||||
$paymentMethodIds = $this->paymentMethodUtil->getVRPaymentPaymentMethodIds($event->getContext());
|
||||
foreach ($paymentMethodIds as $paymentMethodId) {
|
||||
$paymentMethodCollection->remove($paymentMethodId);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param AccountEditOrderPageLoadedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function onAccountOrderEditLoaded(AccountEditOrderPageLoadedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$this->handlePaymentMethodFiltering($event);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Settings $settings
|
||||
* @param int $createdTransactionId
|
||||
* @return void
|
||||
*/
|
||||
private function getAvailablePaymentMethods(Settings $settings, int $createdTransactionId): void
|
||||
{
|
||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||
$possiblePaymentMethods = $transactionService->fetchPaymentMethods($settings->getSpaceId(), $createdTransactionId, 'iframe');
|
||||
$arrayOfPossibleMethods = [];
|
||||
foreach ($possiblePaymentMethods as $possiblePaymentMethod) {
|
||||
$arrayOfPossibleMethods[] = $possiblePaymentMethod->getid();
|
||||
}
|
||||
$_SESSION['arrayOfPossibleMethods'] = $arrayOfPossibleMethods;
|
||||
}
|
||||
/**
|
||||
* @param AccountPaymentMethodPageLoadedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function onAccountPaymentMethodLoaded(AccountPaymentMethodPageLoadedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$this->handlePaymentMethodFiltering($event);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $spaceId
|
||||
* @param CheckoutConfirmPageLoadedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
private function setPossiblePaymentMethods(int $spaceId, CheckoutConfirmPageLoadedEvent $event): void
|
||||
{
|
||||
$localPaymentMethods = [];
|
||||
$paymentMethodConfigurations = $this->paymentMethodConfigurationService->getAllPaymentMethodConfigurations($spaceId, $event->getSalesChannelContext()->getContext());
|
||||
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
||||
$localPaymentMethods[$paymentMethodConfiguration->getId()] = $paymentMethodConfiguration->getPaymentMethodConfigurationId();
|
||||
}
|
||||
/**
|
||||
* @param \Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent $event
|
||||
*/
|
||||
public function onConfirmPageLoaded(CheckoutConfirmPageLoadedEvent $event): void
|
||||
{
|
||||
try {
|
||||
$this->handlePaymentMethodFiltering($event);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
}
|
||||
}
|
||||
|
||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
||||
foreach ($paymentMethodCollection as $paymentMethodCollectionItem) {
|
||||
$isVRPaymentPM = VRPaymentPaymentHandler::class == $paymentMethodCollectionItem->getHandlerIdentifier();
|
||||
if (!$isVRPaymentPM) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
private function handlePaymentMethodFiltering($event): void
|
||||
{
|
||||
$salesChannelContext = $event->getSalesChannelContext();
|
||||
$settings = $this->settingsService->getValidSettings($salesChannelContext->getSalesChannel()->getId());
|
||||
|
||||
$paymentMethodConfigurationId = $localPaymentMethods[$paymentMethodCollectionItem->getId()];
|
||||
if (!\in_array($paymentMethodConfigurationId, $_SESSION['arrayOfPossibleMethods'])) {
|
||||
$paymentMethodCollection->remove($paymentMethodCollectionItem->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_null($settings)) {
|
||||
$this->logger->notice('Removing payment methods because settings are invalid');
|
||||
$this->removeVRPaymentPaymentMethodFromConfirmPage($event);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SalesChannelContext $salesChannelContext
|
||||
* @param int $createdTransactionId
|
||||
* @return void
|
||||
*/
|
||||
private function updateTempTransactionIfNeeded(SalesChannelContext $salesChannelContext, int $createdTransactionId): void
|
||||
{
|
||||
$addressCheck = $_SESSION['addressCheck'] ?? null;
|
||||
$currencyCheck = $_SESSION['currencyCheck'] ?? null;
|
||||
$createdTransactionId = $this->transactionService->createPendingTransaction($salesChannelContext, $event);
|
||||
$this->updateTempTransactionIfNeeded($salesChannelContext, $createdTransactionId);
|
||||
|
||||
$customer = $salesChannelContext->getCustomer();
|
||||
$addressHash = md5(json_encode((array)$customer));
|
||||
$currency = $salesChannelContext->getCurrency()->getIsoCode();
|
||||
if (($addressCheck && $currencyCheck) && $addressCheck !== $addressHash || $currencyCheck !== $currency) {
|
||||
if ($createdTransactionId) {
|
||||
$this->transactionService->updateTempTransaction($salesChannelContext, $createdTransactionId);
|
||||
}
|
||||
$_SESSION['arrayOfPossibleMethods'] = null;
|
||||
$_SESSION['addressCheck'] = $addressHash;
|
||||
$_SESSION['currencyCheck'] = $currency;
|
||||
}
|
||||
}
|
||||
$this->getAvailablePaymentMethods($settings, $createdTransactionId, $salesChannelContext);
|
||||
$this->setPossiblePaymentMethods($settings->getSpaceId(), $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $event
|
||||
* @return void
|
||||
*/
|
||||
private function removeVRPaymentPaymentMethodFromConfirmPage($event): void
|
||||
{
|
||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
||||
$paymentMethodIds = $this->paymentMethodUtil->getVRPaymentPaymentMethodIds($event->getContext());
|
||||
foreach ($paymentMethodIds as $paymentMethodId) {
|
||||
$paymentMethodCollection->remove($paymentMethodId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Settings $settings
|
||||
* @param int $createdTransactionId
|
||||
* @return void
|
||||
*/
|
||||
private function getAvailablePaymentMethods(Settings $settings, int $createdTransactionId, SalesChannelContext $salesChannelContext): void
|
||||
{
|
||||
$transactionService = $settings->getApiClient()->getTransactionService();
|
||||
$possiblePaymentMethods = $transactionService->fetchPaymentMethods(
|
||||
$settings->getSpaceId(),
|
||||
$createdTransactionId,
|
||||
$settings->getIntegration()
|
||||
);
|
||||
$arrayOfPossibleMethods = [];
|
||||
foreach ($possiblePaymentMethods as $possiblePaymentMethod) {
|
||||
$arrayOfPossibleMethods[] = $possiblePaymentMethod->getId();
|
||||
}
|
||||
|
||||
$salesChannelContext->getContext()->addExtension(
|
||||
'possibleMethods',
|
||||
new ArrayEntity(['ids' => $arrayOfPossibleMethods])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $spaceId
|
||||
* @param CheckoutConfirmPageLoadedEvent $event
|
||||
* @return void
|
||||
*/
|
||||
private function setPossiblePaymentMethods(int $spaceId, $event): void
|
||||
{
|
||||
$paymentIds = [];
|
||||
$paymentMethodCollection = $event->getPage()->getPaymentMethods();
|
||||
|
||||
foreach ($paymentMethodCollection as $paymentMethodCollectionItem) {
|
||||
$isVRPaymentPM = VRPaymentPaymentHandler::class === $paymentMethodCollectionItem->getHandlerIdentifier();
|
||||
if (!$isVRPaymentPM) {
|
||||
$paymentIds[] = $paymentMethodCollectionItem->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$allowedWLMethods = [];
|
||||
$paymentMethodConfigurations = $this->paymentMethodConfigurationService
|
||||
->getAllPaymentMethodConfigurations($spaceId, $event->getSalesChannelContext()->getContext());
|
||||
|
||||
foreach ($paymentMethodConfigurations as $paymentMethodConfiguration) {
|
||||
if ($paymentMethodConfiguration->getPaymentMethod() === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$pmId = $paymentMethodConfiguration->getPaymentMethod()->getId();
|
||||
$pmConfigId = $paymentMethodConfiguration->getPaymentMethodConfigurationId();
|
||||
$allowedIds = $this->getAllowedPaymentMethodIds($event->getSalesChannelContext());
|
||||
|
||||
if ($paymentMethodConfiguration->getSpaceId() === $spaceId
|
||||
&& \in_array($pmConfigId, $allowedIds, true)) {
|
||||
$allowedWLMethods[] = $pmId;
|
||||
}
|
||||
}
|
||||
|
||||
$allPaymentIds = array_unique(array_merge($paymentIds, $allowedWLMethods));
|
||||
$collection = new PaymentMethodCollection();
|
||||
if (!empty($allPaymentIds)) {
|
||||
$criteria = new Criteria($allPaymentIds);
|
||||
$criteria->addFilter(new EqualsFilter('active', true));
|
||||
$criteria->addFilter(
|
||||
new EqualsFilter('salesChannels.id', $event->getSalesChannelContext()->getSalesChannelId())
|
||||
);
|
||||
$criteria->addSorting(new FieldSorting('position', FieldSorting::ASCENDING));
|
||||
|
||||
$result = $this->paymentMethodRepository->search($criteria, $event->getContext());
|
||||
foreach ($result->getEntities() as $method) {
|
||||
if (!$collection->has($method->getId())) {
|
||||
$collection->add($method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$event->getPage()->setPaymentMethods($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SalesChannelContext $salesChannelContext
|
||||
* @param int $createdTransactionId
|
||||
* @return void
|
||||
*/
|
||||
private function updateTempTransactionIfNeeded(SalesChannelContext $salesChannelContext, int $createdTransactionId): void
|
||||
{
|
||||
$ctx = $salesChannelContext->getContext();
|
||||
|
||||
/** @var ArrayEntity|null $ext */
|
||||
$ext = $ctx->getExtension('checkoutState');
|
||||
|
||||
$oldAddressHash = $ext instanceof ArrayEntity ? $ext->get('addressHash') : null;
|
||||
$oldCurrency = $ext instanceof ArrayEntity ? $ext->get('currency') : null;
|
||||
|
||||
$customer = $salesChannelContext->getCustomer();
|
||||
$addressHash = md5(json_encode((array) $customer));
|
||||
$currency = $salesChannelContext->getCurrency()->getIsoCode();
|
||||
|
||||
$needsUpdate = ($oldAddressHash !== $addressHash) || ($oldCurrency !== $currency);
|
||||
|
||||
if ($needsUpdate) {
|
||||
if ($createdTransactionId) {
|
||||
$this->transactionService->updateTempTransaction($salesChannelContext, $createdTransactionId);
|
||||
}
|
||||
|
||||
$ctx->addExtension('possibleMethods', new ArrayEntity(['ids' => []]));
|
||||
$ctx->addExtension(
|
||||
'checkoutState',
|
||||
new ArrayEntity([
|
||||
'addressHash' => $addressHash,
|
||||
'currency' => $currency,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SalesChannelContext $salesChannelContext
|
||||
* @return array
|
||||
*/
|
||||
private function getAllowedPaymentMethodIds(SalesChannelContext $salesChannelContext): array
|
||||
{
|
||||
$ext = $salesChannelContext->getContext()->getExtension('possibleMethods');
|
||||
return $ext instanceof ArrayEntity ? ($ext->get('ids') ?? []) : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace VRPaymentPayment\Core\Util\Analytics;
|
||||
|
||||
use VRPayment\Sdk\ApiClient;
|
||||
use Shopware\Core\Kernel;
|
||||
|
||||
/**
|
||||
* Class Analytics
|
||||
@@ -14,29 +15,83 @@ class Analytics {
|
||||
public const SHOP_SYSTEM = 'x-meta-shop-system';
|
||||
public const SHOP_SYSTEM_VERSION = 'x-meta-shop-system-version';
|
||||
public const SHOP_SYSTEM_AND_VERSION = 'x-meta-shop-system-and-version';
|
||||
public const PLUGIN_SYSTEM_VERSION = 'x-meta-plugin-version';
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getDefaultData()
|
||||
public static function getDefaultData(): array
|
||||
{
|
||||
$shopwareVersion = self::getShopwareVersion();
|
||||
|
||||
return [
|
||||
self::SHOP_SYSTEM => 'shopware',
|
||||
self::SHOP_SYSTEM_VERSION => '6',
|
||||
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
|
||||
self::SHOP_SYSTEM => 'shopware',
|
||||
self::SHOP_SYSTEM_VERSION => $shopwareVersion,
|
||||
self::SHOP_SYSTEM_AND_VERSION => 'shopware-' . $shopwareVersion,
|
||||
self::PLUGIN_SYSTEM_VERSION => '6.2.0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \VRPayment\Sdk\ApiClient $apiClient
|
||||
*/
|
||||
public static function addHeaders(ApiClient &$apiClient)
|
||||
public static function addHeaders(ApiClient &$apiClient): void
|
||||
{
|
||||
$data = self::getDefaultData();
|
||||
foreach ($data as $key => $value) {
|
||||
$apiClient->addDefaultHeader($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads Shopware version and caches it for performance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getShopwareVersion(): string
|
||||
{
|
||||
static $cachedVersion = null;
|
||||
|
||||
if ($cachedVersion !== null) {
|
||||
return $cachedVersion;
|
||||
}
|
||||
|
||||
$basePath = dirname(__DIR__, 7);
|
||||
$installedFile = $basePath . '/vendor/composer/installed.php';
|
||||
|
||||
if (is_file($installedFile)) {
|
||||
$installed = include $installedFile;
|
||||
$packages = [];
|
||||
|
||||
if (isset($installed['versions'])) {
|
||||
$packages = $installed['versions'];
|
||||
} elseif (is_array($installed)) {
|
||||
foreach ($installed as $section) {
|
||||
if (isset($section['versions'])) {
|
||||
$packages = $section['versions'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($packages['shopware/core']['pretty_version'])) {
|
||||
return $cachedVersion = ltrim($packages['shopware/core']['pretty_version'], 'v');
|
||||
}
|
||||
}
|
||||
|
||||
$lockFile = $basePath . '/composer.lock';
|
||||
if (is_file($lockFile)) {
|
||||
$data = json_decode((string) file_get_contents($lockFile), true);
|
||||
if (!empty($data['packages'])) {
|
||||
foreach ($data['packages'] as $package) {
|
||||
if (($package['name'] ?? '') === 'shopware/core') {
|
||||
return $cachedVersion = ltrim($package['version'], 'v');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cachedVersion = Kernel::SHOPWARE_FALLBACK_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
|
||||
namespace VRPaymentPayment\Core\Util\Exception;
|
||||
|
||||
class RefundNotSupportedException extends \LogicException{
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use Shopware\Core\{Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
|
||||
Framework\DataAbstractionLayer\Search\Criteria,
|
||||
System\SalesChannel\SalesChannelContext
|
||||
};
|
||||
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use VRPayment\Sdk\{Model\AddressCreate,
|
||||
@@ -193,10 +194,16 @@ class TransactionPayload extends AbstractPayload
|
||||
->setShippingAddress($shippingAddress)
|
||||
->setShippingMethod($transactionData['shipping_method']);
|
||||
|
||||
$paymentConfiguration = $this->getPaymentConfiguration($this->salesChannelContext->getPaymentMethod()->getId());
|
||||
|
||||
$transactionPayload->setAllowedPaymentMethodConfigurations([$paymentConfiguration->getPaymentMethodConfigurationId()]);
|
||||
$paymentConfiguration = $this->getPaymentConfiguration(
|
||||
$this->salesChannelContext->getPaymentMethod()->getId(),
|
||||
$this->settings->getSpaceId()
|
||||
);
|
||||
|
||||
if ($paymentConfiguration) {
|
||||
$transactionPayload->setAllowedPaymentMethodConfigurations([
|
||||
$paymentConfiguration->getPaymentMethodConfigurationId()
|
||||
]);
|
||||
}
|
||||
$successUrl = $this->transaction->getReturnUrl() . '&status=paid';
|
||||
$failedUrl = $this->getFailUrl($this->transaction->getOrder()->getId()) . '&status=fail';
|
||||
$transactionPayload->setSuccessUrl($successUrl)
|
||||
@@ -209,7 +216,24 @@ class TransactionPayload extends AbstractPayload
|
||||
|
||||
return $transactionPayload;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param string $paymentMethodId
|
||||
* @param int $spaceId
|
||||
* @return PaymentMethodConfigurationEntity|null
|
||||
*/
|
||||
protected function getPaymentConfiguration(string $paymentMethodId, int $spaceId): ?PaymentMethodConfigurationEntity
|
||||
{
|
||||
$criteria = new Criteria();
|
||||
$criteria->addFilter(new EqualsFilter('paymentMethodId', $paymentMethodId));
|
||||
$criteria->addFilter(new EqualsFilter('spaceId', $spaceId));
|
||||
|
||||
return $this->container->get('vrpayment_payment_method_configuration.repository')
|
||||
->search($criteria, $this->salesChannelContext->getContext())
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction line items
|
||||
*
|
||||
@@ -220,30 +244,30 @@ class TransactionPayload extends AbstractPayload
|
||||
{
|
||||
$lineItems = [];
|
||||
$items = $this->transaction->getOrder()->getLineItems();
|
||||
|
||||
|
||||
foreach ($items as $shopLineItem) {
|
||||
if ($this->shouldSkipLineItem($shopLineItem)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if ($this->isCustomProductOption($shopLineItem)) {
|
||||
$shopLineItem = $this->updateCustomProductOptionLabel($shopLineItem);
|
||||
}
|
||||
|
||||
|
||||
$lineItem = $this->createLineItem($shopLineItem);
|
||||
$this->validateLineItem($lineItem);
|
||||
|
||||
|
||||
$lineItems[] = $lineItem;
|
||||
}
|
||||
|
||||
|
||||
$this->processDiscounts($items, $lineItems);
|
||||
$this->sortLineItemsByName($lineItems);
|
||||
|
||||
|
||||
$this->addOptionalLineItems($lineItems);
|
||||
|
||||
|
||||
return $lineItems;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if a line item should be skipped.
|
||||
*/
|
||||
@@ -272,7 +296,7 @@ class TransactionPayload extends AbstractPayload
|
||||
$shopLineItem->setLabel($customProductOptionParentLabel . ': ' . $shopLineItem->getLabel());
|
||||
return $shopLineItem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate the created line item.
|
||||
*/
|
||||
@@ -283,7 +307,7 @@ class TransactionPayload extends AbstractPayload
|
||||
throw new InvalidPayloadException('LineItem payload invalid: ' . json_encode($lineItem->listInvalidProperties()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process discounts from the order items and add them to the line items array.
|
||||
*/
|
||||
@@ -293,12 +317,14 @@ class TransactionPayload extends AbstractPayload
|
||||
$discounts = array_filter($itemsArray, function ($orderItem) {
|
||||
return $orderItem->getType() === 'promotion';
|
||||
});
|
||||
|
||||
|
||||
if ($discounts) {
|
||||
$this->addDiscountLineItem(current($discounts), $lineItems);
|
||||
foreach ($discounts as $discount) {
|
||||
$this->addDiscountLineItem($discount, $lineItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add discount line item.
|
||||
*/
|
||||
@@ -306,23 +332,24 @@ class TransactionPayload extends AbstractPayload
|
||||
{
|
||||
$calculatedPrice = $discount->getPrice();
|
||||
$calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes();
|
||||
|
||||
|
||||
foreach ($calculatedTaxesCollection as $calculatedTax) {
|
||||
$rate = $calculatedTax->getTaxRate();
|
||||
$lineItem = new LineItemCreate();
|
||||
$amount = $this->calculateDiscountAmount($calculatedTax);
|
||||
|
||||
|
||||
$discountName = $discount->getLabel();
|
||||
$lineItem->setAmountIncludingTax($amount)
|
||||
->setName(sprintf('DISCOUNT: %s (%s%% tax)', $discount->getLabel(), $rate))
|
||||
->setQuantity(1)
|
||||
->setShippingRequired(false)
|
||||
->setSku('sku-discount-' . $rate, 200)
|
||||
->setSku('sku-discount-' . $rate . '-' . $discountName, 200)
|
||||
->setType(LineItemType::DISCOUNT)
|
||||
->setUniqueId('coupon-sku-discount-' . $rate . '-' . $rate);
|
||||
|
||||
->setUniqueId('coupon-sku-discount-' . $rate . '-' . $rate . '-' . $discountName);
|
||||
|
||||
$taxRate = new TaxCreate(['title' => 'Discount Tax: ' . $rate, 'rate' => $rate]);
|
||||
$lineItem->setTaxes([$taxRate]);
|
||||
|
||||
|
||||
$lineItems[] = $lineItem;
|
||||
}
|
||||
}
|
||||
@@ -348,13 +375,15 @@ class TransactionPayload extends AbstractPayload
|
||||
return strcmp($lineItem1->getName(), $lineItem2->getName());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add optional shipping and adjustment line items.
|
||||
*/
|
||||
protected function addOptionalLineItems(array &$lineItems): void
|
||||
{
|
||||
if (count($this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes()) === 1) {
|
||||
$shippingCosts = $this->transaction->getOrder()->getShippingCosts();
|
||||
|
||||
if ($shippingCosts && $this->transaction->getOrder()->getShippingTotal() > 0) {
|
||||
if ($shippingLineItem = $this->getShippingLineItem()) {
|
||||
$lineItems[] = $shippingLineItem;
|
||||
}
|
||||
@@ -363,7 +392,7 @@ class TransactionPayload extends AbstractPayload
|
||||
$lineItems = array_merge($lineItems, $multipleShippingLineItems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($adjustmentLineItem = $this->getAdjustmentLineItem($lineItems)) {
|
||||
$lineItems[] = $adjustmentLineItem;
|
||||
}
|
||||
@@ -554,7 +583,7 @@ class TransactionPayload extends AbstractPayload
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -564,9 +593,9 @@ class TransactionPayload extends AbstractPayload
|
||||
if ($this->transaction->getOrder()->getShippingTotal() > 0) {
|
||||
$lineItems = [];
|
||||
$shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name');
|
||||
|
||||
|
||||
$isFirst = true;
|
||||
|
||||
|
||||
foreach ($this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes() as $taxItem) {
|
||||
$amount = self::round($taxItem->getPrice());
|
||||
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
|
||||
@@ -576,7 +605,7 @@ class TransactionPayload extends AbstractPayload
|
||||
$tax = (new TaxCreate())
|
||||
->setRate($taxRate)
|
||||
->setTitle('Tax rate: '.$taxRate);
|
||||
|
||||
|
||||
$name = $taxRate . '%-' . $shippingName;
|
||||
$lineItem = (new LineItemCreate())
|
||||
->setAmountIncludingTax($amount)
|
||||
@@ -586,18 +615,18 @@ class TransactionPayload extends AbstractPayload
|
||||
->setSku($this->fixLength($name . '-Shipping', 200))
|
||||
->setType($isFirst ? LineItemType::SHIPPING : LineItemType::FEE) // First item as SHIPPING, rest as FEE
|
||||
->setUniqueId($this->fixLength($name . '-Shipping', 200));
|
||||
|
||||
|
||||
if (!$lineItem->valid()) {
|
||||
$this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties());
|
||||
throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties()));
|
||||
}
|
||||
|
||||
|
||||
$lineItems[] = $lineItem;
|
||||
$isFirst = false;
|
||||
}
|
||||
return $lineItems;
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->critical(__CLASS__ . ' : ' . __FUNCTION__ . ' : ' . $exception->getMessage());
|
||||
}
|
||||
@@ -616,12 +645,22 @@ class TransactionPayload extends AbstractPayload
|
||||
{
|
||||
$lineItem = null;
|
||||
|
||||
$lineItemPriceTotal = array_sum(array_map(static function (LineItemCreate $lineItem) {
|
||||
return $lineItem->getAmountIncludingTax();
|
||||
}, $lineItems));
|
||||
// Calculate total of all current line items
|
||||
$lineItemPriceTotal = array_sum(array_map(static fn(LineItemCreate $li) => $li->getAmountIncludingTax(), $lineItems));
|
||||
|
||||
$adjustmentPrice = $this->transaction->getOrder()->getAmountTotal() - $lineItemPriceTotal;
|
||||
$adjustmentPrice = self::round($adjustmentPrice);
|
||||
$this->logger->debug("LineItem price total before adjustment: $lineItemPriceTotal");
|
||||
|
||||
// Get shipping total including taxes from the order
|
||||
$shippingCosts = $this->transaction->getOrder()->getShippingCosts();
|
||||
$shippingTotal = $shippingCosts ? self::round($shippingCosts->getTotalPrice()) : 0.0;
|
||||
|
||||
// Add shipping to the line items total if it's not already included
|
||||
$hasShippingLineItem = array_filter($lineItems, static fn(LineItemCreate $li) => $li->getType() === LineItemType::SHIPPING);
|
||||
if (!$hasShippingLineItem && $shippingTotal > 0) {
|
||||
$lineItemPriceTotal += $shippingTotal;
|
||||
}
|
||||
|
||||
$adjustmentPrice = self::round($this->transaction->getOrder()->getAmountTotal() - $lineItemPriceTotal);
|
||||
|
||||
if (abs($adjustmentPrice) != 0) {
|
||||
if ($this->settings->isLineItemConsistencyEnabled()) {
|
||||
@@ -777,20 +816,6 @@ class TransactionPayload extends AbstractPayload
|
||||
return $addressPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return \VRPaymentPayment\Core\Api\PaymentMethodConfiguration\Entity\PaymentMethodConfigurationEntity
|
||||
*/
|
||||
protected function getPaymentConfiguration(string $id): PaymentMethodConfigurationEntity
|
||||
{
|
||||
$criteria = (new Criteria([$id]));
|
||||
|
||||
return $this->container->get('vrpayment_payment_method_configuration.repository')
|
||||
->search($criteria, $this->salesChannelContext->getContext())
|
||||
->getEntities()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get failure URL
|
||||
*
|
||||
|
||||
@@ -31,7 +31,7 @@ class Migration1590156974TransactionEntity extends MigrationStep {
|
||||
public function update(Connection $connection): void
|
||||
{
|
||||
$connection->executeStatement('
|
||||
CREATE TABLE IF NOT EXISTS `vrpayment_transaction` (
|
||||
CREATE TABLE IF NOT EXISTS `vrpayment_transaction_tmp` (
|
||||
`id` BINARY(16) NOT NULL,
|
||||
`data` JSON NOT NULL,
|
||||
`payment_method_id` BINARY(16) NOT NULL,
|
||||
|
||||
@@ -42,7 +42,7 @@ class Migration1590646356RefundEntity extends MigrationStep {
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `refund_id_UNIQUE` (`refund_id`),
|
||||
KEY `fk.vrp_refund.transaction_id` (`transaction_id`),
|
||||
CONSTRAINT `fk.vrp_refund.transaction_id` FOREIGN KEY (`transaction_id`) REFERENCES `vrpayment_transaction` (`transaction_id`) ON DELETE CASCADE
|
||||
CONSTRAINT `fk.vrp_refund.transaction_id` FOREIGN KEY (`transaction_id`) REFERENCES `vrpayment_transaction_tmp` (`transaction_id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class Migration1590646356TransactionEntity extends MigrationStep {
|
||||
public function update(Connection $connection): void
|
||||
{
|
||||
try {
|
||||
$connection->executeStatement('ALTER TABLE `vrpayment_transaction` ADD COLUMN `confirmation_email_sent` TINYINT(1) NOT NULL DEFAULT 0 AFTER `id`;');
|
||||
$connection->executeStatement('ALTER TABLE `vrpayment_transaction_tmp` ADD COLUMN `confirmation_email_sent` TINYINT(1) NOT NULL DEFAULT 0 AFTER `id`;');
|
||||
}catch (\Exception $exception){
|
||||
// column probably exists
|
||||
}
|
||||
|
||||
@@ -33,19 +33,19 @@ class Migration1605701048TransactionEntity extends MigrationStep
|
||||
|
||||
try {
|
||||
$connection->executeStatement('
|
||||
ALTER TABLE `vrpayment_transaction`
|
||||
ALTER TABLE `vrpayment_transaction_tmp`
|
||||
ADD `order_version_id` binary(16) NOT NULL AFTER `transaction_id`;
|
||||
');
|
||||
|
||||
$connection->executeStatement('
|
||||
UPDATE `vrpayment_transaction` t1
|
||||
UPDATE `vrpayment_transaction_tmp` t1
|
||||
INNER JOIN `order` t2
|
||||
ON t1.order_id = t2.id
|
||||
SET t1.order_version_id = t2.version_id;
|
||||
');
|
||||
|
||||
$connection->executeStatement('
|
||||
ALTER TABLE `vrpayment_transaction`
|
||||
ALTER TABLE `vrpayment_transaction_tmp`
|
||||
DROP FOREIGN KEY `fk.vrp_transaction.order_id`,
|
||||
DROP FOREIGN KEY `fk.vrp_transaction.order_transaction_id`,
|
||||
DROP FOREIGN KEY `fk.vrp_transaction.payment_method_id`,
|
||||
@@ -53,7 +53,7 @@ class Migration1605701048TransactionEntity extends MigrationStep
|
||||
');
|
||||
|
||||
$connection->executeStatement('
|
||||
ALTER TABLE `vrpayment_transaction`
|
||||
ALTER TABLE `vrpayment_transaction_tmp`
|
||||
ADD CONSTRAINT `fk.vrp_transaction_order_id` FOREIGN KEY (`order_id`, `order_version_id`)
|
||||
REFERENCES `order` (`id`, `version_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
ADD CONSTRAINT `fk.vrp_transaction_payment_method_id` FOREIGN KEY (`payment_method_id`)
|
||||
|
||||
@@ -30,7 +30,7 @@ class Migration1684240994TransactionEntity extends MigrationStep {
|
||||
public function update(Connection $connection): void
|
||||
{
|
||||
try {
|
||||
$connection->executeStatement('ALTER TABLE `vrpayment_transaction` ADD COLUMN `erp_merchant_id` VARCHAR(255) DEFAULT NULL AFTER `confirmation_email_sent`;');
|
||||
$connection->executeStatement('ALTER TABLE `vrpayment_transaction_tmp` ADD COLUMN `erp_merchant_id` VARCHAR(255) DEFAULT NULL AFTER `confirmation_email_sent`;');
|
||||
}catch (\Exception $exception){
|
||||
// column probably exists
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace VRPaymentPayment\Migration;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Shopware\Core\Framework\Migration\MigrationStep;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
/**
|
||||
* Class Migration1766067106TransactionEntity
|
||||
*
|
||||
* @package VRPaymentPayment\Migration
|
||||
*/
|
||||
class Migration1766067106TransactionEntity extends MigrationStep
|
||||
{
|
||||
|
||||
/**
|
||||
* get creation timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCreationTimestamp(): int
|
||||
{
|
||||
return 1766067106;
|
||||
}
|
||||
|
||||
/**
|
||||
* update non-destructive changes
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
*/
|
||||
public function update(Connection $connection): void
|
||||
{
|
||||
$oldTableName = 'vrpayment_transaction';
|
||||
$tempTableName = 'vrpayment_transaction_tmp';
|
||||
$realTableName = 'vrpayment_transaction_data';
|
||||
$logger = new Logger('vrpayment_migration');
|
||||
$logger->pushHandler(new StreamHandler(dirname(__DIR__, 5) . '/var/log/vrpayment-migration.log'));
|
||||
$logger->info(
|
||||
'Migration start', [
|
||||
'old_table_exists' => $this->tableExists($connection, $oldTableName),
|
||||
'temp_table_exists' => $this->tableExists($connection, $tempTableName),
|
||||
'real_table_exists' => $this->tableExists($connection, $realTableName),
|
||||
]
|
||||
);
|
||||
|
||||
if ($this->tableExists($connection, $tempTableName)) {
|
||||
// If _temp table exists, it means that this is a fresh installation.
|
||||
$logger->info('Fresh installation detected.');
|
||||
$connection->executeStatement(
|
||||
sprintf('RENAME TABLE `%s` TO `%s`', $tempTableName, $realTableName)
|
||||
);
|
||||
$logger->info('Fresh installation finished.');
|
||||
} else {
|
||||
// If _temp does not exist, it means that this could be a version upgrade.
|
||||
$logger->info('Possible plugin upgrade detected.');
|
||||
if ($this->tableExists($connection, $oldTableName) && !$this->isOldPluginTable($connection, $oldTableName)) {
|
||||
$logger->info('Old vrpayment_transaction table detected.');
|
||||
// If vrpayment_transaction already exists and does not belong to old plugin,
|
||||
// it means that this is indeed a version update.
|
||||
$this->syncTransactionTable($connection, $oldTableName);
|
||||
$logger->info('Old vrpayment_transaction table sync finished.');
|
||||
$this->syncRefundTable($connection, $oldTableName);
|
||||
$logger->info('Old vrpayment_refund table sync finished.');
|
||||
$connection->executeStatement(
|
||||
sprintf('RENAME TABLE `%s` TO `%s`', $oldTableName, $realTableName)
|
||||
);
|
||||
$logger->info('Old vrpayment_transaction table renaming completed.');
|
||||
}
|
||||
$logger->info('Possible plugin upgrade finished.');
|
||||
// If vrpayment_transaction exists and it does belong to old plugin,
|
||||
// it means we must run it in parallel.
|
||||
}
|
||||
$logger->info('Migration finished.');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if table exists.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function tableExists(Connection $connection, string $table): bool {
|
||||
$result = $connection->fetchOne('SHOW TABLES LIKE :table', ['table' => $table]);
|
||||
return $result !== false && $result !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if table belongs to old plugin.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOldPluginTable(Connection $connection, string $table): bool {
|
||||
$oldTableExclusiveColumns = [
|
||||
'finalized_at' => 'datetime',
|
||||
'refunded_at' => 'datetime',
|
||||
'initial_transaction_mode' => 'varchar',
|
||||
'manual_capture' => 'tinyint',
|
||||
'partial_refunded_at' => 'datetime',
|
||||
'refunded_amount' => 'double',
|
||||
'amount_to_refund' => 'double',
|
||||
];
|
||||
$resultColumns = $connection->fetchAllAssociative(
|
||||
'SELECT LOWER(COLUMN_NAME) AS column_name, LOWER(DATA_TYPE) AS data_type
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = :table',
|
||||
['table' => $table]
|
||||
);
|
||||
$dbColumns = [];
|
||||
foreach($resultColumns as $column) {
|
||||
$dbColumns[$column['column_name']] = $column['data_type'];
|
||||
}
|
||||
|
||||
$oldPluginTable = true;
|
||||
foreach($oldTableExclusiveColumns as $columnName => $columnType) {
|
||||
if(!isset($dbColumns[$columnName])) {
|
||||
$oldPluginTable = false;
|
||||
break;
|
||||
}
|
||||
if ($dbColumns[$columnName] !== $columnType) {
|
||||
$oldPluginTable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $oldPluginTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the transaction table with the current/latest version.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
*/
|
||||
private function syncTransactionTable(Connection $connection, string $table): void {
|
||||
$this->addColumnIfMissing($connection, $table, 'confirmation_email_sent', "TINYINT(1) NOT NULL DEFAULT 0 AFTER `id`");
|
||||
$this->addColumnIfMissing($connection, $table, 'erp_merchant_id', "VARCHAR(255) DEFAULT NULL AFTER `confirmation_email_sent`");
|
||||
$this->addColumnIfMissing($connection, $table, 'data', "LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`data`)) AFTER `erp_merchant_id`");
|
||||
$this->addColumnIfMissing($connection, $table, 'payment_method_id', "BINARY(16) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'order_id', "BINARY(16) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'order_transaction_id', "BINARY(16) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'space_id', "INT(10) UNSIGNED NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'state', "VARCHAR(255) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'sales_channel_id', "BINARY(16) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'transaction_id', "INT(10) UNSIGNED NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'order_version_id', "BINARY(16) NOT NULL AFTER `transaction_id`");
|
||||
|
||||
$this->addColumnIfMissing($connection, $table, 'created_at', "DATETIME(3) NOT NULL");
|
||||
$this->addColumnIfMissing($connection, $table, 'updated_at', "DATETIME(3) DEFAULT NULL");
|
||||
|
||||
$this->ensureIndexBySql($connection, $table, 'fk.vrp_transaction.order_id', "KEY `fk.vrp_transaction.order_id` (`order_id`)");
|
||||
$this->ensureIndexBySql($connection, $table, 'fk.vrp_transaction.order_transaction_id', "KEY `fk.vrp_transaction.order_transaction_id` (`order_transaction_id`)");
|
||||
$this->ensureIndexBySql($connection, $table, 'fk.vrp_transaction.payment_method_id', "KEY `fk.vrp_transaction.payment_method_id` (`payment_method_id`)");
|
||||
$this->ensureIndexBySql($connection, $table, 'fk.vrp_transaction.sales_channel_id', "KEY `fk.vrp_transaction.sales_channel_id` (`sales_channel_id`)");
|
||||
$this->ensureIndexBySql($connection, $table, 'fk.vrp_transaction', "KEY `fk.vrp_transaction` (`order_id`,`order_version_id`)");
|
||||
|
||||
$this->ensureForeignKey(
|
||||
$connection,
|
||||
$table,
|
||||
'fk.vrp_transaction_order_id',
|
||||
['order_id', 'order_version_id'],
|
||||
'order',
|
||||
['id', 'version_id'],
|
||||
'CASCADE',
|
||||
'CASCADE'
|
||||
);
|
||||
$this->ensureForeignKey(
|
||||
$connection,
|
||||
$table,
|
||||
'fk.vrp_transaction_payment_method_id',
|
||||
['payment_method_id'],
|
||||
'payment_method',
|
||||
['id'],
|
||||
'RESTRICT',
|
||||
'CASCADE'
|
||||
);
|
||||
$this->ensureForeignKey(
|
||||
$connection,
|
||||
$table,
|
||||
'fk.vrp_transaction_sales_channel_id',
|
||||
['sales_channel_id'],
|
||||
'sales_channel',
|
||||
['id'],
|
||||
'RESTRICT',
|
||||
'CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the parts of the refund table related to transactions with the current/latest version.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
*/
|
||||
private function syncRefundTable(Connection $connection, string $table): void {
|
||||
$refundTable = 'vrpayment_refund';
|
||||
$this->ensureIndexBySql($connection, $refundTable, 'fk.vrp_refund.transaction_id', "KEY `fk.vrp_refund.transaction_id` (`transaction_id`)");
|
||||
$this->ensureForeignKey(
|
||||
$connection,
|
||||
$refundTable,
|
||||
'fk.vrp_refund.transaction_id',
|
||||
['transaction_id'],
|
||||
$table,
|
||||
['transaction_id'],
|
||||
'CASCADE',
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds column to the table if it's missing.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
* @param string $sqlFragment
|
||||
*/
|
||||
private function addColumnIfMissing(Connection $connection, string $table, string $column, string $sqlFragment): void {
|
||||
if ($this->columnExists($connection, $table, $column)) {
|
||||
return;
|
||||
}
|
||||
$connection->executeStatement(
|
||||
sprintf("ALTER TABLE `%s` ADD COLUMN `%s` %s", $table, $column, $sqlFragment)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds index to the table if it's missing.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
* @param string $indexName
|
||||
* @param string $sqlFragment
|
||||
*/
|
||||
private function ensureIndexBySql(Connection $connection, string $table, string $indexName, string $sqlFragment): void {
|
||||
if ($this->indexExists($connection, $table, $indexName)) {
|
||||
return;
|
||||
}
|
||||
$connection->executeStatement(
|
||||
sprintf("ALTER TABLE `%s` ADD %s", $table, $sqlFragment)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds foreign key constraint to the table if it's missing.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
* @param string $constraintName
|
||||
* @param string $columns
|
||||
* @param string $refTable
|
||||
* @param string $refColumns
|
||||
* @param string|null $onDelete
|
||||
* @param string|null $onUpdate
|
||||
*/
|
||||
private function ensureForeignKey(
|
||||
Connection $connection,
|
||||
string $table,
|
||||
string $constraintName,
|
||||
array $columns,
|
||||
string $refTable,
|
||||
array $refColumns,
|
||||
?string $onDelete,
|
||||
?string $onUpdate
|
||||
): void {
|
||||
if ($this->foreignKeyExists($connection, $table, $constraintName)) {
|
||||
return;
|
||||
}
|
||||
$columnsList = '`' . implode('`,`', $columns) . '`';
|
||||
$refColumnsList = '`' . implode('`,`', $refColumns) . '`';
|
||||
$connection->executeStatement(
|
||||
sprintf(
|
||||
"ALTER TABLE `%s`
|
||||
ADD CONSTRAINT `%s` FOREIGN KEY (%s)
|
||||
REFERENCES `%s` (%s)%s%s",
|
||||
$table,
|
||||
$constraintName,
|
||||
$columnsList,
|
||||
$refTable,
|
||||
$refColumnsList,
|
||||
$onDelete ? " ON DELETE {$onDelete}" : "",
|
||||
$onUpdate ? " ON UPDATE {$onUpdate}" : ""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if foreign key constraint exists.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
* @param string $table
|
||||
* @param string $constraintName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function foreignKeyExists(Connection $connection, string $table, $constraintName): bool {
|
||||
$result = $connection->fetchOne(
|
||||
"SELECT 1 FROM information_schema.referential_constraints
|
||||
WHERE constraint_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
AND constraint_name = ?
|
||||
LIMIT 1",
|
||||
[$table,$constraintName]
|
||||
);
|
||||
return $result !== false && $result !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* update destructive changes
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $connection
|
||||
*/
|
||||
public function updateDestructive(Connection $connection): void
|
||||
{
|
||||
// implement update destructive
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+17
-2
@@ -70,9 +70,24 @@ Component.register('vrpayment-order-action-refund-by-amount', {
|
||||
});
|
||||
}).catch((errorResponse) => {
|
||||
try {
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'refundAmountZero':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messageRefundAmountIsZero');
|
||||
break;
|
||||
case 'refundExceedsAmount':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messageRefundAmountExceedsAvailableBalance');
|
||||
break;
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
+11
-2
@@ -69,9 +69,18 @@ Component.register('vrpayment-order-action-refund-partial', {
|
||||
});
|
||||
}).catch((errorResponse) => {
|
||||
try {
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
+11
-2
@@ -70,9 +70,18 @@ Component.register('vrpayment-order-action-refund-selected', {
|
||||
});
|
||||
}).catch((errorResponse) => {
|
||||
try {
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
+1
@@ -9,6 +9,7 @@
|
||||
:max="this.$parent.$parent.itemRefundableQuantity"
|
||||
:min="0"
|
||||
v-model:value="refundQuantity"
|
||||
number-type="int"
|
||||
:label="$tc('vrpayment-order.refund.refundQuantity.label')">
|
||||
</sw-number-field>
|
||||
|
||||
|
||||
+17
-2
@@ -68,9 +68,24 @@ Component.register('vrpayment-order-action-refund', {
|
||||
});
|
||||
}).catch((errorResponse) => {
|
||||
try {
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'refundQuantityZero':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messageRefundQuantityIsZero');
|
||||
break;
|
||||
case 'refundExceedsQuantity':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messageRefundQuantityExceedsAvailableBalance');
|
||||
break;
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
<template #actions="{ item }">
|
||||
<sw-context-menu-item
|
||||
:disabled="transaction.state != 'FULFILL' || item.refundableQuantity != item.quantity || item.refundableAmount == 0 || item.itemRefundedAmount > 0 || item.itemRefundedQuantity > 0"
|
||||
@click="lineItemRefund(item.uniqueId)">
|
||||
@click="lineItemRefund(item.uniqueId, item.quantity)">
|
||||
{{ $tc('vrpayment-order.buttons.label.refund-whole-line-item') }}
|
||||
</sw-context-menu-item>
|
||||
|
||||
|
||||
+29
-11
@@ -332,12 +332,12 @@ Component.register('vrpayment-order-detail', {
|
||||
this.modalType = '';
|
||||
},
|
||||
|
||||
lineItemRefund(lineItemId) {
|
||||
lineItemRefund(lineItemId, itemQuantity) {
|
||||
this.isLoading = true;
|
||||
this.VRPaymentRefundService.createRefund(
|
||||
this.transactionData.transactions[0].metaData.salesChannelId,
|
||||
this.transactionData.transactions[0].id,
|
||||
0,
|
||||
itemQuantity,
|
||||
lineItemId
|
||||
).then(() => {
|
||||
this.createNotificationSuccess({
|
||||
@@ -351,9 +351,18 @@ Component.register('vrpayment-order-detail', {
|
||||
});
|
||||
}).catch((errorResponse) => {
|
||||
try {
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -385,7 +394,7 @@ Component.register('vrpayment-order-detail', {
|
||||
// Force the DOM to update before proceeding with the asynchronous operations
|
||||
this.$nextTick(() => {
|
||||
const refundPromises = this.selectedItems.map((item) => {
|
||||
return this.lineItemRefundBulk(item.uniqueId); // Simulated refund action with delay
|
||||
return this.lineItemRefundBulk(item.uniqueId, item.quantity); // Simulated refund action with delay
|
||||
});
|
||||
|
||||
// Wait for all refund promises to complete
|
||||
@@ -410,7 +419,7 @@ Component.register('vrpayment-order-detail', {
|
||||
});
|
||||
}
|
||||
},
|
||||
lineItemRefundBulk(lineItemId) {
|
||||
lineItemRefundBulk(lineItemId, itemQuantity) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.VRPaymentRefundService.createRefund(
|
||||
this.transactionData.transactions[0].metaData.salesChannelId,
|
||||
@@ -427,11 +436,20 @@ Component.register('vrpayment-order-detail', {
|
||||
})
|
||||
.catch((errorResponse) => {
|
||||
try {
|
||||
this.createNotificationError({
|
||||
title: errorResponse.response.data.errors[0].title,
|
||||
message: errorResponse.response.data.errors[0].detail,
|
||||
autoClose: false
|
||||
});
|
||||
var errorTitle = errorResponse?.response?.data?.errors?.[0]?.title ?? this.$tc('vrpayment-order.refundAction.refundCreateError.errorTitle')
|
||||
var errorMessage;
|
||||
switch(errorResponse.response.data) {
|
||||
case 'methodDoesNotSupportRefund':
|
||||
errorMessage = this.$tc('vrpayment-order.refundAction.refundCreateError.messagePaymentMethodDoesNotSupportRefund');
|
||||
break;
|
||||
default:
|
||||
errorMessage = errorResponse.response.data.errors[0].detail;
|
||||
}
|
||||
this.createNotificationError({
|
||||
title: errorTitle,
|
||||
message: errorMessage,
|
||||
autoClose: false
|
||||
});
|
||||
} catch (e) {
|
||||
this.createNotificationError({
|
||||
title: errorResponse.title,
|
||||
|
||||
@@ -77,7 +77,15 @@
|
||||
"successMessage": "Ihre Rückerstattung war erfolgreich",
|
||||
"successTitle": "Erfolg",
|
||||
"maxAvailableItemsToRefund": "Maximal Verfügbare Artikel zum Erstatten",
|
||||
"maxAvailableAmountToRefund": "Maximal verfügbarer Erstattungsbetrag"
|
||||
"maxAvailableAmountToRefund": "Maximal verfügbarer Erstattungsbetrag",
|
||||
"refundCreateError": {
|
||||
"errorTitle": "Fehler beim Erstellen der Rückerstattung.",
|
||||
"messageRefundAmountExceedsAvailableBalance": "Der Rückerstattungsbetrag übersteigt das verfügbare Guthaben.",
|
||||
"messageRefundAmountIsZero": "Der Rückerstattungsbetrag muss größer als 0 sein.",
|
||||
"messageRefundQuantityExceedsAvailableBalance": "Rückerstattung nach Menge überschreitet die maximal verfügbare Anzahl an Artikeln zur Rückerstattung.",
|
||||
"messageRefundQuantityIsZero": "Rückerstattung nach Menge muss größer als 0 sein.",
|
||||
"messagePaymentMethodDoesNotSupportRefund": "Die Zahlungsmethode unterstützt keine Online-Rückerstattungen."
|
||||
}
|
||||
},
|
||||
"transactionHistory": {
|
||||
"cardTitle": "Einzelheiten",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"void": "Cancel authorization",
|
||||
"refund-whole-line-item": "Refund whole line item",
|
||||
"refund-line-item-by-quantity": "Refund by quantity",
|
||||
"refund-line-item-selected": "Rembourser sélectionnés",
|
||||
"refund-line-item-selected": "Refund selected",
|
||||
"refund-line-item-parial": "Partial refund"
|
||||
}
|
||||
@@ -78,7 +77,15 @@
|
||||
"successMessage": "Your refund was successful.",
|
||||
"successTitle": "Success",
|
||||
"maxAvailableItemsToRefund": "Maximum available items to refund",
|
||||
"maxAvailableAmountToRefund": "Maximum available amount to refund"
|
||||
"maxAvailableAmountToRefund": "Maximum available amount to refund",
|
||||
"refundCreateError": {
|
||||
"errorTitle": "Error while creating the refund.",
|
||||
"messageRefundAmountExceedsAvailableBalance": "Refund amount exceeds available balance.",
|
||||
"messageRefundAmountIsZero": "Refund amount must be greater than 0.",
|
||||
"messageRefundQuantityExceedsAvailableBalance": "Refund by quantity exceeds maximum available items to refund.",
|
||||
"messageRefundQuantityIsZero": "Refund by quantity must be greater than 0.",
|
||||
"messagePaymentMethodDoesNotSupportRefund": "Payment method does not support online refunds."
|
||||
}
|
||||
},
|
||||
"transactionHistory": {
|
||||
"cardTitle": "Details",
|
||||
|
||||
@@ -77,7 +77,15 @@
|
||||
"successMessage": "Votre remboursement a été effectué avec succès.",
|
||||
"successTitle": "Succès",
|
||||
"maxAvailableItemsToRefund": "Nombre maximum d'articles disponibles pour le remboursement",
|
||||
"maxAvailableAmountToRefund": "Montant maximal disponible pour le remboursement"
|
||||
"maxAvailableAmountToRefund": "Montant maximal disponible pour le remboursement",
|
||||
"refundCreateError": {
|
||||
"errorTitle": "Erreur lors de la création du remboursement.",
|
||||
"messageRefundAmountExceedsAvailableBalance": "Le montant du remboursement dépasse le solde disponible.",
|
||||
"messageRefundAmountIsZero": "Le montant du remboursement doit être supérieur à 0.",
|
||||
"messageRefundQuantityExceedsAvailableBalance": "Le remboursement par quantité dépasse le nombre maximal d’articles remboursables.",
|
||||
"messageRefundQuantityIsZero": "Le remboursement par quantité doit être supérieur à 0.",
|
||||
"messagePaymentMethodDoesNotSupportRefund": "Le mode de paiement ne prend pas en charge les remboursements en ligne."
|
||||
}
|
||||
},
|
||||
"transactionHistory": {
|
||||
"cardTitle": "Détails",
|
||||
|
||||
@@ -77,7 +77,15 @@
|
||||
"successMessage": "Il tuo rimborso è andato a buon fine.",
|
||||
"successTitle": "Successo",
|
||||
"maxAvailableItemsToRefund": "Numero massimo di articoli disponibili da rimborsare",
|
||||
"maxAvailableAmountToRefund": "Importo massimo disponibile per il rimborso"
|
||||
"maxAvailableAmountToRefund": "Importo massimo disponibile per il rimborso",
|
||||
"refundCreateError": {
|
||||
"errorTitle": "Errore durante la creazione del rimborso.",
|
||||
"messageRefundAmountExceedsAvailableBalance": "LL'importo del rimborso supera il saldo disponibile.",
|
||||
"messageRefundAmountIsZero": "L'importo del rimborso deve essere superiore a 0.",
|
||||
"messageRefundQuantityExceedsAvailableBalance": "Il rimborso per quantità supera il numero massimo di articoli rimborsabili.",
|
||||
"messageRefundQuantityIsZero": "Il rimborso per quantità deve essere maggiore di 0.",
|
||||
"messagePaymentMethodDoesNotSupportRefund": "Il metodo di pagamento non supporta i rimborsi online."
|
||||
}
|
||||
},
|
||||
"transactionHistory": {
|
||||
"cardTitle": "Dettagli",
|
||||
|
||||
+6
-6
@@ -14,7 +14,7 @@
|
||||
{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_space_id %}
|
||||
<sw-inherit-wrapper
|
||||
v-model:value="actualConfigData[CONFIG_SPACE_ID]"
|
||||
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_SPACE_ID]"
|
||||
:inheritedValue="getInheritedValue(CONFIG_SPACE_ID)"
|
||||
:customInheritationCheckFunction="checkNumberFieldInheritance">
|
||||
<template #content="props">
|
||||
<sw-number-field
|
||||
@@ -23,7 +23,7 @@
|
||||
:mapInheritance="props"
|
||||
:label="$tc('vrpayment-settings.settingForm.credentials.spaceId.label')"
|
||||
:helpText="$tc('vrpayment-settings.settingForm.credentials.spaceId.tooltipText')"
|
||||
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
|
||||
:disabled="!acl.can('vrpayment.editor')"
|
||||
:value="props.currentValue"
|
||||
:error="spaceIdErrorState"
|
||||
@update:value="props.updateCurrentValue">
|
||||
@@ -35,7 +35,7 @@
|
||||
{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_user_id %}
|
||||
<sw-inherit-wrapper
|
||||
v-model:value="actualConfigData[CONFIG_USER_ID]"
|
||||
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_USER_ID]"
|
||||
:inheritedValue="getInheritedValue(CONFIG_USER_ID)"
|
||||
:customInheritationCheckFunction="checkNumberFieldInheritance">
|
||||
<template #content="props">
|
||||
<sw-number-field
|
||||
@@ -44,7 +44,7 @@
|
||||
:mapInheritance="props"
|
||||
:label="$tc('vrpayment-settings.settingForm.credentials.userId.label')"
|
||||
:helpText="$tc('vrpayment-settings.settingForm.credentials.userId.tooltipText')"
|
||||
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
|
||||
:disabled="!acl.can('vrpayment.editor')"
|
||||
:value="props.currentValue"
|
||||
:error="userIdErrorState"
|
||||
@update:value="props.updateCurrentValue">
|
||||
@@ -56,7 +56,7 @@
|
||||
{% block vrpayment_settings_content_card_channel_config_credentials_card_container_settings_application_key %}
|
||||
<sw-inherit-wrapper
|
||||
v-model:value="actualConfigData[CONFIG_APPLICATION_KEY]"
|
||||
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_APPLICATION_KEY]"
|
||||
:inheritedValue="getInheritedValue(CONFIG_APPLICATION_KEY)"
|
||||
:customInheritationCheckFunction="checkTextFieldInheritance">
|
||||
<template #content="props">
|
||||
<sw-password-field
|
||||
@@ -66,7 +66,7 @@
|
||||
:mapInheritance="props"
|
||||
:label="$tc('vrpayment-settings.settingForm.credentials.applicationKey.label')"
|
||||
:helpText="$tc('vrpayment-settings.settingForm.credentials.applicationKey.tooltipText')"
|
||||
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
|
||||
:disabled="!acl.can('vrpayment.editor')"
|
||||
:value="props.currentValue"
|
||||
:error="applicationKeyErrorState"
|
||||
@update:value="props.updateCurrentValue">
|
||||
|
||||
+30
-22
@@ -6,7 +6,7 @@ import constants from '../../page/vrpayment-settings/configuration-constants'
|
||||
const {Component, Mixin} = Shopware;
|
||||
|
||||
Component.register('sw-vrpayment-credentials', {
|
||||
template: template,
|
||||
template,
|
||||
|
||||
name: 'VRPaymentCredentials',
|
||||
|
||||
@@ -29,7 +29,9 @@ Component.register('sw-vrpayment-credentials', {
|
||||
},
|
||||
|
||||
selectedSalesChannelId: {
|
||||
required: true
|
||||
type: [String, null],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
spaceIdFilled: {
|
||||
type: Boolean,
|
||||
@@ -68,38 +70,44 @@ Component.register('sw-vrpayment-credentials', {
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
currentConfig() {
|
||||
if (this.selectedSalesChannelId && this.allConfigs[this.selectedSalesChannelId]) {
|
||||
return this.allConfigs[this.selectedSalesChannelId];
|
||||
}
|
||||
return this.allConfigs['null'] || {};
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
checkTextFieldInheritance(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return true;
|
||||
}
|
||||
checkTextFieldInheritance(value) {
|
||||
return !value || value.length <= 0;
|
||||
},
|
||||
|
||||
return value.length <= 0;
|
||||
},
|
||||
checkNumberFieldInheritance(value) {
|
||||
return value == null || value === '';
|
||||
},
|
||||
|
||||
checkNumberFieldInheritance(value) {
|
||||
if (typeof value !== 'number') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value.length <= 0;
|
||||
},
|
||||
|
||||
checkBoolFieldInheritance(value) {
|
||||
return typeof value !== 'boolean';
|
||||
},
|
||||
checkBoolFieldInheritance(value) {
|
||||
return typeof value !== 'boolean';
|
||||
},
|
||||
|
||||
// Emits the 'check-api-connection-event' with the current API connection parameters.
|
||||
// Used to trigger API connection testing from this component.
|
||||
emitCheckApiConnectionEvent() {
|
||||
const apiConnectionParams = {
|
||||
spaceId: this.actualConfigData[constants.CONFIG_SPACE_ID],
|
||||
userId: this.actualConfigData[constants.CONFIG_USER_ID],
|
||||
applicationKey: this.actualConfigData[constants.CONFIG_APPLICATION_KEY]
|
||||
spaceId: this.currentConfig[constants.CONFIG_SPACE_ID],
|
||||
userId: this.currentConfig[constants.CONFIG_USER_ID],
|
||||
applicationKey: this.currentConfig[constants.CONFIG_APPLICATION_KEY]
|
||||
};
|
||||
|
||||
this.$emit('check-api-connection-event', apiConnectionParams);
|
||||
},
|
||||
|
||||
getInheritedValue(key) {
|
||||
return this.allConfigs['null']?.[key] ?? null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+4
-4
@@ -41,13 +41,13 @@ Component.register('sw-vrpayment-options', {
|
||||
computed: {
|
||||
integrationOptions() {
|
||||
return [
|
||||
{
|
||||
id: 'iframe',
|
||||
name: this.$tc('vrpayment-settings.settingForm.options.integration.options.iframe')
|
||||
},
|
||||
{
|
||||
id: 'payment_page',
|
||||
name: this.$tc('vrpayment-settings.settingForm.options.integration.options.payment_page')
|
||||
},
|
||||
{
|
||||
id: 'iframe',
|
||||
name: this.$tc('vrpayment-settings.settingForm.options.integration.options.iframe')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
+141
-141
@@ -1,145 +1,145 @@
|
||||
{% block vrpayment_settings %}
|
||||
<sw-page class="vrpayment-settings">
|
||||
|
||||
{% block vrpayment_settings_header %}
|
||||
<template #smart-bar-header>
|
||||
<h2>
|
||||
{{ $tc('sw-settings.index.title') }}
|
||||
<sw-icon name="small-arrow-medium-right" small></sw-icon>
|
||||
{{ $tc('vrpayment-settings.header') }}
|
||||
</h2>
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_actions %}
|
||||
<template #smart-bar-actions>
|
||||
{% block vrpayment_settings_actions_save %}
|
||||
<sw-button-process
|
||||
v-model:value="isSaveSuccessful"
|
||||
class="sw-settings-login-registration__save-action"
|
||||
variant="primary"
|
||||
:isLoading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="onSave">
|
||||
{{ $tc('vrpayment-settings.settingForm.save') }}
|
||||
</sw-button-process>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content %}
|
||||
<template #content>
|
||||
|
||||
{% block vrpayment_settings_content_card %}
|
||||
<sw-card-view>
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config %}
|
||||
<sw-sales-channel-config v-model:value="config"
|
||||
ref="configComponent"
|
||||
:domain="CONFIG_DOMAIN">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel %}
|
||||
<template #select="{ onInput, selectedSalesChannelId, salesChannel }">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card %}
|
||||
<sw-card title="Sales Channel Switch">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_title %}
|
||||
<sw-single-select
|
||||
v-model:value="selectedSalesChannelId"
|
||||
labelProperty="translated.name"
|
||||
valueProperty="id"
|
||||
:mapInheritance="props"
|
||||
:isLoading="isLoading"
|
||||
:options="salesChannel"
|
||||
@update:value="onInput">
|
||||
</sw-single-select>
|
||||
{% endblock %}
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer %}
|
||||
<template #footer>
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container %}
|
||||
<sw-container columns="2fr 1fr" gap="0px 30px">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_text %}
|
||||
<p>{{ $tc('vrpayment-settings.salesChannelCard.button.description') }}</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_button %}
|
||||
<sw-button-process
|
||||
v-model:value="isSetDefaultPaymentSuccessful"
|
||||
:isLoading="isSettingDefaultPaymentMethods"
|
||||
@click="onSetPaymentMethodDefault">
|
||||
{{ $tc('vrpayment-settings.salesChannelCard.button.label') }}
|
||||
</sw-button-process>
|
||||
{% endblock %}
|
||||
</sw-container>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
</sw-card>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_cards %}
|
||||
<template #content="{ actualConfigData, allConfigs, selectedSalesChannelId }">
|
||||
<div v-if="actualConfigData">
|
||||
|
||||
<sw-vrpayment-credentials
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
:spaceIdErrorState="spaceIdErrorState"
|
||||
:userIdErrorState="userIdErrorState"
|
||||
:applicationKeyErrorState="applicationKeyErrorState"
|
||||
:spaceIdFilled="spaceIdFilled"
|
||||
:userIdFilled="userIdFilled"
|
||||
:applicationKeyFilled="applicationKeyFilled"
|
||||
:isLoading="isLoading"
|
||||
:isTesting="isTesting"
|
||||
@check-api-connection-event="onCheckApiConnection"
|
||||
></sw-vrpayment-credentials>
|
||||
|
||||
<sw-vrpayment-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-options>
|
||||
|
||||
<sw-vrpayment-storefront-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-storefront-options>
|
||||
|
||||
<sw-vrpayment-advanced-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-advanced-options>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
</sw-sales-channel-config>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_loading %}
|
||||
<sw-loader v-if="isLoading"></sw-loader>
|
||||
{% endblock %}
|
||||
</sw-card-view>
|
||||
<sw-page class="vrpayment-settings">
|
||||
{% block vrpayment_settings_header %}
|
||||
<template #smart-bar-header>
|
||||
<h2>
|
||||
{{ $tc('sw-settings.index.title') }}
|
||||
<mt-icon name="small-arrow-medium-right" size="16px"></mt-icon>
|
||||
{{ $tc('vrpayment-settings.header') }}
|
||||
</h2>
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
</template>
|
||||
{% endblock %}
|
||||
</sw-page>
|
||||
{% endblock %}
|
||||
{% block vrpayment_settings_actions %}
|
||||
<template #smart-bar-actions>
|
||||
{% block vrpayment_settings_actions_save %}
|
||||
<mt-button
|
||||
v-model:value="isSaveSuccessful"
|
||||
class="sw-settings-login-registration__save-action"
|
||||
variant="primary"
|
||||
:isLoading="isLoading"
|
||||
:disabled="isLoading"
|
||||
@click="onSave">
|
||||
{{ $tc('vrpayment-settings.settingForm.save') }}
|
||||
</mt-button>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content %}
|
||||
<template #content>
|
||||
|
||||
{% block vrpayment_settings_content_card %}
|
||||
<mt-card-view>
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config %}
|
||||
<sw-sales-channel-config v-model:value="config"
|
||||
ref="configComponent"
|
||||
:domain="CONFIG_DOMAIN">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel %}
|
||||
<template #select="{ onInput, selectedSalesChannelId, salesChannel }">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card %}
|
||||
<mt-card title="Sales Channel Switch">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_title %}
|
||||
<sw-single-select
|
||||
:value="selectedSalesChannelId"
|
||||
:options="salesChannel.map(sc => ({ id: sc.id, name: sc.translated.name }))"
|
||||
labelProperty="name"
|
||||
valueProperty="id"
|
||||
:isLoading="isLoading"
|
||||
@update:value="onInput"
|
||||
/>
|
||||
{% endblock %}
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer %}
|
||||
<template #footer>
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container %}
|
||||
<sw-container columns="2fr 1fr" gap="0px 30px">
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_text %}
|
||||
<p>{{ $tc('vrpayment-settings.salesChannelCard.button.description') }}</p>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_button %}
|
||||
<sw-button
|
||||
variant="primary"
|
||||
v-model:value="isSetDefaultPaymentSuccessful"
|
||||
:isLoading="isSettingDefaultPaymentMethods"
|
||||
@click="onSetPaymentMethodDefault">
|
||||
{{ $tc('vrpayment-settings.salesChannelCard.button.label') }}
|
||||
</sw-button>
|
||||
{% endblock %}
|
||||
</sw-container>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
</mt-card>
|
||||
{% endblock %}
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_channel_config_cards %}
|
||||
<template #content="{ actualConfigData, allConfigs, selectedSalesChannelId }">
|
||||
<div v-if="actualConfigData">
|
||||
|
||||
<sw-vrpayment-credentials
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
:spaceIdErrorState="spaceIdErrorState"
|
||||
:userIdErrorState="userIdErrorState"
|
||||
:applicationKeyErrorState="applicationKeyErrorState"
|
||||
:spaceIdFilled="spaceIdFilled"
|
||||
:userIdFilled="userIdFilled"
|
||||
:applicationKeyFilled="applicationKeyFilled"
|
||||
:isLoading="isLoading"
|
||||
:isTesting="isTesting"
|
||||
@check-api-connection-event="onCheckApiConnection"
|
||||
></sw-vrpayment-credentials>
|
||||
|
||||
<sw-vrpayment-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-options>
|
||||
|
||||
<sw-vrpayment-storefront-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-storefront-options>
|
||||
|
||||
<sw-vrpayment-advanced-options
|
||||
:actualConfigData="actualConfigData"
|
||||
:allConfigs="allConfigs"
|
||||
:isLoading="isLoading"
|
||||
:selectedSalesChannelId="selectedSalesChannelId"
|
||||
>
|
||||
</sw-vrpayment-advanced-options>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
{% endblock %}
|
||||
|
||||
</sw-sales-channel-config>
|
||||
{% endblock %}
|
||||
|
||||
{% block vrpayment_settings_content_card_loading %}
|
||||
<mt-loader v-if="isLoading"></mt-loader>
|
||||
{% endblock %}
|
||||
</mt-card-view>
|
||||
{% endblock %}
|
||||
|
||||
</template>
|
||||
{% endblock %}
|
||||
</sw-page>
|
||||
{% endblock %}
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ Component.register('vrpayment-settings', {
|
||||
isSetDefaultPaymentSuccessful: false,
|
||||
isSettingDefaultPaymentMethods: false,
|
||||
|
||||
configIntegrationDefaultValue: 'iframe',
|
||||
configIntegrationDefaultValue: 'payment_page',
|
||||
configEmailEnabledDefaultValue: true,
|
||||
configLineItemConsistencyEnabledDefaultValue: true,
|
||||
configStorefrontInvoiceDownloadEnabledEnabledDefaultValue: true,
|
||||
@@ -80,7 +80,7 @@ Component.register('vrpayment-settings', {
|
||||
watch: {
|
||||
config: {
|
||||
handler(configData) {
|
||||
const defaultConfig = this.$refs.configComponent.allConfigs.null;
|
||||
const defaultConfig = (this.$refs.configComponent.allConfigs || {}).null || {};
|
||||
const salesChannelId = this.$refs.configComponent.selectedSalesChannelId;
|
||||
if (salesChannelId === null) {
|
||||
|
||||
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -27,6 +27,13 @@
|
||||
<argument type="service" id="service_container"/>
|
||||
<argument type="service" id="Shopware\Core\Content\ImportExport\DataAbstractionLayer\Serializer\Entity\MediaSerializer"/>
|
||||
<argument type="service" id="Shopware\Core\Content\ImportExport\DataAbstractionLayer\Serializer\SerializerRegistry"/>
|
||||
<argument type="service" id="sales_channel_payment_method.repository"/>
|
||||
<argument type="service" id="payment_method.repository"/>
|
||||
<argument type="service" id="media.repository"/>
|
||||
<argument type="service" id="media_folder.repository"/>
|
||||
<argument type="service" id="media_default_folder.repository"/>
|
||||
<argument type="service" id="rule.repository"/>
|
||||
<argument type="service" id="vrpayment_payment_method_configuration.repository"/>
|
||||
<call method="setLogger">
|
||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||
</call>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<service id="VRPaymentPayment\Core\Api\Refund\Controller\RefundController" public="true">
|
||||
<argument type="service" id="VRPaymentPayment\Core\Api\Refund\Service\RefundService"/>
|
||||
<argument type="service" id="VRPaymentPayment\Core\Settings\Service\SettingsService"/>
|
||||
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||
<call method="setLogger">
|
||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||
</call>
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
|
||||
<argument type="service" id="Shopware\Storefront\Page\GenericPageLoader"/>
|
||||
<argument type="service" id="Shopware\Core\Checkout\Order\SalesChannel\OrderRoute"/>
|
||||
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>
|
||||
<argument type="service" id="Shopware\Core\System\StateMachine\StateMachineRegistry"/>
|
||||
<argument type="service" id="Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface"/>
|
||||
<call method="setLogger">
|
||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||
</call>
|
||||
@@ -30,6 +33,7 @@
|
||||
<argument id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService" type="service"/>
|
||||
<argument id="VRPaymentPayment\Core\Settings\Service\SettingsService" type="service"/>
|
||||
<argument id="VRPaymentPayment\Core\Util\PaymentMethodUtil" type="service"/>
|
||||
<argument id="payment_method.repository" type="service"/>
|
||||
<call method="setLogger">
|
||||
<argument type="service" id="monolog.logger.vrpayment_payment"/>
|
||||
</call>
|
||||
|
||||
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user