Files
2026-04-05 01:00:07 -05:00

290 lines
10 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://js.stripe.com/v3/"></script>
<title>Stripe Mobile SDK ECE Bridge</title>
</head>
<body>
<div id="express-checkout-element">
</div>
<script>
function hashToString(hash) {
return JSON.stringify(hash, 0, 2);
}
var ECE_OPTIONS = JSON.parse(`
{
"layout": {
"maxColumns": 1,
"maxRows": 0,
"overflow": "never"
}
}
`);
function initializeStripeElements() {
const initParams = window.NativeStripeECE.getShopPayInitParams();
console.log("initParams", JSON.stringify(initParams));
var options = {
mode: "payment",
amount: initParams.amountTotal,
currency: "usd",
payment_method_types: ["card", "link", "shop_pay"],
customerSessionClientSecret: initParams.customerSessionClientSecret,
__elementsInitSource: 'native_sdk',
};
// Only add paymentMethodOptions if shop_id is not null or blank
if (initParams.shopId && initParams.shopId.trim() !== '') {
options.paymentMethodOptions = {
shop_pay: {
shop_id: initParams.shopId,
},
};
}
console.log("Initializing stripe elements with options", options);
let stripe, elements, expressCheckoutElement;
try {
stripe = window.Stripe(window.NativeStripeECE.getStripePublishableKey(), {});
} catch (error) {
console.error("Error creating Stripe instance:", error);
console.log(`❌ Failed to create Stripe instance: ${error.message}`);
throw error;
}
try {
elements = stripe.elements(options);
} catch (error) {
console.error("Error creating Elements with options:", options, error);
console.log(`❌ Failed to create Elements: ${error.message}`);
throw error;
}
try {
expressCheckoutElement = elements.create(
"expressCheckout",
ECE_OPTIONS
);
} catch (error) {
console.error("Error creating Express Checkout Element:", error);
console.log(`❌ Failed to create Express Checkout Element: ${error.message}`);
throw error;
}
var mode = options["mode"];
var captureMethod = options["captureMethod"]
? options["captureMethod"]
: "automatic";
console.log("Ready to mount");
expressCheckoutElement.mount("#express-checkout-element");
//When expressCheckoutElement is mounted, ready event tries to show the available payment methods
let clickEventReceived = false;
expressCheckoutElement.on("ready", ({ availablePaymentMethods }) => {
const expressCheckoutDiv = document.getElementById(
"express-checkout-element"
);
if (!availablePaymentMethods) {
console.log("No Payment Options Available");
} else {
expressCheckoutDiv.style.visibility = "initial";
}
console.log("Attempting to trigger Shop Pay click immediately...");
try {
// Check if a click event was already received
if (!clickEventReceived) {
expressCheckoutElement._sendNativeSdkClick({paymentMethodType: 'shop_pay'});
console.log("Initial Shop Pay click triggered");
} else {
console.log("Click event already received, skipping initial click");
}
} catch (error) {
console.error("Failed on initial Shop Pay click:", error);
}
// Then continue with additional attempts every 100ms for 1 second, up to 5 clicks
let clickCount = 1;
const maxClicks = 5;
const clickInterval = setInterval(() => {
// Stop if a click event was received
if (clickEventReceived) {
console.log("Click event received, stopping further click attempts");
clearInterval(clickInterval);
return;
}
// Stop if we've reached the maximum number of clicks
if (clickCount >= maxClicks) {
console.log(`Reached maximum ${maxClicks} click attempts, stopping further attempts`);
clearInterval(clickInterval);
return;
}
clickCount++;
console.log(`Attempting Shop Pay click #${clickCount}...`);
try {
expressCheckoutElement._sendNativeSdkClick({paymentMethodType: 'shop_pay'});
console.log(`Shop Pay click #${clickCount} triggered`);
} catch (error) {
console.error(`Failed on Shop Pay click #${clickCount}:`, error);
}
}, 100);
});
expressCheckoutElement.on("click", async function (event) {
// We need to know when to stop sending _sendNativeSdkClick
clickEventReceived = true;
console.log(`Click received with event:\n${hashToString(event)}`);
try {
// Extract only serializable data from the event
// The event object contains functions (resolve, complete) that can't be cloned
// for message passing, so we only send the data we need
const eventData = {
walletType: event.walletType,
expressPaymentType: event.expressPaymentType
};
const resolvePayload = await window.NativeStripeECE.handleClick(eventData);
console.log(`Bridge response:\n${hashToString(resolvePayload)}`);
event.resolve(resolvePayload);
} catch (error) {
console.error("ECE click error:", error);
// On error, reject the click
event.reject();
}
});
expressCheckoutElement.on("shippingaddresschange", async function (event) {
console.log(
`ShippingAddressChange: Name: ${event.name}\nAddress:\n${hashToString(
event.address
)}`
);
try {
const response = await window.NativeStripeECE.calculateShipping({
name: event.name,
address: event.address
});
// Check if merchant rejected the address
if (response.error) {
console.log(`Shipping rejected: ${response.error}`);
event.reject();
return;
}
// Update the total amount from the response if provided
if (response.totalAmount) {
elements.update({ amount: response.totalAmount });
}
event.resolve({
shippingRates: response.shippingRates,
lineItems: response.lineItems,
applePay: response.applePay
});
} catch (error) {
console.log(`Error calculating shipping: ${error.message}`);
console.error("Shipping calculation error:", error);
// On error, reject the shipping address
event.reject();
}
});
expressCheckoutElement.on("shippingratechange", async function (event) {
console.log(`Selected Shipping Rate:\n${hashToString(event.shippingRate)}`);
console.log("Shipping rate change event:", event);
try {
const response = await window.NativeStripeECE.calculateShippingRateChange(event.shippingRate);
// Check if merchant rejected the rate
if (response.error) {
console.log(`Shipping rate rejected: ${response.error}`);
event.reject();
return;
}
// Update the total amount from the response if provided
if (response.totalAmount) {
elements.update({ amount: response.totalAmount });
}
event.resolve({
lineItems: response.lineItems,
shippingRates: response.shippingRates,
applePay: response.applePay
});
} catch (error) {
console.log(`Error validating shipping rate: ${error.message}`);
console.error("Shipping rate validation error:", error);
// On error, reject the shipping rate
event.reject();
}
});
expressCheckoutElement.on("cancel", function () {
console.log("cancel");
});
//Observes confirm action & responds with return_url, elements & clientSecret for completing transaction
expressCheckoutElement.on("confirm", async (event) => {
console.log(
`Confirm: BillingDetails:\n${hashToString(
event.billingDetails
)}\nShippingAddress:\n${hashToString(
event.shippingAddress
)}\nShippingRate:\n${hashToString(event.shippingRate)}}}`
);
console.log("confirm payment event triggered", event);
const { error: submitError } = await elements.submit();
if (submitError) {
console.log("submit error");
return;
}
try {
// Prepare payment details for native processing
const paymentDetails = {
billingDetails: event.billingDetails,
shippingAddress: event.shippingAddress,
shippingRate: event.shippingRate,
mode: mode,
captureMethod: captureMethod,
paymentMethodOptions: event._paymentMethodOptions,
};
await window.NativeStripeECE.confirmPayment(paymentDetails);
} catch (error) {
console.log(`Error confirming payment: ${error.message}`);
console.error("Payment confirmation error:", error);
event.paymentFailed({ reason: error.message });
}
});
}
// Function to initialize everything - called from Swift
function initializeApp() {
try {
initializeStripeElements();
} catch (error) {
console.error("Error initializing Stripe Elements:", error);
console.log(`❌ Failed to initialize Stripe Elements: ${error.message}`);
// Re-throw to maintain error propagation if needed
throw error;
}
}
console.log("Waiting for bridge to call initializeApp()");
</script>
</body>
</html>