Move old backend model to new format (#284)

This commit is contained in:
Ildar Galeev 2024-03-13 20:35:45 +07:00 committed by GitHub
parent 67746f9556
commit 6a6d7e025f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 700 additions and 229 deletions

130
package-lock.json generated
View File

@ -11,8 +11,8 @@
"dependencies": {
"@dinero.js/currencies": "2.0.0-alpha.1",
"@fingerprintjs/fingerprintjs": "3.4.2",
"@sentry/integrations": "7.57.0",
"@sentry/react": "7.57.0",
"@sentry/integrations": "7.106.1",
"@sentry/react": "7.106.1",
"card-validator": "8.1.1",
"credit-card-type": "9.1.0",
"framer-motion": "10.12.18",
@ -2761,68 +2761,99 @@
}
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.57.0",
"license": "MIT",
"node_modules/@sentry-internal/feedback": {
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.106.1.tgz",
"integrity": "sha512-udYR7rQnnQJ0q4PP3R7lTFx7cUz3SB4ghm8T/fJzdItrk+Puv6y8VqI19SFfDgvwgStInEzE5yys6SUQcXLBtA==",
"dependencies": {
"@sentry/core": "7.57.0",
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry/core": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.106.1.tgz",
"integrity": "sha512-r+nhLrQuTQih93gZ08F6MLdmaoBy/bQFcVt/2ZVqe1SkDY+MxRlXxq8ydo3FfgEjMRHdody3yT1dj6E174h23w==",
"dependencies": {
"@sentry/core": "7.106.1",
"@sentry/replay": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.106.1.tgz",
"integrity": "sha512-Ui9zSmW88jTdmNnNBLYYpNoAi31esX5/auysC3v7+SpwxIsC3AGLFvXs4EPziyz8d0F62Ji0fNQZ96ui4fO6BQ==",
"dependencies": {
"@sentry/core": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.106.1.tgz",
"integrity": "sha512-+Yp7OUx78ZwFFYfIvOKZGjMPW7Ds3zZSO8dsMxvDRzkA9NyyAmYMZ/dNTcsGb+PssgkCasF2XA07f6WgkNW92A==",
"dependencies": {
"@sentry-internal/tracing": "7.57.0",
"@sentry/core": "7.57.0",
"@sentry/replay": "7.57.0",
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry-internal/feedback": "7.106.1",
"@sentry-internal/replay-canvas": "7.106.1",
"@sentry-internal/tracing": "7.106.1",
"@sentry/core": "7.106.1",
"@sentry/replay": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/core": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.106.1.tgz",
"integrity": "sha512-cwCd66wkbutXCI8j14JLkyod9RHtqSNfzGpx/ieBE+N786jX+Yj1DiaZJ6ZYjKQpnToipFnacEakCd9Vc9oePA==",
"dependencies": {
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/integrations": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.106.1.tgz",
"integrity": "sha512-2wIDaHGWE5QOnTAkQe5itH32K3gcZ5rluAWxcq+hG+xZd7JA6EDGnDEMHieGweFbkYazBteSE8qaxjDUAGYYJA==",
"dependencies": {
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0",
"localforage": "^1.8.1",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry/core": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1",
"localforage": "^1.8.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/react": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.106.1.tgz",
"integrity": "sha512-XcvjXGist0vLapzxZxdbPSdLC4drhGOETtlA/kO+KrIKqlqRKuYw+kieU+YeyF9A/L8uSVvbj9rpjl5WVUTdIw==",
"dependencies": {
"@sentry/browser": "7.57.0",
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry/browser": "7.106.1",
"@sentry/core": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
"node": ">=8"
@ -2832,30 +2863,33 @@
}
},
"node_modules/@sentry/replay": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.106.1.tgz",
"integrity": "sha512-UnuY6bj7v7CVv3T1sbLHjLutSG4hzcQQj6CjEB2NUpM+QAIguFrwAcYG4U42iNg4Qeg5q4kHi1rPpdpvh6unSA==",
"dependencies": {
"@sentry/core": "7.57.0",
"@sentry/types": "7.57.0",
"@sentry/utils": "7.57.0"
"@sentry-internal/tracing": "7.106.1",
"@sentry/core": "7.106.1",
"@sentry/types": "7.106.1",
"@sentry/utils": "7.106.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/types": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.106.1.tgz",
"integrity": "sha512-g3OcyAHGugBwkQP4fZYCCZqF2ng9K7yQc9FVngKq/y7PwHm84epXdYYGDGgfQOIC1d5/GMaPxmzI5IIrZexzkg==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.57.0",
"license": "MIT",
"version": "7.106.1",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.106.1.tgz",
"integrity": "sha512-NIeuvB9MeDwrObbi6W5xRrNTcQj8klVvwWWYQB0zotY/LDjyl+c+cZzUshFOxBTp9ljVnYzWqZ7J8x/i4baj7w==",
"dependencies": {
"@sentry/types": "7.57.0",
"tslib": "^2.4.1 || ^1.9.3"
"@sentry/types": "7.106.1"
},
"engines": {
"node": ">=8"
@ -9072,10 +9106,6 @@
"node": ">=4"
}
},
"node_modules/tslib": {
"version": "1.14.1",
"license": "0BSD"
},
"node_modules/type-check": {
"version": "0.3.2",
"dev": true,

View File

@ -18,8 +18,8 @@
"dependencies": {
"@dinero.js/currencies": "2.0.0-alpha.1",
"@fingerprintjs/fingerprintjs": "3.4.2",
"@sentry/integrations": "7.57.0",
"@sentry/react": "7.57.0",
"@sentry/integrations": "7.106.1",
"@sentry/react": "7.106.1",
"card-validator": "8.1.1",
"credit-card-type": "9.1.0",
"framer-motion": "10.12.18",

View File

@ -35,7 +35,7 @@ creditCardType.addCard({
});
const initSentry = async (dsn: string) => {
const { init, BrowserTracing, Replay } = await import('@sentry/react');
const { init, BrowserTracing } = await import('@sentry/react');
const { CaptureConsole } = await import('@sentry/integrations');
const env = await getEnv();
init({
@ -44,14 +44,11 @@ const initSentry = async (dsn: string) => {
integrations: [
new BrowserTracing(),
new CaptureConsole({
levels: ['error'],
levels: ['error', 'warn'],
}),
new Replay(),
],
tracesSampleRate: 0.1,
release: env.version,
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
});
};

View File

@ -0,0 +1,19 @@
import { InvoiceAndToken } from './types';
import { fetchApi } from '../../../common/utils';
export type InvoiceParamsWithTemplate = {
amount: number;
currency: string;
metadata: object;
};
export const createInvoiceWithTemplate = async (
capiEndpoint: string,
accessToken: string,
invoiceTemplateID: string,
params: InvoiceParamsWithTemplate,
): Promise<InvoiceAndToken> => {
const path = `v2/processing/invoice-templates/${invoiceTemplateID}/invoices`;
const response = await fetchApi(capiEndpoint, accessToken, 'POST', path, params);
return response.json();
};

View File

@ -0,0 +1,12 @@
import { Invoice } from './types';
import { fetchApi } from '../../../common/utils';
export const getInvoiceByID = async (
capiEndpoint: string,
accessToken: string,
invoiceID: string,
): Promise<Invoice> => {
const path = `v2/processing/invoices/${invoiceID}`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,20 @@
import { InvoiceEvent } from './types';
import { fetchApi, isNil } from '../../../common/utils';
export const getInvoiceEvents = async (
capiEndpoint: string,
accessToken: string,
invoiceID: string,
limit: number,
eventID?: number,
): Promise<InvoiceEvent[]> => {
const queryParams = new URLSearchParams({
limit: limit.toString(),
});
if (!isNil(eventID)) {
queryParams.append('eventID', eventID.toString());
}
const path = `v2/processing/invoices/${invoiceID}/events?${queryParams.toString()}`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,12 @@
import { PaymentMethod } from './types';
import { fetchApi } from '../../../common/utils';
export const getInvoicePaymentMethods = async (
capiEndpoint: string,
accessToken: string,
invoiceID: string,
): Promise<PaymentMethod[]> => {
const path = `v2/processing/invoices/${invoiceID}/payment-methods`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,12 @@
import { PaymentMethod } from './types';
import { fetchApi } from '../../../common/utils';
export const getInvoicePaymentMethodsByTemplateID = async (
capiEndpoint: string,
accessToken: string,
invoiceTemplateID: string,
): Promise<PaymentMethod[]> => {
const path = `v2/processing/invoice-templates/${invoiceTemplateID}/payment-methods`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,12 @@
import { InvoiceTemplate } from './types';
import { fetchApi } from '../../../common/utils';
export const getInvoiceTemplateByID = async (
capiEndpoint: string,
accessToken: string,
invoiceTemplateID: string,
): Promise<InvoiceTemplate> => {
const path = `v2/processing/invoice-templates/${invoiceTemplateID}`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,12 @@
import { ServiceProvider } from './types';
import { fetchApi } from '../../../common/utils';
export const getServiceProviderByID = async (
capiEndpoint: string,
accessToken: string,
serviceProviderID: string,
): Promise<ServiceProvider> => {
const path = `v2/processing/service-providers/${serviceProviderID}`;
const response = await fetchApi(capiEndpoint, accessToken, 'GET', path);
return response.json();
};

View File

@ -0,0 +1,38 @@
export { getInvoiceByID } from './getInvoiceByID';
export { getInvoicePaymentMethods } from './getInvoicePaymentMethods';
export { getServiceProviderByID } from './getServiceProviderByID';
export { getInvoiceTemplateByID } from './getInvoiceTemplateByID';
export { getInvoicePaymentMethodsByTemplateID } from './getInvoicePaymentMethodsByTemplateID';
export { getInvoiceEvents } from './getInvoiceEvents';
export { createInvoiceWithTemplate } from './createInvoiceWithTemplate';
export type {
Invoice,
InvoiceAndToken,
InvoiceStatus,
InvoiceEvent,
InvoiceChangeType,
PaymentMethod,
ServiceProvider,
Payment,
PaymentStatus,
PaymentError,
InvoiceTemplate,
InvoiceTemplateMultiLine,
InvoiceTemplateSingleLine,
InvoiceTemplateLineCostFixed,
PaymentStarted,
UserInteraction,
BrowserRequest,
BrowserGetRequest,
BrowserPostRequest,
UserInteractionForm,
} from './types';
export type {
ServiceProviderMetadata,
CheckoutServiceProviderMetadata,
ServiceProviderContactInfo,
ServiceProviderMetadataField,
METADATA_NAMESPACE,
} from './serviceProviderMetadata';

View File

@ -0,0 +1,96 @@
export const METADATA_NAMESPACE = 'dev.vality.checkout';
export type MetadataTextLocalization = {
ja?: string;
en?: string;
ru?: string;
pt?: string;
};
export type MetadataFieldFormatter = {
type: 'numbersOnly';
maxLength?: number;
};
export type AttributeType = JSX.IntrinsicElements['input']['type'];
export type AttributeInputMode = JSX.IntrinsicElements['input']['inputMode'];
export type ServiceProviderMetadataField = {
type: AttributeType;
name: string;
required: boolean;
pattern?: string;
localization?: MetadataTextLocalization;
index?: number;
formatter?: MetadataFieldFormatter;
inputMode?: AttributeInputMode;
addon?: 'vpa';
replaceValuePattern?: string;
};
export type MetadataSelectSource = {
type: 'countrySubdivisions';
countryCode?: string;
};
export type ServiceProviderMetadataSelect = {
type: 'select';
name: string;
required: boolean;
src: MetadataSelectSource;
localization?: MetadataTextLocalization;
index?: number;
};
export type ServiceProviderIconMetadata = {
src: string;
width: string;
height: string;
};
export type ServiceProviderTitleMetadata = {
icon: 'wallets' | 'online-banking' | 'bank-card';
localization?: MetadataTextLocalization;
};
export type ServiceProviderContactInfo = {
email: boolean;
phoneNumber: boolean;
};
export type UserInteractionMetadata = {
type: 'frame' | 'self';
};
export type QrCodeFormMetadata = {
isCopyCodeBlock: boolean;
qrCodeRedirect: 'mobile' | 'none';
};
export type PaymentSessionInfoMetadata = {
redirectUrlInfo: {
type: 'self' | 'outer';
};
};
export type PrefilledMetadataValues = {
[key: string]: string | number | boolean;
};
export type ServiceProviderMetadataForm = ServiceProviderMetadataField[] | ServiceProviderMetadataSelect[];
export type CheckoutServiceProviderMetadata = {
form?: ServiceProviderMetadataForm;
logo?: ServiceProviderIconMetadata;
title?: ServiceProviderTitleMetadata;
signUpLink?: string;
contactInfo?: ServiceProviderContactInfo;
userInteraction?: UserInteractionMetadata;
qrCodeForm?: QrCodeFormMetadata;
paymentSessionInfo?: PaymentSessionInfoMetadata;
prefilledMetadataValues?: PrefilledMetadataValues;
};
export type ServiceProviderMetadata = {
[METADATA_NAMESPACE]: CheckoutServiceProviderMetadata;
};

View File

@ -0,0 +1,264 @@
import { ServiceProviderMetadata } from './serviceProviderMetadata';
export type PaymentError = {
code: string;
subError?: PaymentError;
};
export type PaymentFlowInstant = {
type: 'PaymentFlowInstant';
};
export type PaymentFlowHold = {
type: 'PaymentFlowHold';
onHoldExpiration: 'cancel' | 'capture';
heldUntil?: string;
};
export type PaymentFlow = PaymentFlowInstant | PaymentFlowHold;
export type ContactInfo = {
email: string;
phoneNumber: string;
};
export type PaymentToolDetailsBankCard = {
detailsType: 'PaymentToolDetailsBankCard';
cardNumberMask: string;
first6: string;
last4: string;
paymentSystem: string;
};
export type PaymentToolDetailsPaymentTerminal = {
detailsType: 'PaymentToolDetailsPaymentTerminal';
provider: string;
};
export type PaymentToolDetails = PaymentToolDetailsBankCard | PaymentToolDetailsPaymentTerminal;
export type PaymentResourcePayer = {
payerType: 'PaymentResourcePayer';
contactInfo: ContactInfo;
paymentToolToken?: string;
paymentSession?: string;
sessionInfo?: {
redirectUrl: string;
};
paymentToolDetails?: PaymentToolDetails;
};
export type Payer = PaymentResourcePayer;
export type Payment = {
id: string;
invoiceID: string;
createdAt: string;
amount: number;
currency: string;
flow: PaymentFlow;
payer: Payer;
status: PaymentStatus;
error: PaymentError;
externalID: string;
};
export type PaymentStatus = 'processed' | 'failed' | 'cancelled' | 'pending' | 'captured' | 'refunded';
export type UserInteractionForm = {
key: string;
template: string;
};
export type BrowserGetRequest = {
requestType: 'BrowserGetRequest';
uriTemplate: string;
};
export type BrowserPostRequest = {
requestType: 'BrowserPostRequest';
uriTemplate: string;
form: UserInteractionForm[];
};
export type BrowserRequest = BrowserGetRequest | BrowserPostRequest;
export type ApiExtensionRequest = {
interactionType: 'ApiExtensionRequest';
apiType: string;
};
export type QrCodeDisplayRequest = {
interactionType: 'QrCodeDisplayRequest';
qrCode: string;
};
export type Redirect = {
interactionType: 'Redirect';
request: BrowserRequest;
};
export type UserInteraction = ApiExtensionRequest | QrCodeDisplayRequest | Redirect;
export type PaymentInteractionRequested = {
changeType: 'PaymentInteractionRequested';
paymentID: string;
userInteraction: UserInteraction;
};
export type PaymentInteractionCompleted = {
changeType: 'PaymentInteractionCompleted';
paymentID: string;
userInteraction?: UserInteraction;
};
export type PaymentStatusChanged = {
changeType: 'PaymentStatusChanged';
status: PaymentStatus;
paymentID: string;
error?: PaymentError;
};
export type PaymentStarted = {
changeType: 'PaymentStarted';
payment: Payment;
};
export type InvoiceStatusChanged = {
changeType: 'InvoiceStatusChanged';
status: InvoiceStatus;
reason?: string;
};
export type InvoiceCreated = {
changeType: 'InvoiceCreated';
invoice: Invoice;
};
export type InvoiceChangeType =
| 'InvoiceCreated'
| 'InvoiceStatusChanged'
| 'PaymentStatusChanged'
| 'PaymentStarted'
| 'PaymentInteractionRequested'
| 'PaymentInteractionCompleted';
export type InvoiceChange =
| InvoiceCreated
| InvoiceStatusChanged
| PaymentStatusChanged
| PaymentStarted
| PaymentInteractionRequested
| PaymentInteractionCompleted;
export type InvoiceEvent = {
id: number;
createdAt: string;
changes: InvoiceChange[];
};
type LifetimeInterval = {
days: number;
months: number;
years: number;
};
type InvoiceLine = {
product: string;
quantity: number;
price: number;
cost: number;
};
type CostAmountRange = {
lowerBound: number;
upperBound: number;
};
export type InvoiceTemplateLineCostUnlim = {
costType: 'InvoiceTemplateLineCostUnlim';
};
export type InvoiceTemplateLineCostRange = {
costType: 'InvoiceTemplateLineCostRange';
range: CostAmountRange;
currency: string;
};
export type InvoiceTemplateLineCostFixed = {
costType: 'InvoiceTemplateLineCostFixed';
amount: number;
currency: string;
};
export type InvoiceTemplateSingleLine = {
templateType: 'InvoiceTemplateSingleLine';
product: string;
price: InvoiceTemplateLineCostFixed | InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
};
export type InvoiceTemplateMultiLine = {
templateType: 'InvoiceTemplateMultiLine';
cart: InvoiceLine[];
currency: string;
};
export type InvoiceTemplate = {
id: string;
shopID: string;
product: string;
description: string;
lifetime: LifetimeInterval;
details: InvoiceTemplateMultiLine | InvoiceTemplateSingleLine;
metadata: object;
};
export type ServiceProvider = {
id: string;
brandName: string;
category: string;
metadata?: ServiceProviderMetadata;
};
export type PaymentTerminal = {
method: 'PaymentTerminal';
providers: string[];
};
export type DigitalWallet = {
method: 'DigitalWallet';
providers: string[];
};
export type BankCard = {
method: 'BankCard';
paymentSystems: string[];
};
export type PaymentMethod = PaymentTerminal | BankCard | DigitalWallet;
export type InvoiceStatus = 'unpaid' | 'cancelled' | 'paid' | 'fulfilled';
export type InvoiceAndToken = {
invoice: Invoice;
invoiceAccessToken: {
payload: string;
};
};
export type Invoice = {
id: string;
shopID: string;
invoiceTemplateID: string;
createdAt: string;
dueDate: string;
amount: number;
currency: string;
metadata: object;
product: string;
description: string;
status: InvoiceStatus;
reason: string;
cart: InvoiceLine[];
externalID: string;
};

View File

@ -1,14 +1,8 @@
import {
InvoiceStatuses,
ServiceProviderContactInfo,
ServiceProviderMetadataField,
getInvoiceEvents,
} from 'checkout/backend';
import { PaymentCondition } from './types';
import { invoiceEventsToConditions, provideInstantPayment } from './utils';
import { ServiceProviderContactInfo, ServiceProviderMetadataField, getInvoiceEvents } from '../backend/payments';
import { InitContext, PaymentModel, PaymentModelInvoice, PaymentTerminal } from '../paymentModel';
import { extractError, isNil, last } from '../utils';
import { extractError, isNil, last, withRetry } from '../utils';
import { findMetadata } from '../utils/findMetadata';
/*
@ -94,7 +88,8 @@ const provideInvoiceUnpaid = async (model: PaymentModelInvoice): Promise<Payment
apiEndpoint,
initContext: { skipUserInteraction },
} = model;
const events = await getInvoiceEvents(
const getInvoiceEventsWithRetry = withRetry(getInvoiceEvents);
const events = await getInvoiceEventsWithRetry(
apiEndpoint,
invoiceParams.invoiceAccessToken,
invoiceParams.invoiceID,
@ -125,12 +120,11 @@ const provideInvoiceUnpaid = async (model: PaymentModelInvoice): Promise<Payment
const provideInvoice = async (model: PaymentModelInvoice): Promise<PaymentCondition[]> => {
switch (model.status) {
case InvoiceStatuses.cancelled:
case InvoiceStatuses.fulfilled:
case InvoiceStatuses.paid:
case InvoiceStatuses.refunded:
case 'cancelled':
case 'fulfilled':
case 'paid':
return [{ name: 'invoiceStatusChanged', status: model.status }];
case InvoiceStatuses.unpaid:
case 'unpaid':
return provideInvoiceUnpaid(model);
}
};

View File

@ -1,5 +1,4 @@
import { BrowserRequest, InvoiceStatuses, PaymentError, PaymentStatuses } from 'checkout/backend';
import { BrowserRequest, InvoiceStatus, PaymentError, PaymentStatus } from '../backend/payments';
import { InvoiceContext } from '../paymentModel';
export type InvoiceDetermined = {
@ -29,7 +28,7 @@ export type PaymentStatusChanged = {
name: 'paymentStatusChanged';
eventId: number;
paymentId: string;
status: PaymentStatuses;
status: PaymentStatus;
error?: PaymentError;
};
@ -39,7 +38,7 @@ export type PaymentStatusUnknown = {
export type InvoiceStatusChanged = {
name: 'invoiceStatusChanged';
status: InvoiceStatuses;
status: InvoiceStatus;
};
export type PaymentInteractionRedirectType = 'frame' | 'self';

View File

@ -1,47 +1,31 @@
import {
InteractionType,
InvoiceChangeType,
InvoiceEvent,
PaymentInteractionCompleted,
PaymentInteractionRequested,
PaymentResourcePayer,
PaymentStarted,
PaymentStatusChanged,
PaymentToolDetailsPaymentTerminal,
PaymentToolDetailsType,
QrCodeDisplayRequest,
Redirect,
UserInteraction,
} from 'checkout/backend';
import { InvoiceEvent, PaymentStarted, UserInteraction } from '../../../common/backend/payments';
import { isNil } from '../../../common/utils';
import { Interaction, PaymentCondition } from '../types';
const getProvider = (started: PaymentStarted): string | null => {
const payer = (started as PaymentStarted).payment.payer;
const paymentToolDetails = (payer as PaymentResourcePayer).paymentToolDetails;
if (paymentToolDetails.detailsType === PaymentToolDetailsType.PaymentToolDetailsPaymentTerminal) {
return (paymentToolDetails as PaymentToolDetailsPaymentTerminal).provider;
const paymentToolDetails = started.payment.payer.paymentToolDetails;
if (paymentToolDetails.detailsType === 'PaymentToolDetailsPaymentTerminal') {
return paymentToolDetails.provider;
}
return null;
};
const toInteraction = (userInteraction: UserInteraction, skipUserInteraction: boolean): Interaction | null => {
switch (userInteraction.interactionType) {
case InteractionType.Redirect:
case 'Redirect':
if (skipUserInteraction) {
return null;
}
return {
type: 'PaymentInteractionRedirect',
request: (userInteraction as Redirect).request,
request: userInteraction.request,
};
case InteractionType.QrCodeDisplayRequest:
case 'QrCodeDisplayRequest':
return {
type: 'PaymentInteractionQRCode',
qrCode: (userInteraction as QrCodeDisplayRequest).qrCode,
qrCode: userInteraction.qrCode,
};
case InteractionType.ApiExtensionRequest:
case 'ApiExtensionRequest':
return {
type: 'PaymentInteractionApiExtension',
};
@ -52,34 +36,31 @@ export const invoiceEventsToConditions = (events: InvoiceEvent[], skipUserIntera
return events.reduce((result, { changes, id }) => {
const conditions = changes.reduce((acc, change) => {
switch (change.changeType) {
case InvoiceChangeType.PaymentStarted: {
const started = change as PaymentStarted;
case 'PaymentStarted': {
return [
...acc,
{
name: 'paymentStarted',
eventId: id,
provider: getProvider(started),
paymentId: started.payment.id,
provider: getProvider(change),
paymentId: change.payment.id,
},
];
}
case InvoiceChangeType.PaymentStatusChanged: {
const statusChanged = change as PaymentStatusChanged;
case 'PaymentStatusChanged': {
return [
...acc,
{
name: 'paymentStatusChanged',
eventId: id,
status: statusChanged.status,
paymentId: statusChanged.paymentID,
error: statusChanged?.error,
status: change.status,
paymentId: change.paymentID,
error: change?.error,
},
];
}
case InvoiceChangeType.PaymentInteractionRequested: {
const interactionRequested = change as PaymentInteractionRequested;
const interaction = toInteraction(interactionRequested.userInteraction, skipUserInteraction);
case 'PaymentInteractionRequested': {
const interaction = toInteraction(change.userInteraction, skipUserInteraction);
if (isNil(interaction)) {
return acc;
}
@ -88,19 +69,18 @@ export const invoiceEventsToConditions = (events: InvoiceEvent[], skipUserIntera
{
name: 'interactionRequested',
eventId: id,
interaction: toInteraction(interactionRequested.userInteraction, skipUserInteraction),
paymentId: interactionRequested.paymentID,
interaction: toInteraction(change.userInteraction, skipUserInteraction),
paymentId: change.paymentID,
},
];
}
case InvoiceChangeType.PaymentInteractionCompleted: {
const interactionCompleted = change as PaymentInteractionCompleted;
case 'PaymentInteractionCompleted': {
return [
...acc,
{
name: 'interactionCompleted',
eventId: id,
paymentId: interactionCompleted.paymentID,
paymentId: change.paymentID,
},
];
}

View File

@ -1,6 +1,5 @@
import { createInvoiceWithTemplate as request } from 'checkout/backend';
import { invoiceToInvoiceContext } from './invoiceToInvoiceContext';
import { createInvoiceWithTemplate as request } from '../backend/payments';
import { InvoiceContext, PaymentModelInvoiceTemplate } from '../paymentModel';
export const createInvoiceWithTemplate = async (model: PaymentModelInvoiceTemplate): Promise<InvoiceContext> => {

View File

@ -1,19 +1,16 @@
import { InvoiceAndToken } from 'checkout/backend';
import { InvoiceAndToken } from '../backend/payments';
import { InvoiceContext } from '../paymentModel';
export const invoiceToInvoiceContext = ({
invoice: { id, externalID, dueDate, status },
invoiceAccessToken,
}: InvoiceAndToken): InvoiceContext => {
return {
type: 'InvoiceContext',
invoiceParams: {
invoiceID: id,
invoiceAccessToken: invoiceAccessToken.payload,
},
externalID,
dueDate,
status,
};
};
}: InvoiceAndToken): InvoiceContext => ({
type: 'InvoiceContext',
invoiceParams: {
invoiceID: id,
invoiceAccessToken: invoiceAccessToken.payload,
},
externalID,
dueDate,
status,
});

View File

@ -1,6 +1,5 @@
import { InvoiceChangeType, InvoiceEvent, getInvoiceEvents } from 'checkout/backend';
import { delay, isNil, last } from '../utils';
import { InvoiceChangeType, InvoiceEvent, getInvoiceEvents } from '../backend/payments';
import { delay, extractError, isNil, last } from '../utils';
const GET_INVOICE_EVENTS_LIMIT = 20;
@ -16,9 +15,24 @@ const isChangeFound = (event: InvoiceEvent | undefined, stopPollingTypes: Invoic
}, false);
};
const fetchInvoiceEventsSafe = async (
capiEndpoint: string,
accessToken: string,
invoiceID: string,
limit: number,
eventID?: number,
): Promise<InvoiceEvent[]> => {
try {
return await getInvoiceEvents(capiEndpoint, accessToken, invoiceID, limit, eventID);
} catch (exception) {
console.warn(`Failed to fetch invoice events. ${extractError(exception)}`);
return [];
}
};
const fetchEvents = async (params: PollInvoiceEventsParams, isStop: () => boolean): Promise<PollingResult> => {
const { apiEndpoint, invoiceAccessToken, invoiceID, startFromEventID, stopPollingTypes, delays } = params;
const events = await getInvoiceEvents(
const events = await fetchInvoiceEventsSafe(
apiEndpoint,
invoiceAccessToken,
invoiceID,

View File

@ -1,13 +1,6 @@
import {
CostType,
InvoiceTemplateLineCostFixed,
InvoiceTemplateMultiLine,
InvoiceTemplateSingleLine,
TemplateType,
} from 'checkout/backend';
import { BackendModel, BackendModelInvoice, BackendModelInvoiceTemplate } from './getBackendModel';
import { PaymentAmount } from './types';
import { InvoiceTemplateLineCostFixed, InvoiceTemplateMultiLine, InvoiceTemplateSingleLine } from '../backend/payments';
const fromInvoiceTemplateMultiLine = (details: InvoiceTemplateMultiLine): PaymentAmount => ({
value: details.cart.reduce((p, c) => p + c.price * c.quantity, 0),
@ -22,10 +15,10 @@ const fromInvoiceTemplateLineCostFixed = (fixed: InvoiceTemplateLineCostFixed):
export const fromInvoiceTemplateSingleLine = (details: InvoiceTemplateSingleLine): PaymentAmount => {
const price = details.price;
switch (price.costType) {
case CostType.InvoiceTemplateLineCostFixed:
case 'InvoiceTemplateLineCostFixed':
return fromInvoiceTemplateLineCostFixed(price as InvoiceTemplateLineCostFixed);
case CostType.InvoiceTemplateLineCostRange:
case CostType.InvoiceTemplateLineCostUnlim:
case 'InvoiceTemplateLineCostRange':
case 'InvoiceTemplateLineCostUnlim':
throw new Error(`Unsupported invoice template cost type: ${price.costType}`);
}
};
@ -33,9 +26,9 @@ export const fromInvoiceTemplateSingleLine = (details: InvoiceTemplateSingleLine
const fromInvoiceTemplate = ({ invoiceTemplate }: BackendModelInvoiceTemplate): PaymentAmount => {
const details = invoiceTemplate.details;
switch (details.templateType) {
case TemplateType.InvoiceTemplateMultiLine:
case 'InvoiceTemplateMultiLine':
return fromInvoiceTemplateMultiLine(details as InvoiceTemplateMultiLine);
case TemplateType.InvoiceTemplateSingleLine:
case 'InvoiceTemplateSingleLine':
return fromInvoiceTemplateSingleLine(details as InvoiceTemplateSingleLine);
}
};

View File

@ -1,7 +1,6 @@
import { PaymentMethodName, PaymentTerminal, ServiceProvider } from 'checkout/backend';
import { BackendModel } from './getBackendModel';
import { KnownProviderCategory, PaymentMethod } from './types';
import { ServiceProvider } from '../backend/payments';
import { assertUnreachable, groupBy } from '../utils';
const categoryReducer = (
@ -47,11 +46,10 @@ const fromPaymentTerminal = (terminalProviderIDs: string[], serviceProviders: Se
export const backendModelToPaymentMethods = (model: BackendModel): PaymentMethod[] => {
return model.paymentMethods.reduce((result, backendPaymentMethod) => {
switch (backendPaymentMethod.method) {
case PaymentMethodName.BankCard:
case 'BankCard':
return result.concat([{ methodName: 'BankCard' }]);
case PaymentMethodName.PaymentTerminal:
const terminal = backendPaymentMethod as PaymentTerminal;
return result.concat(fromPaymentTerminal(terminal.providers, model.serviceProviders));
case 'PaymentTerminal':
return result.concat(fromPaymentTerminal(backendPaymentMethod.providers, model.serviceProviders));
default:
return result;
}

View File

@ -1,3 +1,5 @@
import { getServiceProviders } from './getServiceProviders';
import { InvoiceContext, InvoiceParams, InvoiceTemplateContext, InvoiceTemplateParams } from './types';
import {
Invoice,
InvoiceTemplate,
@ -7,10 +9,8 @@ import {
getInvoicePaymentMethods,
getInvoicePaymentMethodsByTemplateID,
getInvoiceTemplateByID,
} from 'checkout/backend';
import { getServiceProviders } from './getServiceProviders';
import { InvoiceContext, InvoiceParams, InvoiceTemplateContext, InvoiceTemplateParams } from './types';
} from '../backend/payments';
import { withRetry } from '../utils';
type CommonBackendModel = {
paymentMethods: PaymentMethod[];
@ -33,9 +33,11 @@ const getInvoiceTemplateModel = async (
apiEndpoint: string,
{ invoiceTemplateID, invoiceTemplateAccessToken }: InvoiceTemplateParams,
): Promise<BackendModelInvoiceTemplate> => {
const getInvoiceTemplateByIDWithRetry = withRetry(getInvoiceTemplateByID);
const getInvoicePaymentMethodsByTemplateIDWithRetry = withRetry(getInvoicePaymentMethodsByTemplateID);
const [invoiceTemplate, paymentMethods] = await Promise.all([
getInvoiceTemplateByID(apiEndpoint, invoiceTemplateAccessToken, invoiceTemplateID),
getInvoicePaymentMethodsByTemplateID(apiEndpoint, invoiceTemplateAccessToken, invoiceTemplateID),
getInvoiceTemplateByIDWithRetry(apiEndpoint, invoiceTemplateAccessToken, invoiceTemplateID),
getInvoicePaymentMethodsByTemplateIDWithRetry(apiEndpoint, invoiceTemplateAccessToken, invoiceTemplateID),
]);
const serviceProviders = await getServiceProviders(paymentMethods, apiEndpoint, invoiceTemplateAccessToken);
return { type: 'BackendModelInvoiceTemplate', paymentMethods, invoiceTemplate, serviceProviders };
@ -45,9 +47,11 @@ const getInvoiceModel = async (
apiEndpoint: string,
{ invoiceID, invoiceAccessToken }: InvoiceParams,
): Promise<BackendModelInvoice> => {
const getInvoiceByIDWithRetry = withRetry(getInvoiceByID);
const getInvoicePaymentMethodsWithRetry = withRetry(getInvoicePaymentMethods);
const [invoice, paymentMethods] = await Promise.all([
getInvoiceByID(apiEndpoint, invoiceAccessToken, invoiceID),
getInvoicePaymentMethods(apiEndpoint, invoiceAccessToken, invoiceID),
getInvoiceByIDWithRetry(apiEndpoint, invoiceAccessToken, invoiceID),
getInvoicePaymentMethodsWithRetry(apiEndpoint, invoiceAccessToken, invoiceID),
]);
const serviceProviders = await getServiceProviders(paymentMethods, apiEndpoint, invoiceAccessToken);
return { type: 'BackendModelInvoice', paymentMethods, invoice, serviceProviders };

View File

@ -1,33 +1,12 @@
import {
getServiceProviderByID,
PaymentMethod,
PaymentMethodName,
PaymentTerminal,
ServiceProvider,
} from 'checkout/backend';
import { isNil } from '../utils';
export async function getServiceProviderOrNull(
endpoint: string,
accessToken: string,
id: string,
): Promise<ServiceProvider | null> {
try {
return await getServiceProviderByID(endpoint, accessToken, id);
} catch (err) {
console.error(`Failed to load provider "${id}".`, err);
return null;
}
}
import { PaymentMethod, ServiceProvider, getServiceProviderByID } from '../backend/payments';
import { isNil, withRetry } from '../utils';
const providerIDsReducer = (result: string[], method: PaymentMethod): string[] => {
switch (method.method) {
case PaymentMethodName.PaymentTerminal:
case PaymentMethodName.DigitalWallet:
const terminalMethod = method as PaymentTerminal;
if (!isNil(terminalMethod.providers)) {
return result.concat(terminalMethod.providers);
case 'PaymentTerminal':
case 'DigitalWallet':
if (!isNil(method.providers)) {
return result.concat(method.providers);
}
return result;
default:
@ -35,6 +14,8 @@ const providerIDsReducer = (result: string[], method: PaymentMethod): string[] =
}
};
const getServiceProviderByIDWithRetry = withRetry(getServiceProviderByID);
export const getServiceProviders = async (
paymentMethods: PaymentMethod[],
endpoint: string,
@ -42,7 +23,7 @@ export const getServiceProviders = async (
): Promise<ServiceProvider[]> => {
const providerIDs = paymentMethods.reduce(providerIDsReducer, []);
const serviceProvidersOrNull = await Promise.all(
providerIDs.map((id) => getServiceProviderOrNull(endpoint, accessToken, id)),
providerIDs.map((id) => getServiceProviderByIDWithRetry(endpoint, accessToken, id)),
);
return serviceProvidersOrNull.filter((provider): provider is ServiceProvider => !isNil(provider));
};

View File

@ -1,4 +1,4 @@
import { InvoiceStatuses, ServiceProviderMetadata } from 'checkout/backend';
import { InvoiceStatus, ServiceProviderMetadata } from '../backend/payments';
export type PaymentAmount = {
readonly value: number;
@ -81,7 +81,7 @@ export type InvoiceContext = {
readonly invoiceParams: InvoiceParams;
readonly dueDate: string;
readonly externalID: string;
readonly status: InvoiceStatuses;
readonly status: InvoiceStatus;
};
export type PaymentModelInvoice = InvoiceContext & CommonPaymentModel;

View File

@ -1,10 +1,10 @@
import { useCallback, useReducer } from 'react';
import { extractError } from 'src/common/utils';
import { InitParams } from 'checkout/initialize';
import { initPaymentCondition, PaymentCondition } from '../../common/paymentCondition';
import { initPaymentModel, PaymentModel } from '../../common/paymentModel';
import { extractError } from '../../common/utils';
type ModelsStateData = {
paymentModel: PaymentModel;
@ -61,7 +61,7 @@ export const useInitModels = () => {
payload: { paymentModel, conditions },
});
} catch (error) {
console.error(`useInitModels error: ${extractError(error)}`, error);
console.error(`useInitModels error. ${extractError(error)}`);
dispatch({ type: 'INIT_FAILED', error });
}
})();

View File

@ -1,9 +1,8 @@
import { useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { BrowserRequest } from 'checkout/backend';
import { prepareForm } from './utils';
import { BrowserRequest } from '../../common/backend/payments';
import { PaymentContext } from '../../common/contexts';
import { device, isNil } from '../../common/utils';

View File

@ -1,9 +1,8 @@
import { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { BrowserRequest } from 'checkout/backend';
import { prepareForm } from './utils';
import { BrowserRequest } from '../../common/backend/payments';
import { LayoutLoader } from '../legacy';
const RedirectContainer = styled.div`

View File

@ -1,22 +1,21 @@
import { BrowserPostRequest, BrowserRequest, RequestType } from 'checkout/backend';
import { toGetFormInputs } from './toGetFormInputs';
import { toPostFormInputs } from './toPostFormInputs';
import { BrowserRequest } from '../../../common/backend/payments';
const toInputs = (origin: string, request: BrowserRequest): HTMLInputElement[] => {
switch (request.requestType) {
case RequestType.BrowserPostRequest:
return toPostFormInputs(origin, (request as BrowserPostRequest).form);
case RequestType.BrowserGetRequest:
case 'BrowserPostRequest':
return toPostFormInputs(origin, request.form);
case 'BrowserGetRequest':
return toGetFormInputs(origin, request);
}
};
const toMethod = (request: BrowserRequest): 'POST' | 'GET' => {
switch (request.requestType) {
case RequestType.BrowserPostRequest:
case 'BrowserPostRequest':
return 'POST';
case RequestType.BrowserGetRequest:
case 'BrowserGetRequest':
return 'GET';
}
};

View File

@ -1,6 +1,5 @@
import { BrowserGetRequest } from 'checkout/backend';
import { expandWithRedirect, hasTerminationUriTemplate } from './expandWithRedirect';
import { BrowserGetRequest } from '../../../common/backend/payments';
import { getEncodedUrlParams } from '../../../common/utils';
const createInput = (name: string, value: any): HTMLInputElement => {

View File

@ -1,8 +1,7 @@
import { FormField } from 'checkout/backend';
import { expandWithRedirect } from './expandWithRedirect';
import { UserInteractionForm } from '../../../common/backend/payments';
const createInput = (origin: string, formField: FormField): HTMLInputElement => {
const createInput = (origin: string, formField: UserInteractionForm): HTMLInputElement => {
const formParam = document.createElement('input');
formParam.name = formField.key;
formField.key === 'TermUrl'
@ -11,5 +10,5 @@ const createInput = (origin: string, formField: FormField): HTMLInputElement =>
return formParam;
};
export const toPostFormInputs = (origin: string, form: FormField[]): HTMLInputElement[] =>
export const toPostFormInputs = (origin: string, form: UserInteractionForm[]): HTMLInputElement[] =>
form.map((field) => createInput(origin, field));

View File

@ -1,59 +1,52 @@
import { InvoiceStatuses, PaymentStatuses } from 'checkout/backend';
import { InvoiceStatusChanged, PaymentCondition, PaymentStatusChanged } from '../../../../common/paymentCondition';
import { ResultInfo } from '../types';
const fromInvoiceStatusChanged = (condition: InvoiceStatusChanged): ResultInfo => {
switch (condition.status) {
case InvoiceStatuses.paid:
case 'paid':
return {
iconName: 'SuccessIcon',
label: 'form.header.final.invoice.paid.label',
};
case InvoiceStatuses.cancelled:
case 'cancelled':
return {
iconName: 'WarningIcon',
label: 'form.header.final.invoice.cancelled.label',
};
case InvoiceStatuses.fulfilled:
case 'fulfilled':
return {
iconName: 'WarningIcon',
label: 'form.header.final.invoice.fulfilled.label',
};
case InvoiceStatuses.refunded:
return {
iconName: 'WarningIcon',
label: 'form.header.final.invoice.refunded.label',
};
}
};
const fromPaymentStatusChanged = (condition: PaymentStatusChanged): ResultInfo => {
switch (condition.status) {
case PaymentStatuses.captured:
case PaymentStatuses.processed:
case 'captured':
case 'processed':
return {
iconName: 'SuccessIcon',
label: 'form.header.final.success.label',
};
case PaymentStatuses.cancelled:
case 'cancelled':
return {
iconName: 'WarningIcon',
label: 'form.header.final.cancelled.label',
};
case PaymentStatuses.refunded:
case 'refunded':
return {
iconName: 'WarningIcon',
label: 'form.header.final.refunded.label',
};
case PaymentStatuses.pending:
case 'pending':
return {
iconName: 'WarningIcon',
label: 'form.header.final.pending.label',
description: 'form.header.final.pending.description',
hasActions: true,
};
case PaymentStatuses.failed:
case 'failed':
const error = condition.error;
return {
iconName: 'ErrorIcon',