Compare commits

...

6 Commits

Author SHA1 Message Date
andrewrowanwallee 88e706b44f Release 7.0.1 2025-07-23 14:54:54 +02:00
andrewrowanwallee 2f5789aea8 Release 7.0.0 2025-06-24 11:48:03 +02:00
andrewrowanwallee 2f9a30ebd3 Release 6.1.14 2025-06-11 11:16:50 +02:00
andrewrowanwallee 2f4d38b4b2 Release 6.1.13 2025-05-27 11:43:52 +02:00
andrewrowanwallee 2cba2a8f3e Release 6.1.12 2025-03-26 11:59:32 +01:00
andrewrowanwallee 3f291ef7ea Release 6.1.12 2025-03-05 12:03:51 +01:00
77 changed files with 6992 additions and 785 deletions
+28 -1
View File
@@ -1,3 +1,30 @@
# 7.0.1
## Feature
- Add plugin version metric
## Bugfix
- Fixed error message when refund amount exceeds total
- Fixed bug where only 25 sales channels showed in the dropdown
- Removed erroneous logs
# 7.0.0
- Compatibility with Shopware 6.7.0
# 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
@@ -141,7 +168,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
+25 -1
View File
@@ -1,3 +1,27 @@
# 7.0.1
- Plugin-Versionsmetrik hinzugefügt
- Fehlermeldung behoben, wenn der Rückerstattungsbetrag den Gesamtbetrag überschreitet
- Fehler behoben, bei dem nur 25 Vertriebskanäle in der Dropdown-Liste angezeigt wurden
- Fehlerhafte Protokolle entfernt
# 7.0.0
- Kompatibilität mit Shopware 6.7.0
# 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
@@ -139,7 +163,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
+70 -44
View File
@@ -1,79 +1,105 @@
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).
The VR Payment plugin wraps around the VR Payment API. This library facilitates your interaction with various services such as transactions.
Please note that this plugin is for versions 6.5, 6.6 or 6.7. For the 6.4 plugin please visit [our Shopware 6.4 plugin](https://github.com/vr-payment/shopware-6-4).
## Requirements
- Shopware 6.5.x or Shopware 6.6.x. See table below.
- Shopware 6.7.x, 6.6.x or 6.5.x. See table below.
- PHP minimum version supported by the each shop version.
## Supported versions
## Documentation
___________________________________________________________________________________
| Shopware 6 version | Plugin major version | Supported until |
|-------------------------------|------------------------|------------------------|
| Shopware 6.6.x | 6.x | Further notice |
| Shopware 6.5.x | 5.x | October 2024 |
-----------------------------------------------------------------------------------
- For English documentation click [here](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/en/documentation.html)
- Für die deutsche Dokumentation klicken Sie [hier](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/de/documentation.html)
- Pour la documentation Française, cliquez [ici](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/fr/documentation.html)
- Per la documentazione in tedesco, clicca [qui](https://docs.plugin-documentation.vr-payment.de/vr-payment/shopware-6/7.0.1/docs/it/documentation.html)
## 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:
### **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
```
#### 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:
1. Download the latest [Release](../../releases)
2. Extract the ZIP to custom/plugins/VRPaymentPayment.
```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
Copy
bin/console plugin:refresh
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.
## 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 |
|-------------------------------|------------------------|------------------------|
| Shopware 6.7.x | 7.x | Further notice |
| Shopware 6.6.x | 6.x | December 2025 |
| Shopware 6.5.x | 5.x | October 2024 |
-----------------------------------------------------------------------------------
### Troubleshooting
**Logs**: Check payment logs with:
### 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.11/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
View File
@@ -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.11"
}
"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.7.0",
"shopware/administration": "~6.7.0",
"shopware/storefront":"~6.7.0",
"vrpayment/sdk": "^4.0.0"
},
"type": "shopware-platform-plugin",
"version": "7.0.1"
}
+692
View File
@@ -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;
}
}
+14
View File
@@ -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
+2
View File
File diff suppressed because one or more lines are too long
+83
View File
@@ -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;
}
+9
View File
@@ -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);
+5
View File
@@ -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
+593 -272
View File
File diff suppressed because it is too large Load Diff
+692
View File
@@ -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;
}
}
+14
View File
@@ -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
+2
View File
File diff suppressed because one or more lines are too long
+83
View File
@@ -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;
}
+9
View File
@@ -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);
+5
View File
@@ -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);
+999
View File
@@ -0,0 +1,999 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="keywords" value="VR Payment, Shopware, Shopware Plugin, Payment, Payment Integration, Documentation"><meta name="description" value="The documentation for the Shopware 6 plugin that enables processing payments with VR Payment.">
<link rel="canonical" href="https://plugin-documentation.wallee.com/wallee-payment/shopware-6/master/VRPaymentPayment/docs/fr/documentation.html" />
<title>Wallee Payment Plugin pour Shopware 6</title>
<link href="assets/monokai-sublime.css" rel="stylesheet" />
<link href="assets/base.css" rel="stylesheet" />
</head>
<body class="documentation">
<div class="layout-wrapper">
<div class="layout-title">
<h1>Wallee Payment Plugin pour Shopware 6</h1>
<h2>Documentation</h2> </div>
<div class="layout-navigation">
<ul class="nav">
<li>
<a href="https://gateway.vr-payment.de/user/login">
Sign Up
</a>
</li>
<li>
<a href="https://github.com/vr-payment/shopware-6/releases/tag/7.0.1/">
Source
</a>
</li>
</ul> </div>
<div class="layout-content">
<div class="col-body">
<div class="col-body-wrapper">
<div class="body-container">
<div class="chapter" id="_sommaire">
<div class="chapter-title">
<h1>
<span class="title-number">1</span>Sommaire </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Le plugin de paiement Wallee intègre un traitement moderne des paiements dans Shopware 6, offrant des fonctionnalités telles que les paiements basés sur iFrame, les remboursements, les captures et la conformité PCI. Il permet une intégration transparente avec le portail Wallee pour la gestion des transactions et des méthodes de paiement.</p>
</div><div class="paragraph">
<p>Important : Veuillez noter que seules les mises à jour majeures (par exemple 6.x.0.0) et mineures (par exemple 6.0.x.0) seront testées pour la compatibilité dans les 2 semaines suivant la publication.</p>
</div> </div>
</div> <div class="chapter" id="_pré_requis">
<div class="chapter-title">
<h1>
<span class="title-number">2</span>Pré-Requis </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p><strong>Shopware Version</strong>: 6.5.x or 6.6.x (voir <a href="#compatibility">tableau de compatibilité</a>).</p>
</div><div class="paragraph">
<p><strong>PHP</strong>: Version minimale requise pour votre installation Shopware (e.g., 7.4+).</p>
</div><div class="paragraph">
<p><strong>Compte Wallee</strong>: Obtenir Space ID, User ID, et clé API du <strong>Portail Wallee</strong> (voir le <a href="#portal-startup-guide">Guide de démarrage du Portail</a>).</p>
</div> </div>
</div> <div class="chapter" id="compatibility">
<div class="chapter-title">
<h1>
<span class="title-number">3</span>Compatibilité </h1>
</div>
<div class="chapter-body">
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 25%;"/>
<col style="width: 25%;"/>
<col style="width: 25%;"/>
<col style="width: 25%;"/>
</colgroup>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Shopware Version</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Plugin Version</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>PHP Version</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Support Until</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">6.6.x</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">6.x.x</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">PHP 8.2 and 8.3</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">En cours</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">6.5.x - Deprecated</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">5.x.x</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">PHP 8.2 and 8.3</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">October 2024</p></td>
</tr>
</tbody>
</table> </div>
</div> <div class="chapter" id="_installation">
<div class="chapter-title">
<h1>
<span class="title-number">4</span>Installation </h1>
</div>
<div class="chapter-body">
<div class="section" id="_via_marketplace">
<div class="section-title">
<h2>
<span class="title-number">4.1</span>Via Marketplace </h2>
</div>
<div class="section-body">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Connectez-vous au backend de votre boutique Shopware.</p>
</li>
<li>
<p>Naviguez vers Paramètres → Système → Plugins.</p>
</li>
<li>
<p>Cliquez sur la flèche du menu et sélectionnez le lien dinstallation du plugin à installer</p>
<div class="imageblock">
<div class="content">
<img src="resource/plugin-installation.png" alt="plugin installation"/>
</div>
</div>
</li>
<li>
<p>Activez le plugin wallee Payment à partir du gestionnaire de plugins</p>
</li>
</ol>
</div> </div>
</div> <div class="section" id="_via_composer_recommendé">
<div class="section-title">
<h2>
<span class="title-number">4.2</span>Via Composer (Recommendé) </h2>
</div>
<div class="section-body">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Naviguez jusqu&#8217;au répertoire racine de votre Shopware.</p>
</li>
<li>
<p>Exécutez:</p>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">composer require vrpayment/shopware-6
php bin/console plugin:refresh
php bin/console plugin:install --activate --clearCache VRPaymentPayment</code></pre>
</div>
</div>
</li>
</ol>
</div> </div>
</div> <div class="section" id="_via_composer_recommended">
<div class="section-title">
<h2>
<span class="title-number">4.3</span>Via Composer (Recommended) </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Téléchargez la <a href="https://github.com/vr-payment/shopware-6/releases" target="_blank">dernière version</a>.
. Extrayez le ZIP dans <code>custom/plugins/</code>
. Exécutez</p>
</div><div class="paragraph">
<p>+</p>
</div><div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">php bin/console plugin:refresh
php bin/console plugin:install --activate --clearCache VRPaymentPayment</code></pre>
</div>
</div> </div>
</div> </div>
</div> <div class="chapter" id="portal-startup-guide">
<div class="chapter-title">
<h1>
<span class="title-number">5</span>Guide de démarrage pour le Portail </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Allez sur <a href="https://gateway.vr-payment.de/user/login/user/login">VR Payment</a> et créez un Compte si vous nen avez pas déjà un</p>
</div><div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
Veuillez sélectionner le plan d&#8217;abonnement approprié - il doit prendre en charge les transactions de e-commerce.
</td>
</tr>
</table>
</div> <div class="section" id="_créez_la_clé_api">
<div class="section-title">
<h2>
<span class="title-number">5.1</span>Créez la clé API: </h2>
</div>
<div class="section-body">
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Une fois que votre compte est activé, naviguez vers Compte → Utilisateurs de l&#8217;application</p>
<div class="imageblock">
<div class="content">
<img src="resource/application-users.png" alt="application users"/>
</div>
</div>
</li>
<li>
<p>Cliquez sur Créer un utilisateur dapplication</p>
<div class="imageblock">
<div class="content">
<img src="resource/user.png" alt="user"/>
</div>
</div>
</li>
<li>
<p>Spécifiez un nom pour cette <strong>Clé API</strong> - par exemple Test Shop - et cliquez sur <strong>Créer un utilisateur user</strong></p>
<div class="imageblock">
<div class="content">
<img src="resource/api-key.png" alt="api key"/>
</div>
</div>
</li>
<li>
<p>Votre utilisateur dapplication est ainsi créé. Veuillez copier l&#8217;User ID et la Clé dauthentification, car vous en aurez besoin pour reliez votre magasin au portail.</p>
<div class="imageblock">
<div class="content">
<img src="resource/token.png" alt="token"/>
</div>
</div>
</li>
<li>
<p>Enfin, vous devrez accorder certaines autorisations à cet utilisateur d&#8217;application afin qu&#8217;il puisse communiquer avec votre espace. Pour ce faire, sous Rôle, naviguez jusqu&#8217;à Gérer.</p>
<div class="imageblock">
<div class="content">
<img src="resource/roles.png" alt="roles"/>
</div>
</div>
</li>
<li>
<p>Cliquez sur le sign + à côté de Space Roles</p>
<div class="imageblock">
<div class="content">
<img src="resource/assign-role.png" alt="assign role"/>
</div>
</div>
</li>
<li>
<p>Veuillez ajouter le rôle <strong>Space Admin</strong> et cliquez sur and click on <strong>Assign Role</strong>.</p>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
Veuillez noter que le chargement des rôles peut durer quelques secondes.
</td>
</tr>
</table>
</div>
<div class="imageblock">
<div class="content">
<img src="resource/loading-roles.png" alt="loading roles"/>
</div>
</div>
</li>
<li>
<p>Enfin, cliquez sur Enregistrer les Roles; vous devrez entrer votre mot de passe pour confirmer.</p>
<div class="imageblock">
<div class="content">
<img src="resource/save-role.png" alt="save role"/>
</div>
</div>
</li>
</ol>
</div> </div>
</div> <div class="section" id="_configurer_les_modes_de_paiement">
<div class="section-title">
<h2>
<span class="title-number">5.2</span>Configurer les modes de paiement </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Vous pouvez maintenant configurer les méthodes de paiement que vous souhaitez utiliser ; à des fins de test, nous utiliserons le Bogus Processor. Veuillez noter que si vous souhaitez effectuer une transaction de production, vous pouvez utiliser notre offre Payfac ou vous connecter directement à un PSP spécifique (Worldline ; ACI ; &#8230;&#8203;).</p>
</div><div class="olist arabic">
<ol class="arabic">
<li>
<p>Naviguez vers Space → Paramètres et cliquez sur Processors</p>
<div class="imageblock">
<div class="content">
<img src="resource/payment-settings.png" alt="payment settings"/>
</div>
</div>
</li>
<li>
<p>Cliquez sur Configurez le processeur</p>
</li>
<li>
<p>Sélectionnez Bogus Processor et cliquez sur Continue</p>
<div class="imageblock">
<div class="content">
<img src="resource/bogus-processor.png" alt="bogus processor"/>
</div>
</div>
</li>
<li>
<p>Définissez le <strong>nom</strong> que vous souhaitez donner à votre processeur - e.g. Test Processor - et cliquez sur <strong>Créer</strong></p>
<div class="imageblock">
<div class="content">
<img src="resource/name-processor.png" alt="name processor"/>
</div>
</div>
</li>
<li>
<p>Sélectionnez tous les connecteurs qui sappliquent et cliquez sur Enregistrer</p>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
Veuillez noter que les connecteurs semblent faire double emploi, mais c&#8217;est parce que l&#8217;un concerne le paiement par terminal physique et l&#8217;autre le paiement par ecommerce.
</td>
</tr>
</table>
</div>
<div class="imageblock">
<div class="content">
<img src="resource/connectors.png" alt="connectors"/>
</div>
</div>
</li>
</ol>
</div><div class="paragraph">
<p>Les méthodes de paiement sont désormais disponibles dans le portail.</p>
</div> </div>
</div> </div>
</div> <div class="chapter" id="_guide_de_démarrage_pour_shopware">
<div class="chapter-title">
<h1>
<span class="title-number">6</span>Guide de démarrage pour Shopware </h1>
</div>
<div class="chapter-body">
<div class="olist arabic">
<ol class="arabic">
<li>
<p><strong>Identifiants API</strong></p>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p>Naviguez vers <strong>Shopware Admin → Paramètres → Extensions → Wallee Payment</strong> et cliquez sur Sauvegarder.</p>
</li>
<li>
<p>Entrez votre <code>Space ID</code>, <code>User ID</code>, et <code>clé API</code></p>
<div class="olist lowerroman">
<ol class="lowerroman" type="i">
<li>
<p>Pour le Space id; vous pouvez naviguer vers le Space et il sera fourni - par exemple, lidentifiant du Space id = 76231</p>
<div class="imageblock">
<div class="content">
<img src="resource/plugin-configuration.png" alt="plugin configuration"/>
</div>
</div>
</li>
</ol>
</div>
</li>
</ol>
</div>
</li>
<li>
<p><strong>Méthodes de Paiment</strong></p>
<div class="paragraph">
<p>Les méthodes de paiement disponibles à la caisse sont gérées par le Portail Wallee. Si vous souhaitez désactiver une méthode de paiement, vous devez la désactiver à partir du portail.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Veuillez noter qu&#8217;en raison de la synchronisation entre le portail et la boutique, vous pouvez effectuer la même opération à partir de la boutique, sous <strong>Paramètres → Méthodes de paiement.</strong>
</td>
</tr>
</table>
</div>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p>a.Allez dans <strong>Space → Paramètres → Paiements → Méthodes de Paiement</strong></p>
<div class="imageblock">
<div class="content">
<img src="resource/payment-methods.png" alt="payment methods"/>
</div>
</div>
</li>
<li>
<p>Sélectionnez le mode de paiement que vous souhaitez désactiver. Cliquez sur le <strong>curseur</strong> pour le désactiver → il devrait devenir <strong>inactif</strong>.</p>
<div class="imageblock">
<div class="content">
<img src="resource/cc-enable.png" alt="cc enable"/>
</div>
</div>
<div class="imageblock">
<div class="content">
<img src="resource/cc-disable.png" alt="cc disable"/>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
Si vous souhaitez désactiver uniquement un Connecteur d&#8217;un mode de paiement (par exemple, Mastercard pour une carte de crédit), veuillez consulter la section Connecteur.
</td>
</tr>
</table>
</div>
</li>
</ol>
</div>
</li>
<li>
<p><strong>Options</strong></p>
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li>
<p><strong>Space View Id</strong>: Ce champ vous permet d&#8217;appliquer un style personnalisé au formulaire et à la page de paiement. Le style est défini dans les paramètres de votre espace dans le portail.</p>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Veuillez noter que si vous n&#8217;utilisez pas Space View Id, cette option doit rester vide.
</td>
</tr>
</table>
</div>
</li>
<li>
<p><strong>Intégration</strong>: Le paramètre Options d&#8217;intégration détermine comment le formulaire de paiement est affiché pendant la procédure de paiement. Les options suivantes sont disponibles :</p>
<div class="olist lowerroman">
<ol class="lowerroman" type="i">
<li>
<p><strong>IFrame</strong>: Incorpore le formulaire de paiement directement dans la page de paiement de Shopware pour une expérience transparente.</p>
</li>
<li>
<p><strong>Lightbox</strong>: Ouvre une fenêtre contextuelle sécurisée pour que les clients puissent effectuer leur paiement sans quitter la page de paiement.</p>
</li>
<li>
<p><strong>Payment Page</strong>: Redirige les clients vers une page de paiement dédiée, hébergée par le fournisseur de paiement.</p>
</li>
</ol>
</div>
</li>
<li>
<p><strong>Consistence des articles</strong>: Shopware calcule les taxes au niveau de l&#8217;article, ce qui peut entraîner des différences mineures (généralement de quelques centimes) entre le total des taxes de la commande et le prix affiché. Cet écart est dû à des différences d&#8217;arrondi lors du calcul de chaque article. Si le paramètre « Renforcer la cohérence » est activé, le portail rejettera automatiquement les commandes présentant de tels écarts. Pour éviter les problèmes liés au traitement des paiements, nous vous recommandons de désactiver ce paramètre, à moins qu&#8217;une validation stricte du total des taxes ne soit nécessaire.</p>
</li>
<li>
<p><strong>Envoyer un email de confirmation de commande</strong>: Activez cette option pour envoyer des emails de confirmation de commande directement depuis Shopware au lieu du portail.</p>
<div class="imageblock">
<div class="content">
<img src="resource/order-confirmation-email.png" alt="order confirmation email"/>
</div>
</div>
</li>
</ol>
</div>
</li>
</ol>
</div> </div>
</div> <div class="chapter" id="_différents_etats_pour_une_transaction">
<div class="chapter-title">
<h1>
<span class="title-number">7</span>Différents Etats pour une Transaction </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Le processus de paiement de wallee est complètement standardisé pour chaque méthode de paiement que vous pouvez traiter. Cela vous permet d&#8217;ajouter simplement une méthode de paiement ou un processeur sans modifier la configuration de votre Shopware. Une vue d&#8217;ensemble des états et des processus de paiement de wallee peut être trouvée dans la <a href="https://gateway.vr-payment.de/en-us/doc/payment/transaction-process" target="_blank">documentation sur les paiments.</a>.</p>
</div><div class="paragraph">
<p>Dans la section suivante, nous vous donnons un aperçu de la façon dont les états de wallee sont mappés dans le graphique des états de Shopware pour les commandes et les états de paiement.</p>
</div> <div class="section" id="_cartographie_des_différents_états_d_une_commande_de_shopware">
<div class="section-title">
<h2>
<span class="title-number">7.1</span>Cartographie des différents états dune commande de Shopware </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Actuellement, nous ne modifions pas l&#8217;état de la commande. Nous ne modifions que l&#8217;état du paiement et l&#8217;état de la livraison.</p>
</div> <div class="section" id="_remarque_générales_concernant_les_status_des_commandes">
<div class="section-title">
<h3>
<span class="title-number">7.1.1</span>Remarque générales concernant les status des commandes </h3>
</div>
<div class="section-body">
<div class="paragraph">
<p>Nous vous recommandons de ne modifier l&#8217;état de la commande que lorsque l&#8217;état du paiement est définitif (Payée, Annulée ou Echouée) .</p>
</div> </div>
</div> </div>
</div> <div class="section" id="_cartographie_des_différents_états_du_paiement_de_shopware">
<div class="section-title">
<h2>
<span class="title-number">7.2</span>Cartographie des différents états du paiement de Shopware </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Vous trouverez ci-dessous un diagramme qui montre lassociation des différents états de Shopware pour l&#8217;état de paiement pour wallee, ainsi que des informations supplémentaires sur les transitions entre les états.</p>
</div><div class="imageblock">
<div class="content">
<img src="resource/shopware_6_stage_graph_order.svg" alt="shopware 6 stage graph order"/>
</div>
</div><div class="olist glossary">
<ol class="glossary">
<li>
<p>Si la transaction est <code>Autorisée</code> dans wallee, le statut du paiement de la commande dans le Shopware est marqué comme étant <code>En Cours</code>.</p>
</li>
<li>
<p>Si la transaction échoue avant ou pendant le processus d&#8217;autorisation, le statut du paiement de la commande du Shopware est marqué comme <code>Échouée</code>.</p>
</li>
<li>
<p>Si la transaction échoue après l&#8217;autorisation, le statut du paiement de la commande du Shopware est marqué comme <code>Annulée</code>.</p>
</li>
<li>
<p>Si la facture de la transaction dans wallee est marquée comme <code>Payée</code> ou <code>Non Applicable</code>, le statut du paiement de la commande dans le Shopware est marqué comme <code>Payée</code>.</p>
</li>
</ol>
</div> <div class="section" id="_remarques_générales_concernant_les_différents_status_pour_les_paiements">
<div class="section-title">
<h3>
<span class="title-number">7.2.1</span>Remarques générales concernant les différents status pour les paiements </h3>
</div>
<div class="section-body">
<div class="paragraph">
<p>Nous vous recommandons de ne pas modifier manuellement l&#8217;état du paiement. Si vous le faites, il peut être modifié à nouveau par le plugin.</p>
</div> </div>
</div> </div>
</div> <div class="section" id="_carthographie_des_différents_états_de_livraison_chez_shopware">
<div class="section-title">
<h2>
<span class="title-number">7.3</span>Carthographie des différents états de livraison chez Shopware </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Vous trouverez ci-dessous un diagramme qui montre les différents états pour la livraison chez Shopware avec des informations supplémentaires pour les transitions d&#8217;états.</p>
</div><div class="imageblock">
<div class="content">
<img src="resource/shopware_6_stage_graph_delivery.svg" alt="shopware 6 stage graph delivery"/>
</div>
</div><div class="olist glossary">
<ol class="glossary">
<li>
<p>Si la transaction est <code>confirmée</code> dans wallee, le statut de livraison de la commande dans le Shopware est indiqué comme étant <code>En Attente</code>.</p>
</li>
<li>
<p>Si la transaction dans wallee est marquée comme <code>Délivrée</code>, le statut de livraison de la commande Shopware est marqué comme <code>Ouvert</code>.</p>
</li>
<li>
<p>Si la transaction est en statut <code>Déclinée</code>, <code>Échouée</code> ou <code>Annulée</code>, le statut de livraison de la commande du Shopware est marqué comme <code>Annulée</code>.</p>
</li>
</ol>
</div> </div>
</div> </div>
</div> <div class="chapter" id="_gestion_des_transactions">
<div class="chapter-title">
<h1>
<span class="title-number">8</span>Gestion des Transactions </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Vous pouvez capturer, annuler et rembourser des transactions directement depuis le backend de Shopware. Veuillez noter que si vous remboursez, annulez ou capturez des transactions dans wallee, les événements seront synchronisés dans Shopware. Cependant, il y a quelques limitations (voir ci-dessous).</p>
</div> <div class="section" id="_complete_capture_an_order">
<div class="section-title">
<h2>
<span class="title-number">8.1</span>Complete (capture) an order </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Vous avez la possibilité pour vos transactions de n&#8217;autoriser le paiement qu&#8217;une fois la commande passée. Dans la configuration du connecteur, vous avez la possibilité, si la méthode de paiement le permet, de définir si le paiement doit être effectué immédiatement ou différé.</p>
</div><div class="paragraph">
<p>Pour capturer une transaction, ouvrez la commande et cliquez sur le bouton Terminer.</p>
</div><div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Lorsque le paiement est en attente dans wallee, la commande reste en attente.
</td>
</tr>
</table>
</div><div class="imageblock">
<div class="content">
<img src="resource/capture-transaction.png" alt="capture transaction"/>
</div>
</div><div class="paragraph">
<p><strong>Finalisation du paiement de manière différée</strong></p>
</div><div class="paragraph">
<p>Les détaillants souhaitent souvent autoriser les transactions et lancer le processus d&#8217;exécution une fois que tous les articles peuvent être expédiés. Cela est également possible avec wallee.</p>
</div><div class="paragraph">
<p>Cependant, certains processus doivent être suivis. Si vous avez configuré la finalisation du paiement pour qu&#8217;il soit différé, vous devez capturer la transaction avant d&#8217;initier l&#8217;expédition, car il peut toujours arriver quune finalisation échoue. Si vous voulez être sûr de ne pas expédier d&#8217;articles pour lesquels vous n&#8217;avez pas été payé, vous devez reporter l&#8217;expédition jusqu&#8217;à ce que l&#8217;état <code>Confirmé</code> soit atteint. Au départ, la transaction sera dans l&#8217;état <code>Autorisé</code> dans wallee et <code>En cours</code> dans Shopware. Si vous souhaitez lancer le processus d&#8217;exécution, assurez-vous de lancer le processus d&#8217;achèvement comme décrit ci-dessus. Une fois le processus terminé avec succès, la commande passera à l&#8217;état <code>Confirmée</code> dans wallee et à l&#8217;état Payée dans Shopware. Vous pouvez maintenant lancer le processus de livraison .</p>
</div> </div>
</div> <div class="section" id="_annuler_une_transaction">
<div class="section-title">
<h2>
<span class="title-number">8.2</span>Annuler une transaction </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Pour annuler une transaction, ouvrez la commande et cliquez sur le bouton <code>Annuler l&#8217;autorisation</code>.</p>
</div><div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Vous ne pouvez annuler que les transactions qui ne sont pas encore complétée..
</td>
</tr>
</table>
</div><div class="imageblock">
<div class="content">
<img src="resource/void-transaction.png" alt="void transaction"/>
</div>
</div> </div>
</div> <div class="section" id="_remboursement_d_une_transaction">
<div class="section-title">
<h2>
<span class="title-number">8.3</span>Remboursement d&#8217;une transaction </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Vous avez la possibilité de rembourser des transactions déjà effectuées. Pour ce faire, ouvrez l&#8217;ordre saisi. En cliquant sur les 3 points (&#8230;&#8203;) sur un article, vous pouvez rembourser l&#8217;article partiellement (s&#8217;il a une quantité supérieure à 1), ou vous pouvez rembourser l&#8217;article en entier. Si la méthode de paiement ne prend pas en charge les remboursements, vous n&#8217;aurez pas la possibilité d&#8217;effectuer des remboursements en ligne.</p>
</div><div class="imageblock">
<div class="content">
<img src="resource/refund-transaction.png" alt="refund transaction"/>
</div>
</div><div class="paragraph">
<p>Vous pouvez effectuer autant de remboursements individuels que vous le souhaitez jusqu&#8217;à ce que vous ayez atteint le montant total de la commande initiale. Le statut de la commande passe alors automatiquement à <code>completée</code>.</p>
</div><div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Il peut s&#8217;écouler un certain temps avant que vous ne voyiez le remboursement dans Shopware. Les remboursements ne seront visibles que lorsqu&#8217;ils auront été traités avec succès.
</td>
</tr>
</table>
</div> </div>
</div> <div class="section" id="_commandes_en_attente">
<div class="section-title">
<h2>
<span class="title-number">8.4</span>Commandes en attente </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>La livraison ne doit pas être effectuée tant que l&#8217;état de la livraison est en attente. Cela se produit lorsque la transaction dans wallee n&#8217;a pas atteint l&#8217;état Confirmé.</p>
</div><div class="paragraph">
<p>Il y a essentiellement deux raisons pour lesquelles cela peut se produire :</p>
</div><div class="ulist">
<ul>
<li>
<p>La transaction n&#8217;est pas terminée. Dans ce cas, vous devez compléter la transaction comme indiqué ci-dessus..</p>
</li>
<li>
<p>Nous ne sommes pas en mesure de déterminer si vous devez honorer la commande. La décision de livraison est prise automatiquement. Si cela ne se produit pas dans le délai défini, wallee génère une tâche manuelle que vous devez observer et suivre les instructions.</p>
</li>
</ul>
</div><div class="paragraph">
<p>Vous trouverez plus d&#8217;informations sur les tâches manuelles dans notre <a href="https://gateway.vr-payment.de/en-us/doc/manual-tasks" target="_blank">Documentation sur les Tâches Manuelles.</a>.</p>
</div> </div>
</div> <div class="section" id="_limites_de_la_synchronisation_entre_wallee_et_shopware">
<div class="section-title">
<h2>
<span class="title-number">8.5</span>Limites de la synchronisation entre wallee et Shopware </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Veuillez noter que les captures, annulations et remboursements effectués dans wallee sont synchronisés. Cependant, il y a quelques limitations. Dans wallee, vous pouvez modifier le <strong>prix unitaire</strong> et <strong>la quantité</strong> en une seule fois. Cela n&#8217;est pas possible dans le backend du Shopware. Nous vous recommandons donc d&#8217;effectuer les remboursements toujours dans le backend de Shopware et non dans wallee. Si un remboursement ne peut pas être synchronisé, il sera envoyé au processeur, mais il se peut que vous ne le voyiez pas dans votre backend Shopware.</p>
</div><div class="paragraph">
<p>Vous pouvez trouver plus d&#8217;informations sur les remboursements dans wallee dans notre <a href="https://gateway.vr-payment.de/en-us/doc/payment/refund" target="_blank">Documentation sur les Remboursements.</a>.</p>
</div> </div>
</div> <div class="section" id="_tokenisation">
<div class="section-title">
<h2>
<span class="title-number">8.6</span>Tokenisation </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p>Si la méthode de paiement prend en charge la tokenisation, vous pouvez stocker les détails de paiement de votre client pour des achats futurs. Afin d&#8217;utiliser cette fonctionnalité, assurez-vous que le Mode de <strong>paiement en un clic</strong> dans la configuration de votre méthode de paiement est réglé sur <strong>autoriser</strong> ou <strong>forcer</strong> le stockage.</p>
</div><div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
La tokenisation n&#8217;est pas disponible pour les paiements par les invités.
</td>
</tr>
</table>
</div> </div>
</div> <div class="section" id="_caractéristiques_pricinpales">
<div class="section-title">
<h2>
<span class="title-number">8.7</span>Caractéristiques Pricinpales </h2>
</div>
<div class="section-body">
<div class="ulist">
<ul>
<li>
<p><strong>Intégration iFrame</strong>: Intégrez des formulaires de paiement directement dans votre checkout.</p>
</li>
<li>
<p><strong>Remboursements &amp; Captures</strong>: Déclenchez des remboursements complets/partiels et des captures à partir de Shopware ou du portail Wallee.</p>
</li>
<li>
<p><strong>Support Multi-Magasins</strong>: Gérez les configurations sur plusieurs magasins.</p>
</li>
<li>
<p><strong>Mises à jour automatiques</strong>: Les méthodes de paiement se synchronisent dynamiquement via l&#8217;API Wallee.</p>
</li>
</ul>
</div> </div>
</div> <div class="section" id="_troubleshooting">
<div class="section-title">
<h2>
<span class="title-number">8.8</span>Troubleshooting </h2>
</div>
<div class="section-body">
<div class="ulist">
<ul>
<li>
<p>Logs: Vérifiez les logs des payments avec:</p>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">tail -f var/log/whitelabelname_payment*.log</code></pre>
</div>
</div>
</li>
<li>
<p>Problèmes courants:</p>
<div class="ulist">
<ul>
<li>
<p>Assurez-vous que la commande <code>composer update whitelabelname/shopware-6</code> est exécutée après les mises à jour.</p>
</li>
<li>
<p>Vérifier que les identifiants de l&#8217;API correspondent à votre compte Wallee.</p>
</li>
</ul>
</div>
</li>
</ul>
</div> </div>
</div> <div class="section" id="_faqs">
<div class="section-title">
<h2>
<span class="title-number">8.9</span>FAQs </h2>
</div>
<div class="section-body">
<div class="paragraph">
<p><strong>Q: Comment s&#8217;assurer que la connexion entre le portail et la boutique fonctionne?</strong>
A: Vous devez vérifier que les webhooks ont été correctement créés. Pour ce faire, naviguez vers votre espace dans le portail, allez dans Paramètres → Général → Webhook Listeners.</p>
</div><div class="imageblock">
<div class="content">
<img src="resource/webhooks.png" alt="webhooks"/>
</div>
</div><div class="imageblock">
<div class="content">
<img src="resource/webhook-listeners.png" alt="webhook listeners"/>
</div>
</div><div class="paragraph">
<p><strong>Q: Ce plugin prend-il en charge les paiements en un clic ?</strong>
A: Oui, via la tokenisation dans le portail Wallee.</p>
</div><div class="paragraph">
<p><strong>Q: Comment gérer la conformité PCI ?</strong>
A: Le plugin utilise l&#8217;intégration iFrame, réduisant les exigences PCI à SAQ-A.</p>
</div><div class="paragraph">
<p><strong>Q: Le plugin prend-il en charge Apple Pay ?</strong>
A: Oui, le plugin prend en charge les portefeuilles comme Apple Pay.</p>
</div> </div>
</div> </div>
</div> <div class="chapter" id="_changelog">
<div class="chapter-title">
<h1>
<span class="title-number">9</span>Changelog </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Pour les mises à jour spécifiques à une version, voir les <a href="https://github.com/vr-payment/shopware-6/releases" target="_blank">GitHub Releases</a>.</p>
</div> </div>
</div> <div class="chapter" id="_contribuer">
<div class="chapter-title">
<h1>
<span class="title-number">10</span>Contribuer </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Signaler les problèmes via <a href="https://github.com/vr-payment/shopware-6/issues" target="_blank">GitHub Issues</a>.</p>
</div><div class="paragraph">
<p>Suivez le <a href="https://developer.shopware.com/docs/guides/plugins/plugins/plugin-base-guide.html" target="_blank">Shopware Plugin Base Guide</a> pour le développement.</p>
</div> </div>
</div> <div class="chapter" id="_support">
<div class="chapter-title">
<h1>
<span class="title-number">11</span>Support </h1>
</div>
<div class="chapter-body">
<div class="paragraph">
<p>Si vous avez besoin daide, nhésitez pas à contacter notre <a href="https://www.vr-payment.de/hotline">support</a>.</p>
</div> </div>
</div> </div>
</div>
</div>
<div class="col-right">
<div class="col-right-wrapper">
<div class="table-of-contents">
<ul class="nav">
<li class="nav-level-1">
<a href="#_sommaire">
<span class="item-number">1</span>
<span class="item-title">Sommaire</span>
</a>
</li> <li class="nav-level-1">
<a href="#_pré_requis">
<span class="item-number">2</span>
<span class="item-title">Pré-Requis</span>
</a>
</li> <li class="nav-level-1">
<a href="#compatibility">
<span class="item-number">3</span>
<span class="item-title">Compatibilité</span>
</a>
</li> <li class="nav-level-1">
<a href="#_installation">
<span class="item-number">4</span>
<span class="item-title">Installation</span>
</a>
<ul class="nav">
<li class="nav-level-2">
<a href="#_via_marketplace">
<span class="item-number">4.1</span>
<span class="item-title">Via Marketplace</span>
</a>
</li> <li class="nav-level-2">
<a href="#_via_composer_recommendé">
<span class="item-number">4.2</span>
<span class="item-title">Via Composer (Recommendé)</span>
</a>
</li> <li class="nav-level-2">
<a href="#_via_composer_recommended">
<span class="item-number">4.3</span>
<span class="item-title">Via Composer (Recommended)</span>
</a>
</li> </ul>
</li> <li class="nav-level-1">
<a href="#portal-startup-guide">
<span class="item-number">5</span>
<span class="item-title">Guide de démarrage pour le Portail</span>
</a>
<ul class="nav">
<li class="nav-level-2">
<a href="#_créez_la_clé_api">
<span class="item-number">5.1</span>
<span class="item-title">Créez la clé API:</span>
</a>
</li> <li class="nav-level-2">
<a href="#_configurer_les_modes_de_paiement">
<span class="item-number">5.2</span>
<span class="item-title">Configurer les modes de paiement</span>
</a>
</li> </ul>
</li> <li class="nav-level-1">
<a href="#_guide_de_démarrage_pour_shopware">
<span class="item-number">6</span>
<span class="item-title">Guide de démarrage pour Shopware</span>
</a>
</li> <li class="nav-level-1">
<a href="#_différents_etats_pour_une_transaction">
<span class="item-number">7</span>
<span class="item-title">Différents Etats pour une Transaction</span>
</a>
<ul class="nav">
<li class="nav-level-2">
<a href="#_cartographie_des_différents_états_d_une_commande_de_shopware">
<span class="item-number">7.1</span>
<span class="item-title">Cartographie des différents états dune commande de Shopware</span>
</a>
<ul class="nav">
<li class="nav-level-3">
<a href="#_remarque_générales_concernant_les_status_des_commandes">
<span class="item-number">7.1.1</span>
<span class="item-title">Remarque générales concernant les status des commandes</span>
</a>
</li> </ul>
</li> <li class="nav-level-2">
<a href="#_cartographie_des_différents_états_du_paiement_de_shopware">
<span class="item-number">7.2</span>
<span class="item-title">Cartographie des différents états du paiement de Shopware</span>
</a>
<ul class="nav">
<li class="nav-level-3">
<a href="#_remarques_générales_concernant_les_différents_status_pour_les_paiements">
<span class="item-number">7.2.1</span>
<span class="item-title">Remarques générales concernant les différents status pour les paiements</span>
</a>
</li> </ul>
</li> <li class="nav-level-2">
<a href="#_carthographie_des_différents_états_de_livraison_chez_shopware">
<span class="item-number">7.3</span>
<span class="item-title">Carthographie des différents états de livraison chez Shopware</span>
</a>
</li> </ul>
</li> <li class="nav-level-1">
<a href="#_gestion_des_transactions">
<span class="item-number">8</span>
<span class="item-title">Gestion des Transactions</span>
</a>
<ul class="nav">
<li class="nav-level-2">
<a href="#_complete_capture_an_order">
<span class="item-number">8.1</span>
<span class="item-title">Complete (capture) an order</span>
</a>
</li> <li class="nav-level-2">
<a href="#_annuler_une_transaction">
<span class="item-number">8.2</span>
<span class="item-title">Annuler une transaction</span>
</a>
</li> <li class="nav-level-2">
<a href="#_remboursement_d_une_transaction">
<span class="item-number">8.3</span>
<span class="item-title">Remboursement d&amp;#8217;une transaction</span>
</a>
</li> <li class="nav-level-2">
<a href="#_commandes_en_attente">
<span class="item-number">8.4</span>
<span class="item-title">Commandes en attente</span>
</a>
</li> <li class="nav-level-2">
<a href="#_limites_de_la_synchronisation_entre_wallee_et_shopware">
<span class="item-number">8.5</span>
<span class="item-title">Limites de la synchronisation entre wallee et Shopware</span>
</a>
</li> <li class="nav-level-2">
<a href="#_tokenisation">
<span class="item-number">8.6</span>
<span class="item-title">Tokenisation</span>
</a>
</li> <li class="nav-level-2">
<a href="#_caractéristiques_pricinpales">
<span class="item-number">8.7</span>
<span class="item-title">Caractéristiques Pricinpales</span>
</a>
</li> <li class="nav-level-2">
<a href="#_troubleshooting">
<span class="item-number">8.8</span>
<span class="item-title">Troubleshooting</span>
</a>
</li> <li class="nav-level-2">
<a href="#_faqs">
<span class="item-number">8.9</span>
<span class="item-title">FAQs</span>
</a>
</li> </ul>
</li> <li class="nav-level-1">
<a href="#_changelog">
<span class="item-number">9</span>
<span class="item-title">Changelog</span>
</a>
</li> <li class="nav-level-1">
<a href="#_contribuer">
<span class="item-number">10</span>
<span class="item-title">Contribuer</span>
</a>
</li> <li class="nav-level-1">
<a href="#_support">
<span class="item-number">11</span>
<span class="item-title">Support</span>
</a>
</li> </ul>
</div> </div>
</div>
</div>
</div>
<script type="text/javascript" src="assets/jquery.js"></script>
<script type="text/javascript" src="assets/scrollspy.js"></script>
<script type="text/javascript" src="assets/sticky-kit.js"></script>
<script type="text/javascript" src="assets/highlight.js"></script>
<script type="text/javascript" src="assets/base.js"></script>
</body>
</html>
+692
View File
@@ -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;
}
}
+14
View File
@@ -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
+2
View File
File diff suppressed because one or more lines are too long
+83
View File
@@ -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;
}
+9
View File
@@ -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);
+5
View File
@@ -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
@@ -213,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);
@@ -254,9 +253,14 @@ class PaymentMethodConfigurationService {
$paymentMethodData = [];
$salesChannelPaymentMethodData = [];
$criteria = (new Criteria())->addFilter(new EqualsFilter('spaceId', $this->getSpaceId()));
$criteria = (new Criteria())->addFilter(new EqualsFilter('state', 'ACTIVE'));
$paymentMethodConfigurationEntities = $this->vRPaymentPaymentMethodConfigurationRepository
/**
* @var $vRPaymentPMConfigurationRepository
*/
$vRPaymentPMConfigurationRepository = $this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository');
$paymentMethodConfigurationEntities = $vRPaymentPMConfigurationRepository
->search($criteria, $context)
->getEntities();
@@ -275,16 +279,11 @@ class PaymentMethodConfigurationService {
'id' => $paymentMethodConfigurationEntity->getId(),
'active' => false,
];
$salesChannelPaymentMethodData[] = [
'paymentMethodId' => $paymentMethodConfigurationEntity->getId(),
];
}
try {
$this->vRPaymentPaymentMethodConfigurationRepository->update($data, $context);
$this->paymentMethodRepository->update($paymentMethodData, $context);
$this->salesChannelPaymentRepository->delete($salesChannelPaymentMethodData, $context);
} catch (\Exception $exception) {
$this->logger->critical($exception->getMessage());
}
@@ -369,7 +368,13 @@ class PaymentMethodConfigurationService {
];
$this->upsertPaymentMethod($id, $paymentMethodConfiguration, $context);
$this->vRPaymentPaymentMethodConfigurationRepository->upsert([$data], $context);
try {
$this->container->get(PaymentMethodConfigurationEntityDefinition::ENTITY_NAME . '.repository')->upsert([$data], $context);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), [$e->getTraceAsString()]);
}
}
}
@@ -494,13 +499,19 @@ class PaymentMethodConfigurationService {
'afterOrderEnabled' => true,
'active' => true,
'translations' => $this->getPaymentMethodConfigurationTranslation($paymentMethodConfiguration, $context),
'technicalName' => $paymentMethodConfiguration->getName(),
];
$data['mediaId'] = $this->upsertMedia($id, $paymentMethodConfiguration, $context);
$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()]);
}
}
/**
@@ -115,7 +115,11 @@ class RefundController extends AbstractController
$apiClient = $settings->getApiClient();
$transaction = $apiClient->getTransactionService()->read($settings->getSpaceId(), $transactionId);
$this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
$refund = $this->refundService->createRefundByAmount($transaction, $refundableAmount, $context);
if ($refund === null) {
return new Response('refundExceedsAmount', Response::HTTP_BAD_REQUEST);
}
return new Response(null, Response::HTTP_NO_CONTENT);
}
@@ -8,7 +8,7 @@ use Shopware\Core\{
Checkout\Cart\CartException,
Checkout\Cart\LineItem\LineItem,
Checkout\Order\OrderEntity,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Payment\Cart\PaymentTransactionStruct,
Framework\Context,
Framework\DataAbstractionLayer\Search\Criteria,
Framework\DataAbstractionLayer\Search\Filter\EqualsFilter,
@@ -115,7 +115,7 @@ class TransactionService
*
* A redirect to the url will be performed
*
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
*
* @return string
@@ -124,21 +124,24 @@ class TransactionService
* @throws \VRPayment\Sdk\VersioningException
*/
public function create(
AsyncPaymentTransactionStruct $transaction,
SalesChannelContext $salesChannelContext
PaymentTransactionStruct $transaction,
SalesChannelContext $salesChannelContext
): string
{
$criteria = new Criteria([$transaction->getOrderTransactionId()]);
$criteria->addAssociation('order');
$orderTransaction = $this->container->get('order_transaction.repository')->search($criteria, $salesChannelContext->getContext())->first();
$salesChannelId = $salesChannelContext->getSalesChannel()->getId();
$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);
@@ -166,7 +169,7 @@ class TransactionService
$redirectUrl = $this->container->get('router')->generate(
'frontend.vrpayment.checkout.pay',
['orderId' => $transaction->getOrder()->getId(),],
['orderId' => $orderTransaction->getOrder()->getId(),],
UrlGeneratorInterface::ABSOLUTE_URL
);
@@ -178,8 +181,8 @@ class TransactionService
$this->upsert(
$createdTransaction,
$salesChannelContext->getContext(),
$transaction->getOrderTransaction()->getPaymentMethodId(),
$transaction->getOrder()->getSalesChannelId()
$orderTransaction->getPaymentMethodId(),
$orderTransaction->getOrder()->getSalesChannelId()
);
$_SESSION['transactionId'] = null;
$_SESSION['arrayOfPossibleMethods'] = null;
@@ -187,26 +190,26 @@ class TransactionService
$_SESSION['currencyCheck'] = null;
$this->holdDelivery($transaction->getOrder()->getId(), $salesChannelContext->getContext());
$this->holdDelivery($orderTransaction->getOrder()->getId(), $salesChannelContext->getContext());
return $redirectUrl;
}
/**
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @param int $vrpaymentTransactionId
* @param int $spaceId
*/
protected function addVRPaymentTransactionId(
AsyncPaymentTransactionStruct $transaction,
PaymentTransactionStruct $transaction,
Context $context,
int $vrpaymentTransactionId,
int $spaceId
): void
{
$data = [
'id' => $transaction->getOrderTransaction()->getId(),
'id' => $transaction->getOrderTransactionId(),
'customFields' => [
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_TRANSACTION_ID => $vrpaymentTransactionId,
TransactionPayload::ORDER_TRANSACTION_CUSTOM_FIELDS_VRPAYMENT_SPACE_ID => $spaceId,
@@ -344,7 +347,7 @@ class TransactionService
*
* @return \Shopware\Core\Checkout\Order\OrderEntity
*/
private function getOrderEntity(string $orderId, Context $context): OrderEntity
protected function getOrderEntity(string $orderId, Context $context): OrderEntity
{
try {
$criteria = (new Criteria([$orderId]))->addAssociations(['deliveries']);
@@ -638,25 +638,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 +710,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;
}
@@ -0,0 +1,59 @@
<?php declare(strict_types=1);
namespace VRPaymentPayment\Core\Checkout\Cart;
use Shopware\Core\Checkout\Cart\AbstractCartPersister;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler;
class CustomCartPersister extends AbstractCartPersister
{
private AbstractCartPersister $inner;
public function __construct(AbstractCartPersister $inner)
{
$this->inner = $inner;
}
public function delete(string $token, SalesChannelContext $context): void
{
if (!$context->getContext()->hasState('do-cart-delete') && $this->isWhiteLabelPayment($context)) {
return;
}
$this->inner->delete($token, $context);
}
public function load(string $token, SalesChannelContext $context): Cart
{
return $this->inner->load($token, $context);
}
public function save(Cart $cart, SalesChannelContext $context): void
{
$this->inner->save($cart, $context);
}
public function replace(string $oldToken, string $newToken, SalesChannelContext $context): void
{
$this->inner->replace($oldToken, $newToken, $context);
}
public function getDecorated(): AbstractCartPersister
{
return $this->inner;
}
private function isWhiteLabelPayment(SalesChannelContext $context): bool
{
$paymentMethod = $context->getPaymentMethod();
if (!$paymentMethod instanceof PaymentMethodEntity) {
return false;
}
return $paymentMethod->getHandlerIdentifier() === VRPaymentPaymentHandler::class;
}
}
@@ -4,15 +4,30 @@ namespace VRPaymentPayment\Core\Checkout\PaymentHandler;
use Psr\Log\LoggerInterface;
use Shopware\Core\{
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity,
Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface,
Checkout\Payment\Exception\AsyncPaymentFinalizeException,
Checkout\Payment\Exception\AsyncPaymentProcessException,
Checkout\Payment\Cart\PaymentTransactionStruct,
Checkout\Payment\Cart\PaymentHandler\AbstractPaymentHandler,
Checkout\Payment\Cart\PaymentHandler\PaymentHandlerType,
Checkout\Payment\PaymentException,
Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException,
Framework\App\AppException,
Framework\Api\Context\SalesChannelApiSource,
Framework\Context,
Framework\DataAbstractionLayer\EntityRepository,
Framework\DataAbstractionLayer\Search\Criteria,
Framework\DataAbstractionLayer\Search\Sorting\FieldSorting,
Framework\Struct\Struct,
Framework\Validation\DataBag\RequestDataBag,
System\SalesChannel\SalesChannelContext
System\SalesChannel\Context\SalesChannelContextService,
System\SalesChannel\Context\SalesChannelContextServiceParameters
};
use Shopware\Core\Framework\Util\Random;
use VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartPersister;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\{
HttpFoundation\RedirectResponse,
HttpFoundation\Request
@@ -26,9 +41,14 @@ use VRPaymentPayment\Core\Api\Transaction\Service\TransactionService;
*
* @package VRPaymentPayment\Core\Checkout\PaymentHandler
*/
class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
class VRPaymentPaymentHandler extends AbstractPaymentHandler
{
/**
* @var CustomCartPersister
*/
private CustomCartPersister $cartPersister;
/**
* @var \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService
*/
@@ -43,22 +63,34 @@ class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
*/
private $orderTransactionStateHandler;
protected SalesChannelContextService $salesChannelContextService;
protected EntityRepository $orderTransactionRepository;
/**
* VRPaymentPaymentHandler constructor.
*
* @param \VRPaymentPayment\Core\Api\Transaction\Service\TransactionService $transactionService
* @param \Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler $orderTransactionStateHandler
* @param SalesChannelContextService $salesChannelContextService
* @param EntityRepository $orderTransactionRepository
*/
public function __construct(TransactionService $transactionService, OrderTransactionStateHandler $orderTransactionStateHandler)
{
public function __construct(
CustomCartPersister $cartPersister,
TransactionService $transactionService,
OrderTransactionStateHandler $orderTransactionStateHandler,
SalesChannelContextService $salesChannelContextService,
EntityRepository $orderTransactionRepository
) {
$this->cartPersister = $cartPersister;
$this->transactionService = $transactionService;
$this->orderTransactionStateHandler = $orderTransactionStateHandler;
$this->salesChannelContextService = $salesChannelContextService;
$this->orderTransactionRepository = $orderTransactionRepository;
}
/**
* @param \Psr\Log\LoggerInterface $logger
* @internal
* @required
*
*/
public function setLogger(LoggerInterface $logger): void
@@ -72,78 +104,123 @@ class VRPaymentPaymentHandler implements AsynchronousPaymentHandlerInterface
*
* A redirect to the url will be performed
*
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Validation\DataBag\RequestDataBag $dataBag
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \Symfony\Component\HttpFoundation\Request
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @param \Shopware\Core\Framework\Struct\Struct $validateStruct
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function pay(
AsyncPaymentTransactionStruct $transaction,
RequestDataBag $dataBag,
SalesChannelContext $salesChannelContext
Request $request,
PaymentTransactionStruct $transaction,
Context $context,
?Struct $validateStruct
): RedirectResponse
{
try {
$orderTransactionId = $transaction->getOrderTransactionId();
$orderTransaction = $this->orderTransactionRepository->search(
(new Criteria([$orderTransactionId]))
->addAssociation('order'), $context
)->getEntities()->first();
$contextSource = $context->getSource();
if ($contextSource instanceof SalesChannelApiSource) {
$salesChannelContextId = $contextSource->getSalesChannelId();
}
$parameters = new SalesChannelContextServiceParameters($salesChannelContextId, $request->getSession()->get("sw-context-token", Random::getAlphanumericString(32)), originalContext: $context);
$salesChannelContext = $this->salesChannelContextService->get($parameters);
$redirectUrl = $transaction->getReturnUrl();
if ($transaction->getOrder()->getAmountTotal() > 0) {
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
$transactionId = $_SESSION['transactionId'] ?? null;
if ($transactionId === null) {
$this->transactionService->createPendingTransaction($salesChannelContext);
$this->transactionService->createPendingTransaction($transaction, $salesChannelContext);
}
$redirectUrl = $this->transactionService->create($transaction, $salesChannelContext);
}
return new RedirectResponse($redirectUrl);
} catch (\Exception $e) {
} catch (\Throwable $e) {
unset($_SESSION['transactionId']);
$errorMessage = 'An error occurred during the communication with external payment gateway : ' . $e->getMessage();
$this->logger->critical($errorMessage);
throw new \Exception($transaction->getOrderTransaction()->getId() . ': ' . $errorMessage);
throw PaymentException::customerCanceled($transaction->getOrderTransaction()->getId(), $errorMessage);
}
}
/**
* The finalize function will be called when the user is redirected back to shop from the payment gateway.
*
* Throw a @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Symfony\Component\HttpFoundation\Request $request
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
* @param \Shopware\Core\Framework\Context $context
* @throws \VRPayment\Sdk\ApiException
* @throws \VRPayment\Sdk\Http\ConnectionException
* @throws \VRPayment\Sdk\VersioningException
* @see AsyncPaymentFinalizeException exception if an error ocurres while calling an external payment API
* Throw a @see CustomerCanceledAsyncPaymentException exception if the customer canceled the payment process on
* payment provider page
*
* @throws \Exception when the payment was canceled by the customer
*/
public function finalize(
AsyncPaymentTransactionStruct $transaction,
Request $request,
SalesChannelContext $salesChannelContext
Request $request,
PaymentTransactionStruct $transaction,
Context $context
): void
{
if ($transaction->getOrder()->getAmountTotal() > 0) {
$orderTransactionId = $transaction->getOrderTransactionId();
$orderTransaction = $this->orderTransactionRepository->search(
(new Criteria([$orderTransactionId]))
->addAssociation('order'), $context
)->getEntities()->first();
if ($orderTransaction->getOrder()->getAmountTotal() > 0) {
$transactionEntity = $this->transactionService->getByOrderId(
$transaction->getOrder()->getId(),
$salesChannelContext->getContext()
$orderTransaction->getOrder()->getId(),
$context
);
$vRPaymentTransaction = $this->transactionService->read(
$transactionEntity->getTransactionId(),
$salesChannelContext->getSalesChannel()->getId()
$transactionEntity->getSalesChannelId()
);
if (in_array($vRPaymentTransaction->getState(), [TransactionState::FAILED])) {
$errorMessage = strtr('Customer canceled payment for :orderId on SalesChannel :salesChannelName', [
':orderId' => $transaction->getOrder()->getId(),
':salesChannelName' => $salesChannelContext->getSalesChannel()->getName(),
':orderId' => $orderTransaction->getOrder()->getId(),
':salesChannelName' => $transactionEntity->getSalesChannelId(),
]);
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());
$this->orderTransactionStateHandler->paid($orderTransaction->getId(), $context);
}
$token = $request->getSession()->get('sw-context-token');
if ($token) {
$salesChannelId = $transactionEntity->getSalesChannelId();
$parameters = new SalesChannelContextServiceParameters($salesChannelId, $token, originalContext: $context);
$salesChannelContext = $this->salesChannelContextService->get($parameters);
$salesChannelContext->getContext()->addState('do-cart-delete');
$this->logger->info('Clearing cart with token: ' . $token);
$this->cartPersister->delete($salesChannelContext->getToken(), $salesChannelContext);
}
}
/**
* {@inheritDoc}
*/
public function supports(
PaymentHandlerType $type,
string $paymentMethodId,
Context $context
): bool {
if ($type === PaymentHandlerType::RECURRING) {
return false;
}
return true;
}
}
+32 -1
View File
@@ -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;
@@ -354,6 +354,19 @@ 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());
return $this->redirect($trans->getSuccessUrl());
}
// End Adoption for Headless Storefronts
try {
$this->cartService->deleteCart($salesChannelContext);
$cart = $this->cartService->createNew($salesChannelContext->getToken());
+2
View File
@@ -14,6 +14,7 @@ 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
@@ -24,6 +25,7 @@ class Analytics {
self::SHOP_SYSTEM => 'shopware',
self::SHOP_SYSTEM_VERSION => '6',
self::SHOP_SYSTEM_AND_VERSION => 'shopware-6',
self::PLUGIN_SYSTEM_VERSION => '7.0.1',
];
}
+187 -75
View File
@@ -8,10 +8,14 @@ use Shopware\Core\{Checkout\Cart\Tax\Struct\CalculatedTaxCollection,
Checkout\Customer\Aggregate\CustomerAddress\CustomerAddressEntity,
Checkout\Customer\CustomerEntity,
Checkout\Order\Aggregate\OrderLineItem\OrderLineItemEntity,
Checkout\Payment\Cart\AsyncPaymentTransactionStruct,
Checkout\Order\OrderEntity,
Checkout\Payment\Cart\PaymentTransactionStruct,
Framework\DataAbstractionLayer\Search\Criteria,
System\SalesChannel\SalesChannelContext
};
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use VRPayment\Sdk\{Model\AddressCreate,
@@ -36,6 +40,10 @@ use VRPaymentPayment\Core\{Api\PaymentMethodConfiguration\Entity\PaymentMethodCo
Util\Payload\CustomProducts\CustomProductsLineItemTypes
};
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Core\Framework\Context;
use Shopware\Core\System\Tax\TaxEntity;
/**
* Class TransactionPayload
*
@@ -61,7 +69,7 @@ class TransactionPayload extends AbstractPayload
protected $salesChannelContext;
/**
* @var \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct
* @var \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct
*/
protected $transaction;
@@ -85,6 +93,10 @@ class TransactionPayload extends AbstractPayload
*/
protected $translator;
protected EntityRepository $orderTransactionRepository;
protected OrderEntity $order;
/**
* TransactionPayload constructor.
*
@@ -92,14 +104,14 @@ class TransactionPayload extends AbstractPayload
* @param \VRPaymentPayment\Core\Util\LocaleCodeProvider $localeCodeProvider
* @param \Shopware\Core\System\SalesChannel\SalesChannelContext $salesChannelContext
* @param \VRPaymentPayment\Core\Settings\Struct\Settings $settings
* @param \Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct $transaction
* @param \Shopware\Core\Checkout\Payment\Cart\PaymentTransactionStruct $transaction
*/
public function __construct(
ContainerInterface $container,
LocaleCodeProvider $localeCodeProvider,
SalesChannelContext $salesChannelContext,
Settings $settings,
AsyncPaymentTransactionStruct $transaction
PaymentTransactionStruct $transaction
)
{
$this->localeCodeProvider = $localeCodeProvider;
@@ -108,6 +120,23 @@ class TransactionPayload extends AbstractPayload
$this->transaction = $transaction;
$this->container = $container;
$this->translator = $this->container->get('translator');
$this->orderTransactionRepository = $this->container->get('order_transaction.repository');
$criteria = (new Criteria());
$criteria->addFilter(new EqualsFilter('id', $this->transaction->getOrderTransactionId()));
$orders = $this->orderTransactionRepository->search($criteria, $this->salesChannelContext->getContext())->getEntities();
$orderId = $orders->first()->getOrderId();
$criteria = new Criteria([$orderId]);
$criteria
->addAssociation('lineItems')
->addAssociation('orderCustomer')
->addAssociation('transactions')
->addAssociation('currency')
;
$this->order = $this->container->get('order.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first();
}
/**
@@ -118,13 +147,21 @@ class TransactionPayload extends AbstractPayload
*/
public function get(int $version): TransactionPending
{
$customer = $this->salesChannelContext->getCustomer();
$customerId = $this->order->getOrderCustomer()->getCustomerId();
$criteria = new Criteria([$customerId]);
$criteria->addAssociation('activeBillingAddress')
->addAssociation('activeShippingAddress')
->addAssociation('activeShippingAddress')
->addAssociation('defaultBillingAddress')
->addAssociation('defaultShippingAddress')
->addAssociation('salutation');
$customer = $this->container->get('customer.repository')->search($criteria, $this->salesChannelContext->getContext())->getEntities()->first();
$lineItems = $this->getLineItems();
$billingAddress = $this->getAddressPayload($customer, $customer->getActiveBillingAddress());
$shippingAddress = $this->getAddressPayload($customer, $customer->getActiveShippingAddress(), false);
$customerId = null;
$customerName = null;
if ($customer->getGuest() === false) {
@@ -137,14 +174,14 @@ class TransactionPayload extends AbstractPayload
}
$transactionData = [
'currency' => $this->salesChannelContext->getCurrency()->getIsoCode(),
'customer_email_address' => $billingAddress->getEmailAddress(),
'currency' => $this->order->getCurrency()->getIsoCode(),
'customer_email_address' => $customer->getEmail(),
'customer_id' => $customerId,
'language' => $this->localeCodeProvider->getLocaleCodeFromContext($this->salesChannelContext->getContext()) ?? null,
'merchant_reference' => $this->fixLength($this->transaction->getOrder()->getOrderNumber(), 100),
'merchant_reference' => $this->fixLength($this->order->getOrderNumber(), 100),
'meta_data' => [
self::VRPAYMENT_METADATA_ORDER_ID => $this->transaction->getOrder()->getId(),
self::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID => $this->transaction->getOrderTransaction()->getId(),
self::VRPAYMENT_METADATA_ORDER_ID => $this->order->getId(),
self::VRPAYMENT_METADATA_ORDER_TRANSACTION_ID => $this->order->getTransactions()->first()->getId(),
self::VRPAYMENT_METADATA_SALES_CHANNEL_ID => $this->salesChannelContext->getSalesChannel()->getId(),
self::VRPAYMENT_METADATA_CUSTOMER_NAME => $customerName,
],
@@ -161,8 +198,8 @@ class TransactionPayload extends AbstractPayload
$transactionData['meta_data']['additionalAddress2'] = $additionalAddress2;
}
if (!empty($this->transaction->getOrder()->getCustomerComment())) {
$transactionData['meta_data']['customer_comment'] = $this->transaction->getOrder()->getCustomerComment();
if (!empty($this->order->getCustomerComment())) {
$transactionData['meta_data']['customer_comment'] = $this->order->getCustomerComment();
}
$vatIds = $customer->getVatIds();
@@ -198,7 +235,7 @@ class TransactionPayload extends AbstractPayload
$transactionPayload->setAllowedPaymentMethodConfigurations([$paymentConfiguration->getPaymentMethodConfigurationId()]);
$successUrl = $this->transaction->getReturnUrl() . '&status=paid';
$failedUrl = $this->getFailUrl($this->transaction->getOrder()->getId()) . '&status=fail';
$failedUrl = $this->getFailUrl($this->order->getId()) . '&status=fail';
$transactionPayload->setSuccessUrl($successUrl)
->setFailedUrl($failedUrl);
@@ -209,7 +246,7 @@ class TransactionPayload extends AbstractPayload
return $transactionPayload;
}
/**
* Get transaction line items
*
@@ -219,31 +256,31 @@ class TransactionPayload extends AbstractPayload
protected function getLineItems(): array
{
$lineItems = [];
$items = $this->transaction->getOrder()->getLineItems();
$items = $this->order->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 +309,7 @@ class TransactionPayload extends AbstractPayload
$shopLineItem->setLabel($customProductOptionParentLabel . ': ' . $shopLineItem->getLabel());
return $shopLineItem;
}
/**
* Validate the created line item.
*/
@@ -283,7 +320,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,47 +330,113 @@ 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.
*/
protected function addDiscountLineItem($discount, array &$lineItems): void
{
$calculatedPrice = $discount->getPrice();
$calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes();
foreach ($calculatedTaxesCollection as $calculatedTax) {
$rate = $calculatedTax->getTaxRate();
$lineItem = new LineItemCreate();
$amount = $this->calculateDiscountAmount($calculatedTax);
$lineItem->setAmountIncludingTax($amount)
->setName(sprintf('DISCOUNT: %s (%s%% tax)', $discount->getLabel(), $rate))
->setQuantity(1)
->setShippingRequired(false)
->setSku('sku-discount-' . $rate, 200)
->setType(LineItemType::DISCOUNT)
->setUniqueId('coupon-sku-discount-' . $rate . '-' . $rate);
$taxRate = new TaxCreate(['title' => 'Discount Tax: ' . $rate, 'rate' => $rate]);
$lineItem->setTaxes([$taxRate]);
$lineItems[] = $lineItem;
$discountName = $discount->getLabel() ?? 'Unnamed';
$definition = $discount->getPriceDefinition();
if ($this->order->getTaxStatus() === 'net' || $definition instanceof \Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition) {
$calculatedTaxesCollection = $calculatedPrice->getCalculatedTaxes();
foreach ($calculatedTaxesCollection as $calculatedTax) {
$rate = $calculatedTax->getTaxRate();
$amount = $this->calculateDiscountAmount($calculatedTax);
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
} else {
$taxRules = $calculatedPrice->getTaxRules();
if ($taxRules && $taxRules->count() > 0) {
foreach ($taxRules as $taxRule) {
$rate = $taxRule->getTaxRate();
$amount = $calculatedPrice->getTotalPrice();
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
} else {
$rate = $this->getDefaultTaxRate();
$amount = $calculatedPrice->getTotalPrice();
$lineItems[] = $this->createDiscountLineItem($discountName, $amount, $rate);
}
}
}
/**
* @param string $discountName
* @param float $amount
* @param float $rate
* @return LineItemCreate
*/
private function createDiscountLineItem(string $discountName, float $amount, float $rate): LineItemCreate
{
$lineItem = new LineItemCreate();
$discountSkuName = 'sku-discount-' . $rate . '-' . $discountName;
$discountTitle = sprintf('DISCOUNT: %s (%s%% tax)', $discountName, $rate);
if ($this->order->getTaxStatus() === 'tax-free') {
$discountSkuName = 'sku-discount-' . $discountName;
$discountTitle = sprintf('DISCOUNT: %s', $discountName);
}
$lineItem->setAmountIncludingTax($amount)
->setName($discountTitle)
->setQuantity(1)
->setShippingRequired(false)
->setSku($discountSkuName, 200)
->setType(LineItemType::DISCOUNT)
->setUniqueId('coupon-' . $discountSkuName);
$taxRate = new TaxCreate([
'title' => 'Discount Tax: ' . $rate,
'rate' => $rate,
]);
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes([$taxRate]);
}
return $lineItem;
}
/**
* @return float
*/
private function getDefaultTaxRate(): float
{
/** @var SystemConfigService $systemConfigService */
$systemConfigService = $this->container->get(SystemConfigService::class);
$taxId = $systemConfigService->get('core.tax.defaultTaxRate');
if (!$taxId || !is_string($taxId)) {
return 21.0;
}
$criteria = new Criteria([$taxId]);
/** @var TaxRepository $taxRepository */
$taxRepository = $this->container->get('tax.repository');
$tax = $taxRepository->search($criteria, Context::createDefaultContext())->get($taxId);
return $tax instanceof TaxEntity ? $tax->getTaxRate() : 21.0;
}
/**
* Calculate discount amount including tax if necessary.
*/
protected function calculateDiscountAmount($calculatedTax): float
{
$amount = self::round($calculatedTax->getPrice());
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $calculatedTax->getTax());
}
return $amount;
@@ -348,13 +451,13 @@ 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) {
if (count($this->order->getShippingCosts()->getCalculatedTaxes()) === 1) {
if ($shippingLineItem = $this->getShippingLineItem()) {
$lineItems[] = $shippingLineItem;
}
@@ -363,7 +466,7 @@ class TransactionPayload extends AbstractPayload
$lineItems = array_merge($lineItems, $multipleShippingLineItems);
}
}
if ($adjustmentLineItem = $this->getAdjustmentLineItem($lineItems)) {
$lineItems[] = $adjustmentLineItem;
}
@@ -376,7 +479,7 @@ class TransactionPayload extends AbstractPayload
protected function getCustomProductOptionLabel(string $lineItemParentId): string
{
$label = '';
foreach ($this->transaction->getOrder()->getLineItems() as $shopLineItem) {
foreach ($this->order->getLineItems() as $shopLineItem) {
if ($shopLineItem->getParentId() === $lineItemParentId && $shopLineItem->getType() === CustomProductsLineItemTypes::LINE_ITEM_TYPE_PRODUCT) {
$label = $shopLineItem->getLabel();
break;
@@ -401,10 +504,11 @@ class TransactionPayload extends AbstractPayload
$sku = $payLoad['productNumber'];
}
$sku = $this->fixLength($sku, 200);
$amount = $shopLineItem->getTotalPrice() ? self::round($shopLineItem->getTotalPrice()) : 0;
//include Tax Excluded for Net Tax display customer group
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $shopLineItem->getPrice()->getCalculatedTaxes()->getAmount());
}
@@ -440,7 +544,9 @@ class TransactionPayload extends AbstractPayload
}
if (!empty($taxes)) {
$lineItem->setTaxes($taxes);
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes($taxes);
}
}
if ($shopLineItem->getTotalPrice() >= 0) {
@@ -516,31 +622,34 @@ class TransactionPayload extends AbstractPayload
{
try {
$amount = $this->transaction->getOrder()->getShippingTotal();
$amount = $this->order->getShippingTotal();
$amount = self::round($amount);
if ($amount > 0) {
$shippingName = $this->salesChannelContext->getShippingMethod()->getName() ?? $this->translator->trans('vrpayment.payload.shipping.name');
$taxes = $this->getTaxes(
$this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes(),
$this->order->getShippingCosts()->getCalculatedTaxes(),
$shippingName
);
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
$amount = self::round($amount + $this->transaction->getOrder()->getShippingCosts()->getCalculatedTaxes()->getAmount());
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $this->order->getShippingCosts()->getCalculatedTaxes()->getAmount());
}
$lineItem = (new LineItemCreate())
->setAmountIncludingTax($amount)
->setName($this->fixLength($shippingName . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150))
->setQuantity($this->transaction->getOrder()->getShippingCosts()->getQuantity() ?? 1)
->setTaxes($taxes)
->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1)
->setSku($this->fixLength($shippingName . '-Shipping', 200))
/** @noinspection PhpParamsInspection */
->setType(LineItemType::SHIPPING)
->setUniqueId($this->fixLength($shippingName . '-Shipping', 200));
if ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes($taxes);
}
if (!$lineItem->valid()) {
$this->logger->critical('Shipping LineItem payload invalid:', $lineItem->listInvalidProperties());
throw new InvalidPayloadException('Shipping LineItem payload invalid:' . json_encode($lineItem->listInvalidProperties()));
@@ -554,50 +663,53 @@ class TransactionPayload extends AbstractPayload
}
return null;
}
/**
* @return array
*/
protected function getMultipleShippingLineItems(): array
{
try {
if ($this->transaction->getOrder()->getShippingTotal() > 0) {
if ($this->order->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) {
foreach ($this->order->getShippingCosts()->getCalculatedTaxes() as $taxItem) {
$amount = self::round($taxItem->getPrice());
if ($this->transaction->getOrder()->getTaxStatus() === 'net') {
if ($this->order->getTaxStatus() === 'net') {
$amount = self::round($amount + $taxItem->getTax());
}
$taxRate = $taxItem->getTaxRate();
$tax = (new TaxCreate())
->setRate($taxRate)
->setTitle('Tax rate: '.$taxRate);
$name = $taxRate . '%-' . $shippingName;
$lineItem = (new LineItemCreate())
->setAmountIncludingTax($amount)
->setName($this->fixLength($name . ' ' . $this->translator->trans('vrpayment.payload.shipping.lineItem'), 150))
->setQuantity($this->transaction->getOrder()->getShippingCosts()->getQuantity() ?? 1)
->setTaxes([$tax])
->setQuantity($this->order->getShippingCosts()->getQuantity() ?? 1)
->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 ($this->order->getTaxStatus() !== 'tax-free') {
$lineItem->setTaxes([$tax]);
}
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());
}
@@ -620,14 +732,14 @@ class TransactionPayload extends AbstractPayload
return $lineItem->getAmountIncludingTax();
}, $lineItems));
$adjustmentPrice = $this->transaction->getOrder()->getAmountTotal() - $lineItemPriceTotal;
$adjustmentPrice = $this->order->getAmountTotal() - $lineItemPriceTotal;
$adjustmentPrice = self::round($adjustmentPrice);
if (abs($adjustmentPrice) != 0) {
if ($this->settings->isLineItemConsistencyEnabled()) {
$error = strtr('LineItems total :lineItemTotal does not add up to order total :orderTotal', [
':lineItemTotal' => $lineItemPriceTotal,
':orderTotal' => $this->transaction->getOrder()->getAmountTotal(),
':orderTotal' => $this->order->getAmountTotal(),
]);
$this->logger->critical($error);
throw new \Exception($error);
@@ -4,21 +4,21 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_completion_amount %}
<sw-checkbox-field
<mt-checkbox
:label="$tc('vrpayment-order.captureAction.button.text')"
v-model:value="isCompletion">
</sw-checkbox-field>
v-model:checked="isCompletion">
</mt-checkbox>
{% endblock %}
{% block vrpayment_order_action_completion_confirm_button %}
<template #modal-footer>
<sw-button variant="primary"
<mt-button variant="primary"
@click="completion">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -4,23 +4,23 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_refund_amount_by_amount %}
<sw-number-field
<mt-number-field
:max="refundableAmount"
:min="0"
v-model:value="refundAmount"
v-model="refundAmount"
:label="$tc('vrpayment-order.refund.refundAmount.label')"
:suffix="currency">
</sw-number-field>
</mt-number-field>
{% endblock %}
{% block vrpayment_order_action_refund_confirm_button_by_amount %}
<template #modal-footer>
<sw-button variant="primary" @click="refundByAmount()">
<mt-button variant="primary" @click="refundByAmount()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -70,9 +70,18 @@ Component.register('vrpayment-order-action-refund-by-amount', {
});
}).catch((errorResponse) => {
try {
var errorTitle;
var errorMessage;
if (errorResponse.response.data == 'refundExceedsAmount') {
errorTitle = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.title');
errorMessage = this.$tc('vrpayment-order.refundAction.refundExceedsTotalError.messageRefundAmountExceedsAvailableBalance');
} else {
errorTitle = errorResponse.response.data.errors[0].title;
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) {
@@ -4,13 +4,13 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_refund_amount_partial %}
<sw-number-field
<mt-number-field
:max="this.$parent.$parent.itemRefundableAmount"
:min="0.00"
v-model:value="refundAmount"
v-model="refundAmount"
:label="$tc('vrpayment-order.refund.refundAmount.label')"
:suffix="currency">
</sw-number-field>
</mt-number-field>
<div>
{{ $tc('vrpayment-order.refundAction.maxAvailableAmountToRefund') }}:
@@ -20,12 +20,12 @@
{% block vrpayment_order_action_refund_confirm_button_partial %}
<template #modal-footer>
<sw-button variant="primary" @click="createPartialRefund(this.$parent.$parent.currentLineItem)">
<mt-button variant="primary" @click="createPartialRefund(this.$parent.$parent.currentLineItem)">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -47,7 +47,9 @@ Component.register('vrpayment-order-action-refund-partial', {
createdComponent() {
this.isLoading = false;
this.currency = this.transactionData.transactions[0].currency;
this.refundAmount = this.$parent.$parent.itemRefundableAmount;
if (!this.refundAmount) {
this.refundAmount = this.$parent.$parent.itemRefundableAmount;
}
},
createPartialRefund(itemUniqueId) {
@@ -5,12 +5,12 @@
{% block vrpayment_order_action_refund_confirm_button_selected %}
<template #modal-footer>
<sw-button variant="primary" @click="refundSelected()">
<mt-button variant="primary" @click="refundSelected()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -5,12 +5,12 @@
{% block vrpayment_order_action_refund_amount %}
<sw-number-field
<mt-number-field
:max="this.$parent.$parent.itemRefundableQuantity"
:min="0"
v-model:value="refundQuantity"
v-model="refundQuantity"
:label="$tc('vrpayment-order.refund.refundQuantity.label')">
</sw-number-field>
</mt-number-field>
<div>
{{ $tc('vrpayment-order.refundAction.maxAvailableItemsToRefund') }}:
@@ -20,12 +20,12 @@
{% block vrpayment_order_action_refund_confirm_button %}
<template #modal-footer>
<sw-button variant="primary" @click="refund()">
<mt-button variant="primary" @click="refund()">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -4,21 +4,22 @@
@modal-close="$emit('modal-close')">
{% block vrpayment_order_action_void_amount %}
<sw-checkbox-field
{# Review if this v-model:checked="isVoid" needs to change to checked #}
<mt-checkbox
:label="$tc('vrpayment-order.voidAction.confirm.message')"
v-model:value="isVoid">
</sw-checkbox-field>
v-model:checked="isVoid">
</mt-checkbox>
{% endblock %}
{% block vrpayment_order_action_void_confirm_button %}
<template #modal-footer>
<sw-button variant="primary"
<mt-button variant="primary"
@click="voidPayment">
{{ $tc('vrpayment-order.refundAction.confirmButton.text') }}
</sw-button>
</mt-button>
</template>
{% endblock %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</sw-modal>
{% endblock %}
@@ -1,6 +1,7 @@
{% block sw_order_detail_content_tabs_general %}
{% parent %}
{# sw-tabs-item will dissappear. See: https://github.com/shopware/shopware/blob/trunk/UPGRADE-6.7.md#sw-tabs-is-removed #}
<sw-tabs-item v-if="isVRPaymentPayment"
:route="{ name: 'vrpayment.order.detail', params: { id: $route.params.id } }"
:title="$tc('vrpayment-order.header')">
@@ -3,7 +3,7 @@
margin-top: 40px;
}
.sw-order-detail-base .sw-card-view__content {
.sw-order-detail-base .mt-card-view__content {
overflow-x: visible;
overflow-y: visible;
}
@@ -1,61 +1,61 @@
{% block vrpayment_order_detail %}
<div class="vrpayment-order-detail">
<div v-if="!isLoading">
<sw-card :title="$tc('vrpayment-order.paymentDetails.cardTitle')">
<mt-card :title="$tc('vrpayment-order.paymentDetails.cardTitle')">
<template #grid>
{% block vrpayment_order_actions_section %}
<sw-card-section secondary slim>
<mt-card-section secondary slim>
{% block vrpayment_order_transaction_refunds_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL' || refundableAmount <= 0"
@click="spawnModal('refundByAmount')">
{{ $tc('vrpayment-order.buttons.label.refund') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_completion_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'AUTHORIZED' || isLoading"
@click="spawnModal('completion')">
{{ $tc('vrpayment-order.buttons.label.completion') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_void_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'AUTHORIZED' || isLoading"
@click="spawnModal('void')">
{{ $tc('vrpayment-order.buttons.label.void') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_download_invoice_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL'"
@click="downloadInvoice()">
{{ $tc('vrpayment-order.buttons.label.download-invoice') }}
</sw-button>
</mt-button>
{% endblock %}
{% block vrpayment_order_transaction_download_packing_slip_action_button %}
<sw-button
<mt-button
variant="primary"
size="small"
:disabled="transaction.state != 'FULFILL'"
@click="downloadPackingSlip()">
{{ $tc('vrpayment-order.buttons.label.download-packing-slip') }}
</sw-button>
</mt-button>
{% endblock %}
</sw-card-section>
</mt-card-section>
{% endblock %}
</template>
</sw-card>
</mt-card>
{% block vrpayment_order_transaction_history_card %}
<sw-card :title="$tc('vrpayment-order.transactionHistory.cardTitle')">
<mt-card :title="$tc('vrpayment-order.transactionHistory.cardTitle')">
<template #grid>
{% block vrpayment_order_transaction_history_grid %}
@@ -78,10 +78,10 @@
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_transaction_line_items_card %}
<sw-card :title="$tc('vrpayment-order.lineItem.cardTitle')">
<mt-card :title="$tc('vrpayment-order.lineItem.cardTitle')">
<template #grid>
{% block vrpayment_order_transaction_line_items_grid %}
@@ -131,10 +131,10 @@
</sw-data-grid>
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_transaction_refunds_card %}
<sw-card :title="$tc('vrpayment-order.refund.cardTitle')" v-if="transactionData.refunds.length > 0">
<mt-card :title="$tc('vrpayment-order.refund.cardTitle')" v-if="transactionData.refunds.length > 0">
<template #grid>
{% block vrpayment_order_transaction_refunds_grid %}
@@ -147,7 +147,7 @@
{% endblock %}
</template>
</sw-card>
</mt-card>
{% endblock %}
{% block vrpayment_order_actions_modal_refund_partial %}
<vrpayment-order-action-refund-partial
@@ -195,6 +195,6 @@
</vrpayment-order-action-void>
{% endblock %}
</div>
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
</div>
{% endblock %}
@@ -77,7 +77,11 @@
"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",
"refundExceedsTotalError": {
"title": "Fehler beim Erstellen der Rückerstattung.",
"messageRefundAmountExceedsAvailableBalance": "Der Rückerstattungsbetrag übersteigt das verfügbare Guthaben."
}
},
"transactionHistory": {
"cardTitle": "Einzelheiten",
@@ -78,7 +78,11 @@
"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",
"refundExceedsTotalError": {
"title": "Error while creating the refund.",
"messageRefundAmountExceedsAvailableBalance": "Refund amount exceeds available balance."
}
},
"transactionHistory": {
"cardTitle": "Details",
@@ -77,7 +77,11 @@
"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",
"refundExceedsTotalError": {
"title": "Erreur lors de la création du remboursement.",
"messageRefundAmountExceedsAvailableBalance": "Le montant du remboursement dépasse le solde disponible."
}
},
"transactionHistory": {
"cardTitle": "Détails",
@@ -77,7 +77,11 @@
"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",
"refundExceedsTotalError": {
"title": "Errore durante la creazione del rimborso.",
"messageRefundAmountExceedsAvailableBalance": "LL'importo del rimborso supera il saldo disponibile."
}
},
"transactionHistory": {
"cardTitle": "Dettagli",
@@ -1,4 +1,4 @@
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.advancedOptions.cardTitle')">
<sw-container>
<div v-if="actualConfigData" class="vrpayment-settings-advanced-options-fields">
@@ -7,16 +7,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_WEBHOOKS_UPDATE_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.advancedOptions.webhooksUpdateEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.advancedOptions.webhooksUpdateEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
@@ -25,19 +25,19 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_PAYMENTS_UPDATE_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.advancedOptions.paymentsUpdateEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.advancedOptions.paymentsUpdateEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
</div>
</sw-container>
</sw-card>
</mt-card>
@@ -1,6 +1,6 @@
{% block vrpayment_settings_content_card_channel_config_credentials %}
<sw-card
class="sw-card"
<mt-card
class="mt-card"
:title="$tc('vrpayment-settings.settingForm.credentials.cardTitle')"
v-if="actualConfigData"
>
@@ -17,17 +17,17 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_SPACE_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_SPACE_ID"
:required="true"
: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')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="spaceIdErrorState"
@update:value="props.updateCurrentValue">
</sw-number-field>
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -38,17 +38,17 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_USER_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_USER_ID"
:required="true"
: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')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="userIdErrorState"
@update:value="props.updateCurrentValue">
</sw-number-field>
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -59,7 +59,7 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_APPLICATION_KEY]"
:customInheritationCheckFunction="checkTextFieldInheritance">
<template #content="props">
<sw-password-field
<mt-password-field
:name="CONFIG_APPLICATION_KEY"
:required="true"
:passwordToggleAble="true"
@@ -67,26 +67,29 @@
:label="$tc('vrpayment-settings.settingForm.credentials.applicationKey.label')"
:helpText="$tc('vrpayment-settings.settingForm.credentials.applicationKey.tooltipText')"
:disabled="props.isInherited || !acl.can('vrpayment.editor')"
:value="props.currentValue"
:model-value="props.currentValue"
:error="applicationKeyErrorState"
@update:value="props.updateCurrentValue">
</sw-password-field>
@update:model-value="props.updateCurrentValue">
</mt-password-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
</div>
{% endblock %}
{% verbatim %}
<sw-container columns="1fr 1fr" gap="0px 30px">
<sw-button-process
<mt-button
variant="primary"
:isLoading="isTesting"
@click="emitCheckApiConnectionEvent">
{{ $tc('vrpayment-settings.settingForm.credentials.button.label') }}
</sw-button-process>
</mt-button>
</sw-container>
{% endverbatim %}
</sw-container>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
@@ -1,5 +1,5 @@
{% block vrpayment_settings_content_card_channel_config_options %}
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.options.cardTitle')">
{% block vrpayment_settings_content_card_channel_config_credentials_card_container %}
@@ -14,15 +14,15 @@
:inheritedValue="selectedSalesChannelId === null ? null : allConfigs['null'][CONFIG_SPACE_VIEW_ID]"
:customInheritationCheckFunction="checkNumberFieldInheritance">
<template #content="props">
<sw-number-field
<mt-number-field
:name="CONFIG_SPACE_VIEW_ID"
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.spaceViewId.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.spaceViewId.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-number-field>
:model-value="props.currentValue"
@update:model-value="props.updateCurrentValue">
</mt-number-field>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -55,16 +55,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_LINE_ITEM_CONSISTENCY_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_LINE_ITEM_CONSISTENCY_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.lineItemConsistencyEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.lineItemConsistencyEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -75,16 +75,16 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_EMAIL_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_EMAIL_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.options.emailEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.options.emailEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
{% endblock %}
@@ -92,6 +92,6 @@
{% endblock %}
</sw-container>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
@@ -1,5 +1,5 @@
{% block vrpayment_settings_icon %}
<span class="sw-icon icon--vrpayment-multicolor sw-icon--multicolor">
<span class="mt-icon icon--vrpayment-multicolor mt-icon--multicolor" style="width: 16px; height: 16px;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" contentScriptType="text/ecmascript" y="0px" zoomAndPan="magnify" style="enable-background:new 0 0 632.126 170.079;" contentStyleType="text/css" viewBox="0 0 632.126 170.079" preserveAspectRatio="xMidYMid meet" xml:space="preserve" version="1.1">
<style type="text/css" xml:space="preserve">
.st0{fill:none;}
@@ -1,4 +1,4 @@
<sw-card class="sw-card"
<mt-card class="mt-card"
:title="$tc('vrpayment-settings.settingForm.storefrontOptions.cardTitle')">
<sw-container>
<div v-if="actualConfigData" class="vrpayment-settings-storefront-options-fields">
@@ -7,19 +7,19 @@
:inheritedValue="selectedSalesChannelId == null ? null : allConfigs['null'][CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED]"
:customInheritationCheckFunction="checkBoolFieldInheritance">
<template #content="props">
<sw-switch-field
<mt-switch
:name="CONFIG_STOREFRONT_INVOICE_DOWNLOAD_ENABLED"
bordered
:mapInheritance="props"
:label="$tc('vrpayment-settings.settingForm.storefrontOptions.invoiceDownloadEnabled.label')"
:helpText="$tc('vrpayment-settings.settingForm.storefrontOptions.invoiceDownloadEnabled.tooltipText')"
:disabled="props.isInherited"
:value="props.currentValue"
@update:value="props.updateCurrentValue">
</sw-switch-field>
:checked="props.currentValue"
@update:checked="props.updateCurrentValue">
</mt-switch>
</template>
</sw-inherit-wrapper>
</div>
</sw-container>
</sw-card>
</mt-card>
@@ -5,7 +5,7 @@
<template #smart-bar-header>
<h2>
{{ $tc('sw-settings.index.title') }}
<sw-icon name="small-arrow-medium-right" small></sw-icon>
<mt-icon name="small-arrow-medium-right" size="16px"></mt-icon>
{{ $tc('vrpayment-settings.header') }}
</h2>
</template>
@@ -14,7 +14,7 @@
{% block vrpayment_settings_actions %}
<template #smart-bar-actions>
{% block vrpayment_settings_actions_save %}
<sw-button-process
<mt-button
v-model:value="isSaveSuccessful"
class="sw-settings-login-registration__save-action"
variant="primary"
@@ -22,7 +22,7 @@
:disabled="isLoading"
@click="onSave">
{{ $tc('vrpayment-settings.settingForm.save') }}
</sw-button-process>
</mt-button>
{% endblock %}
</template>
{% endblock %}
@@ -31,7 +31,7 @@
<template #content>
{% block vrpayment_settings_content_card %}
<sw-card-view>
<mt-card-view>
{% block vrpayment_settings_content_card_channel_config %}
<sw-sales-channel-config v-model:value="config"
@@ -42,18 +42,18 @@
<template #select="{ onInput, selectedSalesChannelId, salesChannel }">
{% block vrpayment_settings_content_card_channel_config_sales_channel_card %}
<sw-card title="Sales Channel Switch">
<mt-card title="Sales Channel Switch">
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_title %}
<sw-single-select
<sw-entity-single-select
v-model:value="selectedSalesChannelId"
labelProperty="translated.name"
valueProperty="id"
:mapInheritance="props"
:isLoading="isLoading"
:options="salesChannel"
entity="sales_channel"
@update:value="onInput">
</sw-single-select>
</sw-entity-single-select>
{% endblock %}
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer %}
<template #footer>
@@ -66,18 +66,19 @@
{% endblock %}
{% block vrpayment_settings_content_card_channel_config_sales_channel_card_footer_container_button %}
<sw-button-process
<sw-button
variant="primary"
v-model:value="isSetDefaultPaymentSuccessful"
:isLoading="isSettingDefaultPaymentMethods"
@click="onSetPaymentMethodDefault">
{{ $tc('vrpayment-settings.salesChannelCard.button.label') }}
</sw-button-process>
</sw-button>
{% endblock %}
</sw-container>
{% endblock %}
</template>
{% endblock %}
</sw-card>
</mt-card>
{% endblock %}
</template>
{% endblock %}
@@ -134,12 +135,12 @@
{% endblock %}
{% block vrpayment_settings_content_card_loading %}
<sw-loader v-if="isLoading"></sw-loader>
<mt-loader v-if="isLoading"></mt-loader>
{% endblock %}
</sw-card-view>
</mt-card-view>
{% endblock %}
</template>
{% endblock %}
</sw-page>
{% endblock %}
{% endblock %}
@@ -65,18 +65,6 @@ Component.register('vrpayment-settings', {
};
},
created() {
// Registers a listener for the 'check-api-connection-event'.
// Triggered when this event is emitted.
this.$on('check-api-connection-event', this.onCheckApiConnection);
},
beforeDestroy() {
// Removes the listener for the 'check-api-connection-event'
// before the component is destroyed to prevent memory leaks.
this.$off('check-api-connection-event', this.onCheckApiConnection);
},
watch: {
config: {
handler(configData) {
File diff suppressed because one or more lines are too long
@@ -6,12 +6,21 @@
<services>
<service id="VRPaymentPayment\Core\Checkout\PaymentHandler\VRPaymentPaymentHandler">
<argument type="service" id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister"/>
<argument type="service" id="VRPaymentPayment\Core\Api\Transaction\Service\TransactionService"/>
<argument type="service" id="Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler"/>
<argument type="service" id="Shopware\Core\System\SalesChannel\Context\SalesChannelContextService"/>
<argument type="service" id="order_transaction.repository"/>
<call method="setLogger">
<argument type="service" id="monolog.logger.vrpayment_payment"/>
</call>
<tag name="shopware.payment.method.async"/>
<tag name="shopware.payment.method"/>
</service>
<service id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister"
decorates="Shopware\Core\Checkout\Cart\CartPersister"
decoration-inner-name="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister.inner">
<argument type="service" id="VRPaymentPayment\Core\Checkout\Cart\CustomCartPersister.inner"/>
</service>
</services>
@@ -19,9 +19,10 @@
<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
<call method="setTwig">
<!-- Removed in 6.7 -->
<!-- <call method="setTwig">
<argument type="service" id="twig"/>
</call>
</call> -->
</service>
<!-- Subscribers -->
@@ -0,0 +1,25 @@
{
"base": "/bundles/vrpaymentpayment/administration/",
"entryPoints": {
"v-r-payment-payment": {
"css": [
"/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-D4AH6HY2.css"
],
"dynamic": [],
"js": [
"/bundles/vrpaymentpayment/administration/assets/v-r-payment-payment-C6eiDWfX.js"
],
"legacy": false,
"preload": []
}
},
"legacy": false,
"metadatas": {},
"version": [
"7.1.0",
7,
1,
0
],
"viteServer": null
}
@@ -0,0 +1,11 @@
{
"main.js": {
"file": "assets/v-r-payment-payment-C6eiDWfX.js",
"name": "v-r-payment-payment",
"src": "main.js",
"isEntry": true,
"css": [
"assets/v-r-payment-payment-D4AH6HY2.css"
]
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.sw-order-detail .sw-tabs{margin-top:40px}.sw-order-detail .sw-order-detail-base .mt-card-view__content{overflow-x:visible;overflow-y:visible}.vrpayment-order-detail__data{display:grid}.vrpayment-order-detail__heading{padding-top:15px}
@@ -1,2 +0,0 @@
.sw-order-detail .sw-tabs{margin-top:40px}.sw-order-detail .sw-order-detail-base .sw-card-view__content{overflow-x:visible;overflow-y:visible}
.vrpayment-order-detail__data{display:grid}.vrpayment-order-detail__heading{padding-top:15px}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -29,7 +29,7 @@
payment_method_handler_status: 'input[name="vrpayment_payment_handler_validation_status"]',
payment_form_id: 'confirmOrderForm',
button_cancel_id: 'vrpaymentOrderCancel',
button_home_override: 'vrpaymentHomeLink',
// button_home_override: 'vrpaymentHomeLink',
loader_id: 'vrpaymentLoader',
checkout_url: null,
checkout_url_id: 'checkoutUrl',
@@ -46,7 +46,7 @@
this.cart_recreate_url = document.getElementById(this.cart_recreate_url_id).value;
document.getElementById(this.button_cancel_id).addEventListener('click', this.recreateCart, false);
document.getElementById(this.button_home_override).addEventListener('click', this.recreateCart, false);
// document.getElementById(this.button_home_override).addEventListener('click', this.recreateCart, false);
document.getElementById(this.payment_form_id).addEventListener('submit', this.submitPayment, false);
VRPaymentCheckout.getIframe();
@@ -1,15 +1,11 @@
{% sw_extends '@Storefront/storefront/page/checkout/_page.html.twig' %}
{% block base_header %}
{% sw_include '@VRPaymentPayment/storefront/page/checkout/order/vrpayment_header.html.twig' %}
{% endblock %}
{% block base_navigation %}{% endblock %}
{% block base_body_classes %}vrpayment-payment is-act-confirmpage{% endblock %}
{% block page_checkout_main_content %}
{% block page_checkout_pay %}
{% block page_checkout_confirm_header %}
<div id="vrpaymentOrderCancel"></div>
{% block page_checkout_pay %}
{% block page_checkout_confirm_header %}
<h1 class="confirm-main-header">
{{ "vrpayment.payHeader"|trans|sw_sanitize }}
</h1>
@@ -144,10 +140,6 @@
</div>
{% endblock %}
{% block base_footer %}
{% sw_include '@Storefront/storefront/layout/footer/footer-minimal.html.twig' %}
{% endblock %}
{% block base_body_script %}
{{ parent() }}
{% if page.extensions.vRPaymentData %}
@@ -1,53 +0,0 @@
{% sw_extends '@Storefront/storefront/layout/header/header-minimal.html.twig' %}
{% block layout_header_minimal_logo %}
<div class="col-6 col-md-4 header-minimal-logo">
{% block layout_header_logo_inner %}
<div class="header-logo-main">
{% block layout_header_logo_link %}
<a class="header-logo-main-link"
id="vrpaymentHomeLink"
href="{{ path('frontend.home.page') }}"
title="{{ "header.logoLink"|trans|striptags }}">
{% block layout_header_logo_image %}
<picture class="header-logo-picture">
{% block layout_header_logo_image_tablet %}
{% if theme_config('sw-logo-tablet') and theme_config('sw-logo-tablet') != theme_config('sw-logo-desktop') %}
<source srcset="{{ theme_config('sw-logo-tablet') |sw_encode_url }}"
media="(min-width: {{ theme_config('breakpoint.md') }}px) and (max-width: {{ theme_config('breakpoint.lg') - 1 }}px)">
{% endif %}
{% endblock %}
{% block layout_header_logo_image_mobile %}
{% if theme_config('sw-logo-mobile') and theme_config('sw-logo-mobile') != theme_config('sw-logo-desktop') %}
<source srcset="{{ theme_config('sw-logo-mobile') |sw_encode_url }}"
media="(max-width: {{ theme_config('breakpoint.md') - 1 }}px)">
{% endif %}
{% endblock %}
{% block layout_header_logo_image_default %}
{% if theme_config('sw-logo-desktop') %}
<img src="{{ theme_config('sw-logo-desktop') |sw_encode_url }}"
alt="{{ "header.logoLink"|trans|striptags }}"
class="img-fluid header-logo-main-img"/>
{% endif %}
{% endblock %}
</picture>
{% endblock %}
</a>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
{% block layout_header_minimal_button %}
<div class="col-md-4 header-minimal-back-to-shop">
<button type="button"
id="vrpaymentOrderCancel"
class="btn btn-outline-primary header-minimal-back-to-shop-button"
title="{{ "checkout.finishButtonBackToShop"|trans|striptags }}">
{{ "checkout.finishButtonBackToShop"|trans|striptags }}
</button>
</div>
{% endblock %}
+10 -6
View File
@@ -3,6 +3,7 @@
namespace VRPaymentPayment;
use Shopware\Core\{
Framework\Feature,
Framework\Plugin,
Framework\Plugin\Context\ActivateContext,
Framework\Plugin\Context\DeactivateContext,
@@ -21,6 +22,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\DirectoryLoader;
use Symfony\Component\DependencyInjection\Loader\GlobFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
// expect the vendor folder on Shopware store releases
if (file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
@@ -82,19 +84,21 @@ class VRPaymentPayment extends Plugin {
{
parent::build($container);
$locator = new FileLocator('Resources/config');
$confDir = \rtrim($this->getPath(), '/') . '/Resources/config';
$locator = new FileLocator($confDir);
$resolver = new LoaderResolver([
new YamlFileLoader($container, $locator),
new GlobFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
new YamlFileLoader($container, $locator),
new XmlFileLoader($container, $locator),
new GlobFileLoader($container, $locator),
new DirectoryLoader($container, $locator),
]);
$configLoader = new DelegatingLoader($resolver);
$confDir = \rtrim($this->getPath(), '/') . '/Resources/config';
$configLoader->load($confDir . '/{packages}/*.yaml', 'glob');
$configLoader->load('services/core/checkout.xml');
}
public function enrichPrivileges(): array