@blocklet/payment-react 1.13.121 → 1.13.123

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.
@@ -4,18 +4,19 @@ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
4
4
  import { useTheme } from "@arcblock/ux/lib/Theme";
5
5
  import Toast from "@arcblock/ux/lib/Toast";
6
6
  import { LoadingButton } from "@mui/lab";
7
- import { Avatar, Fade, InputAdornment, MenuItem, Select, Stack, Typography } from "@mui/material";
7
+ import { Avatar, Card, Fade, InputAdornment, Stack, Typography } from "@mui/material";
8
8
  import { useCreation, useSetState, useSize } from "ahooks";
9
9
  import { PhoneNumberUtil } from "google-libphonenumber";
10
10
  import pWaitFor from "p-wait-for";
11
- import { useEffect } from "react";
11
+ import { useEffect, useState } from "react";
12
12
  import { Controller, useFormContext, useWatch } from "react-hook-form";
13
+ import { joinURL } from "ufo";
13
14
  import { dispatch } from "use-bus";
14
15
  import isEmail from "validator/es/lib/isEmail";
15
16
  import api from "../../api.js";
16
17
  import FormInput from "../../components/input.js";
17
18
  import { usePaymentContext } from "../../contexts/payment.js";
18
- import { formatError, getStatementDescriptor } from "../../util.js";
19
+ import { formatError, getPrefix, getStatementDescriptor } from "../../util.js";
19
20
  import UserButtons from "./addon.js";
20
21
  import AddressForm from "./address.js";
21
22
  import PhoneInput from "./phone.js";
@@ -36,6 +37,19 @@ const waitForCheckoutComplete = async (sessionId) => {
36
37
  );
37
38
  return result;
38
39
  };
40
+ const flatPaymentMethods = (methods = []) => {
41
+ const out = [];
42
+ methods.forEach((method) => {
43
+ const currencies = method.paymentCurrencies || method.payment_currencies || [];
44
+ currencies.forEach((currency) => {
45
+ out.push({
46
+ ...currency,
47
+ method
48
+ });
49
+ });
50
+ });
51
+ return out;
52
+ };
39
53
  PaymentForm.defaultProps = {};
40
54
  export default function PaymentForm({
41
55
  checkoutSession,
@@ -59,6 +73,8 @@ export default function PaymentForm({
59
73
  customer,
60
74
  stripePaying: false
61
75
  });
76
+ const currencies = flatPaymentMethods(paymentMethods);
77
+ const [paymentCurrencyIndex, setPaymentCurrencyIndex] = useState(0);
62
78
  useEffect(() => {
63
79
  if (session?.user) {
64
80
  const values = getValues();
@@ -73,9 +89,11 @@ export default function PaymentForm({
73
89
  }
74
90
  }
75
91
  }, [session?.user, getValues, setValue]);
92
+ useEffect(() => {
93
+ setValue("payment_method", currencies[paymentCurrencyIndex].method.id);
94
+ setValue("payment_currency", currencies[paymentCurrencyIndex].id);
95
+ }, [paymentCurrencyIndex, currencies, setValue]);
76
96
  const paymentMethod = useWatch({ control, name: "payment_method" });
77
- const paymentCurrency = useWatch({ control, name: "payment_currency" });
78
- const paymentCurrencies = paymentMethods.find((x) => x.id === paymentMethod)?.payment_currencies || [];
79
97
  const domSize = useSize(document.body);
80
98
  const isColumnLayout = useCreation(() => {
81
99
  if (domSize) {
@@ -88,12 +106,8 @@ export default function PaymentForm({
88
106
  const payee = getStatementDescriptor(checkoutSession.line_items);
89
107
  const buttonText = session?.user ? t(`payment.checkout.${checkoutSession.mode}`) : t("payment.checkout.connect", { action: t(`payment.checkout.${checkoutSession.mode}`) });
90
108
  const method = paymentMethods.find((x) => x.id === paymentMethod);
91
- const handleMethodChange = (e) => {
92
- setValue("payment_method", e.target.value);
93
- const currencies = paymentMethods.find((x) => x.id === e.target.value)?.payment_currencies || [];
94
- if (currencies.some((x) => x.id === paymentCurrency) === false) {
95
- setValue("payment_currency", currencies[0]?.id);
96
- }
109
+ const handleCurrencyChange = (index) => {
110
+ setPaymentCurrencyIndex(index);
97
111
  };
98
112
  const handleConnected = async () => {
99
113
  try {
@@ -156,6 +170,7 @@ export default function PaymentForm({
156
170
  } else {
157
171
  connect.open({
158
172
  action: checkoutSession.mode,
173
+ prefix: joinURL(getPrefix(), "/api/did"),
159
174
  timeout: 5 * 60 * 1e3,
160
175
  extraParams: { checkoutSessionId: checkoutSession.id },
161
176
  onSuccess: async () => {
@@ -267,36 +282,42 @@ export default function PaymentForm({
267
282
  /* @__PURE__ */ jsx(AddressForm, { mode: checkoutSession.billing_address_collection, stripe: method?.type === "stripe" }),
268
283
  /* @__PURE__ */ jsx(Fade, { in: true, children: /* @__PURE__ */ jsxs(Stack, { direction: "column", alignItems: "flex-start", className: "cko-payment-methods", children: [
269
284
  /* @__PURE__ */ jsx(Typography, { sx: { mb: 2, color: "text.primary", fontWeight: 600 }, children: t("payment.checkout.method") }),
270
- /* @__PURE__ */ jsxs(Stack, { direction: "row", sx: { width: 1 }, spacing: 1, children: [
271
- /* @__PURE__ */ jsx(
272
- Controller,
273
- {
274
- name: "payment_method",
275
- control,
276
- render: ({ field }) => /* @__PURE__ */ jsx(Select, { ...field, onChange: handleMethodChange, sx: { flex: 1 }, size: "small", children: paymentMethods.map((x) => {
277
- const selected = x.id === paymentMethod;
278
- return /* @__PURE__ */ jsx(MenuItem, { value: x.id, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, children: [
279
- /* @__PURE__ */ jsx(Avatar, { src: x.logo, alt: x.name, sx: { width: 20, height: 20 } }),
280
- /* @__PURE__ */ jsx(Typography, { color: selected ? "text.primary" : "text.secondary", children: x.name })
281
- ] }) }, x.id);
282
- }) })
283
- }
284
- ),
285
- /* @__PURE__ */ jsx(
286
- Controller,
287
- {
288
- name: "payment_currency",
289
- control,
290
- render: ({ field }) => /* @__PURE__ */ jsx(Select, { ...field, sx: { flex: 1 }, size: "small", children: paymentCurrencies.map((x) => {
291
- const selected = x.id === paymentCurrency;
292
- return /* @__PURE__ */ jsx(MenuItem, { value: x.id, children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, children: [
293
- /* @__PURE__ */ jsx(Avatar, { src: x.logo, alt: x.name, sx: { width: 20, height: 20 } }),
294
- /* @__PURE__ */ jsx(Typography, { color: selected ? "text.primary" : "text.secondary", children: x.symbol })
295
- ] }) }, x.id);
296
- }) })
297
- }
298
- )
299
- ] }),
285
+ /* @__PURE__ */ jsx(Stack, { direction: "row", sx: { width: "100%" }, children: /* @__PURE__ */ jsx(
286
+ Controller,
287
+ {
288
+ name: "payment_currency",
289
+ control,
290
+ render: () => /* @__PURE__ */ jsx(
291
+ "section",
292
+ {
293
+ style: {
294
+ display: currencies.length > 1 ? "grid" : "block",
295
+ gridTemplateColumns: "50% 50%",
296
+ width: "100%"
297
+ },
298
+ children: currencies.map((x, i) => {
299
+ const selected = i === paymentCurrencyIndex;
300
+ return /* @__PURE__ */ jsx(
301
+ Card,
302
+ {
303
+ variant: "outlined",
304
+ onClick: () => handleCurrencyChange(i),
305
+ className: selected ? "cko-payment-card" : "cko-payment-card-unselect",
306
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", children: [
307
+ /* @__PURE__ */ jsx(Avatar, { src: x.logo, alt: x.name, sx: { width: 30, height: 30, marginRight: "10px" } }),
308
+ /* @__PURE__ */ jsxs("div", { children: [
309
+ /* @__PURE__ */ jsx(Typography, { variant: "h5", component: "div", children: x.symbol }),
310
+ /* @__PURE__ */ jsx(Typography, { sx: { fontSize: 14 }, color: "text.secondary", gutterBottom: true, children: x.method.name })
311
+ ] })
312
+ ] })
313
+ },
314
+ x.id
315
+ );
316
+ })
317
+ }
318
+ )
319
+ }
320
+ ) }),
300
321
  state.stripePaying && state.stripeContext && /* @__PURE__ */ jsx(
301
322
  StripeCheckout,
302
323
  {
@@ -319,7 +340,6 @@ export default function PaymentForm({
319
340
  size: "large",
320
341
  onClick: onAction,
321
342
  fullWidth: true,
322
- loadingPosition: "end",
323
343
  disabled: state.submitting || state.paying || state.stripePaying,
324
344
  loading: state.submitting || state.paying,
325
345
  children: state.submitting || state.paying ? t("payment.checkout.processing") : buttonText
@@ -286,6 +286,52 @@ export const Root = styled(Box)`
286
286
  .cko-payment-methods {
287
287
  }
288
288
 
289
+ .cko-payment-card {
290
+ position: relative;
291
+ border: 2px solid #3773f2;
292
+ padding: 5px 10px;
293
+ margin: 5px 0;
294
+ cursor: pointer;
295
+ }
296
+
297
+ .cko-payment-card::before {
298
+ content: '';
299
+ position: absolute;
300
+ right: 0;
301
+ top: 0;
302
+ border: 12px solid #3773f2;
303
+ border-bottom-color: transparent;
304
+ border-left-color: transparent;
305
+ }
306
+
307
+ .cko-payment-card-unselect {
308
+ border: 2px solid #bbb;
309
+ padding: 5px 10px;
310
+ margin: 5px 0;
311
+ cursor: pointer;
312
+ }
313
+
314
+ .cko-payment-card:nth-child(odd) {
315
+ margin-right: 5px;
316
+ }
317
+
318
+ .cko-payment-card-unselect:nth-child(odd) {
319
+ margin-right: 5px;
320
+ }
321
+
322
+ .cko-payment-card::after {
323
+ content: '';
324
+ width: 6px;
325
+ height: 10px;
326
+ position: absolute;
327
+ right: 3px;
328
+ top: 0px;
329
+ border: 2px solid #fff;
330
+ border-top-color: transparent;
331
+ border-left-color: transparent;
332
+ transform: rotate(35deg);
333
+ }
334
+
289
335
  .cko-payment-submit {
290
336
  .MuiButtonBase-root {
291
337
  border-radius: 0;
@@ -16,6 +16,7 @@ var _googleLibphonenumber = require("google-libphonenumber");
16
16
  var _pWaitFor = _interopRequireDefault(require("p-wait-for"));
17
17
  var _react = require("react");
18
18
  var _reactHookForm = require("react-hook-form");
19
+ var _ufo = require("ufo");
19
20
  var _useBus = require("use-bus");
20
21
  var _isEmail = _interopRequireDefault(require("validator/es/lib/isEmail"));
21
22
  var _api = _interopRequireDefault(require("../../api"));
@@ -45,6 +46,19 @@ const waitForCheckoutComplete = async sessionId => {
45
46
  });
46
47
  return result;
47
48
  };
49
+ const flatPaymentMethods = (methods = []) => {
50
+ const out = [];
51
+ methods.forEach(method => {
52
+ const currencies = method.paymentCurrencies || method.payment_currencies || [];
53
+ currencies.forEach(currency => {
54
+ out.push({
55
+ ...currency,
56
+ method
57
+ });
58
+ });
59
+ });
60
+ return out;
61
+ };
48
62
  PaymentForm.defaultProps = {};
49
63
  function PaymentForm({
50
64
  checkoutSession,
@@ -78,6 +92,8 @@ function PaymentForm({
78
92
  customer,
79
93
  stripePaying: false
80
94
  });
95
+ const currencies = flatPaymentMethods(paymentMethods);
96
+ const [paymentCurrencyIndex, setPaymentCurrencyIndex] = (0, _react.useState)(0);
81
97
  (0, _react.useEffect)(() => {
82
98
  if (session?.user) {
83
99
  const values = getValues();
@@ -92,15 +108,14 @@ function PaymentForm({
92
108
  }
93
109
  }
94
110
  }, [session?.user, getValues, setValue]);
111
+ (0, _react.useEffect)(() => {
112
+ setValue("payment_method", currencies[paymentCurrencyIndex].method.id);
113
+ setValue("payment_currency", currencies[paymentCurrencyIndex].id);
114
+ }, [paymentCurrencyIndex, currencies, setValue]);
95
115
  const paymentMethod = (0, _reactHookForm.useWatch)({
96
116
  control,
97
117
  name: "payment_method"
98
118
  });
99
- const paymentCurrency = (0, _reactHookForm.useWatch)({
100
- control,
101
- name: "payment_currency"
102
- });
103
- const paymentCurrencies = paymentMethods.find(x => x.id === paymentMethod)?.payment_currencies || [];
104
119
  const domSize = (0, _ahooks.useSize)(document.body);
105
120
  const isColumnLayout = (0, _ahooks.useCreation)(() => {
106
121
  if (domSize) {
@@ -115,12 +130,8 @@ function PaymentForm({
115
130
  action: t(`payment.checkout.${checkoutSession.mode}`)
116
131
  });
117
132
  const method = paymentMethods.find(x => x.id === paymentMethod);
118
- const handleMethodChange = e => {
119
- setValue("payment_method", e.target.value);
120
- const currencies = paymentMethods.find(x => x.id === e.target.value)?.payment_currencies || [];
121
- if (currencies.some(x => x.id === paymentCurrency) === false) {
122
- setValue("payment_currency", currencies[0]?.id);
123
- }
133
+ const handleCurrencyChange = index => {
134
+ setPaymentCurrencyIndex(index);
124
135
  };
125
136
  const handleConnected = async () => {
126
137
  try {
@@ -194,6 +205,7 @@ function PaymentForm({
194
205
  } else {
195
206
  connect.open({
196
207
  action: checkoutSession.mode,
208
+ prefix: (0, _ufo.joinURL)((0, _util.getPrefix)(), "/api/did"),
197
209
  timeout: 5 * 60 * 1e3,
198
210
  extraParams: {
199
211
  checkoutSessionId: checkoutSession.id
@@ -348,80 +360,56 @@ function PaymentForm({
348
360
  fontWeight: 600
349
361
  },
350
362
  children: t("payment.checkout.method")
351
- }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
363
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
352
364
  direction: "row",
353
365
  sx: {
354
- width: 1
366
+ width: "100%"
355
367
  },
356
- spacing: 1,
357
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_reactHookForm.Controller, {
358
- name: "payment_method",
359
- control,
360
- render: ({
361
- field
362
- }) => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, {
363
- ...field,
364
- onChange: handleMethodChange,
365
- sx: {
366
- flex: 1
367
- },
368
- size: "small",
369
- children: paymentMethods.map(x => {
370
- const selected = x.id === paymentMethod;
371
- return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.MenuItem, {
372
- value: x.id,
373
- children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
374
- direction: "row",
375
- spacing: 1,
376
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
377
- src: x.logo,
378
- alt: x.name,
379
- sx: {
380
- width: 20,
381
- height: 20
382
- }
383
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
384
- color: selected ? "text.primary" : "text.secondary",
385
- children: x.name
386
- })]
387
- })
388
- }, x.id);
389
- })
390
- })
391
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_reactHookForm.Controller, {
368
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_reactHookForm.Controller, {
392
369
  name: "payment_currency",
393
370
  control,
394
- render: ({
395
- field
396
- }) => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, {
397
- ...field,
398
- sx: {
399
- flex: 1
371
+ render: () => /* @__PURE__ */(0, _jsxRuntime.jsx)("section", {
372
+ style: {
373
+ display: currencies.length > 1 ? "grid" : "block",
374
+ gridTemplateColumns: "50% 50%",
375
+ width: "100%"
400
376
  },
401
- size: "small",
402
- children: paymentCurrencies.map(x => {
403
- const selected = x.id === paymentCurrency;
404
- return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.MenuItem, {
405
- value: x.id,
377
+ children: currencies.map((x, i) => {
378
+ const selected = i === paymentCurrencyIndex;
379
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Card, {
380
+ variant: "outlined",
381
+ onClick: () => handleCurrencyChange(i),
382
+ className: selected ? "cko-payment-card" : "cko-payment-card-unselect",
406
383
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
407
384
  direction: "row",
408
- spacing: 1,
385
+ alignItems: "center",
409
386
  children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
410
387
  src: x.logo,
411
388
  alt: x.name,
412
389
  sx: {
413
- width: 20,
414
- height: 20
390
+ width: 30,
391
+ height: 30,
392
+ marginRight: "10px"
415
393
  }
416
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
417
- color: selected ? "text.primary" : "text.secondary",
418
- children: x.symbol
394
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)("div", {
395
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
396
+ variant: "h5",
397
+ component: "div",
398
+ children: x.symbol
399
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
400
+ sx: {
401
+ fontSize: 14
402
+ },
403
+ color: "text.secondary",
404
+ gutterBottom: true,
405
+ children: x.method.name
406
+ })]
419
407
  })]
420
408
  })
421
409
  }, x.id);
422
410
  })
423
411
  })
424
- })]
412
+ })
425
413
  }), state.stripePaying && state.stripeContext && /* @__PURE__ */(0, _jsxRuntime.jsx)(_stripe.default, {
426
414
  clientSecret: state.stripeContext.client_secret,
427
415
  intentType: state.stripeContext.intent_type,
@@ -442,7 +430,6 @@ function PaymentForm({
442
430
  size: "large",
443
431
  onClick: onAction,
444
432
  fullWidth: true,
445
- loadingPosition: "end",
446
433
  disabled: state.submitting || state.paying || state.stripePaying,
447
434
  loading: state.submitting || state.paying,
448
435
  children: state.submitting || state.paying ? t("payment.checkout.processing") : buttonText
@@ -344,6 +344,52 @@ const Root = exports.Root = (0, _system.styled)(_material.Box)`
344
344
  .cko-payment-methods {
345
345
  }
346
346
 
347
+ .cko-payment-card {
348
+ position: relative;
349
+ border: 2px solid #3773f2;
350
+ padding: 5px 10px;
351
+ margin: 5px 0;
352
+ cursor: pointer;
353
+ }
354
+
355
+ .cko-payment-card::before {
356
+ content: '';
357
+ position: absolute;
358
+ right: 0;
359
+ top: 0;
360
+ border: 12px solid #3773f2;
361
+ border-bottom-color: transparent;
362
+ border-left-color: transparent;
363
+ }
364
+
365
+ .cko-payment-card-unselect {
366
+ border: 2px solid #bbb;
367
+ padding: 5px 10px;
368
+ margin: 5px 0;
369
+ cursor: pointer;
370
+ }
371
+
372
+ .cko-payment-card:nth-child(odd) {
373
+ margin-right: 5px;
374
+ }
375
+
376
+ .cko-payment-card-unselect:nth-child(odd) {
377
+ margin-right: 5px;
378
+ }
379
+
380
+ .cko-payment-card::after {
381
+ content: '';
382
+ width: 6px;
383
+ height: 10px;
384
+ position: absolute;
385
+ right: 3px;
386
+ top: 0px;
387
+ border: 2px solid #fff;
388
+ border-top-color: transparent;
389
+ border-left-color: transparent;
390
+ transform: rotate(35deg);
391
+ }
392
+
347
393
  .cko-payment-submit {
348
394
  .MuiButtonBase-root {
349
395
  border-radius: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.13.121",
3
+ "version": "1.13.123",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -89,7 +89,7 @@
89
89
  "@babel/core": "^7.19.3",
90
90
  "@babel/preset-env": "^7.19.3",
91
91
  "@babel/preset-react": "^7.18.6",
92
- "@blocklet/payment-types": "1.13.121",
92
+ "@blocklet/payment-types": "1.13.123",
93
93
  "@storybook/addon-essentials": "^7.6.10",
94
94
  "@storybook/addon-interactions": "^7.6.10",
95
95
  "@storybook/addon-links": "^7.6.10",
@@ -118,5 +118,5 @@
118
118
  "vite-plugin-babel": "^1.2.0",
119
119
  "vite-plugin-node-polyfills": "^0.19.0"
120
120
  },
121
- "gitHead": "c02f337493cf7dac11324447534cf8684b763064"
121
+ "gitHead": "ea37183dafcf5a89c0593886aa895b33eccf8529"
122
122
  }
@@ -3,14 +3,15 @@ import 'react-international-phone/style.css';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import { useTheme } from '@arcblock/ux/lib/Theme';
5
5
  import Toast from '@arcblock/ux/lib/Toast';
6
- import type { TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
6
+ import type { PaymentCurrency, TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
7
7
  import { LoadingButton } from '@mui/lab';
8
- import { Avatar, Fade, InputAdornment, MenuItem, Select, Stack, Typography } from '@mui/material';
8
+ import { Avatar, Card, Fade, InputAdornment, Stack, Typography } from '@mui/material';
9
9
  import { useCreation, useSetState, useSize } from 'ahooks';
10
10
  import { PhoneNumberUtil } from 'google-libphonenumber';
11
11
  import pWaitFor from 'p-wait-for';
12
- import { useEffect } from 'react';
12
+ import { useEffect, useState } from 'react';
13
13
  import { Controller, useFormContext, useWatch } from 'react-hook-form';
14
+ import { joinURL } from 'ufo';
14
15
  import { dispatch } from 'use-bus';
15
16
  import isEmail from 'validator/es/lib/isEmail';
16
17
 
@@ -18,7 +19,7 @@ import api from '../../api';
18
19
  import FormInput from '../../components/input';
19
20
  import { usePaymentContext } from '../../contexts/payment';
20
21
  import { CheckoutCallbacks, CheckoutContext } from '../../types';
21
- import { formatError, getStatementDescriptor } from '../../util';
22
+ import { formatError, getPrefix, getStatementDescriptor } from '../../util';
22
23
  import UserButtons from './addon';
23
24
  import AddressForm from './address';
24
25
  import PhoneInput from './phone';
@@ -56,6 +57,22 @@ const waitForCheckoutComplete = async (sessionId: string) => {
56
57
 
57
58
  type PageData = CheckoutContext & CheckoutCallbacks;
58
59
 
60
+ const flatPaymentMethods = (methods: TPaymentMethodExpanded[] = []) => {
61
+ const out: PaymentCurrency[] = [];
62
+
63
+ methods.forEach((method: any) => {
64
+ const currencies = method.paymentCurrencies || method.payment_currencies || [];
65
+ currencies.forEach((currency: any) => {
66
+ out.push({
67
+ ...currency,
68
+ method,
69
+ });
70
+ });
71
+ });
72
+
73
+ return out;
74
+ };
75
+
59
76
  PaymentForm.defaultProps = {};
60
77
 
61
78
  // FIXME: https://stripe.com/docs/elements/address-element
@@ -98,6 +115,9 @@ export default function PaymentForm({
98
115
  stripePaying: false,
99
116
  });
100
117
 
118
+ const currencies = flatPaymentMethods(paymentMethods);
119
+ const [paymentCurrencyIndex, setPaymentCurrencyIndex] = useState(0);
120
+
101
121
  useEffect(() => {
102
122
  if (session?.user) {
103
123
  const values = getValues();
@@ -113,9 +133,12 @@ export default function PaymentForm({
113
133
  }
114
134
  }, [session?.user, getValues, setValue]);
115
135
 
136
+ useEffect(() => {
137
+ setValue('payment_method', (currencies[paymentCurrencyIndex] as any).method.id);
138
+ setValue('payment_currency', currencies[paymentCurrencyIndex].id);
139
+ }, [paymentCurrencyIndex, currencies, setValue]);
140
+
116
141
  const paymentMethod = useWatch({ control, name: 'payment_method' });
117
- const paymentCurrency = useWatch({ control, name: 'payment_currency' });
118
- const paymentCurrencies = paymentMethods.find((x) => x.id === paymentMethod)?.payment_currencies || [];
119
142
 
120
143
  const domSize = useSize(document.body);
121
144
 
@@ -135,12 +158,8 @@ export default function PaymentForm({
135
158
 
136
159
  const method = paymentMethods.find((x) => x.id === paymentMethod) as TPaymentMethodExpanded;
137
160
 
138
- const handleMethodChange = (e: any) => {
139
- setValue('payment_method', e.target.value);
140
- const currencies = paymentMethods.find((x) => x.id === e.target.value)?.payment_currencies || [];
141
- if (currencies.some((x) => x.id === paymentCurrency) === false) {
142
- setValue('payment_currency', currencies[0]?.id);
143
- }
161
+ const handleCurrencyChange = (index: number) => {
162
+ setPaymentCurrencyIndex(index);
144
163
  };
145
164
 
146
165
  const handleConnected = async () => {
@@ -208,6 +227,7 @@ export default function PaymentForm({
208
227
  } else {
209
228
  connect.open({
210
229
  action: checkoutSession.mode,
230
+ prefix: joinURL(getPrefix(), '/api/did'),
211
231
  timeout: 5 * 60 * 1000,
212
232
  extraParams: { checkoutSessionId: checkoutSession.id },
213
233
  onSuccess: async () => {
@@ -322,43 +342,40 @@ export default function PaymentForm({
322
342
  <Fade in>
323
343
  <Stack direction="column" alignItems="flex-start" className="cko-payment-methods">
324
344
  <Typography sx={{ mb: 2, color: 'text.primary', fontWeight: 600 }}>{t('payment.checkout.method')}</Typography>
325
- <Stack direction="row" sx={{ width: 1 }} spacing={1}>
326
- <Controller
327
- name="payment_method"
328
- control={control}
329
- render={({ field }) => (
330
- <Select {...field} onChange={handleMethodChange} sx={{ flex: 1 }} size="small">
331
- {paymentMethods.map((x) => {
332
- const selected = x.id === paymentMethod;
333
- return (
334
- <MenuItem key={x.id} value={x.id}>
335
- <Stack direction="row" spacing={1}>
336
- <Avatar src={x.logo} alt={x.name} sx={{ width: 20, height: 20 }} />
337
- <Typography color={selected ? 'text.primary' : 'text.secondary'}>{x.name}</Typography>
338
- </Stack>
339
- </MenuItem>
340
- );
341
- })}
342
- </Select>
343
- )}
344
- />
345
+ <Stack direction="row" sx={{ width: '100%' }}>
345
346
  <Controller
346
347
  name="payment_currency"
347
348
  control={control}
348
- render={({ field }) => (
349
- <Select {...field} sx={{ flex: 1 }} size="small">
350
- {paymentCurrencies.map((x) => {
351
- const selected = x.id === paymentCurrency;
349
+ render={() => (
350
+ <section
351
+ style={{
352
+ display: currencies.length > 1 ? 'grid' : 'block',
353
+ gridTemplateColumns: '50% 50%',
354
+ width: '100%',
355
+ }}>
356
+ {currencies.map((x, i) => {
357
+ const selected = i === paymentCurrencyIndex;
352
358
  return (
353
- <MenuItem key={x.id} value={x.id}>
354
- <Stack direction="row" spacing={1}>
355
- <Avatar src={x.logo} alt={x.name} sx={{ width: 20, height: 20 }} />
356
- <Typography color={selected ? 'text.primary' : 'text.secondary'}>{x.symbol}</Typography>
359
+ <Card
360
+ key={x.id}
361
+ variant="outlined"
362
+ onClick={() => handleCurrencyChange(i)}
363
+ className={selected ? 'cko-payment-card' : 'cko-payment-card-unselect'}>
364
+ <Stack direction="row" alignItems="center">
365
+ <Avatar src={x.logo} alt={x.name} sx={{ width: 30, height: 30, marginRight: '10px' }} />
366
+ <div>
367
+ <Typography variant="h5" component="div">
368
+ {x.symbol}
369
+ </Typography>
370
+ <Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
371
+ {(x as any).method.name}
372
+ </Typography>
373
+ </div>
357
374
  </Stack>
358
- </MenuItem>
375
+ </Card>
359
376
  );
360
377
  })}
361
- </Select>
378
+ </section>
362
379
  )}
363
380
  />
364
381
  </Stack>
@@ -383,7 +400,6 @@ export default function PaymentForm({
383
400
  size="large"
384
401
  onClick={onAction}
385
402
  fullWidth
386
- loadingPosition="end"
387
403
  disabled={state.submitting || state.paying || state.stripePaying}
388
404
  loading={state.submitting || state.paying}>
389
405
  {state.submitting || state.paying ? t('payment.checkout.processing') : buttonText}
@@ -331,6 +331,52 @@ export const Root = styled(Box)<{ mode: LiteralUnion<'standalone' | 'inline' | '
331
331
  .cko-payment-methods {
332
332
  }
333
333
 
334
+ .cko-payment-card {
335
+ position: relative;
336
+ border: 2px solid #3773f2;
337
+ padding: 5px 10px;
338
+ margin: 5px 0;
339
+ cursor: pointer;
340
+ }
341
+
342
+ .cko-payment-card::before {
343
+ content: '';
344
+ position: absolute;
345
+ right: 0;
346
+ top: 0;
347
+ border: 12px solid #3773f2;
348
+ border-bottom-color: transparent;
349
+ border-left-color: transparent;
350
+ }
351
+
352
+ .cko-payment-card-unselect {
353
+ border: 2px solid #bbb;
354
+ padding: 5px 10px;
355
+ margin: 5px 0;
356
+ cursor: pointer;
357
+ }
358
+
359
+ .cko-payment-card:nth-child(odd) {
360
+ margin-right: 5px;
361
+ }
362
+
363
+ .cko-payment-card-unselect:nth-child(odd) {
364
+ margin-right: 5px;
365
+ }
366
+
367
+ .cko-payment-card::after {
368
+ content: '';
369
+ width: 6px;
370
+ height: 10px;
371
+ position: absolute;
372
+ right: 3px;
373
+ top: 0px;
374
+ border: 2px solid #fff;
375
+ border-top-color: transparent;
376
+ border-left-color: transparent;
377
+ transform: rotate(35deg);
378
+ }
379
+
334
380
  .cko-payment-submit {
335
381
  .MuiButtonBase-root {
336
382
  border-radius: 0;