@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.
@@ -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 './icons/eth'
26
- import Btc from './icons/btc'
27
- import Usdt from './icons/usdt'
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.log(error)
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 payWidget = !!!(auth.user?.walletAddress) ? (
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 variant='outline' className='w-full flex items-center gap-2' onClick={auth.associateWallet.bind(auth)}>
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'><Eth height={10}/>ETH</div>
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 === 'unpaid' || transactionStatus === 'error' ? (
171
- <Button
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
- {payWidget}
188
- <div className='flex gap-4 items-center mt-6'>
189
- <Button variant='outline' onClick={() => setCurrentStep(1)} className='mx-auto w-full'>Back</Button>
190
- <Button onClick={() => setCurrentStep(3)} disabled={transactionStatus !== 'confirmed'} className='mx-auto w-full'>Continue</Button>
191
- </div>
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 && paymentMethod) {
69
- await cmmc.updateOrder(orderId, auth.user.email, paymentMethod, values)
66
+ if (auth.user && orderId) {
67
+ await cmmc.updateOrderShippingInfo(orderId, values)
70
68
  }
71
- setCurrentStep(4)
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.0.0",
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.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
- paymentMethod: string
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
- paymentMethod,
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 updateOrder = async (
71
+ const updateOrderShippingInfo = async (
71
72
  orderId: string,
72
- email: string,
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
- items,
97
- } satisfies SavedOrder)
90
+ }, { merge: true })
98
91
  }
99
92
  catch (e) {
100
- console.error('Error writing item document: ', e)
101
- error = e
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, updateOrder }
129
+ export { createOrder, updateOrderShippingInfo, updateOrderPaymentInfo }
@@ -17,7 +17,8 @@ import type {
17
17
 
18
18
  import {
19
19
  createOrder as createOrderHelper,
20
- updateOrder as updateOrderHelper
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, paymentMethod: string): Promise<string | undefined> {
92
+ async createOrder(email: string, name?: string): Promise<string | undefined> {
92
93
  const snapshot = this.takeSnapshot()
93
- const order = await createOrderHelper(email, paymentMethod, snapshot.items, this._options) // didn't want to have two levels of 'items'
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 updateOrder(orderId: string, email: string, paymentMethod: string, shippingInfo?: any): Promise<void> {
99
- const snapshot = this.takeSnapshot()
100
- updateOrderHelper(orderId, email, paymentMethod, snapshot.items, this._options, shippingInfo) // didn't want to have two levels of 'items'
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 => ({
@@ -0,0 +1,6 @@
1
+
2
+ type TransactionStatus = 'unpaid' | 'paid' | 'confirmed' | 'error'
3
+
4
+ export {
5
+ type TransactionStatus as default
6
+ }
@@ -13,9 +13,9 @@ interface CommerceService extends ObsLineItemRef {
13
13
 
14
14
  getCartCategorySubtotal(categoryId: string): number
15
15
 
16
- createOrder(email: string, paymentMethod: string): Promise<string | undefined>
17
- // TODO: add shippingInfo type
18
- updateOrder(orderId: string, email: string, paymentMethod: string, shippingInfo?: any): Promise<void>
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
+ }