@koomipay/react 2.1.0 → 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 +300 -115
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/types/components/ApplePayComponent.d.ts +12 -8
- package/dist/cjs/types/components/CardComponent.d.ts +24 -4
- package/dist/cjs/types/components/CheckoutContainer.d.ts +2 -1
- package/dist/cjs/types/components/GooglePayComponent.d.ts +13 -19
- package/dist/cjs/types/components/QrPayComponent.d.ts +23 -9
- package/dist/cjs/types/components/RedirectPayComponent.d.ts +19 -0
- package/dist/cjs/types/components/RedirectShopperInIframe.d.ts +6 -0
- package/dist/cjs/types/index.d.ts +8 -2
- package/dist/cjs/types/lib/client.d.ts +44 -13
- package/dist/cjs/types/utils/format.d.ts +4 -0
- package/dist/es/index.js +1 -1
- package/dist/es/types/components/ApplePayComponent.d.ts +12 -8
- package/dist/es/types/components/CardComponent.d.ts +24 -4
- package/dist/es/types/components/CheckoutContainer.d.ts +2 -1
- package/dist/es/types/components/GooglePayComponent.d.ts +13 -19
- package/dist/es/types/components/QrPayComponent.d.ts +23 -9
- package/dist/es/types/components/RedirectPayComponent.d.ts +19 -0
- package/dist/es/types/components/RedirectShopperInIframe.d.ts +6 -0
- package/dist/es/types/index.d.ts +8 -2
- package/dist/es/types/lib/client.d.ts +44 -13
- package/dist/es/types/utils/format.d.ts +4 -0
- package/dist/koomipay.css +1 -1
- package/package.json +1 -1
- package/dist/cjs/types/components/GrabPayComponent.d.ts +0 -9
- package/dist/es/types/components/GrabPayComponent.d.ts +0 -9
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
|
-
###
|
|
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
|
-
###
|
|
38
|
+
### Waiting for client to be ready
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
Call function `isAvailable()` and waiting it's done
|
|
38
41
|
|
|
39
42
|
```jsx
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
})
|
|
43
|
+
koomipay.isAvailable().then(() => {
|
|
44
|
+
setPaymentMethods(koomipay.getPaymentMethods())
|
|
45
|
+
}).catch(err => console.error(err))
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
###
|
|
48
|
+
### Get payment methods
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
When koomipay client is ready (after calling `isAvailable()`), call function `getPaymentMethods()` to get available payment methods
|
|
49
51
|
|
|
50
52
|
```jsx
|
|
51
|
-
|
|
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
|
-
|
|
56
|
+
### Checkout
|
|
71
57
|
|
|
72
|
-
|
|
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
|
-
###
|
|
60
|
+
### Example
|
|
84
61
|
|
|
85
62
|
```jsx
|
|
86
|
-
import React, { useEffect, useCallback } from "react"
|
|
87
|
-
import
|
|
88
|
-
import {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 [
|
|
96
|
-
const
|
|
97
|
-
const [
|
|
98
|
-
const [paymentMethods, setPaymentMethods] = useState([])
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
<
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
<
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|