@vince
// v1.5.0: Set Stripe as Default Vendor
import Component from "@ember/component";
import { observer } from "@ember/object";
import { scheduleOnce } from "@ember/runloop";
import { inject as service } from "@ember/service";
import SmartViewMixin from "client/mixins/smart-view-mixin";
import $ from "jquery";
const CARD_STYLE = {
base: {
color: "#54BD7E",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "14px",
"::placeholder": {
color: "#aab7c4", // --color-beta-on-surface_light
},
},
invalid: {
color: "#FB6669",
iconColor: "#FB6669",
},
};
const ELEMENT_IDS = {
STRIPE_CARD_ELEMENT: "stripe-card-element",
STRIPE_CARD_ERROR: "stripe-card-errors",
STRIPE_CARD_HOLDER_NAME: "stripe-card-holder-name",
STRIPE_CARD_HOLDER_NAME_ERROR: "stripe-card-holder-name-error",
STRIPE_ACH_HOLDER_NAME: "stripe-ach-holder-name",
STRIPE_ACH_ACCOUNT_NUMBER: "stripe-ach-account-number",
STRIPE_ACH_ROUTING_NUMBER: "stripe-ach-routing-number",
STRIPE_ACH_ACCOUNT_TYPE: "stripe-ach-account-type",
STRIPE_ACH_HOLDER_NAME_ERROR: "stripe-ach-holder-name-error",
STRIPE_ACH_ACCOUNT_NUMBER_ERROR: "stripe-ach-account-number-error",
STRIPE_ACH_ROUTING_NUMBER_ERROR: "stripe-ach-routing-number-error",
};
const PAYMENT_METHOD = {
CARD: "CARD",
ACH: "ACH",
PAD: "PAD",
};
const PAYMENT_METHOD_VENDOR = {
STRIPE: "stripe",
PAYIX: "payix",
};
const STRIPE_ACH_DEFAULT_VALUE = {
BANK_COUNTRY_CODE: "US",
CURRENCY: "USD",
};
const obfuscateToken = (token) =>
token.includes("_")
? token.replace(/^(.*)_((.{3}).*(.{3}))$/, "$1_$3***$4")
: token.replace(/^(.{3})(.*)(.{3})$/, "$1***$3");
export default Component.extend(SmartViewMixin, {
customAction: service(),
store: service(),
selectedRecord: null,
resultToken: null,
stripe: null,
isLoadedStripe: false,
stripeCard: null,
isCreatingStripeCard: false,
isCreatingStripeACH: false,
isClickedToSync: false,
stripePublicKey: null,
payixIframeCardAchURL: null,
payixIframePadURL: null,
subscriptionId: null,
vendor: null,
init(...args) {
this._super(...args);
console.log("===== Smart view: v1.5.0: Set Stripe as Default Vendor =====");
},
didInsertElement() {
this.setDefaultSelectedRecord();
this.onCreatedPayixToken = this.onCreatedPayixToken.bind(this);
window.addEventListener("message", this.onCreatedPayixToken);
},
willRemoveElement() {
window.removeEventListener("message", this.onCreatedPayixToken);
},
onRecordsChange: observer("records.[]", function() {
this.setDefaultSelectedRecord();
}),
isSupportedStripe() {
return this.vendor === PAYMENT_METHOD_VENDOR.STRIPE;
},
setURLs(data) {
this.set("payixIframeCardAchURL", data["forest-payix_iframe_card_ach_url"]);
this.set("payixIframePadURL", data["forest-payix_iframe_pad_url"]);
this.set("stripePublicKey", data["forest-stripe_public_key"]);
},
setSubscriptionIdAndVendor(record) {
this.set("subscriptionId", record["forest-subscription"]["id"]);
this.set("vendor", record["forest-subscription"]["forest-vendor"]);
},
setDefaultSelectedRecord() {
if (this.records && this.records.recordData) {
this.set("subscriptionId", this.records.recordData.id);
this.set("vendor", PAYMENT_METHOD_VENDOR.STRIPE);
if (this.records.recordData.__data) {
this.setURLs(this.records.recordData.__data);
//Setting the vendor here again as if it's a Canadian User
//The Vendor should be Payix not Stripe
if (this.records.recordData.__data["forest-vendor"]) {
this.set("vendor", this.records.recordData.__data["forest-vendor"]);
}
}
}
if (!this.selectedRecord && this.records) {
if (this.records.firstObject) {
this.set("selectedRecord", this.records.firstObject);
this.setURLs(this.records.firstObject);
this.setSubscriptionIdAndVendor(this.records.firstObject);
}
if (!this.stripe) this.loadPlugins();
}
},
loadPlugins() {
scheduleOnce("afterRender", this, () => {
try {
const stripePublicKey = this.stripePublicKey;
$.getScript("//js.stripe.com/v2/", () => {
$.getScript("//js.stripe.com/v3/", () => {
Stripe.setPublishableKey(stripePublicKey); // v2
const stripe = Stripe(stripePublicKey); // v3
this.set("stripe", stripe);
this.set("isLoadedStripe", true);
if (this.isSupportedStripe()) {
this.renderStripeElements();
}
});
});
} catch (error) {
console.error("Internal Error: Failed to load Stripe:\n", error);
}
});
},
renderStripeCard() {
const { stripe } = this;
const elements = stripe.elements();
const stripeCard = elements.create("card", { style: CARD_STYLE });
stripeCard.mount(`#${ELEMENT_IDS.STRIPE_CARD_ELEMENT}`);
stripeCard.on("change", function(event) {
let errorElement = document.getElementById(ELEMENT_IDS.STRIPE_CARD_ERROR);
if (event.error) {
errorElement.textContent = event.error.message;
} else {
errorElement.textContent = "";
}
});
this.set("stripeCard", stripeCard);
},
renderStripeElements() {
if (!this.stripe) return;
this.renderStripeCard();
},
onCreatedStripeCardToken(cardData, cardName) {
try {
const {
id,
billing_details: {
address: { postal_code },
},
card: { brand, last4, exp_month, exp_year },
} = cardData;
const expiry = `${exp_year}-${("0" + exp_month).slice(-2)}-01`;
const resultToken = {
paymentMethod: PAYMENT_METHOD.CARD,
name: cardName,
token: id,
last4,
type: brand,
expiry,
billingZip: postal_code,
};
console.log("===== Created Stripe Card Token =====\n", {
...resultToken,
token: obfuscateToken(resultToken.token),
});
this.set("resultToken", resultToken);
} catch (error) {
console.error(
"Internal Error: Failed to add new Card with Stripe:\n",
error
);
}
},
onCreatedStripeACHToken(achData, achValues) {
try {
const {
id,
bank_account: { last4, bank_name },
} = achData;
const resultToken = {
paymentMethod: PAYMENT_METHOD.ACH,
token: id,
last4,
bankName: bank_name,
name: achValues.achHolderName,
type: achValues.achAccountType,
};
console.log("===== Created Stripe ACH Token =====\n", {
...resultToken,
token: obfuscateToken(resultToken.token),
});
this.set("resultToken", resultToken);
} catch (error) {
console.error(
"Internal Error: Failed to add new ACH with Stripe:\n",
error
);
}
},
onCreatedPayixToken(event) {
try {
const isAddedWithPayixSuccessfully =
event.data &&
event.data.token !== "" &&
event.data.token !== null &&
event.data.token !== undefined;
if (isAddedWithPayixSuccessfully) {
const {
token,
nameOnCard,
nameOnAch,
nameOnPad,
cardBrand,
expiryDate,
lastFourDigits,
billingZip,
bankName,
} = event.data;
const isCard = !!nameOnCard;
const isACH = !isCard && !!nameOnAch;
const isPAD = !isCard && !isACH && !!nameOnPad;
let resultToken;
if (isCard) {
const expiry = expiryDate.replace(/(\d{2})\/(\d{4})/, "$2-$1-01");
resultToken = {
paymentMethod: PAYMENT_METHOD.CARD,
name: nameOnCard,
token,
last4: lastFourDigits,
type: cardBrand,
expiry,
billingZip,
};
} else if (isACH) {
resultToken = {
paymentMethod: PAYMENT_METHOD.ACH,
name: nameOnAch,
token,
last4: lastFourDigits,
bankName,
};
} else if (isPAD) {
resultToken = {
paymentMethod: PAYMENT_METHOD.PAD,
name: nameOnPad,
token,
last4: lastFourDigits,
bankName,
};
}
if (resultToken) {
console.log(
`===== Created Payix ${resultToken.paymentMethod} Token =====\n`,
{
...resultToken,
token: obfuscateToken(resultToken.token),
}
);
this.set("resultToken", resultToken);
}
}
} catch (error) {
console.error(
"Internal Error: Failed to add new payment method with Payix:\n",
error
);
}
},
validateStripeACHForm() {
const { value: achHolderName } = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_HOLDER_NAME
);
const { value: achAccountNumber } = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_ACCOUNT_NUMBER
);
const { value: achRoutingNumber } = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_ROUTING_NUMBER
);
const { value: achAccountType } = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_ACCOUNT_TYPE
);
const holderNameErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_HOLDER_NAME_ERROR
);
const accountNumberErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_ACCOUNT_NUMBER_ERROR
);
const routingNumberErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_ACH_ROUTING_NUMBER_ERROR
);
let isValidHolderName = false;
if (!achHolderName) {
holderNameErrorElement.textContent =
"Please input Account ownner's fullname";
} else {
holderNameErrorElement.textContent = "";
isValidHolderName = true;
}
let isValidAccountNumber = false;
if (!achAccountNumber) {
accountNumberErrorElement.textContent = "Please input Account number";
} else {
const isValidAccountNumberByStripe = Stripe.bankAccount.validateAccountNumber(
achAccountNumber,
STRIPE_ACH_DEFAULT_VALUE.BANK_COUNTRY_CODE
);
if (!isValidAccountNumberByStripe) {
accountNumberErrorElement.textContent =
"Your Account number is invalid";
} else {
accountNumberErrorElement.textContent = "";
isValidAccountNumber = true;
}
}
let isValidRoutingNumber = false;
if (!achRoutingNumber) {
routingNumberErrorElement.textContent = "Please input Routing number";
} else {
const isValidRoutingNumberByStripe = Stripe.bankAccount.validateRoutingNumber(
achRoutingNumber,
STRIPE_ACH_DEFAULT_VALUE.BANK_COUNTRY_CODE
);
if (!isValidRoutingNumberByStripe) {
routingNumberErrorElement.textContent =
"Your Routing number is invalid";
} else {
routingNumberErrorElement.textContent = "";
isValidRoutingNumber = true;
}
}
const isValid = [
isValidHolderName,
isValidAccountNumber,
isValidRoutingNumber,
].every((isValidValue) => isValidValue);
if (!isValid) {
return null;
}
return {
achHolderName,
achAccountNumber,
achRoutingNumber,
achAccountType,
};
},
actions: {
onSelectRecord(record) {
this.set("selectedRecord", record);
this.set("resultToken", null);
this.setURLs(record);
this.setSubscriptionIdAndVendor(record);
if (this.isSupportedStripe()) {
this.renderStripeElements();
}
},
onSyncToPaymentService() {
try {
const subscriptionId = this.subscriptionId;
if (!this.resultToken || !this.resultToken.token) {
throw new Error("Missing Token data");
}
const tokenData = {
...this.resultToken,
subscriptionId,
vendor: this.vendor,
};
console.log("===== Sync to Payment Service =====\n", {
...tokenData,
token: obfuscateToken(tokenData.token),
vendor: this.vendor,
});
this.set("isClickedToSync", true);
//Getting Add Token Custom Action
const customActions = this.store
.peekAll("custom-action")
.filter((c) => c.name === "Add Token");
console.log("===== CustomAction is retreived =====\n");
//Setting the customerActionValues with the tokenData
customActions[0].fields.forEach((f) => {
f.customActionValue = tokenData[f.fieldName];
});
console.log("===== CustomAction Fields are set =====\n");
if (!this.selectedRecord) {
this.selectedRecord = {};
}
//Triggering CustomAction
this.customAction.triggerCustomAction(
customActions[0],
[this.selectedRecord],
tokenData
);
console.log("===== CustomAction is triggered =====\n");
} catch (error) {
console.error(
"Internal Error: Failed to sync to Payment Service:\n",
error
);
}
},
async onSubmitToCreateStripeCard(event) {
event.preventDefault();
const { value: cardHolderName } = document.getElementById(
ELEMENT_IDS.STRIPE_CARD_HOLDER_NAME
);
let stripeCardNameErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_CARD_HOLDER_NAME_ERROR
);
if (!cardHolderName) {
stripeCardNameErrorElement.textContent =
"Please input card's owner name";
} else {
stripeCardNameErrorElement.textContent = "";
try {
this.set("isCreatingStripeCard", true);
const { value: cardName } = document.getElementById(
ELEMENT_IDS.STRIPE_CARD_HOLDER_NAME
);
const { stripe, stripeCard } = this;
const { paymentMethod: cardData } = await stripe.createPaymentMethod({
type: "card",
card: stripeCard,
billing_details: { name: cardName },
});
this.set("isCreatingStripeCard", false);
this.onCreatedStripeCardToken(cardData, cardName);
} catch (error) {
let stripeCardErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_CARD_ERROR
);
stripeCardErrorElement.textContent = error.message;
}
}
},
onSubmitToCreateStripeACH(event) {
event.preventDefault();
const achValues = this.validateStripeACHForm();
if (!achValues) {
return;
}
try {
this.set("isCreatingStripeACH", true);
Stripe.bankAccount.createToken(
{
country: STRIPE_ACH_DEFAULT_VALUE.BANK_COUNTRY_CODE,
currency: STRIPE_ACH_DEFAULT_VALUE.CURRENCY,
account_holder_name: achValues.achHolderName,
account_number: achValues.achAccountNumber,
routing_number: achValues.achRoutingNumber,
account_holder_type: achValues.achAccountType,
},
(status, response) => {
if (response.error) {
throw error;
}
this.set("isCreatingStripeACH", false);
this.onCreatedStripeACHToken(response, achValues);
}
);
} catch (error) {
let stripeCardErrorElement = document.getElementById(
ELEMENT_IDS.STRIPE_CARD_ERROR
);
stripeCardErrorElement.textContent = error.message;
}
},
},
});