@artaio/arta-browser 2.21.0 → 2.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -3
- package/dist/arta.d.ts +9 -0
- package/dist/arta.js +41 -0
- package/dist/bundle.js +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -0
- package/dist/infoConfig.d.ts +13 -0
- package/dist/infoConfig.js +10 -0
- package/dist/infoModal.d.ts +23 -0
- package/dist/infoModal.js +174 -0
- package/dist/messaging.d.ts +60 -0
- package/dist/messaging.js +86 -0
- package/dist/monthlyEstimate.d.ts +9 -0
- package/dist/monthlyEstimate.js +35 -0
- package/dist/pay.d.ts +37 -0
- package/dist/pay.js +336 -0
- package/dist/payConfig.d.ts +39 -0
- package/dist/payConfig.js +24 -0
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,17 @@ import './components/Modal/index.css';
|
|
|
3
3
|
import './components/TrackingDrawer/index.css';
|
|
4
4
|
import Estimate from './estimate';
|
|
5
5
|
import Tracking from './tracking';
|
|
6
|
+
import Pay from './pay';
|
|
7
|
+
import InfoModal from './infoModal';
|
|
6
8
|
export * from './arta';
|
|
7
9
|
export * from './estimate';
|
|
8
10
|
export * from './tracking';
|
|
9
|
-
export { Estimate, Tracking };
|
|
11
|
+
export { Estimate, Tracking, Pay, InfoModal };
|
|
12
|
+
export { monthlyEstimate } from './monthlyEstimate';
|
|
13
|
+
export type { InfoInput, InfoConfig, PartialInfoConfig, InfoFullConfig, } from './infoConfig';
|
|
14
|
+
export type { MonthlyEstimate, MonthlyEstimateInput } from './monthlyEstimate';
|
|
15
|
+
export type { PayInput, PayCallbacks, PayCompletion, PayCompletionStatus, PayCloseEvent, PayCloseReason, PayError, PayPosition, PayConfig, PartialPayConfig, PayFullConfig, } from './payConfig';
|
|
16
|
+
export type { PayInboundMessage, PayOutboundMessage } from './messaging';
|
|
10
17
|
export * from './MetadataTypes';
|
|
11
18
|
export type { EstimateConfig, EstimateBody, PartialEstimateConfig, } from './estimateConfig';
|
|
12
19
|
export type { TrackingConfig, PartialTrackingConfig } from './trackingConfig';
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var arta = require('./arta.js');
|
|
6
6
|
var estimate = require('./estimate.js');
|
|
7
7
|
var tracking = require('./tracking.js');
|
|
8
|
+
var pay = require('./pay.js');
|
|
9
|
+
var infoModal = require('./infoModal.js');
|
|
10
|
+
var monthlyEstimate = require('./monthlyEstimate.js');
|
|
8
11
|
|
|
9
12
|
const init = () => {
|
|
10
13
|
window.Arta = new arta.default();
|
|
@@ -14,4 +17,7 @@ var index = window.Arta;
|
|
|
14
17
|
|
|
15
18
|
exports.Estimate = estimate.default;
|
|
16
19
|
exports.Tracking = tracking.default;
|
|
20
|
+
exports.Pay = pay.default;
|
|
21
|
+
exports.InfoModal = infoModal.default;
|
|
22
|
+
exports.monthlyEstimate = monthlyEstimate.monthlyEstimate;
|
|
17
23
|
exports.default = index;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ArtaJsFullConfig } from './arta';
|
|
2
|
+
import { type PayPosition, DEFAULT_PAY_ORIGIN } from './payConfig';
|
|
3
|
+
export interface InfoInput {
|
|
4
|
+
totalPrice?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface InfoConfig {
|
|
7
|
+
position: PayPosition;
|
|
8
|
+
}
|
|
9
|
+
export type PartialInfoConfig = Partial<InfoConfig>;
|
|
10
|
+
export interface InfoFullConfig extends InfoConfig, ArtaJsFullConfig {
|
|
11
|
+
}
|
|
12
|
+
export declare const getFullInfoConfig: (artaConfig: ArtaJsFullConfig, infoConfig?: PartialInfoConfig) => InfoFullConfig;
|
|
13
|
+
export { DEFAULT_PAY_ORIGIN };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const defaultInfoConfig = {
|
|
4
|
+
position: 'center',
|
|
5
|
+
};
|
|
6
|
+
const getFullInfoConfig = (artaConfig, infoConfig = {}) => {
|
|
7
|
+
return Object.assign({}, defaultInfoConfig, artaConfig, infoConfig);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
exports.getFullInfoConfig = getFullInfoConfig;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InfoFullConfig, InfoInput } from './infoConfig';
|
|
2
|
+
export default class InfoModal {
|
|
3
|
+
private readonly input;
|
|
4
|
+
private readonly config;
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
private overlay;
|
|
7
|
+
private iframe;
|
|
8
|
+
private destroyed;
|
|
9
|
+
private lastFrameHeight;
|
|
10
|
+
private readonly payOrigin;
|
|
11
|
+
private readonly messageListener;
|
|
12
|
+
private readonly keydownListener;
|
|
13
|
+
constructor(input: InfoInput, config: InfoFullConfig);
|
|
14
|
+
open(): void;
|
|
15
|
+
close(): void;
|
|
16
|
+
destroy(): void;
|
|
17
|
+
private mount;
|
|
18
|
+
private centerHeightPx;
|
|
19
|
+
private frameSrc;
|
|
20
|
+
private reveal;
|
|
21
|
+
private handleMessage;
|
|
22
|
+
private handleResize;
|
|
23
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var messaging = require('./messaging.js');
|
|
6
|
+
var payConfig = require('./payConfig.js');
|
|
7
|
+
|
|
8
|
+
// Arta Pay CTA info modal: a login-free marketing modal opened on demand via
|
|
9
|
+
// `Arta.openArtaPayInfoModal()`. Unlike the checkout modal it has no purchase
|
|
10
|
+
// request, no client token and no handshake — it mounts on `open()`, shows
|
|
11
|
+
// immediately, and only listens for the iframe's resize/close reports. Every
|
|
12
|
+
// message is accepted only from the iframe this modal created.
|
|
13
|
+
const OVERLAY_ID = 'arta-pay-info-overlay';
|
|
14
|
+
const FRAME_MIN_HEIGHT_PX = 240;
|
|
15
|
+
const RESIZE_DEAD_BAND_PX = 2;
|
|
16
|
+
const overlayBaseCss = 'box-sizing:border-box;position:fixed;inset:0;display:flex;background:rgba(17,15,16,0.55);' +
|
|
17
|
+
'z-index:2147483000;visibility:hidden;pointer-events:none;';
|
|
18
|
+
const overlayPositionCss = {
|
|
19
|
+
center: 'align-items:center;justify-content:center;',
|
|
20
|
+
full_screen: 'align-items:stretch;justify-content:stretch;',
|
|
21
|
+
left: 'align-items:stretch;justify-content:flex-start;',
|
|
22
|
+
right: 'align-items:stretch;justify-content:flex-end;',
|
|
23
|
+
};
|
|
24
|
+
const frameBaseCss = 'box-sizing:content-box;border:0;background:#fff;transition:height 0.15s ease;';
|
|
25
|
+
// The centered card opens on the height the info view will actually report,
|
|
26
|
+
// so the frame never resizes once the iframe measures itself. The view has two
|
|
27
|
+
// heights: the plain CTA, and the taller one that carries a financing estimate
|
|
28
|
+
// (rendered only when a total price is supplied). Keep in step with
|
|
29
|
+
// #arta-pay-info .arta-pay-shell in the widget's CSS.
|
|
30
|
+
const CENTER_HEIGHT_PX = 412;
|
|
31
|
+
const CENTER_WITH_ESTIMATE_HEIGHT_PX = 753;
|
|
32
|
+
const framePositionCss = (centerHeightPx) => ({
|
|
33
|
+
center: `width:min(576px, calc(100vw - 32px));height:min(${centerHeightPx}px, calc(100vh - 32px));` +
|
|
34
|
+
'border:1px solid #d2d2d2;border-radius:8px;' +
|
|
35
|
+
'box-shadow:0 24px 64px rgba(17,15,16,0.35);',
|
|
36
|
+
full_screen: 'width:100vw;height:100vh;',
|
|
37
|
+
left: 'width:min(460px, 100vw);height:100vh;border-right:1px solid #d2d2d2;' +
|
|
38
|
+
'box-shadow:24px 0 64px rgba(17,15,16,0.35);',
|
|
39
|
+
right: 'width:min(460px, 100vw);height:100vh;border-left:1px solid #d2d2d2;' +
|
|
40
|
+
'box-shadow:-24px 0 64px rgba(17,15,16,0.35);',
|
|
41
|
+
});
|
|
42
|
+
class InfoModal {
|
|
43
|
+
input;
|
|
44
|
+
config;
|
|
45
|
+
isOpen = false;
|
|
46
|
+
overlay;
|
|
47
|
+
iframe;
|
|
48
|
+
destroyed = false;
|
|
49
|
+
lastFrameHeight;
|
|
50
|
+
payOrigin;
|
|
51
|
+
messageListener = (event) => this.handleMessage(event);
|
|
52
|
+
keydownListener = (event) => {
|
|
53
|
+
if (event.key === 'Escape' && this.isOpen) {
|
|
54
|
+
this.close();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
constructor(input, config) {
|
|
58
|
+
this.input = input;
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.payOrigin = config.payOrigin ?? payConfig.DEFAULT_PAY_ORIGIN;
|
|
61
|
+
}
|
|
62
|
+
open() {
|
|
63
|
+
if (this.destroyed) {
|
|
64
|
+
throw new Error('This Arta Pay info modal has been destroyed');
|
|
65
|
+
}
|
|
66
|
+
if (!this.overlay) {
|
|
67
|
+
this.mount();
|
|
68
|
+
}
|
|
69
|
+
this.reveal();
|
|
70
|
+
}
|
|
71
|
+
// Hides the overlay without tearing down the iframe, so a later `open()`
|
|
72
|
+
// shows it again instantly.
|
|
73
|
+
close() {
|
|
74
|
+
if (this.overlay) {
|
|
75
|
+
this.overlay.style.visibility = 'hidden';
|
|
76
|
+
this.overlay.style.pointerEvents = 'none';
|
|
77
|
+
}
|
|
78
|
+
document.removeEventListener('keydown', this.keydownListener);
|
|
79
|
+
this.isOpen = false;
|
|
80
|
+
}
|
|
81
|
+
destroy() {
|
|
82
|
+
this.close();
|
|
83
|
+
window.removeEventListener('message', this.messageListener);
|
|
84
|
+
this.overlay?.remove();
|
|
85
|
+
this.overlay = undefined;
|
|
86
|
+
this.iframe = undefined;
|
|
87
|
+
this.destroyed = true;
|
|
88
|
+
}
|
|
89
|
+
mount() {
|
|
90
|
+
if (this.destroyed || this.overlay || !document.body) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
window.addEventListener('message', this.messageListener);
|
|
94
|
+
const position = this.config.position;
|
|
95
|
+
this.overlay = document.createElement('div');
|
|
96
|
+
this.overlay.id = OVERLAY_ID;
|
|
97
|
+
this.overlay.style.cssText = overlayBaseCss + overlayPositionCss[position];
|
|
98
|
+
this.overlay.addEventListener('click', (event) => {
|
|
99
|
+
if (event.target === this.overlay) {
|
|
100
|
+
this.close();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this.iframe = document.createElement('iframe');
|
|
104
|
+
this.iframe.style.cssText =
|
|
105
|
+
frameBaseCss + framePositionCss(this.centerHeightPx())[position];
|
|
106
|
+
this.iframe.setAttribute('title', 'Arta Pay');
|
|
107
|
+
this.iframe.src = this.frameSrc();
|
|
108
|
+
this.overlay.appendChild(this.iframe);
|
|
109
|
+
document.body.appendChild(this.overlay);
|
|
110
|
+
}
|
|
111
|
+
// The estimate is what makes the view taller, and it is rendered exactly when
|
|
112
|
+
// a total price rides the URL, so the same condition picks the opening height.
|
|
113
|
+
centerHeightPx() {
|
|
114
|
+
return this.input.totalPrice === undefined
|
|
115
|
+
? CENTER_HEIGHT_PX
|
|
116
|
+
: CENTER_WITH_ESTIMATE_HEIGHT_PX;
|
|
117
|
+
}
|
|
118
|
+
// The public key is an identifier, not a credential; the optional total price
|
|
119
|
+
// only drives a financing estimate. Both ride the URL because the info modal
|
|
120
|
+
// has no postMessage handshake.
|
|
121
|
+
frameSrc() {
|
|
122
|
+
let src = this.payOrigin +
|
|
123
|
+
'/artapay-widget/info?pk=' +
|
|
124
|
+
encodeURIComponent(this.config.apiKey);
|
|
125
|
+
if (this.input.totalPrice !== undefined) {
|
|
126
|
+
src +=
|
|
127
|
+
'&total_price=' + encodeURIComponent(String(this.input.totalPrice));
|
|
128
|
+
}
|
|
129
|
+
return src;
|
|
130
|
+
}
|
|
131
|
+
reveal() {
|
|
132
|
+
if (!this.overlay) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.overlay.style.visibility = 'visible';
|
|
136
|
+
this.overlay.style.pointerEvents = 'auto';
|
|
137
|
+
document.addEventListener('keydown', this.keydownListener);
|
|
138
|
+
this.isOpen = true;
|
|
139
|
+
}
|
|
140
|
+
handleMessage(event) {
|
|
141
|
+
if (event.origin !== this.payOrigin ||
|
|
142
|
+
!this.iframe ||
|
|
143
|
+
event.source !== this.iframe.contentWindow ||
|
|
144
|
+
!messaging.isPayInboundMessage(event.data)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const data = event.data;
|
|
148
|
+
if (data.type === 'arta-pay:resize') {
|
|
149
|
+
this.handleResize(data);
|
|
150
|
+
}
|
|
151
|
+
else if (data.type === 'arta-pay:close') {
|
|
152
|
+
this.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
handleResize(data) {
|
|
156
|
+
// Side panels and full screen keep the whole viewport height.
|
|
157
|
+
if (this.config.position !== 'center' || !this.iframe) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const height = messaging.parseResizeHeight(data);
|
|
161
|
+
if (height === undefined) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const px = Math.max(FRAME_MIN_HEIGHT_PX, Math.round(height));
|
|
165
|
+
if (this.lastFrameHeight !== undefined &&
|
|
166
|
+
Math.abs(px - this.lastFrameHeight) < RESIZE_DEAD_BAND_PX) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.lastFrameHeight = px;
|
|
170
|
+
this.iframe.style.height = `min(${px}px, calc(100vh - 32px))`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
exports.default = InfoModal;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type PayCloseReason, type PayCompletionStatus, type PayError, type PayPosition } from './payConfig';
|
|
2
|
+
export interface PayHandshakeMessage {
|
|
3
|
+
type: 'arta-pay:handshake';
|
|
4
|
+
}
|
|
5
|
+
export interface PayReadyMessage {
|
|
6
|
+
type: 'arta-pay:ready';
|
|
7
|
+
}
|
|
8
|
+
export interface PayResizeMessage {
|
|
9
|
+
type: 'arta-pay:resize';
|
|
10
|
+
height: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PayCompleteMessage {
|
|
13
|
+
type: 'arta-pay:complete';
|
|
14
|
+
purchaseRequestId: string;
|
|
15
|
+
purchaseId?: string;
|
|
16
|
+
status: PayCompletionStatus;
|
|
17
|
+
}
|
|
18
|
+
export interface PayCloseMessage {
|
|
19
|
+
type: 'arta-pay:close';
|
|
20
|
+
purchaseRequestId: string;
|
|
21
|
+
reason: PayCloseReason;
|
|
22
|
+
}
|
|
23
|
+
export interface PayErrorMessage {
|
|
24
|
+
type: 'arta-pay:error';
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
recoverable: boolean;
|
|
28
|
+
requestId?: string;
|
|
29
|
+
}
|
|
30
|
+
export type PayInboundMessage = PayHandshakeMessage | PayReadyMessage | PayResizeMessage | PayCompleteMessage | PayCloseMessage | PayErrorMessage;
|
|
31
|
+
export type PayInboundMessageType = PayInboundMessage['type'];
|
|
32
|
+
export interface PayInitMessage {
|
|
33
|
+
type: 'arta-pay:init';
|
|
34
|
+
purchaseRequestId: string;
|
|
35
|
+
clientToken: string;
|
|
36
|
+
position: PayPosition;
|
|
37
|
+
}
|
|
38
|
+
export interface PayOpenMessage {
|
|
39
|
+
type: 'arta-pay:open';
|
|
40
|
+
}
|
|
41
|
+
export interface PayCloseRequestMessage {
|
|
42
|
+
type: 'arta-pay:close-request';
|
|
43
|
+
}
|
|
44
|
+
export type PayOutboundMessage = PayInitMessage | PayOpenMessage | PayCloseRequestMessage;
|
|
45
|
+
export type PayOutboundMessageType = PayOutboundMessage['type'];
|
|
46
|
+
export interface RawPayMessage {
|
|
47
|
+
type: PayInboundMessageType;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
export declare const isPayInboundMessage: (data: unknown) => data is RawPayMessage;
|
|
51
|
+
export declare const parseResizeHeight: (data: unknown) => number | undefined;
|
|
52
|
+
export interface ParsedCompletion {
|
|
53
|
+
purchaseRequestId?: string;
|
|
54
|
+
purchaseId?: string;
|
|
55
|
+
status: PayCompletionStatus;
|
|
56
|
+
}
|
|
57
|
+
export declare const parseCompletion: (data: unknown) => ParsedCompletion | undefined;
|
|
58
|
+
export declare const parseError: (data: unknown) => PayError;
|
|
59
|
+
export declare const parseCloseReason: (data: unknown) => PayCloseReason;
|
|
60
|
+
export declare const postPayMessage: (target: Window, targetOrigin: string, message: PayOutboundMessage) => void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var payConfig = require('./payConfig.js');
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Parsing. Anything can arrive over postMessage, so the SDK narrows the raw
|
|
7
|
+
// data on the `type` discriminator first and validates each payload
|
|
8
|
+
// separately: the parsers return `undefined` (or a safe default) instead of
|
|
9
|
+
// trusting the shape.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const INBOUND_TYPES = [
|
|
12
|
+
'arta-pay:handshake',
|
|
13
|
+
'arta-pay:ready',
|
|
14
|
+
'arta-pay:resize',
|
|
15
|
+
'arta-pay:complete',
|
|
16
|
+
'arta-pay:close',
|
|
17
|
+
'arta-pay:error',
|
|
18
|
+
];
|
|
19
|
+
const isRecord = (data) => typeof data === 'object' && data !== null;
|
|
20
|
+
const optionalString = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
21
|
+
const isPayInboundMessage = (data) => {
|
|
22
|
+
return (isRecord(data) &&
|
|
23
|
+
typeof data.type === 'string' &&
|
|
24
|
+
INBOUND_TYPES.indexOf(data.type) !== -1);
|
|
25
|
+
};
|
|
26
|
+
const parseResizeHeight = (data) => {
|
|
27
|
+
if (!isRecord(data)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const height = data.height;
|
|
31
|
+
return typeof height === 'number' && isFinite(height) && height > 0
|
|
32
|
+
? height
|
|
33
|
+
: undefined;
|
|
34
|
+
};
|
|
35
|
+
// `undefined` for anything that is not a v2 outcome (including the v1
|
|
36
|
+
// `authenticated` signal).
|
|
37
|
+
const parseCompletion = (data) => {
|
|
38
|
+
if (!isRecord(data)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const status = data.status;
|
|
42
|
+
if (typeof status !== 'string' ||
|
|
43
|
+
payConfig.PAY_COMPLETION_STATUSES.indexOf(status) === -1) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (data.purchaseId !== undefined && typeof data.purchaseId !== 'string') {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
purchaseRequestId: optionalString(data.purchaseRequestId),
|
|
51
|
+
purchaseId: optionalString(data.purchaseId),
|
|
52
|
+
status: status,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
// Never returns `undefined`: an error message with a broken payload is still
|
|
56
|
+
// an error worth reporting, so malformed fields fall back to safe defaults
|
|
57
|
+
// (`unknown`, non-recoverable).
|
|
58
|
+
const parseError = (data) => {
|
|
59
|
+
const record = isRecord(data) ? data : {};
|
|
60
|
+
return {
|
|
61
|
+
code: optionalString(record.code) ?? 'unknown',
|
|
62
|
+
message: optionalString(record.message) ?? 'Unknown error',
|
|
63
|
+
recoverable: record.recoverable === true,
|
|
64
|
+
requestId: optionalString(record.requestId),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const parseCloseReason = (data) => {
|
|
68
|
+
if (!isRecord(data)) {
|
|
69
|
+
return 'customer';
|
|
70
|
+
}
|
|
71
|
+
const reason = data.reason;
|
|
72
|
+
return typeof reason === 'string' &&
|
|
73
|
+
payConfig.PAY_CLOSE_REASONS.indexOf(reason) !== -1
|
|
74
|
+
? reason
|
|
75
|
+
: 'customer';
|
|
76
|
+
};
|
|
77
|
+
const postPayMessage = (target, targetOrigin, message) => {
|
|
78
|
+
target.postMessage(message, targetOrigin);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
exports.isPayInboundMessage = isPayInboundMessage;
|
|
82
|
+
exports.parseCloseReason = parseCloseReason;
|
|
83
|
+
exports.parseCompletion = parseCompletion;
|
|
84
|
+
exports.parseError = parseError;
|
|
85
|
+
exports.parseResizeHeight = parseResizeHeight;
|
|
86
|
+
exports.postPayMessage = postPayMessage;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MonthlyEstimateInput {
|
|
2
|
+
totalPrice: number;
|
|
3
|
+
currency?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface MonthlyEstimate {
|
|
6
|
+
amountPerMonth: number;
|
|
7
|
+
amountPerMonthCurrency: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const monthlyEstimate: (input: MonthlyEstimateInput) => Promise<MonthlyEstimate>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MINIMUM_DEPOSIT_RATIO = 0.1;
|
|
4
|
+
const FLAT_FEE_RATE = 0.125;
|
|
5
|
+
const INSTALLMENT_COUNT = 9;
|
|
6
|
+
// A lightweight "from $X/mo" estimate for placements and CTAs, mirroring the
|
|
7
|
+
// checkout slider's math (Buyers.ArtaPay.DepositMath + FinancingPreview): the
|
|
8
|
+
// minimum 10% deposit, a flat 12.5% finance charge on the financed amount, split
|
|
9
|
+
// over 9 monthly payments, and rounded up to the next whole dollar as shown in
|
|
10
|
+
// the interface. Computed in integer cents to avoid floating-point drift. The
|
|
11
|
+
// checkout shows the buyer's exact, quoted schedule.
|
|
12
|
+
//
|
|
13
|
+
// Async on purpose: the rates above are a copy of the server's model, and this
|
|
14
|
+
// is meant to move behind an API call so the two cannot drift. Returning a
|
|
15
|
+
// promise now means that move is an implementation change rather than a
|
|
16
|
+
// breaking one for callers.
|
|
17
|
+
const monthlyEstimate = async (input) => {
|
|
18
|
+
const currency = input.currency || 'USD';
|
|
19
|
+
const totalCents = Math.round(input.totalPrice * 100);
|
|
20
|
+
if (!isFinite(totalCents) || totalCents <= 0) {
|
|
21
|
+
return { amountPerMonth: 0, amountPerMonthCurrency: currency };
|
|
22
|
+
}
|
|
23
|
+
const minDepositCents = Math.ceil(totalCents * MINIMUM_DEPOSIT_RATIO);
|
|
24
|
+
const financedCents = totalCents - minDepositCents;
|
|
25
|
+
const financeChargeCents = Math.round(financedCents * FLAT_FEE_RATE);
|
|
26
|
+
const principalCents = Math.round(financedCents / INSTALLMENT_COUNT);
|
|
27
|
+
const feeCents = Math.round(financeChargeCents / INSTALLMENT_COUNT);
|
|
28
|
+
const monthlyCents = principalCents + feeCents;
|
|
29
|
+
return {
|
|
30
|
+
amountPerMonth: Math.ceil(monthlyCents / 100),
|
|
31
|
+
amountPerMonthCurrency: currency,
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.monthlyEstimate = monthlyEstimate;
|
package/dist/pay.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PayCallbacks, type PayFullConfig, type PayInput } from './payConfig';
|
|
2
|
+
export default class Pay {
|
|
3
|
+
private readonly input;
|
|
4
|
+
private readonly callbacks;
|
|
5
|
+
private readonly config;
|
|
6
|
+
isReady: boolean;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
private overlay;
|
|
9
|
+
private iframe;
|
|
10
|
+
private handshakeTimer;
|
|
11
|
+
private destroyed;
|
|
12
|
+
private completed;
|
|
13
|
+
private openWhenMounted;
|
|
14
|
+
private lastFrameHeight;
|
|
15
|
+
private readonly payOrigin;
|
|
16
|
+
private readonly messageListener;
|
|
17
|
+
private readonly keydownListener;
|
|
18
|
+
private readonly domReadyListener;
|
|
19
|
+
constructor(input: PayInput, callbacks: PayCallbacks, config: PayFullConfig);
|
|
20
|
+
open(): void;
|
|
21
|
+
close(): void;
|
|
22
|
+
destroy(): void;
|
|
23
|
+
private mount;
|
|
24
|
+
private reveal;
|
|
25
|
+
private requestClose;
|
|
26
|
+
private handleMessage;
|
|
27
|
+
private handleHandshake;
|
|
28
|
+
private handleReady;
|
|
29
|
+
private handleResize;
|
|
30
|
+
private handleComplete;
|
|
31
|
+
private handleError;
|
|
32
|
+
private handleHandshakeTimeout;
|
|
33
|
+
private abort;
|
|
34
|
+
private postToFrame;
|
|
35
|
+
private startHandshakeTimer;
|
|
36
|
+
private clearHandshakeTimer;
|
|
37
|
+
}
|