Skip to content

Commit

Permalink
feat(applepay): calculate tax amount dynamically while changing shipp…
Browse files Browse the repository at this point in the history
…ing address in apple pay sdk

Calculate tax amount and shipping cost dynamically while changing shipping address in apple pay sdk
and update the new amount.
  • Loading branch information
Sagnik Mitra authored and Sagnik Mitra committed Sep 12, 2024
1 parent 65dc747 commit 9822330
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Components/SavedMethods.res
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ let make = (
| Some("apple_pay") =>
switch applePayToken {
| ApplePayTokenOptional(optToken) =>
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj=optToken, ~componentName)
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj=optToken, ~componentName, ~paymentMethodListValue)
| _ =>
// TODO - To be replaced with proper error message
intent(
Expand Down
2 changes: 1 addition & 1 deletion src/Payments/ApplePay.res
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ let make = (~sessionObj: option<JSON.t>, ~walletOptions, ~paymentType: CardTheme
~isManualRetryEnabled,
)
} else {
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName)
ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName, ~paymentMethodListValue)
}
} else {
let bodyDict = PaymentBody.applePayRedirectBody(~connectors)
Expand Down
3 changes: 3 additions & 0 deletions src/Payments/PaymentMethodsRecord.res
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ type paymentMethodList = {
payment_type: payment_type,
merchant_name: string,
collect_billing_details_from_wallets: bool,
is_tax_calculation_enabled: bool,
}

let defaultPaymentMethodType = {
Expand All @@ -826,6 +827,7 @@ let defaultList = {
payment_type: NONE,
merchant_name: "",
collect_billing_details_from_wallets: true,
is_tax_calculation_enabled: false,
}

let getPaymentExperienceType = str => {
Expand Down Expand Up @@ -1025,6 +1027,7 @@ let itemToObjMapper = dict => {
"collect_billing_details_from_wallets",
true,
),
is_tax_calculation_enabled: getBool(dict,"is_tax_calculation_enabled",false)
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/Types/ApplePayTypes.res
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ type shippingContact = {

type paymentResult = {token: JSON.t, billingContact: JSON.t, shippingContact: JSON.t}
type event = {validationURL: string, payment: paymentResult}
type lineItem = {
label: string,
amount: string,
@optional \"type": string,
}
type shippingAddressChangeEvent = {shippingContact: JSON.t}
type updatedOrderDetails = {newTotal: lineItem, newLineItems: array<lineItem>}
type innerSession
type session = {
begin: unit => unit,
Expand All @@ -34,6 +41,8 @@ type session = {
mutable onvalidatemerchant: event => unit,
completeMerchantValidation: JSON.t => unit,
mutable onpaymentauthorized: event => unit,
mutable onshippingcontactselected: shippingAddressChangeEvent => unit,
completeShippingContactSelection: updatedOrderDetails => unit,
completePayment: JSON.t => unit,
\"STATUS_SUCCESS": string,
\"STATUS_FAILURE": string,
Expand Down
83 changes: 81 additions & 2 deletions src/Utilities/ApplePayHelpers.res
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
open ApplePayTypes
open Utils
open TaxCalculation

let processPayment = (
~bodyArr,
Expand Down Expand Up @@ -75,7 +76,11 @@ let startApplePaySession = (
~applePayEvent: option<Types.event>=None,
~callBackFunc,
~resolvePromise: option<Core__JSON.t => unit>=None,
~clientSecret,
~publishableKey,
~isTaxCalculationEnabled=false,
) => {
open Promise
let ssn = applePaySession(3, paymentRequest)
switch applePaySessionRef.contents->Nullable.toOption {
| Some(session) =>
Expand All @@ -100,6 +105,69 @@ let startApplePaySession = (
ssn.completeMerchantValidation(merchantSession)
}

ssn.onshippingcontactselected = shippingAddressChangeEvent => {
if isTaxCalculationEnabled {
let newShippingContact =
shippingAddressChangeEvent.shippingContact
->getDictFromJson
->shippingContactItemToObjMapper
let newShippingAddress =
[
("state", newShippingContact.locality),
("country", newShippingContact.countryCode),
("zip", newShippingContact.postalCode),
]
->Array.map(((key, val)) => (key, val->Js.Json.string))
->getJsonFromArrayOfJson

let paymentMethodType = "apple_pay"->JSON.Encode.string

calculateTax(
~shippingAddress=[("address", newShippingAddress)]->getJsonFromArrayOfJson,
~logger,
~publishableKey,
~clientSecret,
~paymentMethodType,
)
->then(response => {
let taxCalculationResponse = response->getDictFromJson->taxResponseToObjMapper
let (netAmount, ordertaxAmount, shippingCost) = (
taxCalculationResponse.net_amount,
taxCalculationResponse.order_tax_amount,
taxCalculationResponse.shipping_cost,
)
let newTotal: lineItem = {
label: "Net Amount",
amount: netAmount->Int.toString,
\"type": "final",
}
let newLineItems: array<lineItem> = [
{
label: "Bag Subtotal",
amount: (netAmount - ordertaxAmount - shippingCost)->Int.toString,
\"type": "final",
},
{
label: "Order Tax Amount",
amount: ordertaxAmount->Int.toString,
\"type": "final",
},
{
label: "Shipping Cost",
amount: shippingCost->Int.toString,
\"type": "final",
},
]
let updatedOrderDetails: updatedOrderDetails = {
newTotal,
newLineItems,
}
ssn.completeShippingContactSelection(updatedOrderDetails)->resolve
})
->ignore
}
}

ssn.onpaymentauthorized = event => {
ssn.completePayment({"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson)
applePaySessionRef := Nullable.null
Expand Down Expand Up @@ -231,11 +299,19 @@ let useHandleApplePayResponse = (
))
}

let handleApplePayButtonClicked = (~sessionObj, ~componentName) => {
let handleApplePayButtonClicked = (
~sessionObj,
~componentName,
~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList,
) => {
let paymentRequest = ApplePayTypes.getPaymentRequestFromSession(~sessionObj, ~componentName)
let message = [
("applePayButtonClicked", true->JSON.Encode.bool),
("applePayPaymentRequest", paymentRequest),
(
"isTaxCalculationEnabled",
paymentMethodListValue.is_tax_calculation_enabled->JSON.Encode.bool,
),
]
messageParentWindow(message)
}
Expand All @@ -245,13 +321,16 @@ let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => {
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)

React.useCallback((ev: Window.event) => {
if !isWallet {
let json = ev.data->safeParse
let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper
if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty {
options.readOnly ? () : handleApplePayButtonClicked(~sessionObj, ~componentName)
options.readOnly
? ()
: handleApplePayButtonClicked(~sessionObj, ~componentName, ~paymentMethodListValue)
} else if areRequiredFieldsEmpty {
postFailedSubmitResponse(
~errortype="validation_error",
Expand Down
78 changes: 78 additions & 0 deletions src/Utilities/PaymentHelpers.res
Original file line number Diff line number Diff line change
Expand Up @@ -1972,3 +1972,81 @@ let deletePaymentMethod = (~ephemeralKey, ~paymentMethodId, ~logger, ~customPodU
JSON.Encode.null->resolve
})
}

let calculateTax = (
~apiKey,
~paymentId,
~clientSecret,
~paymentMethodType,
~shippingAddress,
~logger,
~customPodUri,
) => {
open Promise
let endpoint = ApiEndpoint.getApiEndPoint()
let headers = [("Content-Type", "application/json"), ("api-key", apiKey)]
let uri = `${endpoint}/payments/${paymentId}/calculate_tax`
let body = [
("client_secret", clientSecret),
("shipping", shippingAddress),
("payment_method_type", paymentMethodType),
]
logApi(
~optLogger=Some(logger),
~url=uri,
~apiLogType=Request,
~eventName=EXTERNAL_TAX_CALCULATION,
~logType=INFO,
~logCategory=API,
)
fetchApi(
uri,
~method=#POST,
~headers=headers->ApiEndpoint.addCustomPodHeader(~customPodUri),
~bodyStr=body->getJsonFromArrayOfJson->JSON.stringify,
)
->then(resp => {
let statusCode = resp->Fetch.Response.status->Int.toString
if statusCode->String.charAt(0) !== "2" {
resp
->Fetch.Response.json
->then(data => {
logApi(
~optLogger=Some(logger),
~url=uri,
~data,
~statusCode,
~apiLogType=Err,
~eventName=EXTERNAL_TAX_CALCULATION,
~logType=ERROR,
~logCategory=API,
)
JSON.Encode.null->resolve
})
} else {
logApi(
~optLogger=Some(logger),
~url=uri,
~statusCode,
~apiLogType=Response,
~eventName=EXTERNAL_TAX_CALCULATION,
~logType=INFO,
~logCategory=API,
)
resp->Fetch.Response.json
}
})
->catch(err => {
let exceptionMessage = err->formatException
logApi(
~optLogger=Some(logger),
~url=uri,
~apiLogType=NoResponse,
~eventName=DELETE_PAYMENT_METHODS_CALL,
~logType=ERROR,
~logCategory=API,
~data=exceptionMessage,
)
JSON.Encode.null->resolve
})
}
27 changes: 27 additions & 0 deletions src/Utilities/TaxCalculation.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
open Utils

type calculateTaxResponse = {
payment_id: string,
net_amount: int,
order_tax_amount: int,
shipping_cost: int,
}

let taxResponseToObjMapper = dict => {
payment_id: dict->getString("payment_id", ""),
net_amount: dict->getInt("net_amount", 0),
order_tax_amount: dict->getInt("order_tax_amount", 0),
shipping_cost: dict->getInt("shipping_cost", 0),
}

let calculateTax = (~shippingAddress, ~logger, ~clientSecret, ~publishableKey, ~paymentMethodType) => {
PaymentHelpers.calculateTax(
~clientSecret=clientSecret->Js.Json.string,
~apiKey=publishableKey,
~paymentId=clientSecret->getPaymentId,
~paymentMethodType,
~shippingAddress,
~logger,
~customPodUri="",
)
}
6 changes: 5 additions & 1 deletion src/orca-loader/Elements.res
Original file line number Diff line number Diff line change
Expand Up @@ -775,8 +775,9 @@ let make = (
switch (
dict->Dict.get("applePayButtonClicked"),
dict->Dict.get("applePayPaymentRequest"),
dict->Dict.get("isTaxCalculationEnabled")->Option.flatMap(JSON.Decode.bool)->Option.getOr(false),
) {
| (Some(val), Some(paymentRequest)) =>
| (Some(val), Some(paymentRequest), isTaxCalculationEnabled) =>
if val->JSON.Decode.bool->Option.getOr(false) {
let isDelayedSessionToken =
applePayPresent
Expand Down Expand Up @@ -810,6 +811,9 @@ let make = (
~logger,
~applePayEvent=Some(applePayEvent),
~callBackFunc,
~clientSecret,
~publishableKey,
~isTaxCalculationEnabled,
)
}
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/orca-loader/PaymentSessionMethods.res
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ let getCustomerSavedPaymentMethods = (
~applePayPresent=Some(applePayTokenRef.contents),
~logger,
~callBackFunc=processPayment,
~clientSecret,
~publishableKey,
)
}

Expand Down
2 changes: 2 additions & 0 deletions src/orca-log-catcher/OrcaLogger.res
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type eventName =
| DELETE_SAVED_PAYMENT_METHOD
| DELETE_PAYMENT_METHODS_CALL_INIT
| DELETE_PAYMENT_METHODS_CALL
| EXTERNAL_TAX_CALCULATION

let eventNameToStrMapper = eventName => {
switch eventName {
Expand Down Expand Up @@ -166,6 +167,7 @@ let eventNameToStrMapper = eventName => {
| DELETE_SAVED_PAYMENT_METHOD => "DELETE_SAVED_PAYMENT_METHOD"
| DELETE_PAYMENT_METHODS_CALL_INIT => "DELETE_PAYMENT_METHODS_CALL_INIT"
| DELETE_PAYMENT_METHODS_CALL => "DELETE_PAYMENT_METHODS_CALL"
| EXTERNAL_TAX_CALCULATION => "EXTERNAL_TAX_CALCULATION"
}
}

Expand Down

0 comments on commit 9822330

Please sign in to comment.