Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(applepay): calculate tax amount dynamically while changing shipping address in apple pay sdk #610

Merged
merged 3 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
\"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
81 changes: 79 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,67 @@ let startApplePaySession = (
ssn.completeMerchantValidation(merchantSession)
}

ssn.onshippingcontactselected = shippingAddressChangeEvent => {
if isTaxCalculationEnabled {
let newShippingContact =
shippingAddressChangeEvent.shippingContact
->getDictFromJson
->shippingContactItemToObjMapper
let newShippingAddress =
[
("state", newShippingContact.locality->JSON.Encode.string),
("country", newShippingContact.countryCode->JSON.Encode.string),
("zip", newShippingContact.postalCode->JSON.Encode.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 +297,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 +319,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)
if !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`
PritishBudhiraja marked this conversation as resolved.
Show resolved Hide resolved
let body = [
("client_secret", clientSecret),
("shipping", shippingAddress),
("payment_method_type", paymentMethodType),
]
logApi(
~optLogger=Some(logger),
~url=uri,
~apiLogType=Request,
~eventName=EXTERNAL_TAX_CALCULATION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~eventName=EXTERNAL_TAX_CALCULATION_CALL_INIT

~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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~eventName=EXTERNAL_TAX_CALCULATION_CALL

~logType=ERROR,
~logCategory=API,
)
JSON.Encode.null->resolve
})
} else {
logApi(
~optLogger=Some(logger),
~url=uri,
~statusCode,
~apiLogType=Response,
~eventName=EXTERNAL_TAX_CALCULATION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~eventName=EXTERNAL_TAX_CALCULATION_CALL

~logType=INFO,
~logCategory=API,
)
resp->Fetch.Response.json
}
})
->catch(err => {
let exceptionMessage = err->formatException
logApi(
~optLogger=Some(logger),
~url=uri,
~apiLogType=NoResponse,
~eventName=EXTERNAL_TAX_CALCULATION,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~eventName=EXTERNAL_TAX_CALCULATION_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->JSON.Encode.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 @@ -774,8 +774,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 @@ -809,6 +810,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| EXTERNAL_TAX_CALCULATION_CALL
| EXTERNAL_TAX_CALCULATION_CALL_INIT


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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|EXTERNAL_TAX_CALCULATION_CALL=>"EXTERNAL_TAX_CALCULATION_CALL"
|EXTERNAL_TAX_CALCULATION_CALL_INIT=>"EXTERNAL_TAX_CALCULATION_CALL_INIT"

}
}

Expand Down
Loading