@blocklet/payment-react 1.18.0 → 1.18.1

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 +40 -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 +603 -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 +39 -4
  16. package/es/payment/product-donation.js +98 -57
  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 +3 -0
  20. package/es/types/index.d.ts +2 -0
  21. package/lib/checkout/donate.js +76 -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 +644 -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 +35 -2
  36. package/lib/payment/product-donation.js +140 -73
  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 +3 -0
  40. package/lib/types/index.d.ts +2 -0
  41. package/package.json +3 -3
  42. package/src/checkout/donate.tsx +54 -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 +646 -0
  49. package/src/payment/error.tsx +13 -4
  50. package/src/payment/form/index.tsx +46 -4
  51. package/src/payment/product-donation.tsx +91 -40
  52. package/src/payment/skeleton/donation.tsx +35 -0
  53. package/src/theme/index.tsx +3 -0
  54. package/src/types/index.ts +2 -0
@@ -0,0 +1,646 @@
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
+ }}>
332
+ {formRender?.cancel}
333
+ <PaymentForm
334
+ currencyId={currencyId}
335
+ checkoutSession={state.checkoutSession}
336
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
337
+ paymentIntent={paymentIntent}
338
+ paymentLink={paymentLink}
339
+ customer={customer}
340
+ onPaid={handlePaid}
341
+ onError={onError}
342
+ mode={mode}
343
+ action={action}
344
+ onlyShowBtn
345
+ />
346
+ </Box>
347
+ )}
348
+ </Stack>
349
+ </>
350
+ )}
351
+ {state.customerLimited && (
352
+ <ConfirmDialog
353
+ onConfirm={() =>
354
+ window.open(
355
+ joinURL(getPrefix(), `/customer/invoice/past-due?referer=${encodeURIComponent(window.location.href)}`),
356
+ '_self'
357
+ )
358
+ }
359
+ onCancel={() => setState({ customerLimited: false })}
360
+ confirm={t('payment.customer.pastDue.alert.confirm')}
361
+ title={t('payment.customer.pastDue.alert.title')}
362
+ message={t('payment.customer.pastDue.alert.description')}
363
+ color="primary"
364
+ />
365
+ )}
366
+ </Stack>
367
+ </FormProvider>
368
+ );
369
+ }
370
+
371
+ DonationForm.defaultProps = {
372
+ completed: false,
373
+ error: null,
374
+ showCheckoutSummary: true,
375
+ formRender: {},
376
+ };
377
+
378
+ export default function DonationForm({
379
+ checkoutSession,
380
+ paymentMethods,
381
+ paymentIntent,
382
+ paymentLink,
383
+ customer,
384
+ completed,
385
+ error,
386
+ mode,
387
+ onPaid,
388
+ onError,
389
+ onChange,
390
+ goBack,
391
+ action,
392
+ showCheckoutSummary = true,
393
+ formRender,
394
+ id,
395
+ }: Props) {
396
+ const { t } = useLocaleContext();
397
+ const { refresh, livemode, setLivemode } = usePaymentContext();
398
+ const { isMobile } = useMobile();
399
+ const [delay, setDelay] = useState(false);
400
+ const isMobileSafariEnv = isMobileSafari();
401
+ const paymentLinkId = id.startsWith('plink_') ? id : undefined;
402
+
403
+ const { data: benefits, loading: benefitLoading } = useRequest(
404
+ () => {
405
+ if (paymentLinkId) {
406
+ return getBenefits(paymentLinkId);
407
+ }
408
+ return Promise.resolve([]);
409
+ },
410
+ {
411
+ refreshDeps: [paymentLinkId || paymentLink?.id],
412
+ ready: !!paymentLinkId || !!paymentLink?.id,
413
+ }
414
+ );
415
+
416
+ useMount(() => {
417
+ setTimeout(() => {
418
+ // 骨架屏 delay
419
+ setDelay(true);
420
+ }, 500);
421
+ // eslint-di
422
+ });
423
+
424
+ useEffect(() => {
425
+ if (checkoutSession) {
426
+ if (livemode !== checkoutSession.livemode) {
427
+ setLivemode(checkoutSession.livemode);
428
+ }
429
+ }
430
+ }, [checkoutSession, livemode, setLivemode, refresh]);
431
+
432
+ const renderContent = () => {
433
+ const footer = (
434
+ <>
435
+ <Divider sx={{ mt: { xs: '16px', md: '-24px' }, mb: { xs: '16px', md: '-16px' } }} />
436
+ <Stack direction="row" justifyContent="flex-end" alignItems="center" flexWrap="wrap" gap={1}>
437
+ <Button variant="outlined" size="large" onClick={formRender?.onCancel}>
438
+ {t('common.cancel')}
439
+ </Button>
440
+ </Stack>
441
+ </>
442
+ );
443
+ if (error) {
444
+ return (
445
+ <>
446
+ <PaymentError mode={mode} title="Oops" description={formatError(error)} button={null} />
447
+ {footer}
448
+ </>
449
+ );
450
+ }
451
+ if (!checkoutSession || !delay || !paymentLink || benefitLoading) {
452
+ return <DonationSkeleton />;
453
+ }
454
+ if (checkoutSession?.expires_at <= Math.round(Date.now() / 1000)) {
455
+ return (
456
+ <>
457
+ <PaymentError
458
+ mode={mode}
459
+ title={t('payment.checkout.expired.title')}
460
+ description={t('payment.checkout.expired.description')}
461
+ button={null}
462
+ />
463
+ {footer}
464
+ </>
465
+ );
466
+ }
467
+ if (!checkoutSession.line_items.length) {
468
+ return (
469
+ <>
470
+ <PaymentError
471
+ mode={mode}
472
+ title={t('payment.checkout.emptyItems.title')}
473
+ description={t('payment.checkout.emptyItems.description')}
474
+ button={null}
475
+ />
476
+ {footer}
477
+ </>
478
+ );
479
+ }
480
+ return (
481
+ <PaymentInner
482
+ formRender={formRender}
483
+ checkoutSession={checkoutSession}
484
+ paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
485
+ paymentLink={paymentLink}
486
+ paymentIntent={paymentIntent}
487
+ completed={completed || checkoutSession.status === 'complete'}
488
+ customer={customer as TCustomer}
489
+ onPaid={onPaid}
490
+ onError={onError}
491
+ onChange={onChange}
492
+ goBack={goBack}
493
+ mode={mode}
494
+ action={action}
495
+ showCheckoutSummary={showCheckoutSummary}
496
+ benefits={benefits}
497
+ />
498
+ );
499
+ };
500
+
501
+ return (
502
+ <Stack
503
+ display="flex"
504
+ flexDirection="column"
505
+ sx={{
506
+ height: mode === 'standalone' ? '100vh' : 'auto',
507
+ overflow: isMobileSafariEnv ? 'visible' : 'hidden',
508
+ }}>
509
+ {mode === 'standalone' ? (
510
+ <Header
511
+ meta={undefined}
512
+ addons={undefined}
513
+ sessionManagerProps={undefined}
514
+ homeLink={undefined}
515
+ theme={undefined}
516
+ hideNavMenu={undefined}
517
+ maxWidth={false}
518
+ sx={{ borderBottom: '1px solid var(--stroke-border-base, #EFF1F5)' }}
519
+ />
520
+ ) : null}
521
+ <Root
522
+ mode={mode}
523
+ sx={{
524
+ flex: 1,
525
+ overflow: {
526
+ xs: isMobileSafariEnv ? 'visible' : 'auto',
527
+ md: 'hidden',
528
+ },
529
+ ...(isMobile && mode === 'standalone'
530
+ ? {
531
+ '.cko-payment-submit-btn': {
532
+ position: 'fixed',
533
+ bottom: 20,
534
+ left: 0,
535
+ right: 0,
536
+ zIndex: 999,
537
+ background: '#fff',
538
+ padding: '10px',
539
+ textAlign: 'center',
540
+ button: {
541
+ color: '#fff',
542
+ maxWidth: 542,
543
+ },
544
+ },
545
+ }
546
+ : {}),
547
+ }}>
548
+ {goBack && (
549
+ <ArrowBackOutlined
550
+ sx={{ mr: 0.5, color: 'text.secondary', alignSelf: 'flex-start', margin: '16px 0', cursor: 'pointer' }}
551
+ onClick={goBack}
552
+ fontSize="medium"
553
+ />
554
+ )}
555
+ <Stack className="cko-container" sx={{ gap: { sm: mode === 'standalone' ? 0 : mode === 'inline' ? 4 : 8 } }}>
556
+ {renderContent()}
557
+ </Stack>
558
+ </Root>
559
+ </Stack>
560
+ );
561
+ }
562
+
563
+ type MainProps = CheckoutContext &
564
+ CheckoutCallbacks & {
565
+ completed?: boolean;
566
+ showCheckoutSummary?: boolean;
567
+ formRender?: Record<string, any>;
568
+ benefits: TBeneficiary[];
569
+ };
570
+
571
+ type RootProps = { mode: LiteralUnion<'standalone' | 'inline' | 'popup', string> } & BoxProps;
572
+ export const Root: React.FC<RootProps> = styled(Box)<RootProps>`
573
+ box-sizing: border-box;
574
+ display: flex;
575
+ flex-direction: column;
576
+ align-items: center;
577
+ overflow: hidden;
578
+ position: relative;
579
+
580
+ .cko-container {
581
+ overflow: hidden;
582
+ width: 100%;
583
+ display: flex;
584
+ flex-direction: column;
585
+ justify-content: center;
586
+ position: relative;
587
+ flex: 1;
588
+ }
589
+
590
+ .cko-overview {
591
+ position: relative;
592
+ flex-direction: column;
593
+ display: ${(props) => (props.mode.endsWith('-minimal') ? 'none' : 'flex')};
594
+ background: var(--backgrounds-bg-base);
595
+ min-height: 'auto';
596
+ }
597
+
598
+ .cko-footer {
599
+ display: ${(props) => (props.mode.endsWith('-minimal') ? 'none' : 'block')};
600
+ text-align: center;
601
+ margin-top: 20px;
602
+ }
603
+
604
+ .cko-payment-form {
605
+ .MuiFormLabel-root {
606
+ color: var(--foregrounds-fg-base, #010714);
607
+ font-weight: 500;
608
+ margin-top: 12px;
609
+ margin-bottom: 4px;
610
+ }
611
+ .MuiBox-root {
612
+ margin: 0;
613
+ }
614
+ .MuiFormHelperText-root {
615
+ margin-left: 14px;
616
+ }
617
+ }
618
+
619
+ @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
620
+ padding-top: 0;
621
+ overflow: auto;
622
+
623
+ .cko-container {
624
+ flex-direction: column;
625
+ justify-content: flex-start;
626
+ gap: 0;
627
+ overflow: visible;
628
+ min-width: 200px;
629
+ }
630
+
631
+ .cko-overview {
632
+ width: 100%;
633
+ min-height: auto;
634
+ flex: none;
635
+ }
636
+
637
+ .cko-footer {
638
+ position: relative;
639
+ margin-bottom: 4px;
640
+ margin-top: 0;
641
+ bottom: 0;
642
+ left: 0;
643
+ transform: translateX(0);
644
+ }
645
+ }
646
+ `;
@@ -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
  );