|
| 1 | +--- |
| 2 | +nav: |
| 3 | + title: Subscription Integration |
| 4 | + position: 60 |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +# B2B Employee Integration for Subscriptions |
| 9 | + |
| 10 | +This guide describes how B2B Employees are integrated with the Subscription module, enabling employee-based subscription management and tracking within a B2B context. |
| 11 | + |
| 12 | +## Overview |
| 13 | + |
| 14 | +The B2B Employee Integration for Subscriptions extends subscription functionality to support B2B employee workflows without modifying the core Subscription module. All B2B-specific logic is contained within the B2B Employee Management module using decorators, extensions, and event subscribers. |
| 15 | + |
| 16 | +This integration enables: |
| 17 | + |
| 18 | +- **Permission-based subscription access** - Control which subscriptions employees can view |
| 19 | +- **Employee tracking** - Track which employee created each subscription |
| 20 | +- **Organization context preservation** - Maintain organization data throughout the subscription lifecycle |
| 21 | + |
| 22 | +## Prerequisites |
| 23 | + |
| 24 | +- Shopware 6.7 with Subscriptions extension |
| 25 | +- B2B Components with Employee Management module |
| 26 | +- Understanding of [Subscription concepts](../../../../extensions/subscriptions/concept.md) |
| 27 | +- Understanding of [B2B Employee Management concepts](../concepts/index.md) |
| 28 | + |
| 29 | +## Key Features |
| 30 | + |
| 31 | +### Employee-Based Subscription Permissions |
| 32 | + |
| 33 | +Employees can view subscriptions based on their assigned permissions: |
| 34 | + |
| 35 | +| Permission | Access Level | |
| 36 | +|---------------------------------------|------------------------------------------------------------------------| |
| 37 | +| `subscription.read.all` | View all subscriptions in the system | |
| 38 | +| `organization_unit.subscription.read` | View subscriptions from assigned organization unit + own subscriptions | |
| 39 | +| (no permission) | View only own subscriptions | |
| 40 | + |
| 41 | +These permissions are checked when employees access subscription lists, ensuring data isolation and security in B2B contexts. |
| 42 | + |
| 43 | +### Employee Tracking |
| 44 | + |
| 45 | +Subscriptions track which employee created them through the `b2b_components_subscription_employee` association table. This enables: |
| 46 | + |
| 47 | +- **Permission-based filtering** - Filter subscriptions by employee and organization |
| 48 | +- **Employee context in orders** - Both initial and renewal orders maintain employee context |
| 49 | +- **Audit trails** - Track which employee initiated each subscription |
| 50 | + |
| 51 | +### Organization Context |
| 52 | + |
| 53 | +Organization data is preserved throughout the subscription lifecycle: |
| 54 | + |
| 55 | +- **Initial orders** - Organization data is added when a subscription is created during checkout |
| 56 | +- **Renewal orders** - Organization data is maintained in later automated orders |
| 57 | +- **Context preservation** - Organization information flows through all subscription-related processes |
| 58 | + |
| 59 | +## Architecture |
| 60 | + |
| 61 | +### Core Components |
| 62 | + |
| 63 | +The integration is built using several key components in the B2B Employee Management module: |
| 64 | + |
| 65 | +**Decorators:** |
| 66 | + |
| 67 | +- `SubscriptionRouteDecorator` - Decorates `SubscriptionRoute` to apply permission-based filtering |
| 68 | +- `SalesChannelContextServiceDecorator` - Adds employee context to subscription sales channel contexts |
| 69 | + |
| 70 | +**Event Subscribers:** |
| 71 | + |
| 72 | +- `SubscriptionTransformedSubscriber` - Adds employee and organization data during subscription creation |
| 73 | +- `SubscriptionCartConvertedSubscriber` - Adds employee and organization data to initial orders |
| 74 | +- `SubscriptionOrderPlacedSubscriber` - Maintains employee context in renewal orders |
| 75 | + |
| 76 | +**Entity Extension:** |
| 77 | + |
| 78 | +- `SubscriptionExtension` - Extends `SubscriptionDefinition` with `subscriptionEmployee` association |
| 79 | +- `SubscriptionEmployeeDefinition` - Defines the subscription-employee relationship |
| 80 | + |
| 81 | +**Filter Service:** |
| 82 | + |
| 83 | +- `SubscriptionEmployeeFilter` - Implements permission-based subscription filtering logic |
| 84 | + |
| 85 | +### Integration Patterns |
| 86 | + |
| 87 | +The integration follows Shopware best practices: |
| 88 | + |
| 89 | +**1. Decorator Pattern** |
| 90 | + |
| 91 | +Instead of modifying core Subscription code, the integration uses decorators to extend functionality: |
| 92 | + |
| 93 | +```php |
| 94 | +// SubscriptionRouteDecorator wraps the core SubscriptionRoute |
| 95 | +$this->decorated->load($request, $context, $criteria); |
| 96 | +// Then applies employee-based filtering |
| 97 | +$this->subscriptionEmployeeFilter->applyEmployeeFilter($criteria, $employee); |
| 98 | +``` |
| 99 | + |
| 100 | +**2. Event-Based Integration** |
| 101 | + |
| 102 | +Key subscription events are used to inject employee data: |
| 103 | + |
| 104 | +- `SubscriptionTransformedEvent` - Fired when subscription is created from cart |
| 105 | +- `SUBSCRIPTION_CART_CONVERTED` - Fired when subscription cart is converted to order |
| 106 | +- `CheckoutOrderPlacedEvent` - Fired when renewal order is placed |
| 107 | + |
| 108 | +**3. Entity Extension Pattern** |
| 109 | + |
| 110 | +The `SubscriptionExtension` adds the employee association to subscriptions without modifying the core entity: |
| 111 | + |
| 112 | +```php |
| 113 | +new OneToOneAssociationField( |
| 114 | + 'subscriptionEmployee', |
| 115 | + 'id', |
| 116 | + 'subscription_id', |
| 117 | + SubscriptionEmployeeDefinition::class, |
| 118 | + false |
| 119 | +); |
| 120 | +``` |
| 121 | + |
| 122 | +## Database Schema |
| 123 | + |
| 124 | +### b2b_components_subscription_employee Table |
| 125 | + |
| 126 | +This table links subscriptions to the employees who created them: |
| 127 | + |
| 128 | +| Column | Type | Description | |
| 129 | +|-------------------|-------------|----------------------------------------| |
| 130 | +| `id` | BINARY(16) | Primary key | |
| 131 | +| `subscription_id` | BINARY(16) | Foreign key to `subscription` (UNIQUE) | |
| 132 | +| `employee_id` | BINARY(16) | Foreign key to `b2b_employee` | |
| 133 | +| `created_at` | DATETIME(3) | Creation timestamp | |
| 134 | +| `updated_at` | DATETIME(3) | Update timestamp | |
| 135 | + |
| 136 | +**Key characteristics:** |
| 137 | + |
| 138 | +- One-to-one relationship between subscription and subscription_employee |
| 139 | +- `subscription_id` has a UNIQUE constraint ensuring one employee per subscription |
| 140 | +- Foreign keys maintain referential integrity |
| 141 | + |
| 142 | +## Technical Flows |
| 143 | + |
| 144 | +### Initial Subscription Order Flow |
| 145 | + |
| 146 | +When an employee checks out with a subscription product, the following flow occurs: |
| 147 | + |
| 148 | +```mermaid |
| 149 | +sequenceDiagram |
| 150 | + participant Customer as Employee Customer |
| 151 | + participant Checkout as Checkout Process |
| 152 | + participant Transformer as SubscriptionTransformer |
| 153 | + participant TransformedSub as SubscriptionTransformedSubscriber |
| 154 | + participant CartSub as SubscriptionCartConvertedSubscriber |
| 155 | + participant Order as Initial Order |
| 156 | +
|
| 157 | + Customer->>Checkout: Checkout with subscription product |
| 158 | + Checkout->>Transformer: transform() subscription cart |
| 159 | + Transformer->>TransformedSub: dispatch SubscriptionTransformedEvent |
| 160 | + TransformedSub->>TransformedSub: Add subscriptionEmployee data |
| 161 | + TransformedSub->>TransformedSub: Add orderEmployee to convertedOrder |
| 162 | + TransformedSub->>TransformedSub: Add organization to convertedOrder |
| 163 | + Transformer->>CartSub: dispatch SUBSCRIPTION_CART_CONVERTED |
| 164 | + CartSub->>CartSub: Add orderEmployee to cart |
| 165 | + CartSub->>CartSub: Add organization to cart |
| 166 | + CartSub->>Order: Create initial order with employee + org |
| 167 | +
|
| 168 | + Note over Order: Initial Order has:<br/>- orderEmployee ✓<br/>- organization ✓ |
| 169 | + Note over Transformer: Subscription has:<br/>- subscriptionEmployee ✓<br/>- convertedOrder with employee ✓ |
| 170 | +``` |
| 171 | + |
| 172 | +**Key Points:** |
| 173 | + |
| 174 | +1. `SubscriptionTransformedSubscriber` adds employee and organization data to the subscription being created |
| 175 | +2. `SubscriptionCartConvertedSubscriber` ensures the initial order contains employee and organization data |
| 176 | +3. The subscription's `convertedOrder` field stores this data for future renewal orders |
| 177 | + |
| 178 | +### Permission-Based Subscription Filtering |
| 179 | + |
| 180 | +When an employee views their subscriptions, filtering is applied based on permissions: |
| 181 | + |
| 182 | +```mermaid |
| 183 | +flowchart TD |
| 184 | + Start[Employee loads subscriptions] |
| 185 | + Start --> Decorator[SubscriptionRouteDecorator] |
| 186 | + Decorator --> Filter[SubscriptionEmployeeFilter] |
| 187 | + Filter --> CheckAll{Has subscription.read.all?} |
| 188 | +
|
| 189 | + CheckAll -->|Yes| NoFilter[No filter applied] |
| 190 | + NoFilter --> ResultAll[View ALL subscriptions] |
| 191 | +
|
| 192 | + CheckAll -->|No| CheckOrg{Has organization_unit<br/>.subscription.read?} |
| 193 | +
|
| 194 | + CheckOrg -->|Yes| OrgFilter[Apply OR filter] |
| 195 | + OrgFilter --> OwnSubs[Own subscriptions] |
| 196 | + OrgFilter --> OrgSubs[Organization subscriptions] |
| 197 | + OrgSubs --> ResultOrg[View own + org subscriptions] |
| 198 | + OwnSubs --> ResultOrg |
| 199 | +
|
| 200 | + CheckOrg -->|No| EmpFilter[Filter by employeeId] |
| 201 | + EmpFilter --> ResultOwn[View ONLY own subscriptions] |
| 202 | +``` |
| 203 | + |
| 204 | +**Permission Logic:** |
| 205 | + |
| 206 | +- **No filter** (`subscription.read.all`) - Employee sees all subscriptions |
| 207 | +- **OR filter** (`organization_unit.subscription.read`) - Employee sees their own subscriptions or subscriptions from their organization unit |
| 208 | +- **Default filter** - Employee sees only subscriptions they created |
| 209 | + |
| 210 | +## Developer Integration Points |
| 211 | + |
| 212 | +### Accessing Employee Data from Subscriptions |
| 213 | + |
| 214 | +To access the employee who created a subscription: |
| 215 | + |
| 216 | +```php |
| 217 | +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; |
| 218 | + |
| 219 | +// Add association when loading subscriptions |
| 220 | +$criteria = new Criteria(); |
| 221 | +$criteria->addAssociation('subscriptionEmployee.employee'); |
| 222 | + |
| 223 | +$subscription = $subscriptionRepository->search($criteria, $context)->first(); |
| 224 | + |
| 225 | +if ($subscription->getSubscriptionEmployee()) { |
| 226 | + $employee = $subscription->getSubscriptionEmployee()->getEmployee(); |
| 227 | + // Access employee data |
| 228 | +} |
| 229 | +``` |
| 230 | + |
| 231 | +### Accessing Employee Data from Orders |
| 232 | + |
| 233 | +Employee data is stored in order extensions: |
| 234 | + |
| 235 | +```php |
| 236 | +// For initial or renewal orders |
| 237 | +$order = $orderRepository->search($criteria, $context)->first(); |
| 238 | + |
| 239 | +// Check if the order has employee context |
| 240 | +$orderEmployee = $order->getExtension('orderEmployee'); |
| 241 | +if ($orderEmployee) { |
| 242 | + $employeeId = $orderEmployee->getEmployeeId(); |
| 243 | +} |
| 244 | + |
| 245 | +// Check if the order has organization context |
| 246 | +$organization = $order->getExtension('organization'); |
| 247 | +if ($organization) { |
| 248 | + $organizationId = $organization->getId(); |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +### Event Subscribers |
| 253 | + |
| 254 | +The integration provides several event subscribers you can use as reference: |
| 255 | + |
| 256 | +**SubscriptionTransformedSubscriber** - Priority: 0 |
| 257 | + |
| 258 | +Listens to: `SubscriptionTransformedEvent` |
| 259 | + |
| 260 | +Use case: Add custom data when a subscription is created from a cart |
| 261 | + |
| 262 | +**SubscriptionCartConvertedSubscriber** - Priority: 0 |
| 263 | + |
| 264 | +Listens to: `SUBSCRIPTION_CART_CONVERTED` event |
| 265 | + |
| 266 | +Use case: Modify the initial order data during subscription checkout |
| 267 | + |
| 268 | +**SubscriptionOrderPlacedSubscriber** - Priority: 0 |
| 269 | + |
| 270 | +Listens to: `CheckoutOrderPlacedCriteriaEvent` and `CheckoutOrderPlacedEvent` |
| 271 | + |
| 272 | +Use case: Add employee context to renewal orders |
| 273 | + |
| 274 | +### Adding Custom Logic |
| 275 | + |
| 276 | +To add custom B2B logic to subscriptions: |
| 277 | + |
| 278 | +1. **Create a decorator** for subscription services (follow `SubscriptionRouteDecorator` pattern) |
| 279 | +2. **Subscribe to subscription events** to inject your data |
| 280 | +3. **Use entity extensions** to add custom associations without modifying core entities |
| 281 | +4. **Check context state** instead of relying on event priorities for more robust integration |
| 282 | + |
| 283 | +Example decorator pattern: |
| 284 | + |
| 285 | +```php |
| 286 | +use Shopware\Core\System\SalesChannel\SalesChannelContext; |
| 287 | + |
| 288 | +class CustomSubscriptionServiceDecorator extends AbstractSubscriptionService |
| 289 | +{ |
| 290 | + public function __construct( |
| 291 | + private readonly AbstractSubscriptionService $decorated, |
| 292 | + private readonly CustomLogicService $customService |
| 293 | + ) {} |
| 294 | + |
| 295 | + public function getDecorated(): AbstractService |
| 296 | + { |
| 297 | + return $this->decorated; |
| 298 | + } |
| 299 | + |
| 300 | + public function someMethod(SalesChannelContext $context): void |
| 301 | + { |
| 302 | + // Your custom logic before |
| 303 | + $this->customService->doSomething($context); |
| 304 | + |
| 305 | + // Call decorated service |
| 306 | + $this->decorated->someMethod($context); |
| 307 | + |
| 308 | + // Your custom logic after |
| 309 | + } |
| 310 | +} |
| 311 | +``` |
| 312 | + |
| 313 | +## Related Documentation |
| 314 | + |
| 315 | +- [Subscription Concept](../../../../extensions/subscriptions/concept.md) - Core subscription concepts |
| 316 | +- [Mixed Checkout](../../../../extensions/subscriptions/guides/mixed-checkout.md) - Mixed cart checkout flow |
| 317 | +- [Separate Checkout](../../../../extensions/subscriptions/guides/separate-checkout.md) - Separate subscription checkout |
| 318 | +- [B2B Employee Management Concepts](../concepts/index.md) - Employee and role concepts |
| 319 | +- [Creating Permissions via Plugin](./creating-own-permissions-via-plugin.md) - How to extend permissions |
| 320 | +- [API Route Restriction](./api-route-restriction-for-employees.md) - Route-level permission control |
| 321 | + |
| 322 | +## Summary |
| 323 | + |
| 324 | +The B2B Employee Integration for Subscriptions provides a clean, maintainable way to add employee context to subscriptions without modifying core code. By using decorators, event subscribers, and entity extensions, the integration: |
| 325 | + |
| 326 | +- Maintains separation of concerns between modules |
| 327 | +- Follows Shopware architectural patterns |
| 328 | +- Enables permission-based subscription access |
| 329 | +- Preserves employee and organization context throughout the subscription lifecycle |
| 330 | +- Provides clear extension points for custom logic |
0 commit comments