Skip to content

Commit a795949

Browse files
Abhishek ChorotiyaAbhishek Chorotiya
authored andcommitted
feat: amazonPay
1 parent 7870ba4 commit a795949

File tree

9 files changed

+427
-2
lines changed

9 files changed

+427
-2
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
open AmazonPayHelpers
2+
open AmazonPayHooks
3+
4+
@react.component
5+
let make = (~amazonPayToken) => {
6+
let token = amazonPayToken->amazonPayTokenMapper
7+
useAmazonPay(token)
8+
9+
let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
10+
11+
let showFullScreenLoader = () => {
12+
Utils.messageParentWindow([
13+
("fullscreen", true->JSON.Encode.bool),
14+
("param", "paymentloader"->JSON.Encode.string),
15+
("iframeId", iframeId->JSON.Encode.string),
16+
])
17+
}
18+
<div onClick={_ => showFullScreenLoader()} id="AmazonPayButton" />
19+
}
20+
21+
let default = make
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
open Utils
2+
open AmazonPayTypes
3+
4+
external renderAmazonPayButton: (~buttonId: string, ~config: amazonPayConfigType) => unit =
5+
"amazon.Pay.renderJSButton"
6+
7+
let deliveryPriceMapper = dict => {
8+
{
9+
amount: dict->getInt("amount", 0),
10+
displayAmount: dict->getString("display_amount", ""),
11+
currencyCode: dict->getString("currency_code", ""),
12+
}
13+
}
14+
15+
let shippingMethodMapper = dict => {
16+
{
17+
shippingMethodName: dict->getString("shipping_method_name", ""),
18+
shippingMethodCode: dict->getString("shipping_method_code", ""),
19+
}
20+
}
21+
22+
let deliveryOptionMapper = dict => {
23+
let priceData = dict->getDictFromDict("price")->deliveryPriceMapper
24+
{
25+
id: dict->getString("id", ""),
26+
price: {
27+
amount: priceData.displayAmount,
28+
currencyCode: priceData.currencyCode,
29+
},
30+
shippingMethod: dict->getDictFromDict("shipping_method")->shippingMethodMapper,
31+
isDefault: dict->getBool("is_default", false),
32+
}
33+
}
34+
35+
let amazonPayTokenMapper = dict => {
36+
{
37+
walletName: dict->getString("wallet_name", ""),
38+
merchantId: dict->getString("merchant_id", ""),
39+
ledgerCurrency: dict->getString("ledger_currency", ""),
40+
storeId: dict->getString("store_id", ""),
41+
paymentIntent: dict->getString("payment_intent", ""),
42+
totalTaxAmount: dict->getString("total_tax_amount", ""),
43+
totalBaseAmount: dict->getString("total_base_amount", ""),
44+
deliveryOptions: dict
45+
->getArray("delivery_options")
46+
->Array.map(item => item->getDictFromJson->deliveryOptionMapper),
47+
}
48+
}
49+
50+
let amazonPayBody = (amazonCheckoutSessionId, shipping) => {
51+
let wallet = {
52+
amazon_pay: {
53+
checkout_session_id: amazonCheckoutSessionId,
54+
},
55+
}
56+
let paymentMethodData = [("wallet", wallet->Identity.anyTypeToJson)]->getJsonFromArrayOfJson
57+
[
58+
("payment_method", "wallet"->JSON.Encode.string),
59+
("payment_method_data", paymentMethodData),
60+
("capture_method", "automatic"->JSON.Encode.string),
61+
("payment_experience", "invoke_sdk_client"->JSON.Encode.string),
62+
("payment_method_type", "amazon_pay"->JSON.Encode.string),
63+
("shipping", shipping->Identity.anyTypeToJson),
64+
]
65+
}
66+
67+
let defaultShipping = {
68+
address: {
69+
line1: "",
70+
line2: "",
71+
line3: "",
72+
city: "",
73+
state: "",
74+
zip: "",
75+
country: "",
76+
first_name: "",
77+
last_name: "",
78+
},
79+
phone: {number: ""},
80+
}
81+
82+
let getShippingAddressFromEvent = event => {
83+
let eventDict = event->getDictFromJson
84+
let shippingAddressDict = eventDict->getDictFromDict("shippingAddress")
85+
let fullName = shippingAddressDict->getString("name", "")
86+
let addressLine1 = shippingAddressDict->getString("addressLine1", "")
87+
let addressLine2 = shippingAddressDict->getString("addressLine2", "")
88+
let addressLine3 = shippingAddressDict->getString("addressLine3", "")
89+
let city = shippingAddressDict->getString("city", "")
90+
let state = shippingAddressDict->getString("stateOrRegion", "")
91+
let zip = shippingAddressDict->getString("postalCode", "")
92+
let country = shippingAddressDict->getString("countryCode", "")
93+
let phoneNumber = shippingAddressDict->getString("phoneNumber", "")
94+
95+
let (firstName, lastName) = fullName->getFirstAndLastNameFromFullName
96+
97+
{
98+
address: {
99+
line1: addressLine1,
100+
line2: addressLine2,
101+
line3: addressLine3,
102+
city,
103+
state,
104+
zip,
105+
country,
106+
first_name: firstName->JSON.Decode.string->Option.getOr(""),
107+
last_name: lastName->JSON.Decode.string->Option.getOr(""),
108+
},
109+
phone: {number: phoneNumber},
110+
}
111+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
open AmazonPayTypes
2+
open AmazonPayHelpers
3+
open Utils
4+
5+
let useAmazonPay = token => {
6+
let scriptLoadStatus = CommonHooks.useScript("https://static-na.payments-amazon.com/checkout.js")
7+
8+
let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
9+
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
10+
let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), AmazonPay)
11+
let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
12+
let isManualRetryEnabled = Recoil.useRecoilValueFromAtom(RecoilAtoms.isManualRetryEnabled)
13+
let shippingAddressRef = React.useRef(defaultShipping)
14+
15+
let getAmazonPayConfig = (sessionToken: amazonPayTokenType): amazonPayConfigType => {
16+
let baseAmount = sessionToken.totalBaseAmount->Float.fromString->Option.getOr(0.0)
17+
let taxAmount = sessionToken.totalTaxAmount->Float.fromString->Option.getOr(0.0)
18+
let defaultShippingAmount =
19+
sessionToken.deliveryOptions
20+
->Array.find(option => option.isDefault)
21+
->Option.mapOr("0.0", option => option.price.amount)
22+
23+
let shippingAmount = defaultShippingAmount->Float.fromString->Option.getOr(0.0)
24+
25+
let totalOrderAmount = (baseAmount +. taxAmount +. shippingAmount)->Float.toString
26+
let currencyCode = sessionToken.ledgerCurrency
27+
28+
{
29+
merchantId: sessionToken.merchantId,
30+
ledgerCurrency: sessionToken.ledgerCurrency,
31+
sandbox: true,
32+
checkoutLanguage: "en_US",
33+
productType: "PayAndShip",
34+
placement: "Checkout",
35+
buttonColor: "Gold",
36+
estimatedOrderAmount: {
37+
amount: totalOrderAmount,
38+
currencyCode: sessionToken.ledgerCurrency,
39+
},
40+
checkoutSessionConfig: {
41+
storeId: sessionToken.storeId,
42+
scopes: ["name", "email", "phoneNumber", "billingAddress"],
43+
paymentDetails: {
44+
paymentIntent: sessionToken.paymentIntent,
45+
canHandlePendingAuthorization: false,
46+
},
47+
},
48+
onInitCheckout: event => {
49+
shippingAddressRef.current = event->getShippingAddressFromEvent
50+
51+
{
52+
totalShippingAmount: {amount: defaultShippingAmount, currencyCode},
53+
totalBaseAmount: {amount: sessionToken.totalBaseAmount, currencyCode},
54+
totalTaxAmount: {amount: sessionToken.totalTaxAmount, currencyCode},
55+
totalChargeAmount: {amount: totalOrderAmount, currencyCode},
56+
totalDiscountAmount: {amount: "0.00", currencyCode},
57+
deliveryOptions: sessionToken.deliveryOptions,
58+
}
59+
},
60+
onShippingAddressSelection: event => {
61+
shippingAddressRef.current = event->getShippingAddressFromEvent
62+
63+
{
64+
totalShippingAmount: {amount: defaultShippingAmount, currencyCode},
65+
totalBaseAmount: {amount: sessionToken.totalBaseAmount, currencyCode},
66+
totalTaxAmount: {amount: sessionToken.totalTaxAmount, currencyCode},
67+
totalChargeAmount: {amount: totalOrderAmount, currencyCode},
68+
totalDiscountAmount: {amount: "0.00", currencyCode},
69+
deliveryOptions: sessionToken.deliveryOptions,
70+
}
71+
},
72+
onDeliveryOptionSelection: event => {
73+
let selectedOption =
74+
sessionToken.deliveryOptions->Array.find(option => option.id === event.deliveryOptions.id)
75+
let newShippingAmount = selectedOption->Option.mapOr("0.0", option => option.price.amount)
76+
let baseAmount = sessionToken.totalBaseAmount->Float.fromString->Option.getOr(0.0)
77+
let taxAmount = sessionToken.totalTaxAmount->Float.fromString->Option.getOr(0.0)
78+
let shippingAmount = newShippingAmount->Float.fromString->Option.getOr(0.0)
79+
let newTotalAmount = (baseAmount +. taxAmount +. shippingAmount)->Float.toString
80+
81+
{
82+
totalShippingAmount: {amount: newShippingAmount, currencyCode},
83+
totalBaseAmount: {amount: sessionToken.totalBaseAmount, currencyCode},
84+
totalTaxAmount: {amount: sessionToken.totalTaxAmount, currencyCode},
85+
totalChargeAmount: {amount: newTotalAmount, currencyCode},
86+
totalDiscountAmount: {amount: "0.00", currencyCode},
87+
}
88+
},
89+
onCompleteCheckout: event => {
90+
let amazonCheckoutSessionId =
91+
event->getDictFromJson->getString("amazonCheckoutSessionId", "")
92+
93+
intent(
94+
~bodyArr=amazonPayBody(amazonCheckoutSessionId, shippingAddressRef.current),
95+
~confirmParam={
96+
return_url: options.wallets.walletReturnUrl,
97+
publishableKey,
98+
},
99+
~handleUserError=true,
100+
~manualRetry=isManualRetryEnabled,
101+
)
102+
},
103+
onCancel: _ => {
104+
intent(
105+
~bodyArr=amazonPayBody("", shippingAddressRef.current),
106+
~confirmParam={
107+
return_url: options.wallets.walletReturnUrl,
108+
publishableKey,
109+
},
110+
~handleUserError=true,
111+
~manualRetry=isManualRetryEnabled,
112+
)
113+
},
114+
}
115+
}
116+
117+
let isRenderedOnce = React.useRef(false)
118+
let config = React.useMemo(() => getAmazonPayConfig(token), [token])
119+
120+
React.useEffect3(() => {
121+
let shouldRender =
122+
scriptLoadStatus == "ready" && config.merchantId != "" && !isRenderedOnce.current
123+
124+
if shouldRender {
125+
try {
126+
renderAmazonPayButton(~buttonId="#AmazonPayButton", ~config)
127+
isRenderedOnce.current = true
128+
} catch {
129+
| e => Console.error2("Error rendering Amazon Pay button:", e)
130+
}
131+
}
132+
None
133+
}, (config, scriptLoadStatus, isRenderedOnce.current))
134+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let make = React.lazy_(() => Js.import(AmazonPay.default))
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
type deliveryPrice = {
2+
amount: int,
3+
displayAmount: string,
4+
currencyCode: string,
5+
}
6+
7+
type shippingMethod = {
8+
shippingMethodName: string,
9+
shippingMethodCode: string,
10+
}
11+
12+
type priceAmount = {
13+
amount: string,
14+
currencyCode: string,
15+
}
16+
17+
type deliveryOption = {
18+
id: string,
19+
price: priceAmount,
20+
shippingMethod: shippingMethod,
21+
isDefault: bool,
22+
}
23+
24+
type amazonPayTokenType = {
25+
walletName: string,
26+
merchantId: string,
27+
ledgerCurrency: string,
28+
storeId: string,
29+
paymentIntent: string,
30+
totalTaxAmount: string,
31+
totalBaseAmount: string,
32+
deliveryOptions: array<deliveryOption>,
33+
}
34+
35+
type estimatedOrderAmount = {
36+
amount: string,
37+
currencyCode: string,
38+
}
39+
40+
type paymentDetails = {
41+
paymentIntent: string,
42+
canHandlePendingAuthorization: bool,
43+
}
44+
45+
type checkoutSessionConfig = {
46+
storeId: string,
47+
scopes: array<string>,
48+
paymentDetails: paymentDetails,
49+
}
50+
51+
type amountDetails = {
52+
amount: string,
53+
currencyCode: string,
54+
}
55+
56+
type cartDetails = {
57+
totalShippingAmount: amountDetails,
58+
totalBaseAmount: amountDetails,
59+
totalTaxAmount: amountDetails,
60+
totalChargeAmount: amountDetails,
61+
totalDiscountAmount: amountDetails,
62+
deliveryOptions: array<deliveryOption>,
63+
}
64+
65+
type shippingAddressResponse = {
66+
totalShippingAmount: amountDetails,
67+
totalBaseAmount: amountDetails,
68+
totalTaxAmount: amountDetails,
69+
totalChargeAmount: amountDetails,
70+
totalDiscountAmount: amountDetails,
71+
deliveryOptions: array<deliveryOption>,
72+
}
73+
74+
type deliveryOptionResponse = {
75+
totalShippingAmount: amountDetails,
76+
totalBaseAmount: amountDetails,
77+
totalTaxAmount: amountDetails,
78+
totalChargeAmount: amountDetails,
79+
totalDiscountAmount: amountDetails,
80+
}
81+
82+
type deliveryOptionEventDetails = {id: string}
83+
84+
type deliveryOptionEvent = {deliveryOptions: deliveryOptionEventDetails}
85+
86+
type amazonPayConfigType = {
87+
merchantId: string,
88+
ledgerCurrency: string,
89+
sandbox: bool,
90+
checkoutLanguage: string,
91+
productType: string,
92+
placement: string,
93+
buttonColor: string,
94+
estimatedOrderAmount: estimatedOrderAmount,
95+
checkoutSessionConfig: checkoutSessionConfig,
96+
onInitCheckout: JSON.t => cartDetails,
97+
onShippingAddressSelection: JSON.t => shippingAddressResponse,
98+
onDeliveryOptionSelection: deliveryOptionEvent => deliveryOptionResponse,
99+
onCompleteCheckout: JSON.t => unit,
100+
onCancel: JSON.t => unit,
101+
}
102+
103+
type amazonPayData = {checkout_session_id: string}
104+
105+
type wallet = {amazon_pay: amazonPayData}
106+
107+
type shippingAddress = {
108+
line1: string,
109+
line2: string,
110+
line3: string,
111+
city: string,
112+
state: string,
113+
zip: string,
114+
country: string,
115+
first_name: string,
116+
last_name: string,
117+
}
118+
119+
type shippingPhone = {number: string}
120+
121+
type shipping = {
122+
address: shippingAddress,
123+
phone: shippingPhone,
124+
}

0 commit comments

Comments
 (0)