@koomipay/react 2.0.4 → 2.1.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 CHANGED
@@ -20,7 +20,9 @@ First, install [@koomipay/react][koomipay-npm] using the following command
20
20
  npm install @koomipay/react --save
21
21
  ```
22
22
 
23
- ### Create Koomipay Client
23
+ ### Guide for version 2.1.1:
24
+
25
+ #### Create Koomipay Client
24
26
 
25
27
  To create a new Koomipay client, use the `createClient()` method from the package, and pass in the **Checkout API Key** getting from [Koomipay Portal][koomipay-portal]
26
28
 
@@ -29,147 +31,330 @@ import { createClient } from "@koomipay/react"
29
31
 
30
32
  const koomipay = createClient({
31
33
  apiKey: "your_checkout_api_key", // should put to env
34
+ countryCode: "SG", // current country code
32
35
  })
33
36
  ```
34
37
 
35
- ### Get available Payment Methods
38
+ ### Waiting for client to be ready
36
39
 
37
- Then get all the available payment methods for your Merchant Account by calling the `getPaymentMethods()` method
40
+ Call function `isAvailable()` and waiting it's done
38
41
 
39
42
  ```jsx
40
- const paymentMethods = await koomipay.getPaymentMethods({
41
- amount: { currency: "SGD", price: 100 },
42
- countryCode: "SG",
43
- })
43
+ koomipay.isAvailable().then(() => {
44
+ setPaymentMethods(koomipay.getPaymentMethods())
45
+ }).catch(err => console.error(err))
44
46
  ```
45
47
 
46
- ### Checkout
48
+ ### Get payment methods
47
49
 
48
- Depending on your scenario, you can use either `checkout()` method (normal checkout without **capturing**) or `instantCheckout()`, which will trigger a capturing immediately after checkout
50
+ When koomipay client is ready (after calling `isAvailable()`), call function `getPaymentMethods()` to get available payment methods
49
51
 
50
52
  ```jsx
51
- const response = await koomipay.instantCheckout({
52
- orderID: "Order #01",
53
- paymentMethod: paymentData,
54
- amount: {
55
- currency: "SGD",
56
- price: 100,
57
- },
58
- items: [
59
- {
60
- productID: "product_01",
61
- productName: "Product 01",
62
- quantity: 1,
63
- price: 100,
64
- },
65
- ],
66
- returnUrl: document.location.origin + "/api/checkout/callback",
67
- })
53
+ koomipay.getPaymentMethods()
68
54
  ```
69
55
 
70
- Remember to check the response for 3Ds redirect url
56
+ ### Checkout
71
57
 
72
- ```jsx
73
- if (response?.data?.success) {
74
- const { data } = response.data
75
- if (data.resultCode === "Authorised") {
76
- window.location.href = "/checkout/success"
77
- } else if (data.resultCode === "RedirectShopper") {
78
- window.open(data.redirect3ds)
79
- }
80
- }
81
- ```
58
+ Function `instantCheckout()` is removed, now we need to handle payment callback.
82
59
 
83
- ### Full example
60
+ ### Example
84
61
 
85
62
  ```jsx
86
- import React, { useEffect, useCallback } from "react"
87
- import ReactDOM from "react-dom"
88
- import { createClient, CheckoutComponent } from "@koomipay/react"
89
-
90
- const koomipay = createClient({
91
- apiKey: "your_test_key", // should put to process.env
92
- })
63
+ import React, { useContext, useState, Fragment, useEffect, useRef, useCallback } from "react"
64
+ import Head from "next/head"
65
+ import { useCartContext } from "contexts/CartContext"
66
+ import Image from "next/image"
67
+ import Layout from "components/Layout"
68
+ import { createClient, GooglePayComponent, ApplePayComponent, CardComponent, QrPayComponent, RedirectPayComponent } from "@koomipay/react"
69
+ import type { CardComponentRef, QrPayComponentRef, KoomiPayClient } from "@koomipay/react"
70
+ import _ from "lodash"
71
+ import rs from "randomstring"
72
+ import { round } from "lodash"
93
73
 
94
74
  function Checkout() {
95
- const [valid, setValid] = useState(false)
96
- const [paymentData, setPaymentData] = useState(false)
97
- const [currentPaymentMethod, setCurrentPaymentMethod] = useState(null)
98
- const [paymentMethods, setPaymentMethods] = useState([])
99
-
100
- const fetchPaymentMethods = useCallback(async () => {
101
- try {
102
- const paymentMethods = await koomipay.getPaymentMethods({
103
- amount: { currency: "SGD", price: 100 },
104
- countryCode: "SG",
105
- })
106
- setPaymentMethods(paymentMethods)
107
- setCurrentPaymentMethod(paymentMethods[0])
108
- } catch (error) {
109
- console.error("Failed to load payment methods")
110
- }
111
- }, [])
75
+ const [loading, setLoading] = useState(false)
76
+ const koomipay = useRef<KoomiPayClient | undefined>(undefined)
77
+ const [paymentMethod, setPaymentMethod] = useState<any>(null)
78
+ const [paymentMethods, setPaymentMethods] = useState<any>([])
79
+ const [orderID, setOrderID] = useState("")
80
+ const { cart, setCart } = useCartContext()
81
+ const [errorMessage, setErrorMessage] = useState<any>("")
82
+ const [checkoutType, setCheckoutType] = useState<"instant" | "normal" | string>("normal")
83
+ const total = round(
84
+ cart.reduce((total, c) => total + c.product.price * c.quantity, 0),
85
+ 2
86
+ )
87
+
88
+ const handleCompleteCheckout = useCallback(() => {
89
+ setCart([])
90
+ window.location.href = `/checkout/complete?orderID=${orderID}&resultCode=Authorised`
91
+ }, [orderID, setCart])
112
92
 
113
93
  useEffect(() => {
114
- fetchPaymentMethods()
115
- }, [fetchPaymentMethods])
116
-
117
- async function handleSubmit(event) {
118
- event.preventDefault()
119
- try {
120
- const response = await koomipay.instantCheckout({
121
- orderID: "Order #01",
122
- paymentMethod: paymentData,
123
- amount: {
124
- currency: "SGD",
125
- price: 100,
126
- },
127
- items: [
128
- {
129
- productID: "product_01",
130
- productName: "Product 01",
131
- quantity: 1,
132
- price: 100,
133
- },
134
- ],
135
- returnUrl: document.location.origin + "/api/checkout/callback",
136
- })
94
+ if (total > 0) {
95
+ const apiKey = localStorage.getItem("api-key")
96
+ setCheckoutType(localStorage.getItem("checkout-type") || "normal")
97
+ if (koomipay.current) return
137
98
 
138
- if (response?.data?.success) {
139
- const { data } = response.data
140
- if (data.resultCode === "Authorised") {
141
- window.location.href = "/checkout/success"
142
- } else if (data.resultCode === "RedirectShopper") {
143
- window.open(data.redirect3ds)
144
- }
145
- }
146
- } catch (error) {
147
- console.log(error)
99
+ koomipay.current = createClient({
100
+ apiKey: apiKey || process.env.NEXT_PUBLIC_PAYMENT_API_KEY || "",
101
+ countryCode: "SG",
102
+ })
103
+ setLoading(true)
104
+ setErrorMessage("")
105
+ koomipay.current
106
+ .isAvailable()
107
+ .then(() => {
108
+ const pms = koomipay.current?.getPaymentMethods()
109
+ setPaymentMethods(pms)
110
+ setPaymentMethod(pms?.[0])
111
+ }).catch((err: any) => {
112
+ console.error(err)
113
+ setErrorMessage(err.message)
114
+ }).finally(() => {
115
+ setLoading(false)
116
+ })
148
117
  }
118
+ }, [total])
119
+
120
+
121
+ function handleSubmitPayment(confirm: (payload: any) => Promise<void>) {
122
+ setErrorMessage("")
123
+ const items = cart?.map((item) => ({
124
+ productID: item.product.id,
125
+ productName: item.product.name,
126
+ quantity: item.quantity,
127
+ price: item.product.price,
128
+ }))
129
+ const orderID = rs.generate(10)
130
+ setOrderID(orderID)
131
+ confirm({
132
+ orderID: orderID,
133
+ items,
134
+ autoCapture: checkoutType !== "normal",
135
+ amount: {
136
+ currency: "SGD",
137
+ price: total,
138
+ },
139
+ metadata: {
140
+ receiptText: "Receipt Test",
141
+ },
142
+ })
149
143
  }
150
144
 
151
145
  return (
152
- <form onSubmit={handleSubmit}>
153
- {paymentMethods.map((method) => (
154
- <div key={method.name}>
155
- <input type="radio" name="method" onClick={() => setCurrentPaymentMethod(method)} />
156
- <span>{method.name}</span>
146
+ <Layout>
147
+ <main>
148
+ <Head>
149
+ <title>Checkout</title>
150
+ </Head>
151
+ <h1 className="mb-4 text-2xl font-bold text-center">Checkout</h1>
152
+ <div className="grid grid-cols-1 gap-8 md:grid-cols-2">
153
+ <div>
154
+ <h3 className="mb-2 text-sm font-semibold text-gray-500">Order Summary</h3>
155
+ <div className="pt-4 mb-4 border-t border-b border-solid border-slate-200">
156
+ {cart.length ? (
157
+ cart.map((c, index) => (
158
+ <div key={c.product.id} className="flex items-center mb-4">
159
+ <div className="flex items-center flex-grow">
160
+ <figure className="relative w-10 h-10 mr-4">
161
+ <Image src={c.product.image} fill alt={`Product ${c.product.name} image`} />
162
+ </figure>
163
+ {c.product.name}
164
+ </div>
165
+ <div className="w-1/12 ">${c.product.price}</div>
166
+ <div className="w-1/12 text-center">{c.quantity}</div>
167
+ <div className="w-1/12 text-right">${round(c.quantity * c.product.price, 2)}</div>
168
+ </div>
169
+ ))
170
+ ) : (
171
+ <p>No item in cart</p>
172
+ )}
173
+ </div>
174
+ <div className="flex items-center justify-end mb-4">
175
+ <span className="mr-8 text-xl">Total</span>
176
+ <strong className="text-xl">${total}</strong>
177
+ </div>
178
+ </div>
179
+ <div>
180
+ {
181
+ errorMessage && (
182
+ <div className="text-lg font-semibold text-red-500">{errorMessage}</div>
183
+ )
184
+ }
185
+ <h3 className="mb-2 text-sm font-semibold text-gray-500">Select Your Payment Method</h3>
186
+ {/* Payment Methods */}
187
+ {loading && (
188
+ <div
189
+ className={`mx-auto w-64 h-64 inset-0 flex flex-col gap-3 items-center justify-center ${loading ? "" : "opacity-0 pointer-events-none"
190
+ }`}
191
+ >
192
+ <div className="w-16 h-16">
193
+ <svg
194
+ className="w-full h-full animate-spin"
195
+ xmlns="http://www.w3.org/2000/svg"
196
+ fill="none"
197
+ viewBox="0 0 24 24"
198
+ >
199
+ <circle
200
+ className="opacity-25"
201
+ cx="12"
202
+ cy="12"
203
+ r="10"
204
+ stroke="currentColor"
205
+ strokeWidth="4"
206
+ ></circle>
207
+ <path
208
+ className="opacity-75"
209
+ fill="currentColor"
210
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
211
+ ></path>
212
+ </svg>
213
+ </div>
214
+ <p className="sr-only">Loading</p>
215
+ </div>
216
+ )}
217
+ {
218
+ (paymentMethods || [])
219
+ .map((pm: any, index: number) => {
220
+ const active = paymentMethod?.type === pm.type
221
+ const id = `payment-method-${index + 1}`
222
+ return (
223
+ <div className="flex items-center mb-4" key={id}>
224
+ <input
225
+ id={id}
226
+ type="radio"
227
+ name="payment-methods"
228
+ value={pm.type}
229
+ className="w-5 h-5 text-transparent appearance-none bg-none checked:text-orange-500 accent-orange-500 checked:ring-1 checked:ring-orange-500"
230
+ checked={active}
231
+ onChange={(event) => {
232
+ if (event.target.checked) {
233
+ setPaymentMethod(pm)
234
+ }
235
+ }}
236
+ />
237
+ <label htmlFor={id} className="block ml-2 text-sm font-medium text-gray-900 cursor-pointer">
238
+ {pm.name}
239
+ </label>
240
+ </div>
241
+ )
242
+ })
243
+ }
244
+
245
+ {/* Card component */}
246
+ {paymentMethod?.type && ["card", "scheme"].includes(paymentMethod?.type) && (
247
+ <>
248
+ <CardComponent
249
+ paymentMethod={paymentMethod}
250
+ client={koomipay.current}
251
+ countryCode={"SG"}
252
+ onPaymentComplete={handleCompleteCheckout}
253
+ onPaymentError={err => {
254
+ setErrorMessage(err)
255
+ }}
256
+ onSubmit={handleSubmitPayment}
257
+ amount={{
258
+ currency: "SGD",
259
+ price: total,
260
+ }}
261
+ showPayButton={true}
262
+ />
263
+ </>
264
+ )}
265
+ {/* End Card component */}
266
+
267
+ {/* GooglePay component */}
268
+ {paymentMethod?.type && ["googlepay"].includes(paymentMethod?.type) && (
269
+ <GooglePayComponent
270
+ paymentMethod={paymentMethod}
271
+ client={koomipay.current}
272
+ countryCode={"SG"}
273
+ onPaymentComplete={handleCompleteCheckout}
274
+ onPaymentError={err => {
275
+ setErrorMessage(err)
276
+ }}
277
+ onSubmit={handleSubmitPayment}
278
+ amount={{
279
+ currency: "SGD",
280
+ price: total,
281
+ }}
282
+ />
283
+ )}
284
+ {/* End GooglePay component */}
285
+
286
+ {/* ApplePay component */}
287
+ {paymentMethod?.type && ["applepay"].includes(paymentMethod?.type) && (
288
+ <ApplePayComponent
289
+ paymentMethod={paymentMethod}
290
+ client={koomipay.current}
291
+ countryCode={"SG"}
292
+ onPaymentComplete={handleCompleteCheckout}
293
+ onPaymentError={err => {
294
+ setErrorMessage(err)
295
+ }}
296
+ onSubmit={handleSubmitPayment}
297
+ amount={{
298
+ currency: "SGD",
299
+ price: total,
300
+ }}
301
+ />
302
+ )}
303
+ {/* End ApplePay component */}
304
+
305
+ {/* QrPay component */}
306
+ {paymentMethod?.type && ["paynow", "wechatpay", "wechatpayQR"].includes(paymentMethod?.type) && (
307
+ <>
308
+ <QrPayComponent
309
+ paymentMethod={{
310
+ ...paymentMethod,
311
+ options: {
312
+ qrCodeSize: "large",
313
+ countDownTime: 3
314
+ }
315
+ }}
316
+ client={koomipay.current}
317
+ countryCode={"SG"}
318
+ onPaymentComplete={handleCompleteCheckout}
319
+ onPaymentError={err => {
320
+ setErrorMessage(err)
321
+ }}
322
+ onSubmit={handleSubmitPayment}
323
+ amount={{
324
+ currency: "SGD",
325
+ price: total,
326
+ }}
327
+ />
328
+ </>
329
+ )}
330
+ {/* End QrPay component */}
331
+
332
+ {/* Alipay component */}
333
+ {paymentMethod?.type && ["alipay"].includes(paymentMethod?.type) && (
334
+ <>
335
+ <RedirectPayComponent
336
+ paymentMethod={paymentMethod}
337
+ client={koomipay.current}
338
+ countryCode={"SG"}
339
+ onPaymentComplete={handleCompleteCheckout}
340
+ onPaymentError={err => {
341
+ setErrorMessage(err)
342
+ }}
343
+ onSubmit={handleSubmitPayment}
344
+ amount={{
345
+ currency: "SGD",
346
+ price: total,
347
+ }}
348
+ showPayButton={true}
349
+ />
350
+ </>
351
+ )}
352
+ {/* End alipay component */}
353
+
354
+ </div>
157
355
  </div>
158
- ))}
159
- <CheckoutContainer
160
- client={koomipay}
161
- amount={{
162
- currency: "SGD",
163
- price: 100,
164
- }}
165
- paymentMethod={currentPaymentMethod}
166
- onValid={setValid}
167
- onChange={setPaymentData}
168
- />
169
- <button type="submit" disabled={!valid}>
170
- Pay
171
- </button>
172
- </form>
356
+ </main>
357
+ </Layout>
173
358
  )
174
359
  }
175
360