@hanzo/commerce 2.0.0 → 2.1.0
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/components/checkout/index.tsx +17 -65
- package/components/checkout/payment/contact-info.tsx +59 -0
- package/components/checkout/payment/index.tsx +117 -0
- package/components/checkout/payment/pay-with-bank-transfer.tsx +106 -0
- package/components/checkout/payment/pay-with-card.tsx +193 -0
- package/components/checkout/{pay-with-crypto.tsx → payment/pay-with-crypto.tsx} +51 -28
- package/components/checkout/payment/payment-methods/amex.tsx +32 -0
- package/components/checkout/payment/payment-methods/diners-club.tsx +13 -0
- package/components/checkout/payment/payment-methods/discover.tsx +25 -0
- package/components/checkout/payment/payment-methods/index.tsx +24 -0
- package/components/checkout/payment/payment-methods/jcb.tsx +26 -0
- package/components/checkout/payment/payment-methods/mastercard.tsx +27 -0
- package/components/checkout/payment/payment-methods/visa.tsx +25 -0
- package/components/checkout/shipping-info.tsx +4 -10
- package/package.json +5 -3
- package/service/impls/standalone/orders/index.ts +42 -20
- package/service/impls/standalone/standalone-service.ts +11 -6
- package/types/checkout.ts +6 -0
- package/types/commerce-service.ts +3 -3
- package/types/index.ts +1 -3
- package/util/index.ts +2 -1
- package/util/square-payment.ts +43 -0
- package/components/checkout/contact-info.tsx +0 -83
- package/components/checkout/pay-by-bank-transfer.tsx +0 -76
|
@@ -22,11 +22,9 @@ import {
|
|
|
22
22
|
import { useAuth } from '@hanzo/auth/service'
|
|
23
23
|
import { Ethereum as EthIconFromAuth } from '@hanzo/auth/icons'
|
|
24
24
|
|
|
25
|
-
import Eth from '
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import { useCommerce } from '../..'
|
|
29
|
-
import { formatPrice } from '../../util'
|
|
25
|
+
import Eth from '../icons/eth'
|
|
26
|
+
import { useCommerce, type TransactionStatus } from '../../..'
|
|
27
|
+
import { formatPrice } from '../../../util'
|
|
30
28
|
|
|
31
29
|
declare global {
|
|
32
30
|
interface Window{
|
|
@@ -36,10 +34,15 @@ declare global {
|
|
|
36
34
|
|
|
37
35
|
const PayWithCrypto: React.FC<{
|
|
38
36
|
setCurrentStep: (currentStep: number) => void
|
|
37
|
+
transactionStatus: TransactionStatus
|
|
38
|
+
setTransactionStatus: (status: TransactionStatus) => void
|
|
39
|
+
storePaymentInfo: (paymentInfo: any) => Promise<void>
|
|
39
40
|
}> = observer(({
|
|
40
|
-
setCurrentStep
|
|
41
|
+
setCurrentStep,
|
|
42
|
+
transactionStatus,
|
|
43
|
+
setTransactionStatus,
|
|
44
|
+
storePaymentInfo
|
|
41
45
|
}) => {
|
|
42
|
-
|
|
43
46
|
const c = useCommerce()
|
|
44
47
|
const auth = useAuth()
|
|
45
48
|
const [loadingPrice, setLoadingPrice] = useState(false)
|
|
@@ -47,7 +50,6 @@ const PayWithCrypto: React.FC<{
|
|
|
47
50
|
const [amount, setAmount] = useState<number>()
|
|
48
51
|
const [availableAmount, setAvailableAmount] = useState<number>()
|
|
49
52
|
const [provider, setProvider] = useState<ethers.BrowserProvider>()
|
|
50
|
-
const [transactionStatus, setTransactionStatus] = useState<'unpaid' | 'paid' | 'confirmed' | 'error'>('unpaid')
|
|
51
53
|
|
|
52
54
|
//const selectedToken = 'eth'
|
|
53
55
|
|
|
@@ -126,12 +128,17 @@ const PayWithCrypto: React.FC<{
|
|
|
126
128
|
setTransactionStatus('paid')
|
|
127
129
|
|
|
128
130
|
provider.waitForTransaction(tx.hash)
|
|
129
|
-
.then((receipt) => {
|
|
131
|
+
.then(async (receipt) => {
|
|
130
132
|
console.log(receipt)
|
|
133
|
+
await storePaymentInfo({
|
|
134
|
+
ether,
|
|
135
|
+
addr: process.env.NEXT_PUBLIC_ETH_PAYMENT_ADDRESS,
|
|
136
|
+
receipt
|
|
137
|
+
})
|
|
131
138
|
setTransactionStatus('confirmed')
|
|
132
139
|
})
|
|
133
140
|
.catch((error) => {
|
|
134
|
-
console.
|
|
141
|
+
console.error(error)
|
|
135
142
|
setTransactionStatus('error')
|
|
136
143
|
})
|
|
137
144
|
} catch (err) {
|
|
@@ -141,9 +148,20 @@ const PayWithCrypto: React.FC<{
|
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
const
|
|
151
|
+
const nextStep = async () => {
|
|
152
|
+
await storePaymentInfo({
|
|
153
|
+
paymentMethod: 'crypto'
|
|
154
|
+
})
|
|
155
|
+
setCurrentStep(2)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const cryptoPriceWidget = !!!(auth.user?.walletAddress) ? (
|
|
145
159
|
<div className='w-full mx-auto max-w-[20rem]'>
|
|
146
|
-
<Button
|
|
160
|
+
<Button
|
|
161
|
+
variant='outline'
|
|
162
|
+
className='w-full flex items-center gap-2'
|
|
163
|
+
onClick={auth.associateWallet.bind(auth)}
|
|
164
|
+
>
|
|
147
165
|
<EthIconFromAuth height={20}/>Connect your wallet
|
|
148
166
|
</Button>
|
|
149
167
|
</div>
|
|
@@ -165,30 +183,35 @@ const PayWithCrypto: React.FC<{
|
|
|
165
183
|
<div>Available funds in your wallet: {availableAmount} ETH</div>
|
|
166
184
|
<div>
|
|
167
185
|
<Input value={amount ? amount/(10**18) : amount} contentEditable={false}/>
|
|
168
|
-
<div className='relative flex items-center gap-2 -top-[32px] justify-end px-2 py-1 rounded-lg bg-muted-4 w-fit text-xs float-right mr-3'
|
|
186
|
+
<div className='relative flex items-center gap-2 -top-[32px] justify-end px-2 py-1 rounded-lg bg-muted-4 w-fit text-xs float-right mr-3'>
|
|
187
|
+
<Eth height={10}/>
|
|
188
|
+
ETH
|
|
189
|
+
</div>
|
|
169
190
|
</div>
|
|
170
|
-
{transactionStatus === '
|
|
171
|
-
<
|
|
172
|
-
onClick={() => sendPayment(amount ? amount/(10**18) : 0)}
|
|
173
|
-
disabled={!amount || loadingPrice}
|
|
174
|
-
>
|
|
175
|
-
Pay now
|
|
176
|
-
</Button>
|
|
191
|
+
{transactionStatus === 'error' ? (
|
|
192
|
+
<h4 className='text-destructive'>There was an error while confirming the transaction.</h4>
|
|
177
193
|
) : transactionStatus === 'paid' ? (
|
|
178
194
|
<h4>Waiting for transaction to be confirmed on chain.</h4>
|
|
179
|
-
) : (
|
|
195
|
+
) : transactionStatus === 'confirmed' ? (
|
|
180
196
|
<h4>Transaction confirmed!</h4>
|
|
181
|
-
)}
|
|
197
|
+
) : null}
|
|
182
198
|
</div>
|
|
183
199
|
)
|
|
184
200
|
|
|
185
201
|
return (
|
|
186
|
-
<div className='flex flex-col gap-6'>
|
|
187
|
-
{
|
|
188
|
-
|
|
189
|
-
<Button
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
<div className='flex flex-col gap-6 mt-6'>
|
|
203
|
+
{cryptoPriceWidget}
|
|
204
|
+
{transactionStatus === 'unpaid' ? (
|
|
205
|
+
<Button
|
|
206
|
+
onClick={() => sendPayment(amount ? amount/(10**18) : 0)}
|
|
207
|
+
className='mx-auto w-full'
|
|
208
|
+
disabled={loadingPrice}
|
|
209
|
+
>
|
|
210
|
+
Pay
|
|
211
|
+
</Button>
|
|
212
|
+
) : (
|
|
213
|
+
<Button onClick={nextStep} className='mx-auto w-full'>Continue</Button>
|
|
214
|
+
)}
|
|
192
215
|
</div>
|
|
193
216
|
)
|
|
194
217
|
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Amex: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
role="img"
|
|
8
|
+
viewBox="0 0 38 24"
|
|
9
|
+
aria-labelledby="pi-american_express"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<title id="pi-american_express">American Express</title>
|
|
13
|
+
<g fill="none">
|
|
14
|
+
<path
|
|
15
|
+
fill="#000"
|
|
16
|
+
d="M35,0 L3,0 C1.3,0 0,1.3 0,3 L0,21 C0,22.7 1.4,24 3,24 L35,24 C36.7,24 38,22.7 38,21 L38,3 C38,1.3 36.6,0 35,0 Z"
|
|
17
|
+
opacity=".07"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
fill="#006FCF"
|
|
21
|
+
d="M35,1 C36.1,1 37,1.9 37,3 L37,21 C37,22.1 36.1,23 35,23 L3,23 C1.9,23 1,22.1 1,21 L1,3 C1,1.9 1.9,1 3,1 L35,1"
|
|
22
|
+
/>
|
|
23
|
+
<path
|
|
24
|
+
fill="#FFF"
|
|
25
|
+
d="M8.971,10.268 L9.745,12.144 L8.203,12.144 L8.971,10.268 Z M25.046,10.346 L22.069,10.346 L22.069,11.173 L24.998,11.173 L24.998,12.412 L22.075,12.412 L22.075,13.334 L25.052,13.334 L25.052,14.073 L27.129,11.828 L25.052,9.488 L25.046,10.346 L25.046,10.346 Z M10.983,8.006 L14.978,8.006 L15.865,9.941 L16.687,8 L27.057,8 L28.135,9.19 L29.25,8 L34.013,8 L30.494,11.852 L33.977,15.68 L29.143,15.68 L28.065,14.49 L26.94,15.68 L10.03,15.68 L9.536,14.49 L8.406,14.49 L7.911,15.68 L4,15.68 L7.286,8 L10.716,8 L10.983,8.006 Z M19.646,9.084 L17.407,9.084 L15.907,12.62 L14.282,9.084 L12.06,9.084 L12.06,13.894 L10,9.084 L8.007,9.084 L5.625,14.596 L7.18,14.596 L7.674,13.406 L10.27,13.406 L10.764,14.596 L13.484,14.596 L13.484,10.661 L15.235,14.602 L16.425,14.602 L18.165,10.673 L18.165,14.603 L19.623,14.603 L19.647,9.083 L19.646,9.084 Z M28.986,11.852 L31.517,9.084 L29.695,9.084 L28.094,10.81 L26.546,9.084 L20.652,9.084 L20.652,14.602 L26.462,14.602 L28.076,12.864 L29.624,14.602 L31.499,14.602 L28.987,11.852 L28.986,11.852 Z"
|
|
26
|
+
/>
|
|
27
|
+
</g>
|
|
28
|
+
</svg>
|
|
29
|
+
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export default Amex
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const DinersClub: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg enableBackground="new 0 0 780 500" viewBox="0 0 780 500" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
6
|
+
<path d="m40 0h700c22.092 0 40 17.909 40 40v420c0 22.092-17.908 40-40 40h-700c-22.091 0-40-17.908-40-40v-420c0-22.091 17.909-40 40-40z" fill="#0079be"/>
|
|
7
|
+
<path d="m599.93 251.45c0-99.415-82.98-168.13-173.9-168.1h-78.242c-92.003-.033-167.73 68.705-167.73 168.1 0 90.93 75.727 165.64 167.73 165.2h78.242c90.914.436 173.9-74.294 173.9-165.2z" fill="#fff"/>
|
|
8
|
+
<path d="m348.28 97.43c-84.07.027-152.19 68.308-152.21 152.58.02 84.258 68.144 152.53 152.21 152.56 84.09-.027 152.23-68.303 152.24-152.56-.011-84.272-68.149-152.55-152.24-152.58z" fill="#0079be"/>
|
|
9
|
+
<path d="m252.07 249.6c.08-41.181 25.746-76.297 61.94-90.25v180.48c-36.194-13.948-61.861-49.045-61.94-90.23zm131 90.274v-180.53c36.207 13.92 61.914 49.057 61.979 90.257-.065 41.212-25.772 76.322-61.979 90.269z" fill="#fff"/>
|
|
10
|
+
</svg>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
export default DinersClub
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Discover: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
role="img"
|
|
8
|
+
viewBox="0 0 38 24"
|
|
9
|
+
aria-labelledby="pi-discover"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<title id="pi-discover">Discover</title>
|
|
13
|
+
<path
|
|
14
|
+
d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"
|
|
15
|
+
fill="#000"
|
|
16
|
+
opacity=".07"
|
|
17
|
+
/>
|
|
18
|
+
<path d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32" fill="#FFF" />
|
|
19
|
+
<path d="M37 16.95V21c0 1.1-.9 2-2 2H23.228c7.896-1.815 12.043-4.601 13.772-6.05z" fill="#EDA024" />
|
|
20
|
+
<path fill="#494949" d="M9 11h20v2H9z" />
|
|
21
|
+
<path d="M22 12c0 1.7-1.3 3-3 3s-3-1.4-3-3 1.4-3 3-3c1.7 0 3 1.3 3 3z" fill="#EDA024" />
|
|
22
|
+
</svg>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
export default Discover
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LockKeyhole } from 'lucide-react'
|
|
2
|
+
import Amex from './amex'
|
|
3
|
+
import Discover from './discover'
|
|
4
|
+
import Mastercard from './mastercard'
|
|
5
|
+
import Visa from './visa'
|
|
6
|
+
import DinersClub from './diners-club'
|
|
7
|
+
import Jcb from './jcb'
|
|
8
|
+
|
|
9
|
+
const PaymentMethods: React.FC = () => {
|
|
10
|
+
return (
|
|
11
|
+
<div className='flex gap-1 items-center'>
|
|
12
|
+
<LockKeyhole className='w-4 h-4'/>
|
|
13
|
+
<span className='hidden sm:flex text-sm'>Secure payments with</span>
|
|
14
|
+
<Amex className='w-9 h-5'/>
|
|
15
|
+
<Discover className='w-9 h-5'/>
|
|
16
|
+
<Mastercard className='w-9 h-5'/>
|
|
17
|
+
<Visa className='w-9 h-5'/>
|
|
18
|
+
<DinersClub className='w-9 h-5'/>
|
|
19
|
+
<Jcb className='w-9 h-5'/>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default PaymentMethods
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Jcb: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg viewBox="0 0 750 471" xmlns="http://www.w3.org/2000/svg" {...props}>
|
|
6
|
+
<linearGradient id="a" x1=".031608%" x2="99.974315%" y1="49.999857%" y2="49.999857%">
|
|
7
|
+
<stop offset="0" stop-color="#007b40"/>
|
|
8
|
+
<stop offset="1" stop-color="#55b330"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="b" x1=".471693%" x2="99.986009%" y1="49.999826%" y2="49.999826%">
|
|
11
|
+
<stop offset="0" stop-color="#1d2970"/><stop offset="1" stop-color="#006dba"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
<linearGradient id="c" x1=".113881%" x2="99.986%" y1="50.000896%" y2="50.000896%">
|
|
14
|
+
<stop offset="0" stop-color="#6e2b2f"/><stop offset="1" stop-color="#e30138"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<g fill="none">
|
|
17
|
+
<rect fill="#0e4c96" height="471" rx="40" width="750"/>
|
|
18
|
+
<path d="m617.243183 346.766281c0 41.614606-33.728291 75.359693-75.359693 75.359693h-409.126667v-297.881058c0-41.6262334 33.733028-75.3704593 75.36486-75.3704593l409.1215-.0004307-.000431 297.892255z" fill="#fff"/>
|
|
19
|
+
<path d="m483.858874 242.044797c11.683825.253488 23.437314-.515991 35.07713.400086 11.78724 2.200795 14.627911 20.042991 4.156336 25.887628-7.141594 3.849604-15.632844 1.432185-23.379012 2.113697h-15.854454zm41.832952-32.14431c2.596665 9.164192-6.237923 17.391631-15.065909 16.130079h-26.767043c.184884-8.642125-.367529-18.021593.272179-26.208903 10.723889.301723 21.548523-.615814 32.209341.48019 4.581405 1.149705 8.413541 4.915859 9.351432 9.598634zm64.428586-135.9032616c.49746 17.5012286.071059 35.9264246.213178 53.7829666-.034453 72.596166.072352 145.193982-.054694 217.789111-.468476 27.207289-24.582372 50.844375-51.600147 51.387391-27.045829.111757-54.094452.015934-81.141353.047803v-109.751206c29.469604-.153488 58.958644.307709 88.416125-.231697 13.667199-.858825 28.632506-9.875899 29.269759-24.91422 1.610381-15.101891-12.631152-25.550189-26.152184-27.20169-5.198323-.135142-5.043964-1.514838 0-2.116712 12.891974-2.786489 23.019579-16.133185 19.22569-29.498743-3.236266-14.057855-18.772947-19.498809-31.69642-19.472461-26.351818-.179156-52.709067-.025409-79.06297-.076658.171189-20.488702-.35448-41.000457.285314-61.473746 2.087651-26.7159874 26.805893-48.7478321 53.447049-48.269708 26.283407-.0002868 52.567978.0005741 78.850653-.0004306z" fill="url(#a)"/>
|
|
20
|
+
<path d="m159.740429 125.040498c.67326-27.1638388 24.88819-50.6114681 51.874368-51.0079582 26.944696-.0825712 53.891407-.0116279 80.836874-.0353144-.074031 90.8852626.148234 181.7764466-.11137 272.6579966-1.038003 26.83358-24.989753 49.833439-51.678945 50.30707-26.996341.098665-53.995081.013782-80.992344.042205v-113.453622c26.222611 6.194616 53.722126 8.831643 80.473394 4.721367 15.992103-2.574874 33.487529-10.424216 38.901855-27.014675 3.985861-14.1913 1.741462-29.125965 2.333766-43.691102v-33.824924h-46.296781c-.20814 22.370604.426012 44.780906-.3351 67.125566-1.248296 13.734107-14.845999 22.459889-27.799718 21.994694-16.066681.168734-47.898878-11.640005-47.898878-11.640005-.080058-41.916996.466764-94.407411.692879-136.181298z" fill="url(#b)"/>
|
|
21
|
+
<path d="m309.719995 197.390136c-2.434207.517244-.490854-8.300677-1.113697-11.646172.165935-21.150327-.346253-42.323013.28342-63.458137 2.082823-26.8287443 26.991544-48.9157165 53.73903-48.288171h78.765812c-.073902 90.88469.147933 181.775284-.111154 272.656278-1.038994 26.834093-24.992062 49.833168-51.681319 50.308358-26.997482.099699-53.99738.014212-80.995789.042636v-124.297304c18.440112 15.128222 43.49944 17.484624 66.471655 17.52586 17.317057-.006029 34.533908-2.675619 51.35019-6.67055v-22.772637c-18.953485 9.446312-41.233335 15.445518-62.243398 10.017669-14.655694-3.650599-25.293855-17.811283-25.056232-32.935762-1.698503-15.72852 7.524118-32.335319 22.981724-37.011349 19.190686-6.00831 40.10755-1.412401 58.096107 6.39794 3.854182 2.018155 7.764316 4.521547 6.221799-1.920751v-17.899686c-30.084562-7.157407-62.101499-9.791953-92.328705-2.004739-8.748245 2.468155-17.271248 6.211028-24.379443 11.956517z" fill="url(#c)"/>
|
|
22
|
+
</g>
|
|
23
|
+
</svg>
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
export default Jcb
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Mastercard: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg
|
|
6
|
+
viewBox="0 0 38 24"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
role="img"
|
|
9
|
+
aria-labelledby="pi-master"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<title id="pi-master">Mastercard</title>
|
|
13
|
+
<path
|
|
14
|
+
opacity=".07"
|
|
15
|
+
d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"
|
|
16
|
+
/>
|
|
17
|
+
<path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32" />
|
|
18
|
+
<circle fill="#EB001B" cx="15" cy="12" r="7" />
|
|
19
|
+
<circle fill="#F79E1B" cx="23" cy="12" r="7" />
|
|
20
|
+
<path
|
|
21
|
+
fill="#FF5F00"
|
|
22
|
+
d="M22 12c0-2.4-1.2-4.5-3-5.7-1.8 1.3-3 3.4-3 5.7s1.2 4.5 3 5.7c1.8-1.2 3-3.3 3-5.7z"
|
|
23
|
+
/>
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
export default Mastercard
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { type LucideProps } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
const Visa: React.FC<LucideProps> = (props: LucideProps) => (
|
|
5
|
+
<svg
|
|
6
|
+
viewBox="0 0 38 24"
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
role="img"
|
|
9
|
+
aria-labelledby="pi-visa"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<title id="pi-visa">Visa</title>
|
|
13
|
+
<path
|
|
14
|
+
opacity=".07"
|
|
15
|
+
d="M35 0H3C1.3 0 0 1.3 0 3v18c0 1.7 1.4 3 3 3h32c1.7 0 3-1.3 3-3V3c0-1.7-1.4-3-3-3z"
|
|
16
|
+
/>
|
|
17
|
+
<path fill="#fff" d="M35 1c1.1 0 2 .9 2 2v18c0 1.1-.9 2-2 2H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h32" />
|
|
18
|
+
<path
|
|
19
|
+
d="M28.3 10.1H28c-.4 1-.7 1.5-1 3h1.9c-.3-1.5-.3-2.2-.6-3zm2.9 5.9h-1.7c-.1 0-.1 0-.2-.1l-.2-.9-.1-.2h-2.4c-.1 0-.2 0-.2.2l-.3.9c0 .1-.1.1-.1.1h-2.1l.2-.5L27 8.7c0-.5.3-.7.8-.7h1.5c.1 0 .2 0 .2.2l1.4 6.5c.1.4.2.7.2 1.1.1.1.1.1.1.2zm-13.4-.3l.4-1.8c.1 0 .2.1.2.1.7.3 1.4.5 2.1.4.2 0 .5-.1.7-.2.5-.2.5-.7.1-1.1-.2-.2-.5-.3-.8-.5-.4-.2-.8-.4-1.1-.7-1.2-1-.8-2.4-.1-3.1.6-.4.9-.8 1.7-.8 1.2 0 2.5 0 3.1.2h.1c-.1.6-.2 1.1-.4 1.7-.5-.2-1-.4-1.5-.4-.3 0-.6 0-.9.1-.2 0-.3.1-.4.2-.2.2-.2.5 0 .7l.5.4c.4.2.8.4 1.1.6.5.3 1 .8 1.1 1.4.2.9-.1 1.7-.9 2.3-.5.4-.7.6-1.4.6-1.4 0-2.5.1-3.4-.2-.1.2-.1.2-.2.1zm-3.5.3c.1-.7.1-.7.2-1 .5-2.2 1-4.5 1.4-6.7.1-.2.1-.3.3-.3H18c-.2 1.2-.4 2.1-.7 3.2-.3 1.5-.6 3-1 4.5 0 .2-.1.2-.3.2M5 8.2c0-.1.2-.2.3-.2h3.4c.5 0 .9.3 1 .8l.9 4.4c0 .1 0 .1.1.2 0-.1.1-.1.1-.1l2.1-5.1c-.1-.1 0-.2.1-.2h2.1c0 .1 0 .1-.1.2l-3.1 7.3c-.1.2-.1.3-.2.4-.1.1-.3 0-.5 0H9.7c-.1 0-.2 0-.2-.2L7.9 9.5c-.2-.2-.5-.5-.9-.6-.6-.3-1.7-.5-1.9-.5L5 8.2z"
|
|
20
|
+
fill="#142688"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
export default Visa
|
|
@@ -40,11 +40,9 @@ const shippingFormSchema = z.object({
|
|
|
40
40
|
|
|
41
41
|
const ShippingInfo: React.FC<{
|
|
42
42
|
orderId?: string,
|
|
43
|
-
paymentMethod?: string,
|
|
44
43
|
setCurrentStep: (currentStep: number) => void
|
|
45
44
|
}> = ({
|
|
46
45
|
orderId,
|
|
47
|
-
paymentMethod,
|
|
48
46
|
setCurrentStep
|
|
49
47
|
}) => {
|
|
50
48
|
const auth = useAuth()
|
|
@@ -65,10 +63,10 @@ const ShippingInfo: React.FC<{
|
|
|
65
63
|
})
|
|
66
64
|
|
|
67
65
|
const onSubmit = async (values: z.infer<typeof shippingFormSchema>) => {
|
|
68
|
-
if (auth.user && orderId
|
|
69
|
-
await cmmc.
|
|
66
|
+
if (auth.user && orderId) {
|
|
67
|
+
await cmmc.updateOrderShippingInfo(orderId, values)
|
|
70
68
|
}
|
|
71
|
-
setCurrentStep(
|
|
69
|
+
setCurrentStep(3)
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
return (
|
|
@@ -198,11 +196,7 @@ const ShippingInfo: React.FC<{
|
|
|
198
196
|
/>
|
|
199
197
|
</div>
|
|
200
198
|
</div>
|
|
201
|
-
|
|
202
|
-
<div className='flex gap-4 items-center mt-6'>
|
|
203
|
-
<Button variant='outline' onClick={() => setCurrentStep(2)} className='mx-auto w-full'>Back</Button>
|
|
204
|
-
<Button type='submit' className='mx-auto w-full'>Confirm order</Button>
|
|
205
|
-
</div>
|
|
199
|
+
<Button type='submit' className='mx-auto w-full mt-4'>Confirm order</Button>
|
|
206
200
|
</form>
|
|
207
201
|
</Form>
|
|
208
202
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/commerce",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Library with shopping cart components.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -29,10 +29,12 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"ethers": "^6.11.1",
|
|
31
31
|
"next-usequerystate": "^1.17.0",
|
|
32
|
-
"react-mobile-picker": "^1.0.0"
|
|
32
|
+
"react-mobile-picker": "^1.0.0",
|
|
33
|
+
"react-square-web-payments-sdk": "^3.2.1",
|
|
34
|
+
"square": "^35.0.0"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
|
-
"@hanzo/auth": "^2.0.
|
|
37
|
+
"@hanzo/auth": "^2.0.1",
|
|
36
38
|
"@hanzo/ui": "^3.0.0",
|
|
37
39
|
"@hookform/resolvers": "^3.3.4",
|
|
38
40
|
"firebase": "^10.8.0",
|
|
@@ -23,9 +23,10 @@ const getDBInstance = (name: string): Firestore => {
|
|
|
23
23
|
|
|
24
24
|
interface SavedOrder {
|
|
25
25
|
email: string
|
|
26
|
-
|
|
26
|
+
name: string
|
|
27
27
|
// TODO: add shippingInfo type
|
|
28
28
|
shippingInfo?: any
|
|
29
|
+
paymentInfo?: any
|
|
29
30
|
status: string
|
|
30
31
|
timestamp: FieldValue
|
|
31
32
|
items: ActualLineItemSnapshot[]
|
|
@@ -33,16 +34,16 @@ interface SavedOrder {
|
|
|
33
34
|
|
|
34
35
|
const createOrder = async (
|
|
35
36
|
email: string,
|
|
36
|
-
paymentMethod: string,
|
|
37
37
|
items: ActualLineItemSnapshot[],
|
|
38
38
|
options: {
|
|
39
39
|
dbName: string
|
|
40
40
|
ordersTable: string
|
|
41
|
-
}
|
|
41
|
+
},
|
|
42
|
+
name?: string
|
|
42
43
|
): Promise<{
|
|
43
44
|
success: boolean,
|
|
44
45
|
error: any,
|
|
45
|
-
id?: string
|
|
46
|
+
id?: string,
|
|
46
47
|
}> => {
|
|
47
48
|
|
|
48
49
|
let error: any | null = null
|
|
@@ -52,7 +53,7 @@ const createOrder = async (
|
|
|
52
53
|
try {
|
|
53
54
|
await setDoc(doc(ordersRef, orderId), {
|
|
54
55
|
email,
|
|
55
|
-
|
|
56
|
+
name: name ?? '',
|
|
56
57
|
status: 'open',
|
|
57
58
|
timestamp: serverTimestamp(),
|
|
58
59
|
items,
|
|
@@ -67,17 +68,13 @@ const createOrder = async (
|
|
|
67
68
|
return { success: !error, error }
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
const
|
|
71
|
+
const updateOrderShippingInfo = async (
|
|
71
72
|
orderId: string,
|
|
72
|
-
|
|
73
|
-
paymentMethod: string,
|
|
74
|
-
// TODO: add shippingInfo type
|
|
75
|
-
items: ActualLineItemSnapshot[],
|
|
73
|
+
shippingInfo: any,
|
|
76
74
|
options: {
|
|
77
75
|
dbName: string
|
|
78
76
|
ordersTable: string
|
|
79
|
-
}
|
|
80
|
-
shippingInfo?: any
|
|
77
|
+
}
|
|
81
78
|
): Promise<{
|
|
82
79
|
success: boolean,
|
|
83
80
|
error: any
|
|
@@ -88,20 +85,45 @@ const updateOrder = async (
|
|
|
88
85
|
|
|
89
86
|
try {
|
|
90
87
|
await setDoc(doc(ordersRef, orderId), {
|
|
91
|
-
email,
|
|
92
|
-
paymentMethod,
|
|
93
88
|
shippingInfo,
|
|
94
|
-
status: 'open',
|
|
95
89
|
timestamp: serverTimestamp(),
|
|
96
|
-
|
|
97
|
-
} satisfies SavedOrder)
|
|
90
|
+
}, { merge: true })
|
|
98
91
|
}
|
|
99
92
|
catch (e) {
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
console.error('Error writing item document: ', e)
|
|
94
|
+
error = e
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { success: !error, error }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const updateOrderPaymentInfo = async (
|
|
101
|
+
orderId: string,
|
|
102
|
+
paymentInfo: any,
|
|
103
|
+
options: {
|
|
104
|
+
dbName: string
|
|
105
|
+
ordersTable: string
|
|
106
|
+
}
|
|
107
|
+
): Promise<{
|
|
108
|
+
success: boolean,
|
|
109
|
+
error: any
|
|
110
|
+
}> => {
|
|
111
|
+
|
|
112
|
+
let error: any | null = null
|
|
113
|
+
const ordersRef = collection(getDBInstance(options.dbName), options.ordersTable)
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await setDoc(doc(ordersRef, orderId), {
|
|
117
|
+
paymentInfo,
|
|
118
|
+
timestamp: serverTimestamp(),
|
|
119
|
+
}, { merge: true })
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
console.error('Error writing item document: ', e)
|
|
123
|
+
error = e
|
|
102
124
|
}
|
|
103
125
|
|
|
104
126
|
return { success: !error, error }
|
|
105
127
|
}
|
|
106
128
|
|
|
107
|
-
export { createOrder,
|
|
129
|
+
export { createOrder, updateOrderShippingInfo, updateOrderPaymentInfo }
|
|
@@ -17,7 +17,8 @@ import type {
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
createOrder as createOrderHelper,
|
|
20
|
-
|
|
20
|
+
updateOrderShippingInfo as updateOrderShippingInfoHelper,
|
|
21
|
+
updateOrderPaymentInfo as updateOrderPaymentInfoHelper
|
|
21
22
|
} from './orders'
|
|
22
23
|
|
|
23
24
|
import ActualLineItem, { type ActualLineItemSnapshot } from './actual-line-item'
|
|
@@ -88,16 +89,20 @@ class StandaloneService
|
|
|
88
89
|
})
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
async createOrder(email: string,
|
|
92
|
+
async createOrder(email: string, name?: string): Promise<string | undefined> {
|
|
92
93
|
const snapshot = this.takeSnapshot()
|
|
93
|
-
const order = await createOrderHelper(email,
|
|
94
|
+
const order = await createOrderHelper(email, snapshot.items, this._options, name) // didn't want to have two levels of 'items'
|
|
94
95
|
return order.id
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// TODO: add shippingInfo type
|
|
98
|
-
async
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
async updateOrderShippingInfo(orderId: string, shippingInfo: any): Promise<void> {
|
|
100
|
+
updateOrderShippingInfoHelper(orderId, shippingInfo, this._options)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// TODO: add paymentInfo type
|
|
104
|
+
async updateOrderPaymentInfo(orderId: string, paymentInfo: any): Promise<void> {
|
|
105
|
+
updateOrderPaymentInfoHelper(orderId, paymentInfo, this._options)
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
takeSnapshot = (): StandaloneServiceSnapshot => ({
|
|
@@ -13,9 +13,9 @@ interface CommerceService extends ObsLineItemRef {
|
|
|
13
13
|
|
|
14
14
|
getCartCategorySubtotal(categoryId: string): number
|
|
15
15
|
|
|
16
|
-
createOrder(email: string,
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
createOrder(email: string, name?: string): Promise<string | undefined>
|
|
17
|
+
updateOrderShippingInfo(orderId: string, shippingInfo: any): Promise<void>
|
|
18
|
+
updateOrderPaymentInfo(orderId: string, paymentInfo: any): Promise<void>
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Sets the tokens at each level supplied.
|
package/types/index.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
export type { default as CommerceService } from './commerce-service'
|
|
3
3
|
export type { default as Product } from './product'
|
|
4
4
|
export type { default as Category } from './category'
|
|
5
|
+
export type { default as TransactionStatus } from './checkout'
|
|
5
6
|
export * from './line-item'
|
|
6
7
|
export * from './facet'
|
|
7
8
|
export * from './string-mutator'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
package/util/index.ts
CHANGED
|
@@ -28,4 +28,5 @@ export function formatPrice(price: number): string {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
|
|
31
|
+
export { default as useSyncSkuParamWithCurrentItem } from './use-sync-sku-param-w-current-item'
|
|
32
|
+
export { default as processSquareCardPayment } from './square-payment'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import { Client, Environment } from 'square'
|
|
4
|
+
import { randomUUID } from 'crypto'
|
|
5
|
+
|
|
6
|
+
// https://developer.squareup.com/blog/online-payments-with-square-and-react/
|
|
7
|
+
declare global {
|
|
8
|
+
interface BigInt {
|
|
9
|
+
toJSON(): string;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
BigInt.prototype.toJSON = function() { return this.toString(); }
|
|
14
|
+
|
|
15
|
+
const { paymentsApi } = new Client({
|
|
16
|
+
accessToken: process.env.SQUARE_ACCESS_TOKEN,
|
|
17
|
+
environment: process.env.SQUARE_ENVIRONMENT as Environment
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const processPayment = async (sourceId: string, amount: number, verificationToken: string) => {
|
|
21
|
+
// Square API accepts amount in cents
|
|
22
|
+
const amountInCents = amount * 100
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const { result } = await paymentsApi.createPayment({
|
|
26
|
+
idempotencyKey: randomUUID(),
|
|
27
|
+
sourceId: sourceId,
|
|
28
|
+
amountMoney: {
|
|
29
|
+
currency: 'USD',
|
|
30
|
+
amount: BigInt(amountInCents)
|
|
31
|
+
},
|
|
32
|
+
verificationToken
|
|
33
|
+
})
|
|
34
|
+
return result
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error processing payment:', error)
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
processPayment as default
|
|
43
|
+
}
|