feat(next): update cart payment info logic
Because: - Update emitter and glean records with actual payment provider - Add paypal payment provider logic to success page This commit: - Updates emitter calls to include payment provider - Updates emitter event for a failure to allow for undefined payment provider - Removes ConfirmationDetail component from success page and moves logic into page JSX. - Adds card and paypal images Closes #FXA-10503 FXA-10608
|
@ -70,7 +70,7 @@ export default async function CheckoutError({
|
||||||
'checkoutFail',
|
'checkoutFail',
|
||||||
{ ...params },
|
{ ...params },
|
||||||
searchParams,
|
searchParams,
|
||||||
'stripe'
|
cart.paymentInfo?.type
|
||||||
);
|
);
|
||||||
|
|
||||||
const errorReason = getErrorReason(cart.errorReasonId);
|
const errorReason = getErrorReason(cart.errorReasonId);
|
||||||
|
|
|
@ -14,30 +14,41 @@ import {
|
||||||
recordEmitterEventAction,
|
recordEmitterEventAction,
|
||||||
} from '@fxa/payments/ui/actions';
|
} from '@fxa/payments/ui/actions';
|
||||||
import { CheckoutParams } from '@fxa/payments/ui/server';
|
import { CheckoutParams } from '@fxa/payments/ui/server';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Amex from '@fxa/shared/assets/images/payment-methods/amex.svg';
|
||||||
|
import Diners from '@fxa/shared/assets/images/payment-methods/diners.svg';
|
||||||
|
import Discover from '@fxa/shared/assets/images/payment-methods/discover.svg';
|
||||||
|
import Jcb from '@fxa/shared/assets/images/payment-methods/jcb.svg';
|
||||||
|
import Mastercard from '@fxa/shared/assets/images/payment-methods/mastercard.svg';
|
||||||
|
import Paypal from '@fxa/shared/assets/images/payment-methods/paypal.svg';
|
||||||
|
import Unbranded from '@fxa/shared/assets/images/payment-methods/unbranded.svg';
|
||||||
|
import UnionPay from '@fxa/shared/assets/images/payment-methods/unionpay.svg';
|
||||||
|
import Visa from '@fxa/shared/assets/images/payment-methods/visa.svg';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
type ConfirmationDetailProps = {
|
function getCardIcon(cardBrand: string) {
|
||||||
title: string;
|
switch (cardBrand) {
|
||||||
detail1: string | Promise<string>;
|
case 'amex':
|
||||||
detail2: string;
|
return Amex;
|
||||||
};
|
case 'diners':
|
||||||
|
return Diners;
|
||||||
const ConfirmationDetail = ({
|
case 'discover':
|
||||||
title,
|
return Discover;
|
||||||
detail1,
|
case 'jcb':
|
||||||
detail2,
|
return Jcb;
|
||||||
}: ConfirmationDetailProps) => {
|
case 'mastercard':
|
||||||
return (
|
return Mastercard;
|
||||||
<div className="border-b border-grey-200 pb-6 text-sm">
|
case 'paypal':
|
||||||
<div className="font-semibold py-4">{title}</div>
|
return Paypal;
|
||||||
<div className="flex items-center justify-between text-grey-400">
|
case 'unionpay':
|
||||||
<span>{detail1}</span>
|
return UnionPay;
|
||||||
<span>{detail2}</span>
|
case 'visa':
|
||||||
</div>
|
return Visa;
|
||||||
</div>
|
default:
|
||||||
);
|
return Unbranded;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default async function CheckoutSuccess({
|
export default async function CheckoutSuccess({
|
||||||
params,
|
params,
|
||||||
|
@ -66,7 +77,7 @@ export default async function CheckoutSuccess({
|
||||||
'checkoutSuccess',
|
'checkoutSuccess',
|
||||||
{ ...params },
|
{ ...params },
|
||||||
searchParams,
|
searchParams,
|
||||||
'stripe'
|
cart.paymentInfo.type
|
||||||
);
|
);
|
||||||
|
|
||||||
const { successActionButtonUrl, successActionButtonLabel } =
|
const { successActionButtonUrl, successActionButtonLabel } =
|
||||||
|
@ -86,7 +97,7 @@ export default async function CheckoutSuccess({
|
||||||
|
|
||||||
<p className="text-black max-w-sm text-sm leading-5 font-normal">
|
<p className="text-black max-w-sm text-sm leading-5 font-normal">
|
||||||
{l10n.getString(
|
{l10n.getString(
|
||||||
'next-payment-confirmation-thanks-subheading',
|
'payment-confirmation-thanks-subheading-account-exists-2',
|
||||||
{
|
{
|
||||||
email: cart.email || '',
|
email: cart.email || '',
|
||||||
},
|
},
|
||||||
|
@ -96,55 +107,71 @@ export default async function CheckoutSuccess({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmationDetail
|
<div className="border-b border-grey-200 pb-6 text-sm">
|
||||||
title={l10n.getString(
|
<div className="font-semibold py-4">
|
||||||
'next-payment-confirmation-order-heading',
|
{l10n.getString(
|
||||||
'Order details'
|
'next-payment-confirmation-order-heading',
|
||||||
)}
|
'Order details'
|
||||||
detail1={l10n.getString(
|
)}
|
||||||
'next-payment-confirmation-invoice-number',
|
</div>
|
||||||
{
|
<div className="flex items-center justify-between text-grey-400">
|
||||||
invoiceNumber: cart.latestInvoicePreview?.number ?? '',
|
<span>
|
||||||
},
|
{l10n.getString(
|
||||||
`Invoice #${cart.latestInvoicePreview?.number}`
|
'next-payment-confirmation-invoice-number',
|
||||||
)}
|
{
|
||||||
detail2={l10n.getString(
|
invoiceNumber: cart.latestInvoicePreview?.number ?? '',
|
||||||
'next-payment-confirmation-invoice-date',
|
},
|
||||||
{
|
`Invoice #${cart.latestInvoicePreview?.number}`
|
||||||
invoiceDate: l10n.getLocalizedDate(cart.createdAt / 1000),
|
)}
|
||||||
},
|
</span>
|
||||||
l10n.getLocalizedDateString(cart.createdAt / 1000)
|
<span>
|
||||||
)}
|
{l10n.getString(
|
||||||
/>
|
'next-payment-confirmation-invoice-date',
|
||||||
|
{
|
||||||
|
invoiceDate: l10n.getLocalizedDate(cart.createdAt / 1000),
|
||||||
|
},
|
||||||
|
l10n.getLocalizedDateString(cart.createdAt / 1000)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ConfirmationDetail
|
<div className="border-b border-grey-200 pb-6 text-sm">
|
||||||
title={l10n.getString(
|
<div className="font-semibold py-4">
|
||||||
'next-payment-confirmation-details-heading-2',
|
{l10n.getString(
|
||||||
'Payment information'
|
'next-payment-confirmation-details-heading-2',
|
||||||
)}
|
'Payment information'
|
||||||
detail1={l10n.getString(
|
)}
|
||||||
'next-payment-confirmation-amount',
|
</div>
|
||||||
{
|
<div className="flex items-center justify-between text-grey-400">
|
||||||
amount: l10n.getLocalizedCurrency(
|
<span>
|
||||||
|
{formatPlanPricing(
|
||||||
cart.latestInvoicePreview?.totalAmount ?? null,
|
cart.latestInvoicePreview?.totalAmount ?? null,
|
||||||
cart.latestInvoicePreview?.currency ?? ''
|
cart.latestInvoicePreview?.currency ?? '',
|
||||||
),
|
cart.interval
|
||||||
interval: cart.interval,
|
)}
|
||||||
},
|
</span>
|
||||||
formatPlanPricing(
|
{cart.paymentInfo.type === 'external_paypal' ? (
|
||||||
cart.latestInvoicePreview?.totalAmount ?? null,
|
<Image src={getCardIcon('paypal')} alt="paypal" />
|
||||||
cart.latestInvoicePreview?.currency ?? '',
|
) : (
|
||||||
cart.interval
|
<span className="flex items-center gap-2">
|
||||||
)
|
{cart.paymentInfo.brand && (
|
||||||
)}
|
<Image
|
||||||
detail2={l10n.getString(
|
src={getCardIcon(cart.paymentInfo.brand)}
|
||||||
'next-payment-confirmation-cc-card-ending-in',
|
alt={cart.paymentInfo.brand}
|
||||||
{
|
/>
|
||||||
last4: cart.last4 ?? '',
|
)}
|
||||||
},
|
{l10n.getString(
|
||||||
`Card ending in ${cart.last4}`
|
'next-payment-confirmation-cc-card-ending-in',
|
||||||
)}
|
{
|
||||||
/>
|
last4: cart.paymentInfo.last4 ?? '',
|
||||||
|
},
|
||||||
|
`Card ending in ${cart.paymentInfo.last4}`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
className="flex items-center justify-center bg-blue-500 hover:bg-blue-700 font-semibold h-12 my-8 rounded-md text-white w-full"
|
className="flex items-center justify-center bg-blue-500 hover:bg-blue-700 font-semibold h-12 my-8 rounded-md text-white w-full"
|
||||||
|
|
|
@ -155,3 +155,11 @@ export class CartSubscriptionNotFoundError extends CartError {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CartSuccessMissingRequired extends CartError {
|
||||||
|
constructor(cartId: string) {
|
||||||
|
super('Success cart is missing required fields', {
|
||||||
|
cartId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
import { TaxAddressFactory } from '@fxa/payments/customer';
|
import {
|
||||||
|
InvoicePreviewFactory,
|
||||||
|
TaxAddressFactory,
|
||||||
|
} from '@fxa/payments/customer';
|
||||||
import {
|
import {
|
||||||
CartEligibilityStatus,
|
CartEligibilityStatus,
|
||||||
CartErrorReasonId,
|
CartErrorReasonId,
|
||||||
|
@ -14,10 +17,13 @@ import {
|
||||||
CheckoutCustomerData,
|
CheckoutCustomerData,
|
||||||
FinishCart,
|
FinishCart,
|
||||||
FinishErrorCart,
|
FinishErrorCart,
|
||||||
|
PaymentInfo,
|
||||||
ResultCart,
|
ResultCart,
|
||||||
SetupCart,
|
SetupCart,
|
||||||
|
SuccessCart,
|
||||||
TaxAmount,
|
TaxAmount,
|
||||||
UpdateCart,
|
UpdateCart,
|
||||||
|
WithContextCart,
|
||||||
} from './cart.types';
|
} from './cart.types';
|
||||||
|
|
||||||
const OFFERING_CONFIG_IDS = [
|
const OFFERING_CONFIG_IDS = [
|
||||||
|
@ -54,6 +60,18 @@ export const TaxAmountFactory = (override?: Partial<TaxAmount>): TaxAmount => ({
|
||||||
...override,
|
...override,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const PaymentInfoFactory = (
|
||||||
|
override?: Partial<PaymentInfo>
|
||||||
|
): PaymentInfo => ({
|
||||||
|
type: faker.helpers.arrayElement([
|
||||||
|
'card',
|
||||||
|
'google_iap',
|
||||||
|
'apple_iap',
|
||||||
|
'external_paypal',
|
||||||
|
]),
|
||||||
|
...override,
|
||||||
|
});
|
||||||
|
|
||||||
export const UpdateCartFactory = (
|
export const UpdateCartFactory = (
|
||||||
override?: Partial<UpdateCart>
|
override?: Partial<UpdateCart>
|
||||||
): UpdateCart => ({
|
): UpdateCart => ({
|
||||||
|
@ -96,3 +114,22 @@ export const ResultCartFactory = (
|
||||||
eligibilityStatus: faker.helpers.enumValue(CartEligibilityStatus),
|
eligibilityStatus: faker.helpers.enumValue(CartEligibilityStatus),
|
||||||
...override,
|
...override,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const WithContextCartFactory = (
|
||||||
|
override?: Partial<WithContextCart>
|
||||||
|
): WithContextCart => ({
|
||||||
|
...ResultCartFactory(),
|
||||||
|
metricsOptedOut: false,
|
||||||
|
upcomingInvoicePreview: InvoicePreviewFactory(),
|
||||||
|
...override,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SuccessCartFactory = (
|
||||||
|
override?: Partial<SuccessCart>
|
||||||
|
): SuccessCart => ({
|
||||||
|
...WithContextCartFactory(),
|
||||||
|
state: CartState.SUCCESS,
|
||||||
|
latestInvoicePreview: InvoicePreviewFactory(),
|
||||||
|
paymentInfo: PaymentInfoFactory(),
|
||||||
|
...override,
|
||||||
|
});
|
||||||
|
|
|
@ -76,6 +76,7 @@ import {
|
||||||
CheckoutCustomerDataFactory,
|
CheckoutCustomerDataFactory,
|
||||||
FinishErrorCartFactory,
|
FinishErrorCartFactory,
|
||||||
ResultCartFactory,
|
ResultCartFactory,
|
||||||
|
SuccessCartFactory,
|
||||||
UpdateCartFactory,
|
UpdateCartFactory,
|
||||||
} from './cart.factories';
|
} from './cart.factories';
|
||||||
import { CartManager } from './cart.manager';
|
import { CartManager } from './cart.manager';
|
||||||
|
@ -84,7 +85,9 @@ import { CheckoutService } from './checkout.service';
|
||||||
import {
|
import {
|
||||||
CartInvalidCurrencyError,
|
CartInvalidCurrencyError,
|
||||||
CartInvalidPromoCodeError,
|
CartInvalidPromoCodeError,
|
||||||
|
CartInvalidStateForActionError,
|
||||||
CartStateProcessingError,
|
CartStateProcessingError,
|
||||||
|
CartSuccessMissingRequired,
|
||||||
} from './cart.error';
|
} from './cart.error';
|
||||||
import { CurrencyManager } from '@fxa/payments/currency';
|
import { CurrencyManager } from '@fxa/payments/currency';
|
||||||
import { MockCurrencyConfigProvider } from 'libs/payments/currency/src/lib/currency.config';
|
import { MockCurrencyConfigProvider } from 'libs/payments/currency/src/lib/currency.config';
|
||||||
|
@ -687,7 +690,7 @@ describe('CartService', () => {
|
||||||
const mockUpcomingInvoicePreview = InvoicePreviewFactory();
|
const mockUpcomingInvoicePreview = InvoicePreviewFactory();
|
||||||
const mockLatestInvoicePreview = InvoicePreviewFactory();
|
const mockLatestInvoicePreview = InvoicePreviewFactory();
|
||||||
const mockPaymentMethod = StripeResponseFactory(
|
const mockPaymentMethod = StripeResponseFactory(
|
||||||
StripePaymentMethodFactory()
|
StripePaymentMethodFactory({})
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.spyOn(cartManager, 'fetchCartById').mockResolvedValue(mockCart);
|
jest.spyOn(cartManager, 'fetchCartById').mockResolvedValue(mockCart);
|
||||||
|
@ -714,7 +717,11 @@ describe('CartService', () => {
|
||||||
upcomingInvoicePreview: mockUpcomingInvoicePreview,
|
upcomingInvoicePreview: mockUpcomingInvoicePreview,
|
||||||
latestInvoicePreview: mockLatestInvoicePreview,
|
latestInvoicePreview: mockLatestInvoicePreview,
|
||||||
metricsOptedOut: false,
|
metricsOptedOut: false,
|
||||||
last4: mockPaymentMethod.card?.last4,
|
paymentInfo: {
|
||||||
|
type: mockPaymentMethod.type,
|
||||||
|
last4: mockPaymentMethod.card?.last4,
|
||||||
|
brand: mockPaymentMethod.card?.brand,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(result.latestInvoicePreview).not.toEqual(
|
expect(result.latestInvoicePreview).not.toEqual(
|
||||||
result.upcomingInvoicePreview
|
result.upcomingInvoicePreview
|
||||||
|
@ -863,6 +870,44 @@ describe('CartService', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getSuccessCart', () => {
|
||||||
|
const mockSuccessCart = SuccessCartFactory();
|
||||||
|
it('should return success cart', async () => {
|
||||||
|
jest.spyOn(cartService, 'getCart').mockResolvedValue(mockSuccessCart);
|
||||||
|
const result = await cartService.getSuccessCart(mockSuccessCart.id);
|
||||||
|
expect(result).toEqual(mockSuccessCart);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if cart state is not success', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(cartService, 'getCart')
|
||||||
|
.mockResolvedValue(SuccessCartFactory({ state: CartState.FAIL }));
|
||||||
|
await expect(
|
||||||
|
cartService.getSuccessCart(mockSuccessCart.id)
|
||||||
|
).rejects.toThrowError(CartInvalidStateForActionError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if latestInvoicePreview is undefined', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(cartService, 'getCart')
|
||||||
|
.mockResolvedValue(
|
||||||
|
SuccessCartFactory({ latestInvoicePreview: undefined })
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
cartService.getSuccessCart(mockSuccessCart.id)
|
||||||
|
).rejects.toThrowError(CartSuccessMissingRequired);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error if payment method type is undefined', async () => {
|
||||||
|
jest
|
||||||
|
.spyOn(cartService, 'getCart')
|
||||||
|
.mockResolvedValue(SuccessCartFactory({ paymentInfo: undefined }));
|
||||||
|
await expect(
|
||||||
|
cartService.getSuccessCart(mockSuccessCart.id)
|
||||||
|
).rejects.toThrowError(CartSuccessMissingRequired);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('metricsOptedOut', () => {
|
describe('metricsOptedOut', () => {
|
||||||
it('returns true if account has opted out of metrics', async () => {
|
it('returns true if account has opted out of metrics', async () => {
|
||||||
const mockUid = faker.string.hexadecimal({
|
const mockUid = faker.string.hexadecimal({
|
||||||
|
|
|
@ -27,8 +27,10 @@ import { GeoDBManager } from '@fxa/shared/geodb';
|
||||||
import { CartManager } from './cart.manager';
|
import { CartManager } from './cart.manager';
|
||||||
import {
|
import {
|
||||||
CheckoutCustomerData,
|
CheckoutCustomerData,
|
||||||
|
PaymentInfo,
|
||||||
PollCartResponse,
|
PollCartResponse,
|
||||||
ResultCart,
|
ResultCart,
|
||||||
|
SuccessCart,
|
||||||
UpdateCart,
|
UpdateCart,
|
||||||
WithContextCart,
|
WithContextCart,
|
||||||
} from './cart.types';
|
} from './cart.types';
|
||||||
|
@ -38,8 +40,10 @@ import {
|
||||||
CartError,
|
CartError,
|
||||||
CartInvalidCurrencyError,
|
CartInvalidCurrencyError,
|
||||||
CartInvalidPromoCodeError,
|
CartInvalidPromoCodeError,
|
||||||
|
CartInvalidStateForActionError,
|
||||||
CartStateProcessingError,
|
CartStateProcessingError,
|
||||||
CartSubscriptionNotFoundError,
|
CartSubscriptionNotFoundError,
|
||||||
|
CartSuccessMissingRequired,
|
||||||
} from './cart.error';
|
} from './cart.error';
|
||||||
import { AccountManager } from '@fxa/shared/account/account';
|
import { AccountManager } from '@fxa/shared/account/account';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
@ -427,7 +431,7 @@ export class CartService {
|
||||||
|
|
||||||
// Cart latest invoice data
|
// Cart latest invoice data
|
||||||
let latestInvoicePreview: InvoicePreview | undefined;
|
let latestInvoicePreview: InvoicePreview | undefined;
|
||||||
let last4: string | undefined;
|
let paymentInfo: PaymentInfo | undefined;
|
||||||
if (customer && cart.stripeSubscriptionId) {
|
if (customer && cart.stripeSubscriptionId) {
|
||||||
// fetch latest payment info from subscription
|
// fetch latest payment info from subscription
|
||||||
const subscription = await this.subscriptionManager.retrieve(
|
const subscription = await this.subscriptionManager.retrieve(
|
||||||
|
@ -442,13 +446,20 @@ export class CartService {
|
||||||
if (subscription.collection_method === 'send_invoice') {
|
if (subscription.collection_method === 'send_invoice') {
|
||||||
// PayPal payment method collection
|
// PayPal payment method collection
|
||||||
// TODO: render paypal payment info in the UI (FXA-10608)
|
// TODO: render paypal payment info in the UI (FXA-10608)
|
||||||
|
paymentInfo = {
|
||||||
|
type: 'external_paypal',
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
// Stripe payment method collection
|
// Stripe payment method collection
|
||||||
if (customer.invoice_settings.default_payment_method) {
|
if (customer.invoice_settings.default_payment_method) {
|
||||||
const paymentMethod = await this.paymentMethodManager.retrieve(
|
const paymentMethod = await this.paymentMethodManager.retrieve(
|
||||||
customer.invoice_settings.default_payment_method
|
customer.invoice_settings.default_payment_method
|
||||||
);
|
);
|
||||||
last4 = paymentMethod?.card?.last4;
|
paymentInfo = {
|
||||||
|
type: paymentMethod.type,
|
||||||
|
last4: paymentMethod.card?.last4,
|
||||||
|
brand: paymentMethod.card?.brand,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,9 +467,34 @@ export class CartService {
|
||||||
return {
|
return {
|
||||||
...cart,
|
...cart,
|
||||||
upcomingInvoicePreview,
|
upcomingInvoicePreview,
|
||||||
latestInvoicePreview,
|
|
||||||
last4,
|
|
||||||
metricsOptedOut,
|
metricsOptedOut,
|
||||||
|
latestInvoicePreview,
|
||||||
|
paymentInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a success cart from the database by UID
|
||||||
|
*/
|
||||||
|
async getSuccessCart(cartId: string): Promise<SuccessCart> {
|
||||||
|
const cart = await this.getCart(cartId);
|
||||||
|
|
||||||
|
if (cart.state !== CartState.SUCCESS) {
|
||||||
|
throw new CartInvalidStateForActionError(
|
||||||
|
cartId,
|
||||||
|
cart.state,
|
||||||
|
'getSuccessCart'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cart.latestInvoicePreview || !cart.paymentInfo?.type) {
|
||||||
|
throw new CartSuccessMissingRequired(cartId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cart,
|
||||||
|
latestInvoicePreview: cart.latestInvoicePreview,
|
||||||
|
paymentInfo: cart.paymentInfo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
CartErrorReasonId,
|
CartErrorReasonId,
|
||||||
CartState,
|
CartState,
|
||||||
} from '@fxa/shared/db/mysql/account';
|
} from '@fxa/shared/db/mysql/account';
|
||||||
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
export type CheckoutCustomerData = {
|
export type CheckoutCustomerData = {
|
||||||
locale: string;
|
locale: string;
|
||||||
|
@ -40,16 +41,33 @@ export interface Invoice {
|
||||||
number: string | null; // customer-facing invoice identifier
|
number: string | null; // customer-facing invoice identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PaymentProvidersType =
|
||||||
|
| Stripe.PaymentMethod.Type
|
||||||
|
| 'google_iap'
|
||||||
|
| 'apple_iap'
|
||||||
|
| 'external_paypal';
|
||||||
|
|
||||||
|
export interface PaymentInfo {
|
||||||
|
type: PaymentProvidersType;
|
||||||
|
last4?: string;
|
||||||
|
brand?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type ResultCart = Readonly<Omit<Cart, 'id' | 'uid'>> & {
|
export type ResultCart = Readonly<Omit<Cart, 'id' | 'uid'>> & {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly uid?: string;
|
readonly uid?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WithContextCart = ResultCart & {
|
export type WithContextCart = ResultCart & {
|
||||||
|
metricsOptedOut: boolean;
|
||||||
upcomingInvoicePreview: Invoice;
|
upcomingInvoicePreview: Invoice;
|
||||||
latestInvoicePreview?: Invoice;
|
latestInvoicePreview?: Invoice;
|
||||||
metricsOptedOut: boolean;
|
paymentInfo?: PaymentInfo;
|
||||||
last4?: string;
|
};
|
||||||
|
|
||||||
|
export type SuccessCart = WithContextCart & {
|
||||||
|
latestInvoicePreview: Invoice;
|
||||||
|
paymentInfo: PaymentInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SetupCart = {
|
export type SetupCart = {
|
||||||
|
|
|
@ -6,13 +6,17 @@ import { ResultCart } from '@fxa/payments/cart';
|
||||||
export const CheckoutTypes = ['with-accounts', 'without-accounts'] as const;
|
export const CheckoutTypes = ['with-accounts', 'without-accounts'] as const;
|
||||||
export type CheckoutTypesType = (typeof CheckoutTypes)[number];
|
export type CheckoutTypesType = (typeof CheckoutTypes)[number];
|
||||||
|
|
||||||
export const PaymentProviders = [
|
export const PaymentProvidersTypePartial = [
|
||||||
'stripe',
|
'card',
|
||||||
'paypal',
|
'google_iap',
|
||||||
'google',
|
'apple_iap',
|
||||||
'apple',
|
'external_paypal',
|
||||||
] as const;
|
] as const;
|
||||||
export type PaymentProvidersType = (typeof PaymentProviders)[number];
|
export type PaymentProvidersType =
|
||||||
|
| 'card'
|
||||||
|
| 'google_iap'
|
||||||
|
| 'apple_iap'
|
||||||
|
| 'external_paypal';
|
||||||
|
|
||||||
export type CommonMetrics = {
|
export type CommonMetrics = {
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
|
|
|
@ -10,25 +10,71 @@ import { getApp } from '../nestapp/app';
|
||||||
import { GetCartActionArgs } from '../nestapp/validators/GetCartActionArgs';
|
import { GetCartActionArgs } from '../nestapp/validators/GetCartActionArgs';
|
||||||
import { getRedirect, validateCartState } from '../utils/get-cart';
|
import { getRedirect, validateCartState } from '../utils/get-cart';
|
||||||
import { SupportedPages } from '../utils/types';
|
import { SupportedPages } from '../utils/types';
|
||||||
|
import { SuccessCart, WithContextCart } from '@fxa/payments/cart';
|
||||||
|
import { CartInvalidStateForActionError } from 'libs/payments/cart/src/lib/cart.error';
|
||||||
|
import { VError } from 'verror';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Cart or Redirect if cart state does not match supported page
|
* Get Cart or Redirect if cart state does not match supported page
|
||||||
* @@param cartId - Cart ID
|
* @@param cartId - Cart ID
|
||||||
* @@param page - Page that action is being called from
|
* @@param page - Page that action is being called from
|
||||||
*/
|
*/
|
||||||
export const getCartOrRedirectAction = async (
|
async function getCartOrRedirectAction(
|
||||||
|
cartId: string,
|
||||||
|
page: SupportedPages.START
|
||||||
|
): Promise<WithContextCart>;
|
||||||
|
async function getCartOrRedirectAction(
|
||||||
|
cartId: string,
|
||||||
|
page: SupportedPages.PROCESSING
|
||||||
|
): Promise<WithContextCart>;
|
||||||
|
async function getCartOrRedirectAction(
|
||||||
|
cartId: string,
|
||||||
|
page: SupportedPages.ERROR
|
||||||
|
): Promise<WithContextCart>;
|
||||||
|
async function getCartOrRedirectAction(
|
||||||
|
cartId: string,
|
||||||
|
page: SupportedPages.SUCCESS
|
||||||
|
): Promise<SuccessCart>;
|
||||||
|
|
||||||
|
async function getCartOrRedirectAction(
|
||||||
cartId: string,
|
cartId: string,
|
||||||
page: SupportedPages
|
page: SupportedPages
|
||||||
) => {
|
): Promise<WithContextCart | SuccessCart> {
|
||||||
const cart = await getApp().getActionsService().getCart(
|
let cart: WithContextCart | SuccessCart | undefined;
|
||||||
plainToClass(GetCartActionArgs, {
|
switch (page) {
|
||||||
cartId,
|
case SupportedPages.SUCCESS: {
|
||||||
})
|
try {
|
||||||
);
|
cart = await getApp().getActionsService().getSuccessCart(
|
||||||
|
plainToClass(GetCartActionArgs, {
|
||||||
|
cartId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof CartInvalidStateForActionError) {
|
||||||
|
redirect(getRedirect(VError.info(error).state));
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SupportedPages.START:
|
||||||
|
case SupportedPages.PROCESSING:
|
||||||
|
case SupportedPages.ERROR: {
|
||||||
|
cart = await getApp().getActionsService().getCart(
|
||||||
|
plainToClass(GetCartActionArgs, {
|
||||||
|
cartId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!validateCartState(cart.state, page)) {
|
if (!validateCartState(cart.state, page)) {
|
||||||
redirect(getRedirect(cart.state));
|
redirect(getRedirect(cart.state));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cart;
|
return cart;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export { getCartOrRedirectAction };
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { getApp } from '../nestapp/app';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { RecordEmitterEventArgs } from '../nestapp/validators/RecordEmitterEvent';
|
import { RecordEmitterEventArgs } from '../nestapp/validators/RecordEmitterEvent';
|
||||||
import { getAdditionalRequestArgs } from '../utils/getAdditionalRequestArgs';
|
import { getAdditionalRequestArgs } from '../utils/getAdditionalRequestArgs';
|
||||||
import { PaymentProvidersType } from '@fxa/payments/metrics';
|
import { PaymentProvidersType } from '@fxa/payments/cart';
|
||||||
import { PaymentsEmitterEventsKeysType } from '../emitter/emitter.types';
|
import { PaymentsEmitterEventsKeysType } from '../emitter/emitter.types';
|
||||||
|
|
||||||
async function recordEmitterEventAction(
|
async function recordEmitterEventAction(
|
||||||
|
@ -17,7 +17,14 @@ async function recordEmitterEventAction(
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
async function recordEmitterEventAction(
|
async function recordEmitterEventAction(
|
||||||
eventName: 'checkoutSubmit' | 'checkoutSuccess' | 'checkoutFail',
|
eventName: 'checkoutFail',
|
||||||
|
params: Record<string, string | string[]>,
|
||||||
|
searchParams: Record<string, string>,
|
||||||
|
paymentProvider?: PaymentProvidersType
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
async function recordEmitterEventAction(
|
||||||
|
eventName: 'checkoutSubmit' | 'checkoutSuccess',
|
||||||
params: Record<string, string | string[]>,
|
params: Record<string, string | string[]>,
|
||||||
searchParams: Record<string, string>,
|
searchParams: Record<string, string>,
|
||||||
paymentProvider: PaymentProvidersType
|
paymentProvider: PaymentProvidersType
|
||||||
|
|
|
@ -29,12 +29,7 @@ export function CartPoller() {
|
||||||
if (!isPolling) return;
|
if (!isPolling) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
retries = await pollCart(
|
retries = await pollCart(checkoutParams, retries, stripe);
|
||||||
checkoutParams,
|
|
||||||
getCartOrRedirectAction,
|
|
||||||
retries,
|
|
||||||
stripe
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
checkoutCartWithPaypal,
|
checkoutCartWithPaypal,
|
||||||
} from '@fxa/payments/ui/actions';
|
} from '@fxa/payments/ui/actions';
|
||||||
import { CartErrorReasonId } from '@fxa/shared/db/mysql/account/kysely-types';
|
import { CartErrorReasonId } from '@fxa/shared/db/mysql/account/kysely-types';
|
||||||
|
import { PaymentProvidersType } from '@fxa/payments/cart';
|
||||||
|
|
||||||
interface CheckoutFormProps {
|
interface CheckoutFormProps {
|
||||||
cmsCommonContent: {
|
cmsCommonContent: {
|
||||||
|
@ -69,7 +70,7 @@ export function CheckoutForm({
|
||||||
const [stripeFieldsComplete, setStripeFieldsComplete] = useState(false);
|
const [stripeFieldsComplete, setStripeFieldsComplete] = useState(false);
|
||||||
const [fullName, setFullName] = useState('');
|
const [fullName, setFullName] = useState('');
|
||||||
const [hasFullNameError, setHasFullNameError] = useState(false);
|
const [hasFullNameError, setHasFullNameError] = useState(false);
|
||||||
const [showPayPalButton, setShowPayPalButton] = useState(false);
|
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState('');
|
||||||
|
|
||||||
const engageGlean = useCallbackOnce(() => {
|
const engageGlean = useCallbackOnce(() => {
|
||||||
recordEmitterEventAction(
|
recordEmitterEventAction(
|
||||||
|
@ -97,14 +98,7 @@ export function CheckoutForm({
|
||||||
}
|
}
|
||||||
|
|
||||||
//Show or hide the PayPal button
|
//Show or hide the PayPal button
|
||||||
const selectedPaymentMethod = event?.value?.type;
|
setSelectedPaymentMethod(event?.value?.type || '');
|
||||||
if (selectedPaymentMethod === 'external_paypal') {
|
|
||||||
// Show the PayPal button
|
|
||||||
setShowPayPalButton(true);
|
|
||||||
} else {
|
|
||||||
// Hide the PayPal button
|
|
||||||
setShowPayPalButton(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsPaymentElementLoading(false);
|
setIsPaymentElementLoading(false);
|
||||||
|
@ -166,7 +160,7 @@ export function CheckoutForm({
|
||||||
'checkoutSubmit',
|
'checkoutSubmit',
|
||||||
{ ...params },
|
{ ...params },
|
||||||
Object.fromEntries(searchParams),
|
Object.fromEntries(searchParams),
|
||||||
'stripe'
|
selectedPaymentMethod as PaymentProvidersType
|
||||||
);
|
);
|
||||||
|
|
||||||
await checkoutCartWithStripe(cart.id, cart.version, confirmationToken.id, {
|
await checkoutCartWithStripe(cart.id, cart.version, confirmationToken.id, {
|
||||||
|
@ -178,6 +172,7 @@ export function CheckoutForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
const nonStripeFieldsComplete = !!fullName;
|
const nonStripeFieldsComplete = !!fullName;
|
||||||
|
const showPayPalButton = selectedPaymentMethod === 'external_paypal';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Root
|
<Form.Root
|
||||||
|
|
|
@ -46,6 +46,14 @@ export class NextJSActionsService {
|
||||||
return cart;
|
return cart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSuccessCart(args: GetCartActionArgs) {
|
||||||
|
await new Validator().validateOrReject(args);
|
||||||
|
|
||||||
|
const cart = await this.cartService.getSuccessCart(args.cartId);
|
||||||
|
|
||||||
|
return cart;
|
||||||
|
}
|
||||||
|
|
||||||
async updateCart(args: UpdateCartActionArgs) {
|
async updateCart(args: UpdateCartActionArgs) {
|
||||||
await new Validator().validateOrReject(args);
|
await new Validator().validateOrReject(args);
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,10 @@ import {
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { PaymentsEmitterEventsKeys } from '../../emitter/emitter.types';
|
import { PaymentsEmitterEventsKeys } from '../../emitter/emitter.types';
|
||||||
import type { PaymentsEmitterEventsKeysType } from '../../emitter/emitter.types';
|
import type { PaymentsEmitterEventsKeysType } from '../../emitter/emitter.types';
|
||||||
import { PaymentProviders } from '@fxa/payments/metrics';
|
import {
|
||||||
import type { PaymentProvidersType } from '@fxa/payments/metrics';
|
PaymentProvidersTypePartial,
|
||||||
|
type PaymentProvidersType,
|
||||||
|
} from '@fxa/payments/metrics';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common metrics that can be found on all events
|
* Common metrics that can be found on all events
|
||||||
|
@ -44,6 +46,6 @@ export class RecordEmitterEventArgs {
|
||||||
requestArgs!: RequestArgs;
|
requestArgs!: RequestArgs;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsEnum(PaymentProviders)
|
@IsEnum(PaymentProvidersTypePartial)
|
||||||
paymentProvider?: PaymentProvidersType;
|
paymentProvider?: PaymentProvidersType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
pollCartAction,
|
pollCartAction,
|
||||||
finalizeProcessingCartAction,
|
finalizeProcessingCartAction,
|
||||||
finalizeCartWithError,
|
finalizeCartWithError,
|
||||||
|
getCartOrRedirectAction,
|
||||||
} from '@fxa/payments/ui/actions';
|
} from '@fxa/payments/ui/actions';
|
||||||
import { CartErrorReasonId } from '@fxa/shared/db/mysql/account/kysely-types';
|
import { CartErrorReasonId } from '@fxa/shared/db/mysql/account/kysely-types';
|
||||||
|
|
||||||
|
@ -14,14 +15,16 @@ export const pollCart = async (
|
||||||
interval: string;
|
interval: string;
|
||||||
offeringId: string;
|
offeringId: string;
|
||||||
},
|
},
|
||||||
validatePageCb: (cartId: string, page: SupportedPages) => Promise<any>,
|
|
||||||
retries = 0,
|
retries = 0,
|
||||||
stripeClient: Stripe | null
|
stripeClient: Stripe | null
|
||||||
): Promise<number> => {
|
): Promise<number> => {
|
||||||
const pollCartResponse = await pollCartAction(checkoutParams.cartId);
|
const pollCartResponse = await pollCartAction(checkoutParams.cartId);
|
||||||
|
|
||||||
if (pollCartResponse.cartState !== 'processing') {
|
if (pollCartResponse.cartState !== 'processing') {
|
||||||
await validatePageCb(checkoutParams.cartId, SupportedPages.PROCESSING);
|
await getCartOrRedirectAction(
|
||||||
|
checkoutParams.cartId,
|
||||||
|
SupportedPages.PROCESSING
|
||||||
|
);
|
||||||
} else if (
|
} else if (
|
||||||
pollCartResponse.cartState === 'processing' &&
|
pollCartResponse.cartState === 'processing' &&
|
||||||
pollCartResponse.stripeClientSecret
|
pollCartResponse.stripeClientSecret
|
||||||
|
@ -53,12 +56,12 @@ export const pollCart = async (
|
||||||
// TODO: handle other paymentIntent statuses. For now, retry
|
// TODO: handle other paymentIntent statuses. For now, retry
|
||||||
retries += 1;
|
retries += 1;
|
||||||
}
|
}
|
||||||
validatePageCb(checkoutParams.cartId, SupportedPages.PROCESSING);
|
getCartOrRedirectAction(checkoutParams.cartId, SupportedPages.PROCESSING);
|
||||||
return retries;
|
return retries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validatePageCb(checkoutParams.cartId, SupportedPages.PROCESSING),
|
getCartOrRedirectAction(checkoutParams.cartId, SupportedPages.PROCESSING),
|
||||||
(retries += 1);
|
(retries += 1);
|
||||||
return retries;
|
return retries;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg width="38" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M2.24.001h33.6c1.06 0 1.92.86 1.92 1.92v20.16c0 1.06-.86 1.92-1.92 1.92H2.24a1.92 1.92 0 01-1.92-1.92V1.921c0-1.06.86-1.92 1.92-1.92z" fill="#2557D6"/><path d="M.332 11.313H2.13l.406-.936h.907l.404.936h3.536v-.716l.316.72h1.835l.316-.73v.726h8.788l-.004-1.537h.17c.119.004.153.014.153.203v1.334h4.546v-.358c.366.188.936.358 1.687.358H27.1l.409-.936h.907l.4.936h3.685v-.89l.558.89h2.953v-5.88H33.09v.694l-.41-.694h-2.998v.694l-.376-.694h-4.05c-.678 0-1.274.09-1.755.343v-.343h-2.795v.343c-.307-.26-.724-.343-1.188-.343H9.307L8.622 6.95l-.704-1.518H4.702v.694l-.353-.694H1.606L.332 8.229v3.084zm11.345-.848h-1.079l-.004-3.302-1.525 3.302h-.924L6.616 7.16v3.305H4.477l-.404-.94h-2.19l-.409.94H.332l1.884-4.216h1.562l1.79 3.992V6.249h1.716l1.377 2.86 1.264-2.86h1.751v4.216zM3.713 8.649l-.72-1.68-.716 1.68h1.436zm12.255 1.816h-3.514V6.249h3.514v.878h-2.462v.76h2.403v.864h-2.403v.842h2.462v.872zm4.951-3.08c0 .671-.468 1.019-.74 1.123.23.084.426.232.52.355.148.21.174.397.174.774v.828h-1.061l-.004-.532c0-.254.025-.618-.166-.821-.154-.149-.388-.18-.767-.18h-1.129v1.533h-1.052V6.249h2.42c.537 0 .933.013 1.273.202.333.188.532.463.532.933zm-1.33.625c-.144.084-.315.087-.52.087h-1.277v-.936h1.295c.183 0 .374.008.499.076.136.061.22.192.22.372 0 .185-.08.333-.216.401zm3.017 2.455h-1.073V6.249h1.073v4.216zm12.46 0h-1.491L31.58 7.3v3.165h-2.142l-.41-.94h-2.184l-.397.94h-1.232c-.51 0-1.158-.108-1.525-.466-.37-.358-.562-.843-.562-1.61 0-.625.115-1.197.567-1.649.34-.336.873-.491 1.598-.491h1.018v.903h-.997c-.384 0-.6.055-.81.25-.179.177-.302.513-.302.955 0 .452.094.777.29.99.161.167.456.217.733.217h.472L27.18 6.25h1.576l1.781 3.988V6.249h1.602l1.849 2.936V6.25h1.077v4.216zM28.671 8.65l-.728-1.681-.724 1.68h1.452zm9.073 8.547c-.255.358-.753.54-1.427.54h-2.031v-.904h2.023c.2 0 .341-.026.426-.105.08-.07.125-.17.124-.275a.34.34 0 00-.129-.279c-.076-.064-.187-.093-.37-.093-.988-.033-2.22.029-2.22-1.306 0-.611.405-1.255 1.51-1.255h2.094v-.84H35.8c-.588 0-1.015.136-1.317.345v-.344h-2.88c-.46 0-1 .11-1.256.344v-.344h-5.142v.344c-.409-.283-1.099-.344-1.418-.344h-3.391v.344c-.324-.3-1.044-.344-1.483-.344h-3.795l-.869.9-.814-.9h-5.67v5.884h5.563l.895-.915.844.915 3.429.003v-1.384h.337c.455.006.991-.011 1.465-.207v1.588h2.828V17.03h.137c.174 0 .191.007.191.174v1.36h8.593c.545 0 1.115-.134 1.431-.377v.377h2.726c.567 0 1.12-.077 1.542-.271v-1.097zm-17.037-2.263c0 1.171-.912 1.413-1.831 1.413h-1.313v1.415H15.52l-1.295-1.396-1.346 1.396H8.712v-4.218h4.23l1.295 1.383 1.338-1.383h3.36c.835 0 1.773.222 1.773 1.39zm-8.362 1.94H9.76v-.838h2.309v-.86h-2.31v-.768h2.638l1.15 1.23-1.201 1.237zm4.167.484l-1.615-1.718 1.615-1.663v3.38zm2.388-1.876h-1.36V14.41h1.372c.38 0 .644.148.644.517 0 .364-.252.556-.656.556zm7.121-1.938h3.51v.873H27.07v.766h2.403v.86H27.07v.84l2.463.004v.875h-3.51v-4.218zm-1.35 2.258c.235.083.426.231.516.354.148.206.17.398.174.77v.836h-1.056v-.528c0-.254.025-.63-.17-.825-.154-.151-.388-.188-.772-.188H22.24v1.54h-1.057v-4.216h2.429c.532 0 .92.022 1.265.199.332.192.541.455.541.936 0 .673-.469 1.017-.745 1.122zm-.594-.533c-.14.08-.315.087-.52.087h-1.277v-.947h1.295c.187 0 .375.003.502.076.137.068.218.199.218.38a.45.45 0 01-.218.404zm9.496.268c.205.204.314.46.314.894 0 .907-.591 1.33-1.652 1.33h-2.049v-.904h2.04c.2 0 .342-.025.43-.104a.367.367 0 00-.004-.554c-.08-.064-.191-.094-.374-.094-.984-.032-2.216.03-2.216-1.305 0-.612.401-1.255 1.504-1.255h2.108v.897h-1.929c-.191 0-.316.007-.421.076-.116.069-.158.17-.158.304 0 .159.098.267.23.314.11.037.23.048.41.048l.565.014c.571.014.963.108 1.202.34zm4.173-1.129H35.83c-.191 0-.318.007-.425.077-.111.068-.154.17-.154.303 0 .16.094.267.23.314.11.037.23.048.405.048l.57.015c.576.013.96.108 1.194.34.043.031.068.067.097.103v-1.2z" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" d="M.32 0h37.44v24H.32z"/></clipPath></defs></svg>
|
После Ширина: | Высота: | Размер: 3.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="38" height="24" enable-background="new 0 0 780 500" version="1.1" viewBox="0 0 780 500" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M40,0h700c22.092,0,40,17.909,40,40v420c0,22.092-17.908,40-40,40H40c-22.091,0-40-17.908-40-40V40 C0,17.909,17.909,0,40,0z" fill="#0079BE"/><path d="m599.93 251.45c0-99.415-82.98-168.13-173.9-168.1h-78.242c-92.003-0.033-167.73 68.705-167.73 168.1 0 90.93 75.727 165.64 167.73 165.2h78.242c90.914 0.436 173.9-74.294 173.9-165.2z" fill="#fff"/><path d="m348.28 97.43c-84.07 0.027-152.19 68.308-152.21 152.58 0.02 84.258 68.144 152.53 152.21 152.56 84.09-0.027 152.23-68.303 152.24-152.56-0.011-84.272-68.149-152.55-152.24-152.58z" fill="#0079BE"/><path d="m252.07 249.6c0.08-41.181 25.746-76.297 61.94-90.25v180.48c-36.194-13.948-61.861-49.045-61.94-90.23zm131 90.274v-180.53c36.207 13.92 61.914 49.057 61.979 90.257-0.065 41.212-25.772 76.322-61.979 90.269z" fill="#fff"/></svg>
|
После Ширина: | Высота: | Размер: 944 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="39" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)" fill-rule="evenodd" clip-rule="evenodd"><path d="M3.52 0A2.64 2.64 0 00.88 2.64v18.768a2.64 2.64 0 002.64 2.64h32.16a2.64 2.64 0 002.64-2.64V2.64A2.64 2.64 0 0035.68 0H3.52z" fill="#4D4D4D"/><path d="M16.583 7.77c.424 0 .78.086 1.213.293v1.092c-.41-.377-.766-.535-1.236-.535-.925 0-1.652.72-1.652 1.634 0 .964.705 1.642 1.698 1.642.447 0 .796-.15 1.19-.522v1.093c-.448.199-.812.277-1.236.277-1.502 0-2.668-1.084-2.668-2.483 0-1.384 1.197-2.49 2.691-2.49zm-4.661.031c.554 0 1.061.179 1.485.528l-.516.636c-.257-.271-.5-.386-.795-.386-.425 0-.734.228-.734.528 0 .257.173.393.765.599 1.121.386 1.454.728 1.454 1.484 0 .921-.719 1.563-1.743 1.563-.75 0-1.296-.278-1.75-.906l.636-.577c.227.413.606.634 1.076.634.44 0 .766-.286.766-.671 0-.2-.099-.371-.296-.492-.099-.058-.295-.143-.681-.271-.926-.314-1.244-.65-1.244-1.305 0-.78.682-1.364 1.577-1.364zm11.266.083h1.077l1.348 3.196 1.366-3.196h1.069l-2.184 4.88h-.53l-2.146-4.88zM4.116 7.89h1.447c1.599 0 2.714.979 2.714 2.383 0 .7-.341 1.377-.918 1.827-.485.379-1.038.55-1.804.55h-1.44V7.89zm4.614 0h.986v4.76H8.73V7.89zm19.763 0h2.797v.807h-1.811v1.056h1.744v.806h-1.744v1.284h1.81v.806h-2.796V7.891zm3.45 0h1.461c1.138 0 1.79.514 1.79 1.405 0 .729-.41 1.207-1.152 1.35l1.591 2.004H34.42l-1.364-1.912h-.129v1.912h-.984V7.891zm.984.75v1.441h.288c.63 0 .964-.257.964-.736 0-.463-.334-.705-.948-.705h-.304zm-27.826.057v3.146h.265c.637 0 1.039-.114 1.349-.378a1.59 1.59 0 00.546-1.199 1.56 1.56 0 00-.546-1.185c-.326-.277-.712-.384-1.35-.384h-.264z" fill="#fff"/><path d="M20.806 7.738c1.485 0 2.69 1.132 2.69 2.53v.002c0 1.398-1.205 2.531-2.69 2.531-1.485 0-2.689-1.133-2.689-2.531v-.002c0-1.398 1.204-2.53 2.69-2.53zm17.513 6.103c-1.25.88-10.612 7.169-26.82 10.206h24.18a2.64 2.64 0 002.64-2.64V13.84z" fill="#F47216"/></g><defs><clipPath id="clip0"><path fill="#fff" d="M.88 0h37.44v24H.88z"/></clipPath></defs></svg>
|
После Ширина: | Высота: | Размер: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="38" height="24" enable-background="new 0 0 780 500" version="1.1" viewBox="0 0 780 500" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M40,0h700c22.092,0,40,17.909,40,40v420c0,22.092-17.908,40-40,40H40c-22.091,0-40-17.908-40-40V40 C0,17.909,17.909,0,40,0z" fill="#0E4C96"/><path d="m632.24 361.27c0 41.615-33.729 75.36-75.36 75.36h-409.13v-297.88c0-41.626 33.733-75.37 75.365-75.37l409.12-1e-3 1e-3 297.89z" fill="#fff"/><linearGradient id="c" x1="83.977" x2="84.977" y1="645.5" y2="645.5" gradientTransform="matrix(132.87 0 0 -323.02 -10686 2.0876e5)" gradientUnits="userSpaceOnUse"><stop stop-color="#007B40" offset="0"/><stop stop-color="#55B330" offset="1"/></linearGradient><path d="m498.86 256.54c11.684 0.253 23.437-0.516 35.076 0.4 11.787 2.199 14.629 20.043 4.156 25.888-7.141 3.851-15.633 1.433-23.379 2.113h-15.852l-1e-3 -28.401zm41.833-32.145c2.596 9.164-6.238 17.392-15.066 16.13h-26.767c0.185-8.642-0.368-18.021 0.271-26.208 10.725 0.301 21.549-0.616 32.21 0.479 4.581 1.151 8.414 4.917 9.352 9.599zm64.428-135.9c0.498 17.501 0.071 35.927 0.214 53.783-0.035 72.596 0.072 145.19-0.055 217.79-0.47 27.207-24.582 50.844-51.601 51.387-27.046 0.111-54.095 0.016-81.142 0.047v-109.75c29.47-0.154 58.959 0.307 88.417-0.232 13.667-0.859 28.632-9.875 29.27-24.914 1.61-15.103-12.632-25.551-26.152-27.201-5.198-0.135-5.044-1.516 0-2.117 12.892-2.787 23.02-16.133 19.226-29.499-3.236-14.058-18.772-19.499-31.697-19.472-26.351-0.18-52.709-0.026-79.062-0.077 0.172-20.489-0.354-41 0.286-61.474 2.087-26.716 26.806-48.747 53.447-48.27h78.849v-1e-3z" fill="url(#c)"/><linearGradient id="b" x1="83.984" x2="84.979" y1="645.5" y2="645.5" gradientTransform="matrix(133.43 0 0 -323.02 -11031 2.0876e5)" gradientUnits="userSpaceOnUse"><stop stop-color="#1D2970" offset="0"/><stop stop-color="#006DBA" offset="1"/></linearGradient><path d="m174.74 139.54c0.674-27.163 24.889-50.611 51.875-51.007 26.944-0.083 53.891-0.012 80.837-0.036-0.074 90.885 0.148 181.78-0.112 272.66-1.038 26.835-24.99 49.835-51.679 50.308-26.996 0.099-53.995 0.014-80.992 0.042v-113.45c26.223 6.194 53.722 8.832 80.473 4.721 15.993-2.574 33.488-10.424 38.902-27.014 3.986-14.191 1.742-29.126 2.334-43.691v-33.824h-46.297c-0.208 22.369 0.426 44.779-0.335 67.125-1.248 13.734-14.846 22.46-27.8 21.994-16.066 0.17-47.898-11.639-47.898-11.639-0.08-41.918 0.466-94.409 0.692-136.18z" fill="url(#b)"/><linearGradient id="a" x1="83.978" x2="84.977" y1="645.5" y2="645.5" gradientTransform="matrix(132.96 0 0 -323.03 -10842 2.0877e5)" gradientUnits="userSpaceOnUse"><stop stop-color="#6E2B2F" offset="0"/><stop stop-color="#E30138" offset="1"/></linearGradient><path d="m324.72 211.89c-2.434 0.517-0.489-8.301-1.113-11.646 0.165-21.15-0.347-42.323 0.283-63.458 2.083-26.829 26.991-48.916 53.739-48.288h78.766c-0.073 90.884 0.147 181.78-0.111 272.66-1.039 26.834-24.992 49.833-51.681 50.308-26.997 0.1-53.997 0.015-80.997 0.043v-124.3c18.44 15.128 43.5 17.483 66.473 17.524 17.316-6e-3 34.534-2.674 51.35-6.67v-22.772c-18.953 9.446-41.232 15.446-62.243 10.019-14.655-3.65-25.294-17.812-25.056-32.937-1.699-15.728 7.524-32.335 22.981-37.011 19.189-6.008 40.107-1.413 58.096 6.397 3.854 2.019 7.765 4.521 6.222-1.921v-17.9c-30.084-7.156-62.101-9.792-92.329-2.004-8.749 2.469-17.271 6.212-24.38 11.958z" fill="url(#a)"/></svg>
|
После Ширина: | Высота: | Размер: 3.2 KiB |
После Ширина: | Высота: | Размер: 7.0 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
<svg width="91" height="24" viewBox="0 0 91 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M33.6079 4.90845H28.6341C28.4694 4.90834 28.3101 4.96705 28.1849 5.07399C28.0597 5.18093 27.9768 5.32907 27.9512 5.49172L25.9395 18.2459C25.9303 18.3051 25.934 18.3656 25.9504 18.4232C25.9667 18.4808 25.9954 18.5342 26.0344 18.5797C26.0734 18.6252 26.1217 18.6617 26.1762 18.6866C26.2306 18.7116 26.2898 18.7245 26.3497 18.7244H28.7243C28.889 18.7245 29.0484 18.6657 29.1736 18.5586C29.2988 18.4515 29.3817 18.3032 29.4072 18.1404L29.9497 14.7004C29.9752 14.5378 30.0579 14.3896 30.183 14.2825C30.3081 14.1754 30.4673 14.1165 30.6319 14.1164H32.2065C35.4828 14.1164 37.3737 12.531 37.8675 9.38917C38.0901 8.01463 37.877 6.93463 37.2334 6.17827C36.5265 5.34772 35.2726 4.90845 33.6079 4.90845ZM34.1817 9.56663C33.9097 11.3514 32.5461 11.3514 31.2275 11.3514H30.477L31.0035 8.01827C31.019 7.92075 31.0687 7.83194 31.1437 7.76781C31.2188 7.70368 31.3143 7.66845 31.413 7.66845H31.757C32.6552 7.66845 33.5024 7.66845 33.9403 8.18045C34.2014 8.4859 34.2814 8.93972 34.1817 9.56663ZM48.4755 9.50917H46.0937C45.995 9.50918 45.8995 9.54441 45.8245 9.60854C45.7494 9.67267 45.6997 9.76148 45.6843 9.85899L45.5788 10.5252L45.4123 10.2837C44.8966 9.53536 43.7468 9.28517 42.5992 9.28517C39.9672 9.28517 37.7192 11.2786 37.2814 14.075C37.0537 15.4699 37.3774 16.8037 38.1686 17.7339C38.8945 18.5892 39.933 18.9455 41.1686 18.9455C43.2894 18.9455 44.4654 17.5819 44.4654 17.5819L44.3592 18.2437C44.3497 18.3029 44.3532 18.3634 44.3694 18.4211C44.3856 18.4788 44.4141 18.5323 44.4529 18.5779C44.4918 18.6236 44.54 18.6602 44.5944 18.6854C44.6488 18.7106 44.708 18.7236 44.7679 18.7237H46.9134C47.0781 18.7238 47.2375 18.665 47.3627 18.5579C47.4879 18.4508 47.5708 18.3025 47.5963 18.1397L48.8835 9.98772C48.893 9.92868 48.8896 9.86829 48.8734 9.81071C48.8573 9.75314 48.8288 9.69976 48.79 9.65426C48.7512 9.60875 48.703 9.57222 48.6487 9.54717C48.5944 9.52212 48.5353 9.50916 48.4755 9.50917ZM45.1555 14.1448C44.9257 15.5055 43.8457 16.419 42.4683 16.419C41.7766 16.419 41.2239 16.1972 40.869 15.7768C40.517 15.3594 40.3832 14.7652 40.4952 14.1034C40.7097 12.7543 41.8079 11.811 43.1643 11.811C43.8406 11.811 44.3905 12.0357 44.7526 12.4597C45.1155 12.8881 45.2595 13.4859 45.1555 14.1448ZM61.1606 9.50917H58.7672C58.6543 9.50935 58.5432 9.53707 58.4434 9.58992C58.3437 9.64277 58.2584 9.71915 58.1948 9.81245L54.8937 14.675L53.4945 10.0023C53.4515 9.85975 53.3638 9.73485 53.2444 9.64604C53.1249 9.55724 52.98 9.50925 52.8312 9.50917H50.4792C50.4132 9.509 50.3481 9.5246 50.2893 9.55467C50.2305 9.58473 50.1798 9.62841 50.1413 9.68205C50.1028 9.73569 50.0777 9.79775 50.068 9.86306C50.0583 9.92838 50.0644 9.99505 50.0857 10.0575L52.7221 17.7943L50.2435 21.2932C50.1995 21.3552 50.1734 21.4282 50.168 21.5041C50.1627 21.58 50.1783 21.6559 50.2133 21.7235C50.2482 21.7911 50.301 21.8477 50.366 21.8873C50.431 21.9268 50.5056 21.9478 50.5817 21.9477H52.9723C53.0839 21.9479 53.1939 21.921 53.2928 21.8693C53.3917 21.8176 53.4766 21.7427 53.5403 21.651L61.501 10.1601C61.5441 10.0979 61.5695 10.0251 61.5742 9.94959C61.5789 9.87407 61.5629 9.79869 61.5278 9.73163C61.4927 9.66457 61.44 9.60839 61.3753 9.56917C61.3105 9.52996 61.2363 9.50921 61.1606 9.50917Z" fill="#253B80"/>
|
||||||
|
<path d="M69.0851 4.90844H64.1105C63.946 4.90851 63.7869 4.9673 63.6618 5.07422C63.5368 5.18115 63.454 5.32919 63.4283 5.49171L61.4167 18.2459C61.4074 18.305 61.4109 18.3654 61.4272 18.423C61.4434 18.4805 61.4719 18.5339 61.5108 18.5794C61.5496 18.6249 61.5979 18.6614 61.6522 18.6864C61.7065 18.7115 61.7656 18.7244 61.8254 18.7244H64.3782C64.4933 18.7243 64.6047 18.683 64.6921 18.6081C64.7796 18.5332 64.8374 18.4295 64.8553 18.3157L65.4262 14.7004C65.4516 14.5378 65.5344 14.3896 65.6594 14.2825C65.7845 14.1754 65.9437 14.1165 66.1084 14.1164H67.6822C70.9593 14.1164 72.8494 12.531 73.344 9.38917C73.5673 8.01462 73.3527 6.93462 72.7091 6.17826C72.0029 5.34771 70.7498 4.90844 69.0851 4.90844ZM69.6589 9.56662C69.3876 11.3514 68.024 11.3514 66.7047 11.3514H65.9549L66.4822 8.01826C66.4973 7.92074 66.5468 7.83186 66.6218 7.76769C66.6968 7.70353 66.7922 7.66832 66.8909 7.66844H67.2349C68.1323 7.66844 68.9803 7.66844 69.4182 8.18044C69.6793 8.4859 69.7585 8.93971 69.6589 9.56662ZM83.952 9.50917H81.5716C81.4729 9.5089 81.3774 9.54405 81.3024 9.60825C81.2274 9.67244 81.1779 9.76141 81.1629 9.85899L81.0574 10.5252L80.8902 10.2837C80.3745 9.53535 79.2254 9.28517 78.0778 9.28517C75.4458 9.28517 73.1985 11.2786 72.7607 14.075C72.5338 15.4699 72.856 16.8037 73.6473 17.7339C74.3745 18.5892 75.4116 18.9455 76.6473 18.9455C78.768 18.9455 79.944 17.5819 79.944 17.5819L79.8378 18.2437C79.8283 18.303 79.8318 18.3637 79.8481 18.4215C79.8644 18.4793 79.893 18.5328 79.932 18.5785C79.971 18.6241 80.0195 18.6608 80.074 18.6858C80.1286 18.7109 80.1879 18.7238 80.248 18.7237H82.3927C82.5574 18.7236 82.7165 18.6647 82.8416 18.5577C82.9667 18.4506 83.0494 18.3024 83.0749 18.1397L84.3629 9.98771C84.372 9.92849 84.3682 9.86799 84.3518 9.81037C84.3353 9.75275 84.3066 9.69937 84.2675 9.65391C84.2285 9.60845 84.1801 9.57197 84.1256 9.54698C84.0711 9.522 84.0119 9.5091 83.952 9.50917ZM80.632 14.1448C80.4036 15.5055 79.3222 16.419 77.9447 16.419C77.2545 16.419 76.7003 16.1972 76.3454 15.7768C75.9934 15.3594 75.8611 14.7652 75.9716 14.1034C76.1876 12.7543 77.2843 11.811 78.6407 11.811C79.3171 11.811 79.8669 12.0357 80.2291 12.4597C80.5934 12.8881 80.7374 13.4859 80.632 14.1448ZM86.76 5.25826L84.7185 18.2459C84.7092 18.305 84.7127 18.3654 84.729 18.423C84.7452 18.4805 84.7737 18.5339 84.8126 18.5794C84.8514 18.6249 84.8997 18.6614 84.954 18.6864C85.0083 18.7115 85.0674 18.7244 85.1273 18.7244H87.1796C87.5207 18.7244 87.8102 18.4772 87.8625 18.1404L89.8756 5.38699C89.885 5.32787 89.8814 5.26743 89.8652 5.20982C89.8489 5.15222 89.8204 5.09881 89.7816 5.05327C89.7427 5.00773 89.6945 4.97114 89.6402 4.94601C89.5859 4.92089 89.5267 4.90782 89.4669 4.90771H87.1687C87.07 4.90806 86.9747 4.94353 86.8998 5.00777C86.8249 5.072 86.7754 5.1608 86.76 5.25826Z" fill="#179BD7"/>
|
||||||
|
<path d="M5.28446 21.2028L5.66482 18.7868L4.81755 18.7672H0.771729L3.58336 0.939541C3.59174 0.88507 3.6194 0.83542 3.66132 0.799642C3.70324 0.763864 3.75662 0.744342 3.81173 0.744632H10.6335C12.8983 0.744632 14.4612 1.2159 15.2772 2.14609C15.6597 2.58245 15.9034 3.03845 16.0212 3.54027C16.1448 4.06681 16.147 4.6959 16.0263 5.46318L16.0175 5.51918V6.01081L16.4001 6.22754C16.6921 6.3755 16.9546 6.57564 17.1746 6.81809C17.5019 7.19118 17.7135 7.66536 17.803 8.22754C17.8954 8.80572 17.8648 9.49372 17.7135 10.2726C17.539 11.1686 17.2568 11.949 16.8757 12.5875C16.5395 13.1596 16.0874 13.655 15.5485 14.0421C15.0423 14.4014 14.4408 14.6741 13.7608 14.8486C13.1019 15.0203 12.3506 15.1068 11.5266 15.1068H10.9957C10.6161 15.1068 10.2474 15.2435 9.95791 15.4886C9.66887 15.7363 9.47712 16.0786 9.41682 16.4545L9.37682 16.6719L8.70482 20.9301L8.67427 21.0865C8.66627 21.1359 8.65246 21.1606 8.63209 21.1774C8.61237 21.1935 8.58775 21.2025 8.56227 21.2028H5.28446Z" fill="#253B80"/>
|
||||||
|
<path d="M16.7623 5.57593C16.7419 5.70611 16.7187 5.8392 16.6925 5.97593C15.7928 10.5948 12.715 12.1905 8.78411 12.1905H6.78266C6.30193 12.1905 5.89684 12.5396 5.82193 13.0137L4.7972 19.5127L4.50702 21.3548C4.49546 21.4279 4.49987 21.5026 4.51996 21.5738C4.54004 21.645 4.57531 21.711 4.62335 21.7673C4.67139 21.8236 4.73105 21.8687 4.79823 21.8997C4.8654 21.9307 4.93849 21.9468 5.01248 21.9468H8.56229C8.98266 21.9468 9.33975 21.6414 9.40593 21.2268L9.44084 21.0465L10.1092 16.805L10.1521 16.5723C10.2176 16.1563 10.5754 15.8508 10.9957 15.8508H11.5267C14.9659 15.8508 17.6583 14.4545 18.4452 10.4137C18.7739 8.72575 18.6037 7.31629 17.7339 6.32502C17.4582 6.01837 17.129 5.76453 16.7623 5.57593Z" fill="#179BD7"/>
|
||||||
|
<path d="M15.8211 5.20061C15.5338 5.11751 15.2416 5.05266 14.9461 5.00643C14.3624 4.91671 13.7724 4.87367 13.1818 4.8777H7.83487C7.63138 4.87754 7.43455 4.95019 7.27996 5.08251C7.12537 5.21482 7.02321 5.39808 6.99196 5.59916L5.8545 12.8035L5.82178 13.0137C5.85721 12.7844 5.97356 12.5753 6.14978 12.4243C6.326 12.2733 6.55044 12.1903 6.7825 12.1904H8.78396C12.7149 12.1904 15.7927 10.5941 16.6923 5.97588C16.7192 5.83916 16.7418 5.70606 16.7621 5.57588C16.5247 5.45139 16.2773 5.34703 16.0225 5.26388C15.9557 5.24172 15.8885 5.22062 15.8211 5.20061Z" fill="#222D65"/>
|
||||||
|
<path d="M6.99193 5.59927C7.02292 5.39814 7.12502 5.2148 7.27969 5.08255C7.43436 4.9503 7.63134 4.87791 7.83484 4.87855H13.1818C13.8152 4.87855 14.4065 4.92 14.9461 5.00727C15.3113 5.06466 15.6714 5.1505 16.0232 5.264C16.2887 5.352 16.5352 5.456 16.7628 5.576C17.0305 3.86909 16.7607 2.70691 15.8378 1.65455C14.8203 0.496 12.9839 0 10.6341 0H3.8123C3.3323 0 2.92284 0.349091 2.84866 0.824L0.00720706 18.8349C-0.00602796 18.9185 -0.000992484 19.004 0.0219671 19.0855C0.0449267 19.167 0.0852655 19.2426 0.140209 19.307C0.195152 19.3714 0.263396 19.4231 0.340245 19.4586C0.417095 19.4942 0.500727 19.5126 0.585389 19.5127H4.79703L5.85448 12.8036L6.99193 5.59927Z" fill="#253B80"/>
|
||||||
|
</svg>
|
После Ширина: | Высота: | Размер: 8.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="38" height="24" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M34.829 24H3.171a3.208 3.208 0 01-2.231-.87A2.98 2.98 0 010 20.994V3.006A2.98 2.98 0 01.94.87 3.208 3.208 0 013.17 0h31.658a3.208 3.208 0 012.231.87A2.98 2.98 0 0138 3.006v18c-.022 1.67-1.44 3.008-3.171 2.994zM12 3.996A.999.999 0 0010.997 3H4.003A.999.999 0 003 3.996v4.008C3 8.556 3.45 9 4.003 9h6.994A.999.999 0 0012 8.004V3.996z" fill-opacity=".3" fill="#0C0C0D"/><rect stroke="#979797" fill="#D8D8D8" x="14.5" y="17.5" width="9" height="1" rx=".5"/><rect stroke="#979797" fill="#D8D8D8" x="2.5" y="17.5" width="9" height="1" rx=".5"/><rect stroke="#979797" fill="#D8D8D8" x="26.5" y="17.5" width="9" height="1" rx=".5"/></g></svg>
|
После Ширина: | Высота: | Размер: 742 B |
После Ширина: | Высота: | Размер: 9.5 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg width="38" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M1.92 0h33.6c1.06 0 1.92.86 1.92 1.92v20.16c0 1.06-.86 1.92-1.92 1.92H1.92A1.92 1.92 0 010 22.08V1.92C0 .86.86 0 1.92 0z" fill="#0E4595"/><path d="M14.074 16.739l1.6-9.396h2.562l-1.602 9.396h-2.56zm11.813-9.194a6.615 6.615 0 00-2.296-.395c-2.53 0-4.313 1.275-4.328 3.101-.015 1.35 1.272 2.104 2.244 2.553.997.46 1.332.755 1.327 1.166-.006.63-.796.917-1.532.917-1.025 0-1.57-.142-2.41-.493l-.33-.149-.36 2.104c.598.262 1.704.489 2.853.5 2.692 0 4.44-1.26 4.46-3.21.01-1.069-.673-1.882-2.15-2.553-.896-.434-1.444-.724-1.438-1.165 0-.39.464-.808 1.466-.808a4.727 4.727 0 011.917.36l.23.109.347-2.037zm6.59-.203H30.5c-.613 0-1.072.168-1.341.78l-3.804 8.61h2.69s.439-1.157.538-1.411l3.28.004c.077.329.312 1.408.312 1.408h2.377l-2.073-9.39zm-3.14 6.068c.212-.541 1.021-2.627 1.021-2.627-.015.025.21-.544.34-.896l.173.81.593 2.713h-2.126zM11.9 7.342L9.392 13.75l-.267-1.302c-.467-1.5-1.922-3.127-3.548-3.941l2.293 8.217 2.71-.003 4.032-9.379H11.9" fill="#fff"/><path d="M7.052 7.342h-4.13l-.032.196c3.213.777 5.339 2.657 6.221 4.915l-.898-4.318c-.155-.595-.604-.772-1.16-.793" fill="#F2AE14"/></g><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h37.44v24H0z"/></clipPath></defs></svg>
|
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -32,6 +32,7 @@ export enum CartState {
|
||||||
export enum CartErrorReasonId {
|
export enum CartErrorReasonId {
|
||||||
BASIC_ERROR = 'basic-error-message',
|
BASIC_ERROR = 'basic-error-message',
|
||||||
IAP_UPGRADE_CONTACT_SUPPORT = 'iap_upgrade_contact_support',
|
IAP_UPGRADE_CONTACT_SUPPORT = 'iap_upgrade_contact_support',
|
||||||
|
SUCCESS_CART_MISSING_REQUIRED = 'success_cart_missing_required',
|
||||||
Unknown = 'unknown',
|
Unknown = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|