@clockpay/react-native 1.0.0 → 1.0.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.
- package/README.md +50 -13
- package/lib/commonjs/api/client.js +39 -0
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/api/endpoints.js +3 -0
- package/lib/commonjs/api/endpoints.js.map +1 -1
- package/lib/commonjs/components/icons.js +19 -0
- package/lib/commonjs/components/icons.js.map +1 -1
- package/lib/commonjs/components/payment-button.js +53 -39
- package/lib/commonjs/components/payment-button.js.map +1 -1
- package/lib/commonjs/components/selection-sheet.js +393 -0
- package/lib/commonjs/components/selection-sheet.js.map +1 -0
- package/lib/commonjs/internal/format.js +11 -0
- package/lib/commonjs/internal/format.js.map +1 -1
- package/lib/module/api/client.js +39 -0
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/api/endpoints.js +3 -0
- package/lib/module/api/endpoints.js.map +1 -1
- package/lib/module/components/icons.js +18 -0
- package/lib/module/components/icons.js.map +1 -1
- package/lib/module/components/payment-button.js +56 -42
- package/lib/module/components/payment-button.js.map +1 -1
- package/lib/module/components/selection-sheet.js +391 -0
- package/lib/module/components/selection-sheet.js.map +1 -0
- package/lib/module/internal/format.js +10 -0
- package/lib/module/internal/format.js.map +1 -1
- package/lib/typescript/api/client.d.ts +7 -1
- package/lib/typescript/api/client.d.ts.map +1 -1
- package/lib/typescript/api/endpoints.d.ts +3 -0
- package/lib/typescript/api/endpoints.d.ts.map +1 -1
- package/lib/typescript/components/icons.d.ts +1 -0
- package/lib/typescript/components/icons.d.ts.map +1 -1
- package/lib/typescript/components/payment-button.d.ts +3 -2
- package/lib/typescript/components/payment-button.d.ts.map +1 -1
- package/lib/typescript/components/selection-sheet.d.ts +25 -0
- package/lib/typescript/components/selection-sheet.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/internal/format.d.ts +2 -0
- package/lib/typescript/internal/format.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +65 -2
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/client.ts +45 -0
- package/src/api/endpoints.ts +3 -0
- package/src/components/icons.tsx +14 -0
- package/src/components/payment-button.tsx +54 -38
- package/src/components/selection-sheet.tsx +383 -0
- package/src/index.ts +10 -0
- package/src/internal/format.ts +8 -0
- package/src/types.ts +75 -2
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
ScrollView,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
TouchableOpacity,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { SvgUri } from 'react-native-svg';
|
|
12
|
+
import type {
|
|
13
|
+
CheckoutCurrency,
|
|
14
|
+
CheckoutData,
|
|
15
|
+
CheckoutMeta,
|
|
16
|
+
Coin,
|
|
17
|
+
Network,
|
|
18
|
+
} from '../types';
|
|
19
|
+
import type { ResolvedTheme } from '../internal/theme';
|
|
20
|
+
import { ClockPayClient } from '../api/client';
|
|
21
|
+
import { formatMoney } from '../internal/format';
|
|
22
|
+
import { BottomSheet } from './bottom-sheet';
|
|
23
|
+
import { ChevronDownIcon, CloseIcon, LogoIcon } from './icons';
|
|
24
|
+
|
|
25
|
+
/** Renders a coin's remote SVG logo. Falls back to a neutral placeholder circle. */
|
|
26
|
+
function CoinLogo({ uri }: { uri?: string }) {
|
|
27
|
+
if (!uri) return null;
|
|
28
|
+
return (
|
|
29
|
+
<View style={styles.logo}>
|
|
30
|
+
<SvgUri width={20} height={20} uri={uri} />
|
|
31
|
+
</View>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SelectionSheetProps {
|
|
36
|
+
visible: boolean;
|
|
37
|
+
publicKey: string;
|
|
38
|
+
apiBaseUrl?: string;
|
|
39
|
+
theme: ResolvedTheme;
|
|
40
|
+
amount: number;
|
|
41
|
+
currency: CheckoutCurrency;
|
|
42
|
+
reference: string;
|
|
43
|
+
meta: CheckoutMeta;
|
|
44
|
+
onRequestClose: () => void;
|
|
45
|
+
onClosed: () => void;
|
|
46
|
+
onProceed: (checkout: CheckoutData) => void;
|
|
47
|
+
onError?: (error: Error) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Option {
|
|
51
|
+
id: string;
|
|
52
|
+
label: string;
|
|
53
|
+
image?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface DropdownProps {
|
|
57
|
+
label: string;
|
|
58
|
+
placeholder: string;
|
|
59
|
+
value: string;
|
|
60
|
+
options: Option[];
|
|
61
|
+
disabled?: boolean;
|
|
62
|
+
loading?: boolean;
|
|
63
|
+
theme: ResolvedTheme;
|
|
64
|
+
onSelect: (id: string) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A lightweight inline dropdown — taps to expand a list of options in place. */
|
|
68
|
+
function Dropdown({
|
|
69
|
+
label,
|
|
70
|
+
placeholder,
|
|
71
|
+
value,
|
|
72
|
+
options,
|
|
73
|
+
disabled,
|
|
74
|
+
loading,
|
|
75
|
+
theme,
|
|
76
|
+
onSelect,
|
|
77
|
+
}: DropdownProps) {
|
|
78
|
+
const [open, setOpen] = useState(false);
|
|
79
|
+
const selected = options.find((o) => o.id === value);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View style={[styles.field, open && styles.fieldOpen]}>
|
|
83
|
+
<Text style={styles.label}>
|
|
84
|
+
{label} <Text style={styles.req}>*</Text>
|
|
85
|
+
</Text>
|
|
86
|
+
<TouchableOpacity
|
|
87
|
+
activeOpacity={0.7}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
style={[styles.select, disabled && styles.selectDisabled]}
|
|
90
|
+
onPress={() => setOpen((o) => !o)}
|
|
91
|
+
>
|
|
92
|
+
<View style={styles.selectValue}>
|
|
93
|
+
{selected?.image && <CoinLogo uri={selected.image} />}
|
|
94
|
+
<Text style={[styles.selectText, !selected && styles.placeholder]}>
|
|
95
|
+
{loading ? 'Loading…' : selected ? selected.label : placeholder}
|
|
96
|
+
</Text>
|
|
97
|
+
</View>
|
|
98
|
+
{loading ? (
|
|
99
|
+
<ActivityIndicator size='small' color='#94a3b8' />
|
|
100
|
+
) : (
|
|
101
|
+
<ChevronDownIcon />
|
|
102
|
+
)}
|
|
103
|
+
</TouchableOpacity>
|
|
104
|
+
|
|
105
|
+
{open && !disabled && (
|
|
106
|
+
<View style={styles.options}>
|
|
107
|
+
{options.map((o) => (
|
|
108
|
+
<TouchableOpacity
|
|
109
|
+
key={o.id}
|
|
110
|
+
style={styles.option}
|
|
111
|
+
onPress={() => {
|
|
112
|
+
onSelect(o.id);
|
|
113
|
+
setOpen(false);
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{o.image && <CoinLogo uri={o.image} />}
|
|
117
|
+
<Text
|
|
118
|
+
style={[
|
|
119
|
+
styles.optionText,
|
|
120
|
+
o.id === value && { color: theme.accent, fontWeight: '600' },
|
|
121
|
+
]}
|
|
122
|
+
>
|
|
123
|
+
{o.label}
|
|
124
|
+
</Text>
|
|
125
|
+
</TouchableOpacity>
|
|
126
|
+
))}
|
|
127
|
+
</View>
|
|
128
|
+
)}
|
|
129
|
+
</View>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* First step of the payment flow: the buyer picks a coin and then a settlement
|
|
135
|
+
* network. On continue, a payment link is generated (`create-link`) and the
|
|
136
|
+
* resulting checkout initiated, handing a {@link CheckoutData} to the caller so
|
|
137
|
+
* the payment sheet can open.
|
|
138
|
+
*/
|
|
139
|
+
export function SelectionSheet({
|
|
140
|
+
visible,
|
|
141
|
+
publicKey,
|
|
142
|
+
apiBaseUrl,
|
|
143
|
+
theme,
|
|
144
|
+
amount,
|
|
145
|
+
currency,
|
|
146
|
+
reference,
|
|
147
|
+
meta,
|
|
148
|
+
onRequestClose,
|
|
149
|
+
onClosed,
|
|
150
|
+
onProceed,
|
|
151
|
+
onError,
|
|
152
|
+
}: SelectionSheetProps) {
|
|
153
|
+
const client = useMemo(
|
|
154
|
+
() => new ClockPayClient(publicKey, apiBaseUrl),
|
|
155
|
+
[publicKey, apiBaseUrl]
|
|
156
|
+
);
|
|
157
|
+
const [coins, setCoins] = useState<Coin[]>([]);
|
|
158
|
+
const [networks, setNetworks] = useState<Network[]>([]);
|
|
159
|
+
const [coinId, setCoinId] = useState('');
|
|
160
|
+
const [networkId, setNetworkId] = useState('');
|
|
161
|
+
const [loadingCoins, setLoadingCoins] = useState(false);
|
|
162
|
+
const [loadingNetworks, setLoadingNetworks] = useState(false);
|
|
163
|
+
const [submitting, setSubmitting] = useState(false);
|
|
164
|
+
const [error, setError] = useState<string | null>(null);
|
|
165
|
+
|
|
166
|
+
// Load the available coins once the sheet opens.
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!visible) return;
|
|
169
|
+
let active = true;
|
|
170
|
+
setLoadingCoins(true);
|
|
171
|
+
setError(null);
|
|
172
|
+
client
|
|
173
|
+
.getCoins()
|
|
174
|
+
.then((list) => active && setCoins(list))
|
|
175
|
+
.catch((e: Error) => active && setError(e.message))
|
|
176
|
+
.finally(() => active && setLoadingCoins(false));
|
|
177
|
+
return () => {
|
|
178
|
+
active = false;
|
|
179
|
+
};
|
|
180
|
+
}, [visible, client]);
|
|
181
|
+
|
|
182
|
+
// Load the networks for the selected coin.
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
setNetworks([]);
|
|
185
|
+
setNetworkId('');
|
|
186
|
+
if (!coinId) return;
|
|
187
|
+
|
|
188
|
+
let active = true;
|
|
189
|
+
setLoadingNetworks(true);
|
|
190
|
+
setError(null);
|
|
191
|
+
client
|
|
192
|
+
.getNetworks(coinId)
|
|
193
|
+
.then((list) => active && setNetworks(list))
|
|
194
|
+
.catch((e: Error) => active && setError(e.message))
|
|
195
|
+
.finally(() => active && setLoadingNetworks(false));
|
|
196
|
+
return () => {
|
|
197
|
+
active = false;
|
|
198
|
+
};
|
|
199
|
+
}, [coinId, client]);
|
|
200
|
+
|
|
201
|
+
const canSubmit = Boolean(coinId && networkId && !submitting);
|
|
202
|
+
|
|
203
|
+
const proceed = async () => {
|
|
204
|
+
if (!canSubmit) return;
|
|
205
|
+
setSubmitting(true);
|
|
206
|
+
setError(null);
|
|
207
|
+
try {
|
|
208
|
+
const link = await client.createLink({
|
|
209
|
+
amount,
|
|
210
|
+
currency,
|
|
211
|
+
coinId,
|
|
212
|
+
networkId,
|
|
213
|
+
reference,
|
|
214
|
+
meta,
|
|
215
|
+
});
|
|
216
|
+
const response = await client.initiatePayment(link.clientSecret);
|
|
217
|
+
onProceed(response.data);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
const err = e instanceof Error ? e : new Error('Failed to start checkout.');
|
|
220
|
+
setError(err.message);
|
|
221
|
+
onError?.(err);
|
|
222
|
+
} finally {
|
|
223
|
+
setSubmitting(false);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const coinOptions = coins.map((c) => ({
|
|
228
|
+
id: c.id,
|
|
229
|
+
label: c.name.toUpperCase(),
|
|
230
|
+
image: c.image,
|
|
231
|
+
}));
|
|
232
|
+
const networkOptions = networks.map((n) => ({
|
|
233
|
+
id: n.id,
|
|
234
|
+
label: n.name.toUpperCase(),
|
|
235
|
+
}));
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<BottomSheet
|
|
239
|
+
visible={visible}
|
|
240
|
+
onClose={onRequestClose}
|
|
241
|
+
onClosed={onClosed}
|
|
242
|
+
closeOnBackdropPress={false}
|
|
243
|
+
>
|
|
244
|
+
<View style={styles.header}>
|
|
245
|
+
<LogoIcon size={18} />
|
|
246
|
+
<Text style={[styles.title, { color: theme.accent }]}>Pay with Clockpay</Text>
|
|
247
|
+
<Pressable
|
|
248
|
+
onPress={onRequestClose}
|
|
249
|
+
style={styles.cancel}
|
|
250
|
+
hitSlop={8}
|
|
251
|
+
accessibilityRole='button'
|
|
252
|
+
accessibilityLabel='Cancel'
|
|
253
|
+
>
|
|
254
|
+
<CloseIcon size={22} />
|
|
255
|
+
</Pressable>
|
|
256
|
+
</View>
|
|
257
|
+
|
|
258
|
+
<ScrollView showsVerticalScrollIndicator={false} keyboardShouldPersistTaps='handled'>
|
|
259
|
+
<View style={styles.card}>
|
|
260
|
+
<Text style={styles.muted}>You are paying</Text>
|
|
261
|
+
<Text style={styles.amount}>{formatMoney(amount, currency)}</Text>
|
|
262
|
+
</View>
|
|
263
|
+
|
|
264
|
+
<Dropdown
|
|
265
|
+
label='Select coin'
|
|
266
|
+
placeholder='Select a coin'
|
|
267
|
+
value={coinId}
|
|
268
|
+
options={coinOptions}
|
|
269
|
+
disabled={submitting}
|
|
270
|
+
loading={loadingCoins}
|
|
271
|
+
theme={theme}
|
|
272
|
+
onSelect={setCoinId}
|
|
273
|
+
/>
|
|
274
|
+
|
|
275
|
+
<Dropdown
|
|
276
|
+
label='Select preferred network'
|
|
277
|
+
placeholder={!coinId ? 'Select a coin first' : 'Select a network'}
|
|
278
|
+
value={networkId}
|
|
279
|
+
options={networkOptions}
|
|
280
|
+
disabled={!coinId || submitting}
|
|
281
|
+
loading={loadingNetworks}
|
|
282
|
+
theme={theme}
|
|
283
|
+
onSelect={setNetworkId}
|
|
284
|
+
/>
|
|
285
|
+
|
|
286
|
+
{error && <Text style={styles.error}>{error}</Text>}
|
|
287
|
+
|
|
288
|
+
<TouchableOpacity
|
|
289
|
+
style={[
|
|
290
|
+
styles.primaryBtn,
|
|
291
|
+
{ backgroundColor: canSubmit ? theme.accent : '#dff0f7', borderRadius: theme.radius },
|
|
292
|
+
]}
|
|
293
|
+
disabled={!canSubmit}
|
|
294
|
+
onPress={proceed}
|
|
295
|
+
>
|
|
296
|
+
{submitting ? (
|
|
297
|
+
<ActivityIndicator size='small' color='#fff' />
|
|
298
|
+
) : (
|
|
299
|
+
<Text style={[styles.primaryBtnText, { color: canSubmit ? '#fff' : '#9ca3af' }]}>
|
|
300
|
+
Continue
|
|
301
|
+
</Text>
|
|
302
|
+
)}
|
|
303
|
+
</TouchableOpacity>
|
|
304
|
+
</ScrollView>
|
|
305
|
+
</BottomSheet>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const styles = StyleSheet.create({
|
|
310
|
+
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, paddingBottom: 16 },
|
|
311
|
+
title: { fontSize: 16, fontWeight: '700' },
|
|
312
|
+
cancel: { position: 'absolute', right: 0, top: -2, padding: 4 },
|
|
313
|
+
card: {
|
|
314
|
+
borderWidth: 1,
|
|
315
|
+
borderColor: '#edf1f6',
|
|
316
|
+
borderRadius: 16,
|
|
317
|
+
padding: 20,
|
|
318
|
+
alignItems: 'center',
|
|
319
|
+
marginBottom: 20,
|
|
320
|
+
},
|
|
321
|
+
muted: { fontSize: 13, color: '#94a3b8' },
|
|
322
|
+
amount: { fontSize: 24, fontWeight: '700', color: '#000', marginTop: 4 },
|
|
323
|
+
field: { marginBottom: 16, position: 'relative' },
|
|
324
|
+
// Lift the open dropdown (and its overlay list) above the fields below it.
|
|
325
|
+
fieldOpen: { zIndex: 20, elevation: 20 },
|
|
326
|
+
label: { fontSize: 14, fontWeight: '500', color: '#1e1e1e', marginBottom: 6 },
|
|
327
|
+
req: { color: '#fb7185' },
|
|
328
|
+
select: {
|
|
329
|
+
flexDirection: 'row',
|
|
330
|
+
alignItems: 'center',
|
|
331
|
+
justifyContent: 'space-between',
|
|
332
|
+
borderWidth: 1,
|
|
333
|
+
borderColor: '#d9d9d9',
|
|
334
|
+
borderRadius: 8,
|
|
335
|
+
paddingVertical: 12,
|
|
336
|
+
paddingHorizontal: 12,
|
|
337
|
+
},
|
|
338
|
+
selectDisabled: { backgroundColor: '#f5f5f5' },
|
|
339
|
+
selectValue: { flexDirection: 'row', alignItems: 'center', gap: 8, flexShrink: 1 },
|
|
340
|
+
selectText: { fontSize: 14, color: '#1e1e1e' },
|
|
341
|
+
placeholder: { color: '#9ca3af' },
|
|
342
|
+
logo: {
|
|
343
|
+
width: 20,
|
|
344
|
+
height: 20,
|
|
345
|
+
borderRadius: 10,
|
|
346
|
+
overflow: 'hidden',
|
|
347
|
+
backgroundColor: '#f0f2f5',
|
|
348
|
+
alignItems: 'center',
|
|
349
|
+
justifyContent: 'center',
|
|
350
|
+
},
|
|
351
|
+
options: {
|
|
352
|
+
position: 'absolute',
|
|
353
|
+
top: '100%',
|
|
354
|
+
left: 0,
|
|
355
|
+
right: 0,
|
|
356
|
+
marginTop: 6,
|
|
357
|
+
zIndex: 20,
|
|
358
|
+
elevation: 8,
|
|
359
|
+
backgroundColor: '#ffffff',
|
|
360
|
+
borderWidth: 1,
|
|
361
|
+
borderColor: '#edf1f6',
|
|
362
|
+
borderRadius: 8,
|
|
363
|
+
maxHeight: 220,
|
|
364
|
+
overflow: 'hidden',
|
|
365
|
+
shadowColor: '#0f172a',
|
|
366
|
+
shadowOpacity: 0.18,
|
|
367
|
+
shadowRadius: 12,
|
|
368
|
+
shadowOffset: { width: 0, height: 6 },
|
|
369
|
+
},
|
|
370
|
+
option: {
|
|
371
|
+
flexDirection: 'row',
|
|
372
|
+
alignItems: 'center',
|
|
373
|
+
gap: 8,
|
|
374
|
+
paddingVertical: 12,
|
|
375
|
+
paddingHorizontal: 12,
|
|
376
|
+
borderBottomWidth: 1,
|
|
377
|
+
borderBottomColor: '#f1f5f9',
|
|
378
|
+
},
|
|
379
|
+
optionText: { fontSize: 14, color: '#1e1e1e' },
|
|
380
|
+
error: { color: '#ef4444', fontSize: 13, marginBottom: 12 },
|
|
381
|
+
primaryBtn: { width: '100%', paddingVertical: 15, alignItems: 'center', marginTop: 4 },
|
|
382
|
+
primaryBtnText: { fontSize: 16, fontWeight: '600' },
|
|
383
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,18 @@ export type {
|
|
|
3
3
|
PaymentButtonProps,
|
|
4
4
|
ClockPayAppearance,
|
|
5
5
|
ClockPayEnvironment,
|
|
6
|
+
CheckoutCurrency,
|
|
7
|
+
CheckoutMeta,
|
|
8
|
+
CheckoutPhoneNumber,
|
|
6
9
|
CheckoutData,
|
|
7
10
|
CheckoutResponse,
|
|
11
|
+
Coin,
|
|
12
|
+
CoinsResponse,
|
|
13
|
+
Network,
|
|
14
|
+
NetworksResponse,
|
|
15
|
+
CreateLinkPayload,
|
|
16
|
+
CreateLinkData,
|
|
17
|
+
CreateLinkResponse,
|
|
8
18
|
VerifyCryptoPayload,
|
|
9
19
|
VerifyCryptoResponse,
|
|
10
20
|
} from './types';
|
package/src/internal/format.ts
CHANGED
|
@@ -8,6 +8,14 @@ export function formatTime(totalSeconds: number): string {
|
|
|
8
8
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const CURRENCY_SYMBOLS: Record<string, string> = { ngn: '₦', usd: '$' };
|
|
12
|
+
|
|
13
|
+
/** Formats a fiat amount with its currency symbol, e.g. `formatMoney(20000, 'ngn')` → `₦20,000`. */
|
|
14
|
+
export function formatMoney(amount: number, currency: string): string {
|
|
15
|
+
const symbol = CURRENCY_SYMBOLS[currency?.toLowerCase()] ?? '';
|
|
16
|
+
return `${symbol}${amount.toLocaleString()}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
/** Resolves the on-chain code used to verify a transaction for a given checkout. */
|
|
12
20
|
export function resolveNetworkCode(network?: string, fallback?: string): string {
|
|
13
21
|
const key = network?.toLowerCase() ?? '';
|
package/src/types.ts
CHANGED
|
@@ -22,6 +22,73 @@ export interface ClockPayAppearance {
|
|
|
22
22
|
/** The environment a public key belongs to. */
|
|
23
23
|
export type ClockPayEnvironment = 'test' | 'live';
|
|
24
24
|
|
|
25
|
+
/** Fiat currency the order amount is denominated in. */
|
|
26
|
+
export type CheckoutCurrency = 'ngn' | 'usd';
|
|
27
|
+
|
|
28
|
+
/** A coin the buyer can pay with, from `GET /wallet/checkout/coins`. */
|
|
29
|
+
export interface Coin {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
image: string;
|
|
33
|
+
code: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A settlement network for a coin, from `GET /wallet/checkout/networks/{coinId}`. */
|
|
37
|
+
export interface Network {
|
|
38
|
+
id: string;
|
|
39
|
+
tag: string;
|
|
40
|
+
code: string;
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CoinsResponse {
|
|
45
|
+
data: Coin[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NetworksResponse {
|
|
49
|
+
data: Network[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Buyer's phone number, split into dialling code and local number. */
|
|
53
|
+
export interface CheckoutPhoneNumber {
|
|
54
|
+
code: string;
|
|
55
|
+
number: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Order/buyer details the business provides for the payment link. */
|
|
59
|
+
export interface CheckoutMeta {
|
|
60
|
+
title: string;
|
|
61
|
+
description: string;
|
|
62
|
+
/** Buyer email. Optional. */
|
|
63
|
+
email?: string;
|
|
64
|
+
/** Business's own id for the buyer. Optional. */
|
|
65
|
+
customerId?: string;
|
|
66
|
+
fullName: string;
|
|
67
|
+
/** Buyer phone number. Optional. */
|
|
68
|
+
phoneNumber?: CheckoutPhoneNumber;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Body for `POST /payment/client/create-link`. */
|
|
72
|
+
export interface CreateLinkPayload {
|
|
73
|
+
amount: number;
|
|
74
|
+
currency: CheckoutCurrency;
|
|
75
|
+
coinId: string;
|
|
76
|
+
networkId: string;
|
|
77
|
+
reference: string;
|
|
78
|
+
meta: CheckoutMeta;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The generated payment link — its reference and single-use client secret. */
|
|
82
|
+
export interface CreateLinkData {
|
|
83
|
+
reference: string;
|
|
84
|
+
clientSecret: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CreateLinkResponse {
|
|
88
|
+
message: string;
|
|
89
|
+
data: CreateLinkData;
|
|
90
|
+
}
|
|
91
|
+
|
|
25
92
|
/** Payload returned by the checkout endpoint and rendered inside the sheet. */
|
|
26
93
|
export interface CheckoutData {
|
|
27
94
|
reference: string;
|
|
@@ -52,8 +119,14 @@ export interface VerifyCryptoResponse {
|
|
|
52
119
|
export interface PaymentButtonProps {
|
|
53
120
|
/** Public key from the business developer dashboard (e.g. `cpay_test_pk_...`). Required. */
|
|
54
121
|
publicKey: string;
|
|
55
|
-
/**
|
|
56
|
-
|
|
122
|
+
/** Order amount in the given `currency`. Required. */
|
|
123
|
+
amount: number;
|
|
124
|
+
/** Fiat currency of the `amount`. Required. */
|
|
125
|
+
currency: CheckoutCurrency;
|
|
126
|
+
/** Your own reference for this order. Required. */
|
|
127
|
+
reference: string;
|
|
128
|
+
/** Order and buyer details. The business supplies these; the buyer only picks a coin & network. Required. */
|
|
129
|
+
meta: CheckoutMeta;
|
|
57
130
|
/** Visual customization for the button and sheet. */
|
|
58
131
|
appearance?: ClockPayAppearance;
|
|
59
132
|
/** Button label. Defaults to "Pay with Clockpay". */
|