Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "swag/swag-extension-store",
"version": "3.1.7",
"version": "3.2.0",
"description": "SWAG Extension Store",
"type": "shopware-platform-plugin",
"license": "MIT",
Expand Down
8 changes: 2 additions & 6 deletions src/Controller/InAppPurchasesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
use SwagExtensionStore\Exception\ExtensionStoreException;
use SwagExtensionStore\Services\InAppPurchasesService;
use SwagExtensionStore\Struct\InAppPurchaseCartPositionCollection;
use SwagExtensionStore\Struct\InAppPurchaseStruct;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
Expand Down Expand Up @@ -140,12 +139,9 @@ public function refreshInAppPurchases(Context $context): Response
#[Route('/api/_action/in-app-purchases/{technicalName}/{inAppPurchase}', name: 'api.in-app-purchases.in-app-purchase', methods: ['GET'])]
public function getInAppPurchase(string $technicalName, string $inAppPurchase, Context $context): Response
{
$inAppPurchaseCollection = $this->inAppPurchasesService->listPurchases($technicalName, $context);
$iap = $inAppPurchaseCollection->filter(
fn (InAppPurchaseStruct $availableInAppPurchases) => $availableInAppPurchases->getIdentifier() === $inAppPurchase
)->first();
$purchase = $this->inAppPurchasesService->getInAppPurchase($technicalName, $inAppPurchase, $context);

return new JsonResponse($iap);
return new JsonResponse($purchase);
}

private function getAppByName(string $appName, Context $context): ?AppEntity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.sw-in-app-purchase-checkout {
.mt-button {
width: 100%;
}

.sw-modal {
&__footer {
grid-template-columns: 1fr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import './sw-in-app-purchase-checkout-overview.scss';
export default Shopware.Component.wrapComponentConfig({
template,

emits: ['update:tos-accepted', 'update:gtc-accepted', 'update:variant'],

props: {
purchase: {
type: Object as PropType<IAP.InAppPurchase>,
Expand All @@ -24,31 +26,52 @@ export default Shopware.Component.wrapComponentConfig({
producer: {
type: String,
required: true
},
cart: {
type: Object as PropType<IAP.InAppPurchaseCart>,
required: true
},
variant: {
type: String,
required: true
}
},

data(): {
showConditionsModal: boolean;
priceModel: IAP.InAppPurchasePriceModel;
} {
return {
showConditionsModal: false,
priceModel: this.purchase.priceModels[0]
showConditionsModal: false
};
},

created() {
this.setPriceModel();
watch: {
priceModel: {
immediate: true,
handler() {
this.onGtcAcceptedChange(this.priceModel.conditionsType === null);
}
}
},

computed: {
purchaseOptions(): Array<{ value: IAP.InAppPurchasePriceModel; name: string }> {
return this.purchase.priceModels.map((priceModel): { value: IAP.InAppPurchasePriceModel; name: string } => {
purchaseOptions(): Array<{ value: string; name: string }> {
return this.purchase.priceModels.map((priceModel): { value: string; name: string } => {
return {
value: priceModel,
value: priceModel.variant,
name: `€${priceModel.price}* /${this.$t(`sw-in-app-purchase-price-box.duration.${priceModel.variant}`)}`
};
});
},

priceModel(): IAP.InAppPurchasePriceModel {
return this.purchase.priceModels.find(
(pm: IAP.InAppPurchasePriceModel) => this.cart.positions[0].variant === pm.variant
) || this.purchase.priceModels[0];
},

subscriptionChange() {
return this.cart.positions.find(position => position.subscriptionChange !== null);
}
},

Expand All @@ -69,13 +92,10 @@ export default Shopware.Component.wrapComponentConfig({
this.$emit('update:gtc-accepted', value);
},

setPriceModel(priceModel?: IAP.InAppPurchasePriceModel) {
if (!priceModel) {
priceModel = this.purchase.priceModels[0];
updateVariant(variant : string) {
if (this.variant !== variant) {
this.$emit('update:variant', variant);
}
this.priceModel = priceModel;
this.onGtcAcceptedChange(priceModel.conditionsType === null);
this.$emit('update:variant', priceModel.variant);
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@
</p>
</div>

<sw-in-app-purchase-checkout-subscription-change
v-if="subscriptionChange"
:purchase="purchase"
:cart="cart"
/>

<sw-in-app-purchase-price-box
v-if="purchase.priceModels.length === 1"
v-else-if="purchase.priceModels.length === 1"
:price-model="priceModel"
/>

<sw-radio-field
v-else
block
:value="variant"
:options="purchaseOptions"
class="sw-in-app-purchase-checkout-purchase__feature__choices"
@update:value="setPriceModel"
@update:value="updateVariant"
/>

<div class="sw-in-app-purchase-checkout-purchase__subtext">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
font-weight: var(--font-weight-regular);
font-size: var(--font-size-xs);

.sw-field--checkbox {
margin-bottom: 0;
}

.sw-gtc-checkbox {
.sw-field__label {
color: var(--color-text-primary-default);
}

.sw-field--checkbox {
margin-bottom: 0;
}
}

&__feature {
Expand All @@ -40,7 +40,7 @@
padding: 25px 19px;

.sw-field__radio-input {
margin-top: var(--scale-size-4);
margin-top: 4px;
}

.sw-field__radio-option-label {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,23 @@ async function createWrapper() {
},
tosAccepted: false,
gtcAccepted: false,
producer: 'shopware'
producer: 'shopware',
variant: 'monthly',
cart: {
netPrice: 1,
grossPrice: 2.99,
taxPrice: 2.99,
taxValue: 4,
positions: [{
variant: 1,
subscriptionChange: null
}]
}
},
global: {
stubs: {
'sw-in-app-purchase-price-box': true,
'sw-in-app-purchase-checkout-subscription-change': true,
'sw-gtc-checkbox': true,
'sw-radio-field': true,
'sw-button': true
Expand Down Expand Up @@ -79,29 +91,21 @@ describe('sw-in-app-purchase-checkout-overview', () => {
expect(wrapper.vm.showConditionsModal).toBe(false);
});

it('should set the priceModel and emit update:variant when setPriceModel is called', async () => {
// when the component is created, the first price model is set emitting this data
// setPriceModel is called during the component creation, therefor we don't need to explicitly test it
expect(wrapper.vm.priceModel).toStrictEqual(wrapper.vm.purchase.priceModels[0]);
expect(wrapper.emitted('update:gtc-accepted')).toBeTruthy();
expect(wrapper.emitted('update:gtc-accepted')[0]).toEqual([true]);
expect(wrapper.emitted('update:variant')).toBeTruthy();
expect(wrapper.emitted('update:variant')[0]).toStrictEqual(['monthly']);

const priceModel = {
type: 'rent',
price: 10.99,
duration: 12,
variant: 'yearly',
conditionsType: null
};
it('should render not subscription change card', async () => {
expect(wrapper.find('sw-in-app-purchase-checkout-subscription-change-stub').exists()).toBeFalsy();
});

// now we call it with a different price model, to see if it updates and emits accordingly
wrapper.vm.setPriceModel(priceModel);
expect(wrapper.vm.priceModel).toStrictEqual(priceModel);
expect(wrapper.emitted('update:gtc-accepted')).toBeTruthy();
expect(wrapper.emitted('update:gtc-accepted')[1]).toEqual([true]);
expect(wrapper.emitted('update:variant')).toBeTruthy();
expect(wrapper.emitted('update:variant')[1]).toStrictEqual(['yearly']);
it('should render subscription change card', async () => {
await wrapper.setProps({
cart: {
positions: [{
variant: 1,
subscriptionChange: 'upgrade'
}]
}
});

expect(wrapper.find('sw-in-app-purchase-checkout-subscription-change-stub')).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type * as IAP from 'SwagExtensionStore/module/sw-in-app-purchases/types';
import template from './sw-in-app-purchase-checkout-subscription-change.html.twig';
import './sw-in-app-purchase-checkout-subscription-change.scss';

export default Shopware.Component.wrapComponentConfig({
template,

props: {
purchase: {
type: Object as PropType<IAP.InAppPurchase>,
required: true
},
cart: {
type: Object as PropType<IAP.InAppPurchaseCart>,
required: true
}
},

computed: {
locale() {
const local = String(Shopware.State.get('session').currentLocale ??
Shopware.State.get('context').app?.fallbackLocale ?? 'en-GB');

return new Intl.Locale(local);
},

currencyFilter() {
return Shopware.Filter.getByName('currency');
},

formattedStartingDate(): string {
const date = new Date(this.cartPosition.nextBookingDate ?? '');
return date.toLocaleDateString(this.locale, { month: 'numeric', day: 'numeric' });
},

infoHint(): string {
const today = new Date().toLocaleDateString(this.locale, {
month: 'long',
day: 'numeric'
});

const nextBookingDate = this.cartPosition?.nextBookingDate
? new Date(this.cartPosition.nextBookingDate).toLocaleDateString(this.locale, {
month: 'long',
day: 'numeric'
})
: '';

return this.$t('sw-in-app-purchase-checkout-subscription-change.info-lint', {
today,
price: this.currencyFilter(this.cartPosition?.proratedNetPrice, 'EUR', 2),
variant: this.cartPosition?.variant ?? '',
fee: this.currencyFilter(this.cart.netPrice, 'EUR', 2),
start: nextBookingDate
});
},

cartPosition() {
return this.cart.positions[0];
},

getCurrentPrice() {
const price = this.cartPosition?.subscriptionChange?.currentFeature?.priceModels
?.find((priceModel) => priceModel.variant === this.cartPosition.variant)?.price;

return String(this.currencyFilter(price, 'EUR', 2));
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div class="sw-in-app-purchase-checkout-subscription-change">
<div class="sw-in-app-purchase-checkout-subscription-change__item">
<p class="sw-in-app-purchase-checkout-subscription-change__plan-name">
{{ $t('sw-in-app-purchase-checkout-subscription-change.current-plan') }}
</p>
<p class="sw-in-app-purchase-checkout-subscription-change__plan-price">
{{ getCurrentPrice }}*/{{ $t('sw-in-app-purchase-price-box.duration.' + cartPosition.variant) }}
</p>
</div>
<div class="sw-in-app-purchase-checkout-subscription-change__item">
<p class="sw-in-app-purchase-checkout-subscription-change__plan-name">
{{ $t('sw-in-app-purchase-checkout-subscription-change.new-plan') }}
<span class="sw-in-app-purchase-checkout-subscription-change__note">
({{ $t('sw-in-app-purchase-checkout-subscription-change.starting') }} {{ formattedStartingDate }})
</span>
</p>
<p class="sw-in-app-purchase-checkout-subscription-change__plan-price">
{{ currencyFilter(cart.netPrice, 'EUR', 2) }}*/{{ $t('sw-in-app-purchase-price-box.duration.' + cartPosition.variant) }}
</p>
</div>

<div class="sw-in-app-purchase-checkout-subscription-change__divider" />

<div class="sw-in-app-purchase-checkout-subscription-change__item">
<p class="sw-in-app-purchase-checkout-subscription-change__due-day">
{{ $t('sw-in-app-purchase-checkout-subscription-change.due-today') }}
</p>
<p class="sw-in-app-purchase-checkout-subscription-change__due-day">
{{ currencyFilter(cartPosition.proratedNetPrice, 'EUR', 2) }}*
</p>
</div>

<p class="sw-in-app-purchase-checkout-subscription-change__info-hint">
{{ infoHint }}
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.sw-in-app-purchase-checkout-subscription-change {
padding: 16px;
background-color: var(--color-background-brand-default);
border: 1px solid var(--color-shopware-brand-900);
border-radius: var(--border-radius-default);
display: flex;
flex-direction: column;
gap: 24px;
font-size: var(--font-size-xs);
line-height: var(--font-line-height-xs);

&__item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}

&__plan-price, &__due-day {
font-size: var(--font-size-m);
line-height: var(--font-line-height-m);
}

&__due-day {
margin-top: 16px;
font-weight: var(--font-weight-semibold);
}

&__note, &__info-hin {
color: var(--color-text-secondary-default);
}

&__divider {
border-bottom: 1px solid var(--color-shopware-brand-900);
}
}
Loading
Loading