stripe test card number is valid in live mode - javascript

I set to publish and secret keys in live mode. but when I use the 42424242424242 card number, it's valid. I don't find any solution, how to fix it.
$options = [
'amount' => \floatval($req->cost) * 100,
'currency' => 'aud',
'automatic_payment_methods' => ['enabled' => true],
];
$stripKey = \Utility::isLocalHost() ? $_ENV["STRIP_SECRET_KEY"] : $_ENV["STRIP_SECRET_KEY_lIVE"];
Stripe::setApiKey($stripKey);
$intent = PaymentIntent::create($options);
as you can see, my backend is written in PHP language that generates a client secrete key and in front-end, I build an element and stripe form by that key.
this.strip = Stripe(this.key);
const options = { clientSecret: payment.clientSecret };
this.stripElement = this.strip.elements(options);
const paymentElement = this.stripElement.create('payment');
paymentElement.mount('#payment-element');
paymentElement.on('change', (event) => {
if (event.complete && !event.empty) {
this.submitBtn.disabled = false;
}
});
However, I move the code on my server and set the live mode for stripe. The test credit card '42424 ...' is still valid.

Related

HubspotClient - Update contact by email id is not working

In NodeJS, I'm using "#hubspot/api-client": "^7.1.2".
Created hubspot client using accessToken as follows
const hubSpotClient = new hubspot.Client({ accessToken });
When I try to update the contact using email it's throwing error
Request:
const idProperty = 'email';
const response = await hubSpotClient(store).crm.contacts.basicApi.update(email, idProperty, contact);
Response:
ERROR {
"statusCode": 404,
"body": {
"status": "error",
"message": "Object not found. objectId are usually numeric.",
"correlationId": "71e911d3...",
"context": {
"id": [
"testemail#..."
]
},
"category": "OBJECT_NOT_FOUND"
}
Create contact is working fine with this client but updating by email is not working.
Anything out of place or syntax error in passing the idProperty?
The problem is in your implementation, because it seems like you are not using properly the Hubspot API.
If you check the function signature of the basicApi.update
public async update(contactId: string, simplePublicObjectInput: SimplePublicObjectInput, idProperty?: string, _options?: Configuration): Promise<RequestContext> {
Basically, you need to pass down a contactId, and then a simplePublicObjectInput that is basically an object that represents your update.
Your code should look like this:
import { Client } from "#hubspot/api-client";
const hubspotClient = new Client({ accessToken: YOUR_ACCESS_TOKEN });
const contactID = 1234;
const response = await hubspotClient.crm.contacts.basicApi.update(contactID, {
properties: { email: 'my-new-email#gmail.com' },
})
Keep in mind that Hubspot always tries to follow their same guidelines as their endpoints. If your check the endpoint specification you will see the following:
Think about the Hubspot node client as just an abstraction of some http client, but at the end does exactly the same as the endpoints described in their implementations.
For that reason, in your implementation, Hubspot is returning an appropriated error, since you are not giving the contactId in the first argument, Hubspot is telling you: "Object not found. objectId are usually numeric." Because indeed a Contact ID is numeric and you are using the value of an email --string-- instead.
If you want to "update by email"
I think that there is no direct way to do it, you might need to do a previous search of the contact by email.
You could use the searchApi.
And after getting the id just run the update.
const searchResponse = await hubspotClient.crm.contacts.searchApi.doSearch({
filterGroups: [
{
filters: [
{
value: 'email-to-search#gmail.com',
propertyName: 'email',
operator: 'EQ',
},
],
},
],
sorts: [],
properties: [],
limit: 1,
after: 0,
});
// Off course you need to improve a lot the error handling here and so on.
// This is just an example
const [contactID] = searchResponse.results;
const contactUpdateResponse = await hubspotClient.crm.contacts.basicApi.update(contactID, {
properties: { email: 'my-new-email#gmail.com' },
})
I hope this helps you!
You CAN use email as the idProperty for the hubspot/api-client get contact function, but it only works if you fill in all the other query fields before idProperty, even if they are undefined.
Here is my example of a getContactByEmail as a Google Cloud Function in Node, using the api-client, and it works great!
exports.getContactByEmail = functions.https.onCall(async (data, context) => {
const email = data.email;
const contactId = email;
const properties = ["firstname", "lastname", "company"];
const propertiesWithHistory = undefined;
const associations = undefined;
const archived = false;
const idProperty = "email";
try {
const apiResponse = await hubspotClient.crm.contacts.basicApi.getById(
contactId,
properties,
propertiesWithHistory,
associations,
archived,
idProperty
);
console.log(JSON.stringify(apiResponse.body, null, 2));
return apiResponse.properties;
} catch (error) {
error.message === "HTTP request failed"
? console.error(JSON.stringify(error.response, null, 2))
: console.error(error);
return error;
}
});

items array is not being recognized by php function in STRIPE checkout

I am using Stripe quick start code found here https://stripe.com/docs/payments/quickstart
I have narrowed down the problem to the "items" array in the checkout.js not being recognized or not properly constructed or whatever in the create.php file.
My customers will only be purchasing one type of item at different dollar amounts so this function
function calculateOrderAmount(array $items): int {
// Replace this constant with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
}
is useless to me and the amount can just go into this
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => calculateOrderAmount($jsonObj->items),
'currency' => 'eur',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
as the total.
I have tried replacing calculateOrderAmount($jsonObj->items) with array_values($$items)[0] as well as everything else I can think of. Stripe support has been of no help.
Thank you in advance
The HTML: The price is in cents from the "value"
<select id="item-options"">
<option value="">---- SELECT----</option>
<option value="9000">300 ITEMS - &dollar;90.00 USD</option>
<option value="8000">200 ITEMS - &dollar;80.00 USD</option>
<option value="5000">100 ITEMS - &dollar;50.00 USD</option>
<option value="3000">50 ITEMS - &dollar;30.00 USD</option>
<option value="1750">25 ITEMS - &dollar;17.50 USD</option>
<option value="800">10 ITEMS - &dollar;8.00 USD</option>
<option value="450">5 ITEMS - &dollar;4.50 USD</option>
<option value="100">1 ITEMS - &dollar;1.00 USD</option>
</select>
The full javascript:
var price = $('#item-options option:selected').val();
// This is your test publishable API key.
const stripe = Stripe("pk_test_xxxxxxxxxxx....");
// The items the customer wants to buy I have change this to (amount: price)
const items = [{ id: "xl-tshirt" }];
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/create.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items }),
}).then((r) => r.json());
elements = stripe.elements({ clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:4242/public/checkout.html",
receipt_email: document.getElementById("email").value,
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
// ------- UI helpers -------
function showMessage(messageText) {
const messageContainer = document.querySelector("#payment-message");
messageContainer.classList.remove("hidden");
messageContainer.textContent = messageText;
setTimeout(function () {
messageContainer.classList.add("hidden");
messageText.textContent = "";
}, 4000);
}
// Show a spinner on payment submission
function setLoading(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#submit").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.querySelector("#submit").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
}
The php :
<?php
require 'vendor/autoload.php';
// This is your test secret API key.
\Stripe\Stripe::setApiKey('sk_test_XXXXXX.........');
function calculateOrderAmount(array $items): int {
// Replace this constant with a calculation of the order's amount
// Calculate the order total on the server to prevent
// people from directly manipulating the amount on the client
return 1400;
}
header('Content-Type: application/json');
try {
// retrieve JSON from POST body
$jsonStr = file_get_contents('php://input');
$jsonObj = json_decode($jsonStr);
// Create a PaymentIntent with amount and currency
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => calculateOrderAmount($jsonObj->items),
'currency' => 'eur',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
To prevent price manipulation on the client side you should pass to your PHP script ids of products, not the prices directly. By knowing the product ids, you can search for prices on the server side. That's what the calculateOrderAmount function is for in your example code.
Since you intended to sell only one type of product, the price of which will depend on the volume, you may want to send a number of items from the client. Then take a look at tiered pricing and make the code match your pricing model.
I solved this by doing a var_dump($items) in the php file and the console.log showed it as a stdClass Object. Using "$amount = $items[0]->amount;" in the "function calculateOrderAmount(array $items)" now gives me the correct price for the "$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => calculateOrderAmount($jsonObj->items),"
Thank you Justin for responding to my question.

ERROR with Stripe Checkout: One-Time + Subscription Payment Buttons on the same page?

UPDATED I built a pricing page that uses Stripe Checkout to use both a One-Time payment button for product 1 and a Subscription payment button for product 2.
My goal is to redirect the one time payment button to Stripe Checkout with a one time payment, and separately redirect the subscription payment to a checkout with a recurring payment.
According to STRIPE this can be done using Subscription as the Mode in the CheckoutSession in create-checkout-session.php (sample project) :
The mode of the Checkout Session. Required when using prices or setup
mode. Pass subscription if the Checkout Session includes at least one
recurring item.
Contrary to the Stripe Docs the following line of code: 'mode' => 'subscription', activates subscription payments ONLY, but it doesnt redirect one time payments. For one-time payments to work I must change it to: 'mode' => 'payment', but then subscription payments don't work.
Here's the php code in question:
<?php
require_once 'shared.php';
$domain_url = $config['domain'];
// Create new Checkout Session for the order
// Other optional params include:
// [billing_address_collection] - to display billing address details on the page
// [customer] - if you have an existing Stripe Customer ID
// [payment_intent_data] - lets capture the payment later
// [customer_email] - lets you prefill the email input in the form
// For full details see https://stripe.com/docs/api/checkout/sessions/create
// ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain_url . '/canceled.html',
'payment_method_types' => ['card'],
'mode' => 'subscription',
'line_items' => [[
'price' => $body->priceId,
'quantity' => 1,
]]
]);
echo json_encode(['sessionId' => $checkout_session['id']]);
And here's the javascript code:
// Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId) {
return fetch("./create-checkout-session.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId: priceId
})
}).then(function(result) {
return result.json();
});
};
// Handle any errors returned from Checkout
var handleResult = function(result) {
if (result.error) {
var displayError = document.getElementById("error-message");
displayError.textContent = result.error.message;
}
};
/* Get your Stripe publishable key to initialize Stripe.js */
fetch("./config.php")
.then(function(result) {
return result.json();
})
.then(function(json) {
var publishableKey = json.publishableKey;
var subscriptionPriceId = json.subscriptionPrice;
var onetimePriceId = json.onetimePrice;
var stripe = Stripe(publishableKey);
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("subscription-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(subscriptionPriceId).then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("onetime-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(onetimePriceId).then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
});
Is it even possible to have both one time payments and recurring payments on the same page with Stripe Checkout? How can I accomplish this?
Update according to Bemn:
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain_url . '/canceled.html',
'payment_method_types' => ['card'],
'mode' => $body->mode
'line_items' => [[
'price' => $body->price_xxx,
// For metered billing, do not pass quantity
'quantity' => 1,
]],
'line_items' => [[
'price' => $body->price_zzz,
// For metered billing, do not pass quantity
'quantity' => 1,
]]
]);
echo json_encode(['sessionId' => $checkout_session['id']]);
And the JS:
// Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId, mode) {
return fetch("./create-checkout-session.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId: priceId,
mode: mode // <-- passing the mode, e.g. 'payment' or 'subscription'
})
}).then(function(result) {
return result.json();
});
};
And the HTML:
<div data-stripe-priceid="pricexxx" data-stripe-mode="payment" id="onetime-btn" class="bold mt-2 d-inline-block w-100-after-md max-width-xxs py-2 btn btn-secondary">Ore Time</div>
<div data-stripe-priceid="pricexxx" data-stripe-mode="subscription" id="subscription-btn" class="bold mt-2 d-inline-block w-100-after-md max-width-xxs py-2 btn btn-secondary">Ore Time</div>
Is it even possible to have both one time payments and recurring payments on the same page with Stripe Checkout?
Yes. The key is you should pass the correct options to generate the corresponding Stripe Checkout session ID.
How can I accomplish this?
Backend: Have a function to accept Stripe's price ID and payment mode as input and return a Stripe Checkout session ID as the output.
Frontend: Pass payment mode information to /create-checkout-session.php. (see the Note if you are unable to do so)
Details
The following solution assuming that:
You generate a Stripe Checkout Session ID at the backend. That ID will then pass to .createCheckoutSession() in js frontend.
You have a 1-time product (let's call it PAY) and a recurrent subscription (let's call it SUB).
Frontend
I think you are close. What you need to do is passing the mode information to your API endpoint as well:
// Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId, mode) { // <-- add a mode parameter
return fetch("./create-checkout-session.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId: priceId,
mode: mode // <-- passing the mode, e.g. 'payment' or 'subscription'
})
}).then(function(result) {
return result.json();
});
};
If so, each checkout button in the page should have corresponding info of the priceId and payment mode. You can do so by storing them using data attribute:
<div data-stripe-priceid="price_yyy" data-stripe-mode="subscription">Recurrent</div>
<div data-stripe-priceid="price_zzz" data-stripe-mode="payment">1-time</div>
If so, you can get the data attributes by e.g. a click event.
Note: If you cannot add an extra param to indicate mode, you neeed to identify if the given price ID is a 1-time or recurrent product in the backend. See https://stripe.com/docs/api/prices/object?lang=php#price_object-type for more details.
Backend
Here are 2 sample code snippets from Stripe's documentation. Direct copying of them does not work.
Reference for PAY: https://stripe.com/docs/checkout/integration-builder
$checkout_session = \Stripe\Checkout\Session::create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'usd',
'unit_amount' => 2000,
'product_data' => [
'name' => 'Stubborn Attachments',
'images' => ["https://i.imgur.com/EHyR2nP.png"],
],
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' => $YOUR_DOMAIN . '/success.html',
'cancel_url' => $YOUR_DOMAIN . '/cancel.html',
]);
In your case, you may not need to define 'price_data'. Instead, you should use 'price', like the next example.
Reference for SUB: https://stripe.com/docs/billing/subscriptions/checkout#create-session
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => 'https://example.com/canceled.html',
'payment_method_types' => ['card'],
'mode' => 'subscription',
'line_items' => [[
'price' => $body->priceId,
// For metered billing, do not pass quantity
'quantity' => 1,
]],
]);
Take a look at this reference: https://stripe.com/docs/api/checkout/sessions/create. For line_items, you can just simply using 'price' and pass the price ID (e.g. price_xxx), which means your 'line_items' will looks like this:
'line_items' => [[
'price' => $body->priceId,
'quantity' => 1,
]],
For 'mode', use the value from your API request. It should be something like:
'mode' => $body->mode
Which means in your backend you better define a function (e.g. generate_checkout_session) to:
parse the json body received in the API request
get priceId and mode from the parsed data
use the priceId and mode in \Stripe\Checkout\Session::create and
returns the checkout_session ID
Hope this (and the reference urls) can help you.
When you create your Session you can pass both a Price for the recurring amount charged on the subscription and another Price for the one-time fee you want to charge. You can combine multiple recurring Prices and one-time Prices overall.
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain_url . '/canceled.html',
'payment_method_types' => ['card'],
'mode' => 'subscription',
'line_items' => [
// Add a one-time Price for $10
[
'price' => 'price_123',
'quantity' => 1,
],
// Add another one-time Price for $23
[
'price' => 'price_345',
'quantity' => 1,
],
// Add a recurring Price for $100 monthly
[
'price' => 'price_ABC',
'quantity' => 1,
],
]);
The code above will create a session with 3 line items. One for $100 monthly, one for $10 just once and one for $23 just once. The total for the session would be $133 on the first payment. It will also start a subscription for $100 a month and future invoices will be for $100 as expected.
What I have got is that you just need to add a check for either it is one-time or subscription here is what you can do:
JS FILE CHANGES:
var createCheckoutSession = function(priceId, $mode) {
return fetch("./create-checkout-session.php", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
priceId: priceId,
paymentType: $mode, // This vary based on the button clicked either one-time or subscription.
})
}).then(function(result) {
return result.json();
});
};
/* Get your Stripe publishable key to initialize Stripe.js */
fetch("./config.php")
.then(function(result) {
return result.json();
})
.then(function(json) {
var publishableKey = json.publishableKey;
var subscriptionPriceId = json.subscriptionPrice;
var onetimePriceId = json.onetimePrice;
var stripe = Stripe(publishableKey);
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("subscription-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(subscriptionPriceId, 'subscription').then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
// Setup event handler to create a Checkout Session when button is clicked
document
.getElementById("onetime-btn")
.addEventListener("click", function(evt) {
createCheckoutSession(onetimePriceId, 'onetime').then(function(data) {
// Call Stripe.js method to redirect to the new Checkout page
stripe
.redirectToCheckout({
sessionId: data.sessionId
})
.then(handleResult);
});
});
});
Now we need to make changes in PHP file:
PHP FILE CHANGES:
$checkout_session = \Stripe\Checkout\Session::create([
'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $domain_url . '/canceled.html',
'payment_method_types' => ['card'],
'mode' => $body->paymentType, // Here is what we have got from front-end
'line_items' => [[
'price' => $body->priceId,
'quantity' => 1,
]]
]);
For subscription we actually need to set interval, which is not need to set in one time purses. Probably this error happening for this reason.
Add recurring can be solve recurring error.
recurring: {
interval: 'month' // 'month' | 'year'
}

How to add some object to sendMessage or add this object to Extra in telegraf.js library

I have telegram bot using a telegraf.js library. I have some users that have been registered in bot, and I want to send a confirmation of something consent from one user to another. For realizing it, I need to add in sendMessage some consentId, but I don`t know how!
(this is pseudo-code, here I show my imagination of how that can be work)
bot.action('createContract', ctx => {
// we have some consent object from sequlize with right id
const consent = {
id : uuid4(),
status : 'PENDING',
initiatorId : uuid4(),
consonantId : uuid4()
};
sequelize.model.User.findByPk(consent.consonantId).then(user => {
// here i need add consentId to sendMessage but not show it to user
ctx.telegram.sendMessage(
user.chatId,
'Do you confirm ....',
Extra.HTML().markup((m) =>
m.inlineKeyboard([
m.callbackButton('sign', 'sign'),
m.callbackButton('decline', 'decline')
]))
);
});
});
bot.action('sign', ctx => {
// here we must a get consentId that we add in createContract action
sequelize.model.Consent.findByPk(ctx.consentId).then(consent => {
return consent.update({ status: 'SUCCESS' });
});
});

User fields empty in parse-server server.js mail adapter

I use parse-server-amazon-ses-email-adapter and would like to know how to localize the verification email and reset password email.
My clue is to check user's fields and then assign the correct template on server.js or AmazonSESAdapter.js. The problem is that user properties are empty, besides email, username.
For instance, at the example below, firstName is undefined.
Thanks.
emailAdapter: {
module: 'parse-server-amazon-ses-email-adapter',
options: {
// The address that your emails come from
fromAddress: 'Iron rr',
accessKeyId: 'gg',
secretAccessKey: 'gg',
region: 'eu-west-1',
// The template section
templates: {
passwordResetEmail: {
subject: 'Redefinir sua senha Iron Trainers',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/password_reset_email.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/password_reset_email.html',
callback: (user) => {
return {
firstName: user.get('firstName')
}
}
// Now you can use {{firstName}} in your templates
},
verificationEmail: {
subject: 'Confirmar email no Iron Trainers',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/verification_email.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/resendEmailVerification.html',
callback: (user) => {
return {
firstName: user.get('firstName')
}
}
// Now you can use {{firstName}} in your templates
},
customEmailAlert: {
subject: 'Urgent notification!',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/custom_alert.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/custom_alert.html',
}
}
As per my understanding this can be done but through a code change in the plugin.
There is a if-else condition at below line in code
https://github.com/ecohealthalliance/parse-server-amazon-ses-email-adapter/blob/0bce9b6c81681c3829a17b208d839d23c846ab05/src/AmazonSESAdapter.js#L90
Since you have not provided any feedback and I have not way to setup this, I have assume that the else part is what gets executed
const {
link,
appName,
user,
templateConfig
} = options;
const {
callback
} = templateConfig;
let userVars;
if (callback && typeof callback === 'function') {
userVars = callback(user);
// If custom user variables are not packaged in an object, ignore it
const validUserVars = userVars && userVars.constructor && userVars.constructor.name === 'Object';
userVars = validUserVars ? userVars : {};
}
pathPlainText = templateConfig.pathPlainText;
pathHtml = templateConfig.pathHtml;
templateVars = Object.assign({
link,
appName,
username: user.get('username'),
email: user.get('email')
}, userVars);
message = {
from: this.fromAddress,
to: user.get('email'),
subject: templateConfig.subject
};
}
Now in this part, 2 lines decide the template to be used
pathPlainText = templateConfig.pathPlainText;
pathHtml = templateConfig.pathHtml;
By this time, the callback you have provided has been called. Now in the callback you can set a variable, let assume it is name locale. So you can update the code like below
pathPlainText = templateConfig.pathPlainText + (userVars["locale"] || "en");
pathHtml = templateConfig.pathHtml + (userVars["locale"] || "en");
And then you will create templates which have the locale in the file path and with updated code the correct template will be picked.
You can also look at #bgran answer, at first look I do believe that should work as well
You'll need to do your localization in the template callback.
The callback is synchronous, so all of your localization will also need to be synchronous.
emailTemplate.html
<div>
{{localizedText}}
</div>
Other templates for each locale:
emailTemplate.en.html
<p>
Hi {{nome}}...
</p>
The emailer logic:
// The same templater used by parse-server-amazon-ses-email-adapter
import template from 'lodash.template'
/* ... */
const TemplatesByLocale = {
en: fs.readFileSync('./emailTemplate.en.html'),
}
verificationEmail: {
/* ... */
pathHtml: './path/to/emailTemplate.html',
callback: (user) => {
const locale = getLocaleSomehow(user) // needs to be synchronous
const localizedTemplate = TemplatesByLocale[locale]
const compiled = template(localizedTemplate, {
// same interpolation as parse-server-amazon-ses-email-adapter
interpolate: /{{([\s\S]+?)}}/g
})
const localizedText = compiled({
nome: user.get('nome'), /* ... */
})
return {
localizedText: localizedText,
}
},
/* ... */
}
It's worth noting that parse-server-amazon-ses-email-adapter will use the HTML template (the one specified via pathHtml) before it uses the plain text template, so if you've specified an HTML template you can just leave off the pathPlainText property.

Categories