From ab6237a3f9b9f3ae79580d2c92cb70a42d42c048 Mon Sep 17 00:00:00 2001 From: Lennart Tinkloh Date: Thu, 9 Jan 2025 09:43:40 +0100 Subject: [PATCH] NEXT-36303 - In App Purchases docs --- .../gateways/checkout/checkout-gateway.md | 2 +- .../in-app-purchase-gateway.md | 193 ++++++++++++++++++ guides/plugins/apps/in-app-purchase/index.md | 81 ++++++++ .../plugins/plugins/in-app-purchase/index.md | 59 ++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md create mode 100644 guides/plugins/apps/in-app-purchase/index.md create mode 100644 guides/plugins/plugins/in-app-purchase/index.md diff --git a/guides/plugins/apps/gateways/checkout/checkout-gateway.md b/guides/plugins/apps/gateways/checkout/checkout-gateway.md index b4720d838..afbb6ffeb 100644 --- a/guides/plugins/apps/gateways/checkout/checkout-gateway.md +++ b/guides/plugins/apps/gateways/checkout/checkout-gateway.md @@ -183,7 +183,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; -#[Route('gateway', name: 'braintree.gateway.')] +#[Route('/api/gateway', name: 'api.gateway.')] class GatewayController extends AbstractController { public function __construct( diff --git a/guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md b/guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md new file mode 100644 index 000000000..642753aab --- /dev/null +++ b/guides/plugins/apps/gateways/in-app-purchase/in-app-purchase-gateway.md @@ -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. + + + +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] + + + + + + https://my-app.server.com/inAppPurchases/gateway + + +``` + +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. +::: + + + + + +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 + ] +} +``` + + + + + +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 + +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); +} +``` + + + + + +```php +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); + } +} +``` + + + + + +## 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. diff --git a/guides/plugins/apps/in-app-purchase/index.md b/guides/plugins/apps/in-app-purchase/index.md new file mode 100644 index 000000000..89503b563 --- /dev/null +++ b/guides/plugins/apps/in-app-purchase/index.md @@ -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 +#[Route(path: '/app/admin', name: 'admin')] +public function admin(ModuleAction $action): Response { + return $this->render('admin.html.twig', [ + 'inAppPurchases' => $action->inAppPurchases->all(), + ]); +} +``` + +```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 + + + +``` + +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. diff --git a/guides/plugins/plugins/in-app-purchase/index.md b/guides/plugins/plugins/in-app-purchase/index.md new file mode 100644 index 000000000..037d84afe --- /dev/null +++ b/guides/plugins/plugins/in-app-purchase/index.md @@ -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 +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 +if (Shopware.InAppPurchase.isActive('MyExtensionName', 'my-iap-identifier')) {}; +``` + +## Allow users to buy an In-App Purchase + +```js +{ + computed: { + inAppPurchaseCheckout() { + return Shopware.Store.get('inAppPurchaseCheckout'); + } + }, + + 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 . + +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.