tf-checkout-react 2.0.0-beta.1 → 2.0.0-beta.3
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/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.js +662 -341
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +662 -341
- package/dist/index.js.map +1 -1
- package/dist/styles/tickets.css +18 -1
- package/package.json +1 -1
- package/src/EmbeddedCheckout.tsx +9 -6
- package/src/auth/AuthModal.tsx +3 -3
- package/src/auth/ForgotPasswordForm.tsx +4 -6
- package/src/auth/LoginForm.tsx +2 -2
- package/src/auth/RegisterForm.tsx +2 -2
- package/src/auth/ResetPasswordForm.tsx +7 -7
- package/src/configure.ts +1 -5
- package/src/confirmer.ts +1 -5
- package/src/sections/AddOns.tsx +14 -7
- package/src/sections/BillingInfo.tsx +40 -33
- package/src/sections/Confirmation.tsx +10 -8
- package/src/sections/IDVerification.tsx +16 -9
- package/src/sections/OrderConfirmation.tsx +32 -23
- package/src/sections/OrderIDVerification.tsx +23 -11
- package/src/sections/OrderSummary.tsx +7 -5
- package/src/sections/Payment.tsx +18 -14
- package/src/sections/Tickets.tsx +48 -25
- package/src/sections/billing/BillingAddressFields.tsx +19 -13
- package/src/sections/billing/TicketHoldersCard.tsx +103 -94
- package/src/styles/tickets.css +18 -1
- package/src/ui/CheckoutProvider.tsx +5 -1
- package/src/ui/InstallmentPicker.tsx +8 -4
- package/src/ui/Loader.tsx +11 -7
- package/src/ui/PaymentMethods.tsx +82 -76
- package/src/ui/Stepper.tsx +27 -23
- package/src/ui/TimerBanner.tsx +7 -3
|
@@ -15,16 +15,18 @@ import { Button } from '../ui/Button'
|
|
|
15
15
|
import { Card, SectionHeader } from '../ui/Card'
|
|
16
16
|
import { useCheckoutContext } from '../ui/CheckoutProvider'
|
|
17
17
|
import { cx } from '../ui/cx'
|
|
18
|
+
import { useT } from '../ui/I18nProvider'
|
|
18
19
|
|
|
19
20
|
type UiPhase = IdVerificationPhase | 'starting'
|
|
20
21
|
|
|
22
|
+
// Translation keys, not copy: resolved at render so the text follows the locale.
|
|
21
23
|
const MESSAGE: Record<UiPhase, string> = {
|
|
22
|
-
idle: '
|
|
23
|
-
starting: '
|
|
24
|
-
polling: '
|
|
25
|
-
verified: '
|
|
26
|
-
timeout: '
|
|
27
|
-
error: '
|
|
24
|
+
idle: 'verification.idle',
|
|
25
|
+
starting: 'verification.starting',
|
|
26
|
+
polling: 'verification.polling',
|
|
27
|
+
verified: 'verification.verified',
|
|
28
|
+
timeout: 'verification.timeoutCheckAgain',
|
|
29
|
+
error: 'verification.error',
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface IDVerificationProps {
|
|
@@ -32,6 +34,7 @@ export interface IDVerificationProps {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export const IDVerification: FC<IDVerificationProps> = ({ className }) => {
|
|
37
|
+
const t = useT()
|
|
35
38
|
const checkout = useCheckoutContext()
|
|
36
39
|
const order = checkout.order
|
|
37
40
|
const [phase, setPhase] = useState<UiPhase>('idle')
|
|
@@ -61,13 +64,17 @@ export const IDVerification: FC<IDVerificationProps> = ({ className }) => {
|
|
|
61
64
|
|
|
62
65
|
return (
|
|
63
66
|
<Card className={cx('tf-checkout-idverify', className)}>
|
|
64
|
-
<SectionHeader title=
|
|
67
|
+
<SectionHeader title={t('verification.title')} />
|
|
65
68
|
<p className={cx('tf-checkout-note', done && 'tf-checkout-idverify__done')}>
|
|
66
|
-
{MESSAGE[phase]}
|
|
69
|
+
{t(MESSAGE[phase])}
|
|
67
70
|
</p>
|
|
68
71
|
{!done && (
|
|
69
72
|
<Button block onClick={run} loading={running} disabled={running}>
|
|
70
|
-
{phase === 'idle'
|
|
73
|
+
{phase === 'idle'
|
|
74
|
+
? t('verification.verify')
|
|
75
|
+
: running
|
|
76
|
+
? t('verification.verifying')
|
|
77
|
+
: t('verification.checkAgain')}
|
|
71
78
|
</Button>
|
|
72
79
|
)}
|
|
73
80
|
{error && <p className="tf-checkout-error-text">{error}</p>}
|
|
@@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState, type FC, type ReactNode } from 'rea
|
|
|
3
3
|
import { errorMessage, ttf } from 'tf-checkout-shared'
|
|
4
4
|
|
|
5
5
|
import { cx } from '../ui/cx'
|
|
6
|
+
import { useT } from '../ui/I18nProvider'
|
|
6
7
|
import { Loader } from '../ui/Loader'
|
|
7
8
|
|
|
8
9
|
type ConfirmationData = Record<string, any>
|
|
@@ -36,6 +37,9 @@ export interface OrderConfirmationProps {
|
|
|
36
37
|
className?: string
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
// Marks "no order hash" in `failure` state; any other value is a server message.
|
|
41
|
+
const MISSING_REFERENCE = '\u0000missing-reference'
|
|
42
|
+
|
|
39
43
|
/**
|
|
40
44
|
* Reloadable post-purchase confirmation. Its markup intentionally preserves
|
|
41
45
|
* the proven legacy checkout class contract so existing white-label themes do
|
|
@@ -43,7 +47,7 @@ export interface OrderConfirmationProps {
|
|
|
43
47
|
*/
|
|
44
48
|
export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
45
49
|
orderHash,
|
|
46
|
-
title
|
|
50
|
+
title,
|
|
47
51
|
message,
|
|
48
52
|
helper,
|
|
49
53
|
eventTitle,
|
|
@@ -56,6 +60,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
56
60
|
onError,
|
|
57
61
|
className,
|
|
58
62
|
}) => {
|
|
63
|
+
const t = useT()
|
|
59
64
|
const [data, setData] = useState<ConfirmationData | null>(null)
|
|
60
65
|
const [failure, setFailure] = useState('')
|
|
61
66
|
const [copied, setCopied] = useState(false)
|
|
@@ -69,7 +74,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
69
74
|
|
|
70
75
|
useEffect(() => {
|
|
71
76
|
if (!orderHash) {
|
|
72
|
-
setFailure(
|
|
77
|
+
setFailure(MISSING_REFERENCE)
|
|
73
78
|
return
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -98,7 +103,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
98
103
|
return (
|
|
99
104
|
<div className={cx('confirmation-page tf-checkout-confirmation-page', className)}>
|
|
100
105
|
<div className="tf-checkout-auth__alert" role="alert">
|
|
101
|
-
{failure}
|
|
106
|
+
{failure === MISSING_REFERENCE ? t('confirmation.missingReference') : failure}
|
|
102
107
|
</div>
|
|
103
108
|
</div>
|
|
104
109
|
)
|
|
@@ -124,17 +129,19 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
124
129
|
...(Number.isFinite(currentPrice)
|
|
125
130
|
? [
|
|
126
131
|
{
|
|
127
|
-
label: '
|
|
132
|
+
label: t('referral.currentPrice'),
|
|
128
133
|
price: `${symbol}${currentPrice.toFixed(2)}`,
|
|
129
134
|
},
|
|
130
135
|
]
|
|
131
136
|
: []),
|
|
132
137
|
...sales.map((sale: ConfirmationData) => ({
|
|
133
|
-
label:
|
|
138
|
+
label: t('referral.ifFriendsBuy', { count: sale.sales }),
|
|
134
139
|
subLabel:
|
|
135
|
-
Number(sale.price) === 0 ? '
|
|
140
|
+
Number(sale.price) === 0 ? t('referral.becomes') : t('referral.goesDownTo'),
|
|
136
141
|
price:
|
|
137
|
-
Number(sale.price) === 0
|
|
142
|
+
Number(sale.price) === 0
|
|
143
|
+
? t('referral.titleFree')
|
|
144
|
+
: `${symbol}${Number(sale.price).toFixed(2)}`,
|
|
138
145
|
})),
|
|
139
146
|
]
|
|
140
147
|
|
|
@@ -150,7 +157,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
150
157
|
<div className={cx('confirmation-page tf-checkout-confirmation-page', className)}>
|
|
151
158
|
<div className="header-container">
|
|
152
159
|
<div className="header-product-text">
|
|
153
|
-
<p className="title">{title}</p>
|
|
160
|
+
<p className="title">{title ?? t('confirmation.orderConfirmed')}</p>
|
|
154
161
|
{(eventTitle || eventDate || eventLocation) && (
|
|
155
162
|
<div className="event-details">
|
|
156
163
|
{eventTitle && <div className="event-title">{eventTitle}</div>}
|
|
@@ -165,11 +172,11 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
165
172
|
<>
|
|
166
173
|
<span className="main">
|
|
167
174
|
{attachTickets
|
|
168
|
-
? '
|
|
169
|
-
: (message ?? '
|
|
175
|
+
? t('confirmation.emailed')
|
|
176
|
+
: (message ?? t('confirmation.inMyTickets'))}
|
|
170
177
|
</span>
|
|
171
178
|
<span className="helper">
|
|
172
|
-
{attachTickets ? '
|
|
179
|
+
{attachTickets ? t('confirmation.bringThem') : helper}
|
|
173
180
|
</span>
|
|
174
181
|
</>
|
|
175
182
|
)}
|
|
@@ -187,7 +194,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
187
194
|
{orderUrl && (
|
|
188
195
|
<div className="order-details-button-container">
|
|
189
196
|
<a className="view-order-button" href={orderUrl}>
|
|
190
|
-
|
|
197
|
+
{t('confirmation.viewOrder')}
|
|
191
198
|
</a>
|
|
192
199
|
</div>
|
|
193
200
|
)}
|
|
@@ -197,26 +204,26 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
197
204
|
<div className="referral_text_image_section">
|
|
198
205
|
<div className="referral_text_section">
|
|
199
206
|
<div className="referral_title_text">
|
|
200
|
-
|
|
201
|
-
<span className="strong-text">
|
|
202
|
-
|
|
203
|
-
<span className="strong-text">
|
|
207
|
+
{t('referral.titleStart')}
|
|
208
|
+
<span className="strong-text"> {t('referral.titleCheaper')} </span>
|
|
209
|
+
{t('referral.titleOr')}
|
|
210
|
+
<span className="strong-text"> {t('referral.titleFree')}</span>
|
|
204
211
|
</div>
|
|
205
212
|
<div className="referral_text">
|
|
206
|
-
<span className="strong-text">
|
|
207
|
-
|
|
213
|
+
<span className="strong-text">{t('referral.inviteFriends')} </span>
|
|
214
|
+
{t('referral.inviteMiddle')}
|
|
208
215
|
<span className="strong-text"> 100% </span>
|
|
209
|
-
|
|
216
|
+
{t('referral.inviteEnd')}
|
|
210
217
|
</div>
|
|
211
218
|
</div>
|
|
212
219
|
</div>
|
|
213
220
|
<div className="share_wrapper">
|
|
214
221
|
<div className="share_section">
|
|
215
222
|
<div className="invitation_section">
|
|
216
|
-
<div className="invitation_title">
|
|
223
|
+
<div className="invitation_title">{t('referral.howToInvite')}</div>
|
|
217
224
|
<div className="share_buttons">
|
|
218
225
|
<div className="share-by-link">
|
|
219
|
-
<h5 className="share-by-link label">
|
|
226
|
+
<h5 className="share-by-link label">{t('referral.sendThisLink')}</h5>
|
|
220
227
|
<div className="share-btn-inner">
|
|
221
228
|
<input className="share-input" readOnly value={shareUrl} />
|
|
222
229
|
<button
|
|
@@ -224,7 +231,9 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
224
231
|
type="button"
|
|
225
232
|
onClick={copy}
|
|
226
233
|
>
|
|
227
|
-
<span className="copy-icon">
|
|
234
|
+
<span className="copy-icon">
|
|
235
|
+
{copied ? t('referral.copied') : t('referral.copy')}
|
|
236
|
+
</span>
|
|
228
237
|
</button>
|
|
229
238
|
</div>
|
|
230
239
|
</div>
|
|
@@ -255,7 +264,7 @@ export const OrderConfirmation: FC<OrderConfirmationProps> = ({
|
|
|
255
264
|
</div>
|
|
256
265
|
{pricing.length > 0 && (
|
|
257
266
|
<div className="pricing-section">
|
|
258
|
-
<div className="invitation_title">
|
|
267
|
+
<div className="invitation_title">{t('referral.howMuchCheaper')}</div>
|
|
259
268
|
{pricing.map((item, index) => (
|
|
260
269
|
<div className="pricing-section_wrapper" key={`${item.label}-${index}`}>
|
|
261
270
|
<div className="pricing-section_label">
|
|
@@ -10,19 +10,24 @@ import {
|
|
|
10
10
|
import { Button } from '../ui/Button'
|
|
11
11
|
import { Card, SectionHeader } from '../ui/Card'
|
|
12
12
|
import { cx } from '../ui/cx'
|
|
13
|
+
import { useT } from '../ui/I18nProvider'
|
|
13
14
|
|
|
14
15
|
type UiPhase = IdVerificationPhase | 'checking' | 'starting'
|
|
15
16
|
|
|
17
|
+
// Translation keys, not copy: resolved at render so the text follows the locale.
|
|
16
18
|
const PHASE_MESSAGE: Record<UiPhase, string> = {
|
|
17
|
-
checking: '
|
|
18
|
-
idle: '
|
|
19
|
-
starting: '
|
|
20
|
-
polling: '
|
|
21
|
-
verified: '
|
|
22
|
-
timeout: '
|
|
23
|
-
error: '
|
|
19
|
+
checking: 'verification.checking',
|
|
20
|
+
idle: 'verification.idle',
|
|
21
|
+
starting: 'verification.starting',
|
|
22
|
+
polling: 'verification.polling',
|
|
23
|
+
verified: 'verification.verified',
|
|
24
|
+
timeout: 'verification.timeoutSafe',
|
|
25
|
+
error: 'verification.error',
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// Marks "no order hash" in `failure` state; any other value is a server message.
|
|
29
|
+
const MISSING_REFERENCE = '\u0000missing-reference'
|
|
30
|
+
|
|
26
31
|
export interface OrderIDVerificationProps {
|
|
27
32
|
orderHash: string
|
|
28
33
|
onVerified?: () => void
|
|
@@ -35,13 +40,14 @@ export const OrderIDVerification: FC<OrderIDVerificationProps> = ({
|
|
|
35
40
|
onVerified,
|
|
36
41
|
className,
|
|
37
42
|
}) => {
|
|
43
|
+
const t = useT()
|
|
38
44
|
const [phase, setPhase] = useState<UiPhase>('checking')
|
|
39
45
|
const [failure, setFailure] = useState('')
|
|
40
46
|
|
|
41
47
|
useEffect(() => {
|
|
42
48
|
if (!orderHash) {
|
|
43
49
|
setPhase('error')
|
|
44
|
-
setFailure(
|
|
50
|
+
setFailure(MISSING_REFERENCE)
|
|
45
51
|
return
|
|
46
52
|
}
|
|
47
53
|
let active = true
|
|
@@ -98,18 +104,24 @@ export const OrderIDVerification: FC<OrderIDVerificationProps> = ({
|
|
|
98
104
|
|
|
99
105
|
return (
|
|
100
106
|
<Card className={cx('tf-checkout-idverify', className)}>
|
|
101
|
-
<SectionHeader title=
|
|
107
|
+
<SectionHeader title={t('verification.title')} />
|
|
102
108
|
<p
|
|
103
109
|
className={cx(
|
|
104
110
|
'tf-checkout-note',
|
|
105
111
|
phase === 'verified' && 'tf-checkout-idverify__done'
|
|
106
112
|
)}
|
|
107
113
|
>
|
|
108
|
-
{failure
|
|
114
|
+
{failure === MISSING_REFERENCE
|
|
115
|
+
? t('confirmation.missingReference')
|
|
116
|
+
: failure || t(PHASE_MESSAGE[phase])}
|
|
109
117
|
</p>
|
|
110
118
|
{phase !== 'verified' && orderHash && (
|
|
111
119
|
<Button block onClick={run} loading={running} disabled={running || !orderHash}>
|
|
112
|
-
{phase === 'idle'
|
|
120
|
+
{phase === 'idle'
|
|
121
|
+
? t('verification.verify')
|
|
122
|
+
: running
|
|
123
|
+
? t('verification.verifying')
|
|
124
|
+
: t('verification.tryAgain')}
|
|
113
125
|
</Button>
|
|
114
126
|
)}
|
|
115
127
|
</Card>
|
|
@@ -4,6 +4,7 @@ import { hooks } from 'tf-checkout-shared'
|
|
|
4
4
|
|
|
5
5
|
import { Card } from '../ui/Card'
|
|
6
6
|
import { useCheckoutContext } from '../ui/CheckoutProvider'
|
|
7
|
+
import { useT } from '../ui/I18nProvider'
|
|
7
8
|
|
|
8
9
|
export interface OrderSummaryProps {
|
|
9
10
|
eventId?: string
|
|
@@ -12,6 +13,7 @@ export interface OrderSummaryProps {
|
|
|
12
13
|
|
|
13
14
|
/** Storefront-compatible order summary shown after the checkout controls. */
|
|
14
15
|
export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
16
|
+
const t = useT()
|
|
15
17
|
const checkout = useCheckoutContext()
|
|
16
18
|
const event = hooks.useEvent(eventId)
|
|
17
19
|
const breakdown = checkout.breakdown
|
|
@@ -23,13 +25,13 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
23
25
|
<Card className={['tf-checkout-order-summary', className].filter(Boolean).join(' ')}>
|
|
24
26
|
{event.data?.name && (
|
|
25
27
|
<div className="tf-checkout-order-summary__group">
|
|
26
|
-
<div className="tf-checkout-order-summary__label">
|
|
28
|
+
<div className="tf-checkout-order-summary__label">{t('common.event')}</div>
|
|
27
29
|
<div className="tf-checkout-order-summary__event">{event.data.name}</div>
|
|
28
30
|
</div>
|
|
29
31
|
)}
|
|
30
32
|
|
|
31
33
|
<div className="tf-checkout-order-summary__group">
|
|
32
|
-
<div className="tf-checkout-order-summary__label">
|
|
34
|
+
<div className="tf-checkout-order-summary__label">{t('tickets.yourTickets')}</div>
|
|
33
35
|
{breakdown.tickets.map((ticket, index) => {
|
|
34
36
|
const detail = detailed?.tickets[index]
|
|
35
37
|
return (
|
|
@@ -39,7 +41,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
39
41
|
>
|
|
40
42
|
<div className="tf-checkout-order-summary__ticket">
|
|
41
43
|
{ticket.quantity} x {ticket.name} - {detail?.unit_price ?? ticket.unit}{' '}
|
|
42
|
-
each
|
|
44
|
+
{t('common.each')}
|
|
43
45
|
</div>
|
|
44
46
|
{detail?.line_total && detail.line_total !== detail.unit_price && (
|
|
45
47
|
<div className="tf-checkout-order-summary__fees">
|
|
@@ -63,7 +65,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
63
65
|
>
|
|
64
66
|
<div className="tf-checkout-order-summary__ticket">
|
|
65
67
|
{addOn.quantity} x {addOn.name}
|
|
66
|
-
{addOn.show_price ? ` - ${addOn.unit_price} each` : ''}
|
|
68
|
+
{addOn.show_price ? ` - ${addOn.unit_price} ${t('common.each')}` : ''}
|
|
67
69
|
</div>
|
|
68
70
|
</div>
|
|
69
71
|
))}
|
|
@@ -71,7 +73,7 @@ export const OrderSummary: FC<OrderSummaryProps> = ({ eventId, className }) => {
|
|
|
71
73
|
)}
|
|
72
74
|
|
|
73
75
|
<div className="tf-checkout-order-summary__total">
|
|
74
|
-
<div className="tf-checkout-order-summary__label">
|
|
76
|
+
<div className="tf-checkout-order-summary__label">{t('common.total')}</div>
|
|
75
77
|
<div className="tf-checkout-order-summary__amount">
|
|
76
78
|
{detailed?.total ??
|
|
77
79
|
`${breakdown.currencySymbol || `${breakdown.currencyCode} `}${Number(
|
package/src/sections/Payment.tsx
CHANGED
|
@@ -24,6 +24,7 @@ import { CardInput, StripePaymentElement, XenditCardInput } from '../CardInput'
|
|
|
24
24
|
import { Button } from '../ui/Button'
|
|
25
25
|
import { Card, SectionHeader } from '../ui/Card'
|
|
26
26
|
import { useCheckoutContext, useCheckoutValidation } from '../ui/CheckoutProvider'
|
|
27
|
+
import { useT } from '../ui/I18nProvider'
|
|
27
28
|
import { Checkbox } from '../ui/Field'
|
|
28
29
|
import { cx } from '../ui/cx'
|
|
29
30
|
import { Loader } from '../ui/Loader'
|
|
@@ -100,6 +101,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
100
101
|
showTestCardHint = false,
|
|
101
102
|
className,
|
|
102
103
|
}) => {
|
|
104
|
+
const t = useT()
|
|
103
105
|
const checkout = useCheckoutContext()
|
|
104
106
|
const validation = useCheckoutValidation()
|
|
105
107
|
const [error, setError] = useState('')
|
|
@@ -153,7 +155,11 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
153
155
|
const out: PaymentMethodOption[] = []
|
|
154
156
|
if (isXendit) {
|
|
155
157
|
if (methods.xenditCard)
|
|
156
|
-
out.push({
|
|
158
|
+
out.push({
|
|
159
|
+
id: { kind: 'xenditCard' },
|
|
160
|
+
value: 'xenditCard',
|
|
161
|
+
label: t('payment.card'),
|
|
162
|
+
})
|
|
157
163
|
for (const w of methods.xenditEWallets)
|
|
158
164
|
out.push({
|
|
159
165
|
id: { kind: 'xenditEWallet', walletKey: w.key },
|
|
@@ -166,7 +172,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
166
172
|
out.push({ id: { kind: 'razorpay' }, value: 'razorpay', label: 'Razorpay' })
|
|
167
173
|
} else {
|
|
168
174
|
// Stripe (or unknown → fail open to card).
|
|
169
|
-
out.push({ id: { kind: 'card' }, value: 'card', label: '
|
|
175
|
+
out.push({ id: { kind: 'card' }, value: 'card', label: t('payment.card') })
|
|
170
176
|
for (const t of checkout.redirectPaymentMethods ?? []) {
|
|
171
177
|
if (t === 'paypal' && methods.paypal) continue // dedupe vs PayPal Standard
|
|
172
178
|
out.push({
|
|
@@ -179,12 +185,12 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
179
185
|
out.push({
|
|
180
186
|
id: { kind: 'paymentPlan' },
|
|
181
187
|
value: 'paymentPlan',
|
|
182
|
-
label: '
|
|
188
|
+
label: t('payment.paymentPlan'),
|
|
183
189
|
})
|
|
184
190
|
// OXXO cash voucher. `oxxo` is deliberately absent from
|
|
185
191
|
// SUPPORTED_REDIRECT_METHODS, so it never arrives via the loop above.
|
|
186
192
|
if (offersOxxo)
|
|
187
|
-
out.push({ id: { kind: 'oxxo' }, value: 'oxxo', label: '
|
|
193
|
+
out.push({ id: { kind: 'oxxo' }, value: 'oxxo', label: t('payment.oxxo') })
|
|
188
194
|
}
|
|
189
195
|
if (methods.paypal)
|
|
190
196
|
out.push({ id: { kind: 'paypal' }, value: 'paypal', label: 'PayPal' })
|
|
@@ -290,10 +296,10 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
290
296
|
// The plan CTA carries the brand's terminology ("Confirm Layaway"), and the
|
|
291
297
|
// terms sentence quotes this same label — they must agree.
|
|
292
298
|
const payLabel = isFree
|
|
293
|
-
? '
|
|
299
|
+
? t('payment.completeRegistration')
|
|
294
300
|
: isPlanSelected
|
|
295
301
|
? planConfirmLabel(checkout.paymentPlan?.configuration)
|
|
296
|
-
: '
|
|
302
|
+
: t('payment.submitPayment')
|
|
297
303
|
const planTerms = paymentPlanTerms(checkout.paymentPlan?.configuration)
|
|
298
304
|
// 'placing' → order in flight; 'succeeded' → placed, host navigating away.
|
|
299
305
|
// Both keep the CTA busy so it can't be re-clicked during the handoff.
|
|
@@ -422,9 +428,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
422
428
|
)}
|
|
423
429
|
{showTestCardHint && (
|
|
424
430
|
<p className="tf-checkout-note">
|
|
425
|
-
{isXendit
|
|
426
|
-
? 'Test card: 4000 0000 0000 0002 · any future date · any CVC'
|
|
427
|
-
: 'Test card: 4242 4242 4242 4242 · any future date · any CVC'}
|
|
431
|
+
{isXendit ? t('payment.testCardXendit') : t('payment.testCard')}
|
|
428
432
|
</p>
|
|
429
433
|
)}
|
|
430
434
|
</>
|
|
@@ -454,7 +458,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
454
458
|
<Checkbox
|
|
455
459
|
checked={useSavedCard}
|
|
456
460
|
onChange={(e) => setUseSavedCard(e.target.checked)}
|
|
457
|
-
label=
|
|
461
|
+
label={t('payment.useThisCard')}
|
|
458
462
|
/>
|
|
459
463
|
</div>
|
|
460
464
|
)}
|
|
@@ -464,13 +468,13 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
464
468
|
return (
|
|
465
469
|
<div className={['tf-checkout-payment-stack', className].filter(Boolean).join(' ')}>
|
|
466
470
|
<Card>
|
|
467
|
-
<SectionHeader title=
|
|
471
|
+
<SectionHeader title={t('payment.title')} />
|
|
468
472
|
|
|
469
473
|
{/* Payment method */}
|
|
470
474
|
{!isFree && (
|
|
471
475
|
<>
|
|
472
476
|
{!singleCard && (
|
|
473
|
-
<div className="tf-checkout-payment__label">
|
|
477
|
+
<div className="tf-checkout-payment__label">{t('payment.method')}</div>
|
|
474
478
|
)}
|
|
475
479
|
{!methods ? (
|
|
476
480
|
<div className="tf-checkout-payment__cardbox">
|
|
@@ -529,7 +533,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
529
533
|
/>
|
|
530
534
|
{invalid && (
|
|
531
535
|
<div className="tf-checkout-consent__error">
|
|
532
|
-
|
|
536
|
+
{t('payment.conditionRequired')}
|
|
533
537
|
</div>
|
|
534
538
|
)}
|
|
535
539
|
</div>
|
|
@@ -548,7 +552,7 @@ export const Payment: FC<PaymentProps> = ({
|
|
|
548
552
|
loading={finalizing}
|
|
549
553
|
disabled={checkout.isBusy || finalizing || (!isFree && !methods)}
|
|
550
554
|
>
|
|
551
|
-
{finalizing ? '
|
|
555
|
+
{finalizing ? t('payment.placing') : payLabel}
|
|
552
556
|
</Button>
|
|
553
557
|
</div>
|
|
554
558
|
{bottomError && <p className="tf-checkout-error-text">{bottomError}</p>}
|