-
Notifications
You must be signed in to change notification settings - Fork 268
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# In-App Purchase Gateway | ||
|
||
## Context | ||
|
||
Starting with Shopware version **6.6.9.0**, the In-App Purchase Gateway was introduced to enhance flexibility in managing In-App Purchases. | ||
|
||
The gateway enables app servers to restrict specific In-App Purchases based on advanced decision-making processes handled on the app server side. | ||
|
||
::: info | ||
**Current Limitations:** | ||
At present, the In-App Purchase Gateway supports only restricting the checkout process for new In-App Purchases. | ||
**Future Plans:** | ||
We aim to expand its functionality to - for example - include filtering entire lists of In-App Purchases before they are displayed to users. | ||
::: | ||
|
||
## Prerequisites | ||
|
||
You should be familiar with the concept of Apps, their registration flow as well as signing and verifying requests and responses between Shopware and the App backend server. | ||
|
||
<PageRef page="../../app-base-guide.md" title="App base guide" /> | ||
Check warning on line 20 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L20
Raw output
Check warning on line 20 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L20
Raw output
|
||
|
||
Your app server must be also accessible for the Shopware server. | ||
You can use a tunneling service like [ngrok](https://ngrok.com/) for development. | ||
|
||
## Manifest Configuration | ||
|
||
To indicate that your app leverages the In-App Purchase Gateway, include the `inAppPurchase` property within the `gateways` property in your app's `manifest.xml`. | ||
|
||
Below is an example of a properly configured manifest snippet for enabling the checkout gateway: | ||
|
||
```xml [manifest.xml] | ||
Check warning on line 31 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L31
Raw output
|
||
<?xml version="1.0" encoding="UTF-8"?> | ||
Check warning on line 32 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L32
Raw output
|
||
<manifest> | ||
<!-- ... --> | ||
|
||
<gateways> | ||
<inAppPurchases>https://my-app.server.com/inAppPurchases/gateway</inAppPurchases> | ||
</gateways> | ||
</manifest> | ||
``` | ||
|
||
After successful installation of your app, the In-App Purchases gateway will already be used. | ||
|
||
## In-App Purchases gateway endpoint | ||
|
||
During checkout of an In-App Purchase, Shopware checks for any active In-App Purchases gateways and will call the `inAppPurchases` url. | ||
The app server will receive a list containing the single only In-App Purchase the user wants to buy as part of the payload. | ||
|
||
::: warning | ||
**Connection timeouts** | ||
|
||
The Shopware shop will wait for a response for 5 seconds. | ||
Be sure, that your In-App Purchases gateway implementation on your app server responds in time, | ||
otherwise Shopware will time out and drop the connection. | ||
::: | ||
|
||
<Tabs> | ||
|
||
<Tab title="HTTP"> | ||
|
||
Request content is JSON | ||
|
||
```json5 | ||
{ | ||
"source": { | ||
"url": "http:\/\/localhost:8000", | ||
"shopId": "hRCw2xo1EDZnLco4", | ||
"appVersion": "1.0.0", | ||
"inAppPurchases": "eyJWTEncodedTokenOfActiveInAppPurchases" | ||
}, | ||
"purchases": [ | ||
"my-in-app-purchase-bronze", | ||
"my-in-app-purchase-silver", | ||
"my-in-app-purchase-gold", | ||
], | ||
} | ||
``` | ||
|
||
Respond with the In-App Purchases you want the user to be allowed to buy by simply responding with the purchase identifier in the `purchases` array. | ||
During checkout, respond with an empty array to disallow the user from buying the In-App Purchase. | ||
|
||
```json5 | ||
{ | ||
"purchases": [ | ||
"my-in-app-purchase-bronze", | ||
"my-in-app-purchase-silver", | ||
// disallow the user from buying the gold in-app purchase by removing it from the response | ||
] | ||
} | ||
``` | ||
|
||
</Tab> | ||
|
||
<Tab title="App PHP SDK"> | ||
|
||
With version `4.0.0`, support for the In-App Purchases gateway has been added to the `app-php-sdk`. | ||
The SDK will handle the communication with the Shopware shop and provide you with a convenient way to handle the incoming payload and respond with the necessary purchases. | ||
|
||
```php | ||
Check warning on line 99 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L99
Raw output
|
||
|
||
use Psr\Http\Message\RequestInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Shopware\App\SDK\Authentication\ResponseSigner; | ||
use Shopware\App\SDK\Context\Cart\Error; | ||
use Shopware\App\SDK\Context\ContextResolver; | ||
use Shopware\App\SDK\Context\InAppPurchase\InAppPurchaseProvider; | ||
use Shopware\App\SDK\Framework\Collection; | ||
use Shopware\App\SDK\HttpClient\ClientFactory; | ||
use Shopware\App\SDK\Response\InAppPurchaseResponse; | ||
use Shopware\App\SDK\Shop\ShopResolver; | ||
|
||
function inAppPurchasesController(): ResponseInterface { | ||
// injected or build by yourself | ||
$shopResolver = new ShopResolver($repository); | ||
$signer = new ResponseSigner(); | ||
|
||
$shop = $shopResolver->resolveShop($request); | ||
|
||
$inAppPurchaseProvider = new InAppPurchaseProvider(new SBPStoreKeyFetcher( | ||
(new ClientFactory())->createClient($shop) | ||
)); | ||
|
||
$contextResolver = new ContextResolver($inAppPurchaseProvider); | ||
|
||
/** @var Shopware\App\SDK\Context\Gateway\InAppFeatures\FilterAction $action */ | ||
$action = $contextResolver->assembleInAppPurchasesFilterRequest($request, $shop); | ||
|
||
/** @var Shopware\App\SDK\Framework\Collection $purchases */ | ||
$purchases = $action->getPurchases(); | ||
|
||
// filter the purchases based on your business logic | ||
$purchases->remove('my-in-app-purchase-gold'); | ||
|
||
$response = InAppPurchasesResponse::filter($purchases); | ||
|
||
return $signer->sign($response); | ||
} | ||
``` | ||
|
||
</Tab> | ||
|
||
<Tab title="Symfony Bundle"> | ||
|
||
```php | ||
Check warning on line 144 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L144
Raw output
|
||
<?php declare(strict_types=1); | ||
|
||
namespace App\Controller; | ||
|
||
use Shopware\App\SDK\Context\Cart\Error; | ||
use Shopware\App\SDK\Context\Gateway\InAppFeatures\FilterAction; | ||
use Shopware\App\SDK\Framework\Collection; | ||
use Shopware\App\SDK\Gateway\Checkout\CheckoutGatewayCommand; | ||
use Shopware\App\SDK\Gateway\Checkout\Command\AddCartErrorCommand; | ||
use Shopware\App\SDK\Response\GatewayResponse; | ||
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface; | ||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Routing\Attribute\Route; | ||
|
||
#[Route('/api/gateway', name: 'api.gateway.')] | ||
Check warning on line 160 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L160
Raw output
|
||
class GatewayController extends AbstractController | ||
{ | ||
public function __construct( | ||
private readonly HttpFoundationFactoryInterface $httpFoundationFactory | ||
) { | ||
} | ||
|
||
#[Route('/inAppPurchases', name: 'in-app-purchases', methods: ['POST'])] | ||
Check warning on line 168 in guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md#L168
Raw output
|
||
public function inAppPurchases(FilterAction $action): Response | ||
{ | ||
// the user already has the best premium purchase | ||
// disallow him from buying the less premium ones | ||
if ($action->source->inAppPurchases->has('my-in-app-purchase-gold')) { | ||
$action->purchases->remove('my-in-app-purchase-bronze'); | ||
$action->purchases->remove('my-in-app-purchase-silver'); | ||
} | ||
|
||
$response = GatewayResponse::createCheckoutGatewayResponse($commands); | ||
|
||
return $this->httpFoundationFactory->createResponse($response); | ||
} | ||
} | ||
``` | ||
|
||
</Tab> | ||
|
||
</Tabs> | ||
|
||
## Event | ||
|
||
Plugins can listen to the `Shopware\Core\Framework\App\InAppPurchases\Event\InAppPurchasesGatewayEvent`. | ||
This event is dispatched after the In-App Purchases Gateway has received the app server response. | ||
It allows plugins to manipulate the available In-App Purchases, based on the same payload the app servers retrieved. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# In-App Purchases | ||
|
||
In-App Purchases are a way to lock certain features behind a paywall within the same extension. | ||
This is useful for developers who want to offer a free version of their extension with limited features and a paid version with more features. | ||
|
||
## How do I receive bought In-App Purchases? | ||
|
||
Whenever Shopware sends you a request, you'll receive a JWT as a query parameter or in the request body, | ||
depending on whether the request is a GET or POST. | ||
This JWT is signed by our internal systems, ensuring that you, as the app developer, can verify its authenticity and confirm it hasn't been tampered with. | ||
|
||
You can use the `shopware/app-php-sdk` for plain PHP or the `shopware/app-bundle` for Symfony to validate and decode the JWT. | ||
An example for plain PHP is available [here](https://github.com/shopware/app-php-sdk/blob/main/examples/index.php). | ||
For Symfony applications, use the appropriate action argument for your route. | ||
|
||
### Admin | ||
You will also receive In-App Purchases with the initial `sw-main-hidden` admin request. | ||
To make them accessible, inject them into your JavaScript application. | ||
|
||
Here is an example with an `admin.html.twig` and `shopware/app-bundle`: | ||
|
||
```php | ||
Check warning on line 22 in guides/plugins/apps/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/in-app-purchase/index.md#L22
Raw output
|
||
#[Route(path: '/app/admin', name: 'admin')] | ||
public function admin(ModuleAction $action): Response { | ||
return $this->render('admin.html.twig', [ | ||
'inAppPurchases' => $action->inAppPurchases->all(), | ||
]); | ||
} | ||
``` | ||
|
||
```html | ||
Check warning on line 31 in guides/plugins/apps/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/apps/in-app-purchase/index.md#L31
Raw output
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script> | ||
try { | ||
window.inAppPurchases = JSON.parse('{{ inAppPurchases | json_encode | raw }}'); | ||
} catch (e) { | ||
window.inAppPurchases = []; | ||
console.error('Unable to decode In-App Purchases', e); | ||
} | ||
</script> | ||
|
||
<!-- ... --> | ||
</head> | ||
|
||
<!-- ... --> | ||
</html> | ||
``` | ||
|
||
Alternativly you can extract the query parameter from `document.location` on the initial `sw-main-hidden` request, | ||
store it and ask your app-server do properly decode it for you. | ||
|
||
## Allow the purchase of an In-App Purchases | ||
|
||
The checkout process itself is provided by Shopware, you only have to trigger it with an identifier of the In-App Purchase. | ||
To do so, create a button and make use of the [Meteor Admin SDK](https://github.com/shopware/meteor/tree/main/packages/admin-sdk): | ||
|
||
```vue | ||
<template> | ||
<!-- ... --> | ||
<p> | ||
If you buy this you'll get an incredible useful feature: ... | ||
</p> | ||
<mt-button @click="onClick"> | ||
Buy | ||
</mt-button> | ||
<!-- ... --> | ||
</template> | ||
<script setup> | ||
import * as sw from '@shopware/meteor-admin-sdk'; | ||
function onClick() { | ||
sw.iap.purchase({ identifier: 'my-iap-identifier' }); | ||
} | ||
</script> | ||
``` | ||
|
||
Alternatively, you can trigger a checkout manually by sending a properly formatted | ||
[postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) with an IAP identifier to the Admin. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# In-App Purchases | ||
|
||
In-App Purchases are a way to lock certain features behind a paywall within the same extension. | ||
This is useful for developers who want to offer a free version of their extension with limited features, | ||
and then offer a paid version with more features. | ||
|
||
## Active In-App Purchases | ||
|
||
The `InAppPurchase` class contains a list of all In-App Purchases. | ||
Inject this service into your class and you can check against it: | ||
|
||
```php | ||
Check warning on line 12 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L12
Raw output
|
||
class Example | ||
{ | ||
public function __construct( | ||
private readonly InAppPurchase $inAppPurchase, | ||
) {} | ||
|
||
public function someFunction() { | ||
if ($this->inAppPurchase->isActive('MyExtensionName', 'my-iap-identifier')) { | ||
// ... | ||
} | ||
|
||
// ... | ||
} | ||
} | ||
``` | ||
|
||
If you want to check an in-app purchase in the administration: | ||
|
||
```js | ||
Check warning on line 31 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L31
Raw output
|
||
if (Shopware.InAppPurchase.isActive('MyExtensionName', 'my-iap-identifier')) {}; | ||
Check warning on line 32 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L32
Raw output
|
||
``` | ||
|
||
## Allow users to buy an In-App Purchase | ||
|
||
```js | ||
Check warning on line 37 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L37
Raw output
|
||
{ | ||
computed: { | ||
inAppPurchaseCheckout() { | ||
return Shopware.Store.get('inAppPurchaseCheckout'); | ||
Check warning on line 41 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L41
Raw output
|
||
} | ||
}, | ||
|
||
methods: { | ||
onClick() { | ||
this.inAppPurchaseCheckout.request({ identifier: 'my-iap-identifier' }, 'MyExtensionName'); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
## Event | ||
|
||
Apps are also able to manipulate the available In-App Purchases as described in <PageRef page="../../apps/gateways/in-app-purchase/in-app-purchase-gateway.md" title="In App purchase gateway" />. | ||
Check warning on line 55 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L55
Raw output
Check warning on line 55 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L55
Raw output
Check warning on line 55 in guides/plugins/plugins/in-app-purchase/index.md GitHub Actions / LanguageTool[LanguageTool] guides/plugins/plugins/in-app-purchase/index.md#L55
Raw output
|
||
|
||
Plugins can listen to the `Shopware\Core\Framework\App\InAppPurchases\Event\InAppPurchasesGatewayEvent`. | ||
This event is dispatched after the In-App Purchases Gateway has received the app server response from a gateway | ||
and allows plugins to manipulate the available In-App Purchases. |