@blocklet/payment-react 1.18.0 → 1.18.2

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.
Files changed (54) hide show
  1. package/es/checkout/donate.js +55 -10
  2. package/es/checkout/form.d.ts +2 -1
  3. package/es/checkout/form.js +32 -45
  4. package/es/components/payment-beneficiaries.d.ts +24 -0
  5. package/es/components/payment-beneficiaries.js +70 -0
  6. package/es/index.d.ts +2 -1
  7. package/es/index.js +3 -1
  8. package/es/locales/en.js +13 -1
  9. package/es/locales/zh.js +13 -1
  10. package/es/payment/donation-form.d.ts +24 -0
  11. package/es/payment/donation-form.js +604 -0
  12. package/es/payment/error.d.ts +1 -1
  13. package/es/payment/error.js +11 -1
  14. package/es/payment/form/index.d.ts +9 -3
  15. package/es/payment/form/index.js +60 -12
  16. package/es/payment/product-donation.js +101 -56
  17. package/es/payment/skeleton/donation.d.ts +1 -0
  18. package/es/payment/skeleton/donation.js +30 -0
  19. package/es/theme/index.js +13 -0
  20. package/es/types/index.d.ts +2 -0
  21. package/lib/checkout/donate.js +85 -10
  22. package/lib/checkout/form.d.ts +2 -1
  23. package/lib/checkout/form.js +39 -49
  24. package/lib/components/payment-beneficiaries.d.ts +24 -0
  25. package/lib/components/payment-beneficiaries.js +113 -0
  26. package/lib/index.d.ts +2 -1
  27. package/lib/index.js +8 -0
  28. package/lib/locales/en.js +13 -1
  29. package/lib/locales/zh.js +13 -1
  30. package/lib/payment/donation-form.d.ts +24 -0
  31. package/lib/payment/donation-form.js +645 -0
  32. package/lib/payment/error.d.ts +1 -1
  33. package/lib/payment/error.js +2 -2
  34. package/lib/payment/form/index.d.ts +9 -3
  35. package/lib/payment/form/index.js +60 -8
  36. package/lib/payment/product-donation.js +143 -72
  37. package/lib/payment/skeleton/donation.d.ts +1 -0
  38. package/lib/payment/skeleton/donation.js +66 -0
  39. package/lib/theme/index.js +13 -0
  40. package/lib/types/index.d.ts +2 -0
  41. package/package.json +3 -3
  42. package/src/checkout/donate.tsx +64 -11
  43. package/src/checkout/form.tsx +17 -31
  44. package/src/components/payment-beneficiaries.tsx +97 -0
  45. package/src/index.ts +2 -0
  46. package/src/locales/en.tsx +12 -0
  47. package/src/locales/zh.tsx +12 -0
  48. package/src/payment/donation-form.tsx +647 -0
  49. package/src/payment/error.tsx +13 -4
  50. package/src/payment/form/index.tsx +66 -11
  51. package/src/payment/product-donation.tsx +94 -39
  52. package/src/payment/skeleton/donation.tsx +35 -0
  53. package/src/theme/index.tsx +13 -0
  54. package/src/types/index.ts +2 -0
@@ -0,0 +1,647 @@
1
+ /* eslint-disable @typescript-eslint/indent */
2
+ /* eslint-disable no-nested-ternary */
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import Toast from '@arcblock/ux/lib/Toast';
5
+ // eslint-disable-next-line import/no-extraneous-dependencies
6
+ import Header from '@blocklet/ui-react/lib/Header';
7
+ import type { TCustomer, TLineItemExpanded, TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
8
+ import { ArrowBackOutlined, ArrowForwardOutlined, HelpOutlineOutlined } from '@mui/icons-material';
9
+ import { Box, Button, Divider, Stack, Typography, type BoxProps } from '@mui/material';
10
+ import { styled } from '@mui/system';
11
+ import { fromTokenToUnit } from '@ocap/util';
12
+ import { useMount, useRequest, useSetState } from 'ahooks';
13
+ import { useEffect, useState } from 'react';
14
+ import { FormProvider, useForm, useWatch } from 'react-hook-form';
15
+ import type { LiteralUnion } from 'type-fest';
16
+
17
+ import { joinURL } from 'ufo';
18
+ import { usePaymentContext } from '../contexts/payment';
19
+ import api from '../libs/api';
20
+ import {
21
+ findCurrency,
22
+ formatError,
23
+ getPrefix,
24
+ getQueryParams,
25
+ getStatementDescriptor,
26
+ isMobileSafari,
27
+ isValidCountry,
28
+ } from '../libs/util';
29
+ import type { CheckoutCallbacks, CheckoutContext, CheckoutFormData } from '../types';
30
+ import PaymentError from './error';
31
+ import PaymentForm from './form';
32
+ // import PaymentHeader from './header';
33
+ import PaymentSuccess from './success';
34
+ import { useMobile } from '../hooks/mobile';
35
+ import ProductDonation from './product-donation';
36
+ import ConfirmDialog from '../components/confirm';
37
+ import PaymentBeneficiaries, { TBeneficiary } from '../components/payment-beneficiaries';
38
+ import DonationSkeleton from './skeleton/donation';
39
+
40
+ const getBenefits = async (id: string, url?: string) => {
41
+ const { data } = await api.get(`/api/payment-links/${id}/benefits?${url ? `url=${url}` : ''}`);
42
+ return data;
43
+ };
44
+ // eslint-disable-next-line react/no-unused-prop-types
45
+ type Props = CheckoutContext &
46
+ CheckoutCallbacks & {
47
+ completed?: boolean;
48
+ error?: any;
49
+ showCheckoutSummary?: boolean;
50
+ formRender?: Record<string, any>;
51
+ id: string;
52
+ };
53
+
54
+ PaymentInner.defaultProps = {
55
+ completed: false,
56
+ showCheckoutSummary: true,
57
+ formRender: {},
58
+ };
59
+
60
+ function PaymentInner({
61
+ checkoutSession,
62
+ paymentMethods,
63
+ paymentLink,
64
+ paymentIntent,
65
+ customer,
66
+ completed,
67
+ mode,
68
+ onPaid,
69
+ onError,
70
+ onChange,
71
+ action,
72
+ formRender,
73
+ benefits,
74
+ }: MainProps) {
75
+ const { t } = useLocaleContext();
76
+ const { settings, session } = usePaymentContext();
77
+ const [state, setState] = useSetState({
78
+ checkoutSession,
79
+ submitting: false,
80
+ paying: false,
81
+ paid: false,
82
+ paymentIntent,
83
+ stripeContext: undefined,
84
+ customer,
85
+ customerLimited: false,
86
+ stripePaying: false,
87
+ });
88
+ const query = getQueryParams(window.location.href);
89
+ const defaultCurrencyId =
90
+ query.currencyId || state.checkoutSession.currency_id || state.checkoutSession.line_items[0]?.price.currency_id;
91
+ const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
92
+
93
+ const items = state.checkoutSession.line_items;
94
+ const donationSettings = paymentLink?.donation_settings;
95
+
96
+ const [benefitsState, setBenefitsState] = useSetState({
97
+ open: false,
98
+ amount: '0',
99
+ });
100
+ const methods = useForm<CheckoutFormData>({
101
+ defaultValues: {
102
+ customer_name: customer?.name || session?.user?.fullName || '',
103
+ customer_email: customer?.email || session?.user?.email || '',
104
+ customer_phone: customer?.phone || session?.user?.phone || '',
105
+ payment_method: defaultMethodId,
106
+ payment_currency: defaultCurrencyId,
107
+ billing_address: Object.assign(
108
+ {
109
+ country: '',
110
+ state: '',
111
+ city: '',
112
+ line1: '',
113
+ line2: '',
114
+ postal_code: '',
115
+ },
116
+ customer?.address || {},
117
+ { country: isValidCountry(customer?.address?.country || '') ? customer?.address?.country : 'us' }
118
+ ),
119
+ },
120
+ });
121
+
122
+ useEffect(() => {
123
+ if (!isMobileSafari()) {
124
+ return () => {};
125
+ }
126
+ let scrollTop = 0;
127
+ const focusinHandler = () => {
128
+ scrollTop = window.scrollY;
129
+ };
130
+ const focusoutHandler = () => {
131
+ window.scrollTo(0, scrollTop);
132
+ };
133
+ document.body.addEventListener('focusin', focusinHandler);
134
+ document.body.addEventListener('focusout', focusoutHandler);
135
+ return () => {
136
+ document.body.removeEventListener('focusin', focusinHandler);
137
+ document.body.removeEventListener('focusout', focusoutHandler);
138
+ };
139
+ }, []);
140
+
141
+ useEffect(() => {
142
+ if (!methods || query.currencyId) {
143
+ return;
144
+ }
145
+ if (state.checkoutSession.currency_id !== defaultCurrencyId) {
146
+ methods.setValue('payment_currency', state.checkoutSession.currency_id);
147
+ }
148
+ // eslint-disable-next-line react-hooks/exhaustive-deps
149
+ }, [state.checkoutSession, defaultCurrencyId, query.currencyId]);
150
+
151
+ const currencyId = useWatch({ control: methods.control, name: 'payment_currency', defaultValue: defaultCurrencyId });
152
+ const currency =
153
+ (findCurrency(paymentMethods as TPaymentMethodExpanded[], currencyId as string) as TPaymentCurrency) ||
154
+ settings.baseCurrency;
155
+
156
+ useEffect(() => {
157
+ if (onChange) {
158
+ onChange(methods.getValues());
159
+ }
160
+ }, [currencyId]); // eslint-disable-line
161
+
162
+ const onChangeAmount = async ({ priceId, amount }: { priceId: string; amount: string }) => {
163
+ const amountStr = fromTokenToUnit(amount, currency.decimal).toString();
164
+ setBenefitsState({ amount: amountStr });
165
+ try {
166
+ const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/amount`, {
167
+ priceId,
168
+ amount: amountStr,
169
+ });
170
+ setState({ checkoutSession: data });
171
+ } catch (err) {
172
+ console.error(err);
173
+ Toast.error(formatError(err));
174
+ }
175
+ };
176
+
177
+ const handlePaid = (result: any) => {
178
+ setState({ checkoutSession: result.checkoutSession });
179
+ onPaid(result);
180
+ };
181
+
182
+ const renderBenefits = () => {
183
+ if (!benefits) {
184
+ return null;
185
+ }
186
+ if (benefits.length === 1) {
187
+ return t('payment.checkout.donation.benefits.one', {
188
+ name: benefits[0].name,
189
+ });
190
+ }
191
+ return t('payment.checkout.donation.benefits.multiple', {
192
+ count: benefits.length,
193
+ });
194
+ };
195
+
196
+ return (
197
+ <FormProvider {...methods}>
198
+ <Stack className="cko-container" sx={{ gap: { sm: mode === 'standalone' ? 0 : mode === 'inline' ? 4 : 8 } }}>
199
+ {completed ? (
200
+ <Stack>
201
+ <PaymentSuccess
202
+ mode={mode}
203
+ payee={getStatementDescriptor(state.checkoutSession.line_items)}
204
+ action={state.checkoutSession.mode}
205
+ invoiceId={state.checkoutSession.invoice_id}
206
+ subscriptionId={state.checkoutSession.subscription_id}
207
+ message={
208
+ paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
209
+ t('payment.checkout.completed.donate')
210
+ }
211
+ />
212
+ <Divider
213
+ sx={{
214
+ mt: {
215
+ xs: '16px',
216
+ md: '-24px',
217
+ },
218
+ mb: {
219
+ xs: '16px',
220
+ md: '16px',
221
+ },
222
+ }}
223
+ />
224
+ <Stack direction="row" justifyContent="flex-end" alignItems="center" flexWrap="wrap" gap={1}>
225
+ <Button
226
+ variant="outlined"
227
+ size="large"
228
+ onClick={formRender?.onCancel}
229
+ sx={{ width: 'fit-content', minWidth: 120 }}>
230
+ {t('common.close')}
231
+ </Button>
232
+ </Stack>
233
+ </Stack>
234
+ ) : (
235
+ <>
236
+ {benefitsState.open && (
237
+ <PaymentBeneficiaries data={benefits} currency={currency} totalAmount={benefitsState.amount} />
238
+ )}
239
+
240
+ <Stack sx={{ display: benefitsState.open ? 'none' : 'block' }}>
241
+ <Typography
242
+ title={t('payment.checkout.orderSummary')}
243
+ sx={{
244
+ color: 'text.primary',
245
+ fontSize: '18px',
246
+ fontWeight: '500',
247
+ lineHeight: '24px',
248
+ mb: 2,
249
+ }}>
250
+ {t('payment.checkout.donation.tipAmount')}
251
+ </Typography>
252
+ {items.map((x: TLineItemExpanded) => (
253
+ <ProductDonation
254
+ key={`${x.price_id}-${currency.id}`}
255
+ item={x}
256
+ // @ts-ignore
257
+ settings={donationSettings}
258
+ onChange={onChangeAmount}
259
+ currency={currency}
260
+ />
261
+ ))}
262
+ </Stack>
263
+
264
+ <Divider
265
+ sx={{
266
+ mt: {
267
+ xs: '32px',
268
+ md: '0',
269
+ },
270
+ mb: {
271
+ xs: '16px',
272
+ md: '-8px',
273
+ },
274
+ }}
275
+ />
276
+ <Stack direction="row" justifyContent="space-between" alignItems="center" flexWrap="wrap" gap={1}>
277
+ {benefits &&
278
+ benefits.length > 0 &&
279
+ (benefitsState.open ? (
280
+ <Typography
281
+ onClick={() => setBenefitsState({ open: false })}
282
+ sx={{
283
+ cursor: 'pointer',
284
+ color: 'text.secondary',
285
+ '&:hover': {
286
+ color: 'text.primary',
287
+ },
288
+ display: 'flex',
289
+ alignItems: 'center',
290
+ }}>
291
+ <ArrowBackOutlined className="benefits-arrow" sx={{ fontSize: '18px', mr: 0.5 }} />
292
+ {t('common.back')}
293
+ </Typography>
294
+ ) : (
295
+ <Box
296
+ display="flex"
297
+ gap={0.5}
298
+ alignItems="center"
299
+ onClick={() => setBenefitsState({ open: true })}
300
+ sx={{
301
+ color: 'text.secondary',
302
+ cursor: 'pointer',
303
+ '& .benefits-arrow': {
304
+ opacity: 0,
305
+ transform: 'translateX(-4px)',
306
+ transition: 'all 0.2s',
307
+ },
308
+ '&:hover': {
309
+ color: 'text.primary',
310
+ '& .benefits-arrow': {
311
+ opacity: 1,
312
+ transform: 'translateX(0)',
313
+ },
314
+ },
315
+ }}>
316
+ <HelpOutlineOutlined sx={{ fontSize: '18px' }} />
317
+ <Typography>{renderBenefits()}</Typography>
318
+ <ArrowForwardOutlined className="benefits-arrow" sx={{ fontSize: '18px' }} />
319
+ </Box>
320
+ ))}
321
+ {benefitsState.open ? null : (
322
+ <Box
323
+ display="flex"
324
+ gap={2}
325
+ sx={{
326
+ flex: {
327
+ xs: 1,
328
+ md: 'auto',
329
+ },
330
+ justifyContent: 'flex-end',
331
+ whiteSpace: 'nowrap',
332
+ }}>
333
+ {formRender?.cancel}
334
+ <PaymentForm
335
+ currencyId={currencyId}
336
+ checkoutSession={state.checkoutSession}
337
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
338
+ paymentIntent={paymentIntent}
339
+ paymentLink={paymentLink}
340
+ customer={customer}
341
+ onPaid={handlePaid}
342
+ onError={onError}
343
+ mode={mode}
344
+ action={action}
345
+ onlyShowBtn
346
+ />
347
+ </Box>
348
+ )}
349
+ </Stack>
350
+ </>
351
+ )}
352
+ {state.customerLimited && (
353
+ <ConfirmDialog
354
+ onConfirm={() =>
355
+ window.open(
356
+ joinURL(getPrefix(), `/customer/invoice/past-due?referer=${encodeURIComponent(window.location.href)}`),
357
+ '_self'
358
+ )
359
+ }
360
+ onCancel={() => setState({ customerLimited: false })}
361
+ confirm={t('payment.customer.pastDue.alert.confirm')}
362
+ title={t('payment.customer.pastDue.alert.title')}
363
+ message={t('payment.customer.pastDue.alert.description')}
364
+ color="primary"
365
+ />
366
+ )}
367
+ </Stack>
368
+ </FormProvider>
369
+ );
370
+ }
371
+
372
+ DonationForm.defaultProps = {
373
+ completed: false,
374
+ error: null,
375
+ showCheckoutSummary: true,
376
+ formRender: {},
377
+ };
378
+
379
+ export default function DonationForm({
380
+ checkoutSession,
381
+ paymentMethods,
382
+ paymentIntent,
383
+ paymentLink,
384
+ customer,
385
+ completed,
386
+ error,
387
+ mode,
388
+ onPaid,
389
+ onError,
390
+ onChange,
391
+ goBack,
392
+ action,
393
+ showCheckoutSummary = true,
394
+ formRender,
395
+ id,
396
+ }: Props) {
397
+ const { t } = useLocaleContext();
398
+ const { refresh, livemode, setLivemode } = usePaymentContext();
399
+ const { isMobile } = useMobile();
400
+ const [delay, setDelay] = useState(false);
401
+ const isMobileSafariEnv = isMobileSafari();
402
+ const paymentLinkId = id.startsWith('plink_') ? id : undefined;
403
+
404
+ const { data: benefits, loading: benefitLoading } = useRequest(
405
+ () => {
406
+ if (paymentLinkId) {
407
+ return getBenefits(paymentLinkId);
408
+ }
409
+ return Promise.resolve([]);
410
+ },
411
+ {
412
+ refreshDeps: [paymentLinkId || paymentLink?.id],
413
+ ready: !!paymentLinkId || !!paymentLink?.id,
414
+ }
415
+ );
416
+
417
+ useMount(() => {
418
+ setTimeout(() => {
419
+ // 骨架屏 delay
420
+ setDelay(true);
421
+ }, 500);
422
+ // eslint-di
423
+ });
424
+
425
+ useEffect(() => {
426
+ if (checkoutSession) {
427
+ if (livemode !== checkoutSession.livemode) {
428
+ setLivemode(checkoutSession.livemode);
429
+ }
430
+ }
431
+ }, [checkoutSession, livemode, setLivemode, refresh]);
432
+
433
+ const renderContent = () => {
434
+ const footer = (
435
+ <>
436
+ <Divider sx={{ mt: { xs: '16px', md: '-24px' }, mb: { xs: '16px', md: '-16px' } }} />
437
+ <Stack direction="row" justifyContent="flex-end" alignItems="center" flexWrap="wrap" gap={1}>
438
+ <Button variant="outlined" size="large" onClick={formRender?.onCancel}>
439
+ {t('common.cancel')}
440
+ </Button>
441
+ </Stack>
442
+ </>
443
+ );
444
+ if (error) {
445
+ return (
446
+ <>
447
+ <PaymentError mode={mode} title="Oops" description={formatError(error)} button={null} />
448
+ {footer}
449
+ </>
450
+ );
451
+ }
452
+ if (!checkoutSession || !delay || !paymentLink || benefitLoading) {
453
+ return <DonationSkeleton />;
454
+ }
455
+ if (checkoutSession?.expires_at <= Math.round(Date.now() / 1000)) {
456
+ return (
457
+ <>
458
+ <PaymentError
459
+ mode={mode}
460
+ title={t('payment.checkout.expired.title')}
461
+ description={t('payment.checkout.expired.description')}
462
+ button={null}
463
+ />
464
+ {footer}
465
+ </>
466
+ );
467
+ }
468
+ if (!checkoutSession.line_items.length) {
469
+ return (
470
+ <>
471
+ <PaymentError
472
+ mode={mode}
473
+ title={t('payment.checkout.emptyItems.title')}
474
+ description={t('payment.checkout.emptyItems.description')}
475
+ button={null}
476
+ />
477
+ {footer}
478
+ </>
479
+ );
480
+ }
481
+ return (
482
+ <PaymentInner
483
+ formRender={formRender}
484
+ checkoutSession={checkoutSession}
485
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
486
+ paymentLink={paymentLink}
487
+ paymentIntent={paymentIntent}
488
+ completed={completed || checkoutSession.status === 'complete'}
489
+ customer={customer as TCustomer}
490
+ onPaid={onPaid}
491
+ onError={onError}
492
+ onChange={onChange}
493
+ goBack={goBack}
494
+ mode={mode}
495
+ action={action}
496
+ showCheckoutSummary={showCheckoutSummary}
497
+ benefits={benefits}
498
+ />
499
+ );
500
+ };
501
+
502
+ return (
503
+ <Stack
504
+ display="flex"
505
+ flexDirection="column"
506
+ sx={{
507
+ height: mode === 'standalone' ? '100vh' : 'auto',
508
+ overflow: isMobileSafariEnv ? 'visible' : 'hidden',
509
+ }}>
510
+ {mode === 'standalone' ? (
511
+ <Header
512
+ meta={undefined}
513
+ addons={undefined}
514
+ sessionManagerProps={undefined}
515
+ homeLink={undefined}
516
+ theme={undefined}
517
+ hideNavMenu={undefined}
518
+ maxWidth={false}
519
+ sx={{ borderBottom: '1px solid var(--stroke-border-base, #EFF1F5)' }}
520
+ />
521
+ ) : null}
522
+ <Root
523
+ mode={mode}
524
+ sx={{
525
+ flex: 1,
526
+ overflow: {
527
+ xs: isMobileSafariEnv ? 'visible' : 'auto',
528
+ md: 'hidden',
529
+ },
530
+ ...(isMobile && mode === 'standalone'
531
+ ? {
532
+ '.cko-payment-submit-btn': {
533
+ position: 'fixed',
534
+ bottom: 20,
535
+ left: 0,
536
+ right: 0,
537
+ zIndex: 999,
538
+ background: '#fff',
539
+ padding: '10px',
540
+ textAlign: 'center',
541
+ button: {
542
+ color: '#fff',
543
+ maxWidth: 542,
544
+ },
545
+ },
546
+ }
547
+ : {}),
548
+ }}>
549
+ {goBack && (
550
+ <ArrowBackOutlined
551
+ sx={{ mr: 0.5, color: 'text.secondary', alignSelf: 'flex-start', margin: '16px 0', cursor: 'pointer' }}
552
+ onClick={goBack}
553
+ fontSize="medium"
554
+ />
555
+ )}
556
+ <Stack className="cko-container" sx={{ gap: { sm: mode === 'standalone' ? 0 : mode === 'inline' ? 4 : 8 } }}>
557
+ {renderContent()}
558
+ </Stack>
559
+ </Root>
560
+ </Stack>
561
+ );
562
+ }
563
+
564
+ type MainProps = CheckoutContext &
565
+ CheckoutCallbacks & {
566
+ completed?: boolean;
567
+ showCheckoutSummary?: boolean;
568
+ formRender?: Record<string, any>;
569
+ benefits: TBeneficiary[];
570
+ };
571
+
572
+ type RootProps = { mode: LiteralUnion<'standalone' | 'inline' | 'popup', string> } & BoxProps;
573
+ export const Root: React.FC<RootProps> = styled(Box)<RootProps>`
574
+ box-sizing: border-box;
575
+ display: flex;
576
+ flex-direction: column;
577
+ align-items: center;
578
+ overflow: hidden;
579
+ position: relative;
580
+
581
+ .cko-container {
582
+ overflow: hidden;
583
+ width: 100%;
584
+ display: flex;
585
+ flex-direction: column;
586
+ justify-content: center;
587
+ position: relative;
588
+ flex: 1;
589
+ }
590
+
591
+ .cko-overview {
592
+ position: relative;
593
+ flex-direction: column;
594
+ display: ${(props) => (props.mode.endsWith('-minimal') ? 'none' : 'flex')};
595
+ background: var(--backgrounds-bg-base);
596
+ min-height: 'auto';
597
+ }
598
+
599
+ .cko-footer {
600
+ display: ${(props) => (props.mode.endsWith('-minimal') ? 'none' : 'block')};
601
+ text-align: center;
602
+ margin-top: 20px;
603
+ }
604
+
605
+ .cko-payment-form {
606
+ .MuiFormLabel-root {
607
+ color: var(--foregrounds-fg-base, #010714);
608
+ font-weight: 500;
609
+ margin-top: 12px;
610
+ margin-bottom: 4px;
611
+ }
612
+ .MuiBox-root {
613
+ margin: 0;
614
+ }
615
+ .MuiFormHelperText-root {
616
+ margin-left: 14px;
617
+ }
618
+ }
619
+
620
+ @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
621
+ padding-top: 0;
622
+ overflow: auto;
623
+
624
+ .cko-container {
625
+ flex-direction: column;
626
+ justify-content: flex-start;
627
+ gap: 0;
628
+ overflow: visible;
629
+ min-width: 200px;
630
+ }
631
+
632
+ .cko-overview {
633
+ width: 100%;
634
+ min-height: auto;
635
+ flex: none;
636
+ }
637
+
638
+ .cko-footer {
639
+ position: relative;
640
+ margin-bottom: 4px;
641
+ margin-top: 0;
642
+ bottom: 0;
643
+ left: 0;
644
+ transform: translateX(0);
645
+ }
646
+ }
647
+ `;
@@ -6,7 +6,7 @@ type ModeType = LiteralUnion<'standalone' | 'inline' | 'popup' | 'inline-minimal
6
6
  type Props = {
7
7
  title: string;
8
8
  description: string;
9
- button?: string;
9
+ button?: string | React.ReactNode;
10
10
  mode?: ModeType;
11
11
  };
12
12
 
@@ -29,9 +29,18 @@ export default function PaymentError({ title, description, button, mode }: Props
29
29
  <Typography variant="body1" sx={{ mb: 2, textAlign: 'center' }}>
30
30
  {description}
31
31
  </Typography>
32
- <Button variant="text" size="small" sx={{ color: 'text.link' }} component={Link} href={window.blocklet?.appUrl}>
33
- {button}
34
- </Button>
32
+ {typeof button === 'string' ? (
33
+ <Button
34
+ variant="text"
35
+ size="small"
36
+ sx={{ color: 'text.link' }}
37
+ component={Link}
38
+ href={window.blocklet?.appUrl}>
39
+ {button}
40
+ </Button>
41
+ ) : (
42
+ button
43
+ )}
35
44
  </Stack>
36
45
  </Stack>
37
46
  );