I have a Telegram bot that processes payments. The payments work as they should, however, I have not been able to show receipt after a successful payment.
The current behavior is:
user clicks on PAY button, fills the card information and pays for the service
payment is processed and message about successful transaction is sent
at this point, I would like for the PAY button to change to RECEIPT button
The current behavior on screenshot:
Desired behavior:
The desired behavior was screenshoted from chat with #ShopBot, which is mentioned in Telegram docs as a test tool.
The only mention about how the "receipt" is handled I could find in the Telegram docs were these two sentences at https://core.telegram.org/bots/payments :
If the invoice message was sent in the chat with #merchantbot, it becomes a Receipt in the UI for the user — they can open this receipt at any time and see all the details of the transaction.
If the message was sent to any other chat, the Pay button remains and can be used again. It is up to the merchant bot whether to actually accept multiple payments.
However, I don't understand how to achieve this in the code. As far as I know, the invoice message WAS sent to the chat with my bot (as in the first sentence) so it SHOULD become a Receipt.
The bot is written in Node.js and uses webhook to handle messages. The code section of the webhook important for this question:
router.route('/')
.post(async (req, res) => {
try {
// if pre_checkout_query is defined, there was an attempt for payment
if (req.body.pre_checkout_query) {
// use answerPreCheckoutQuery Telegram method
...
}
const message = req.body.message || req.body.edited_message;
// this means user's payment was successful
if (message.successful_payment) {
// success, deliver goods or services
// send message about successful payment
...
}
} catch (err) {
...
}
})
The invoice is sent with sendInvoice method like this:
const url = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendInvoice`;
const response = await axios.get(url, {
params: {
chat_id: chatID,
title: 'SOME TITLE',
description: 'some decription',
payload: 'SOME-PAYLOAD',
provider_token: process.env.STRIPE_API_TOKEN,
currency: 'EUR',
prices: JSON.stringify([
{
label: 'some label',
amount: 200,
},
]),
},
});
The two methods from the API that are used for processing the payments are sendInvoice and answerPreCheckoutQuery but neither of them contains any argument that would possibly change the output the way I want. Am I missing something?
Note at the end: despite all this, the payments works. This is just a cosmetic change I would like to achieve.
I also had this problem, specify a parameter: start_parameter='unique-string'
Related
I'm trying to integrate Stripe One time and Subscription Payment using their Checkout API.
I have also enabled 3D secure payments.
In one time payment when payment is successful it redirects to our success page. And when a payment is failed, it shows error message in checkout form. Which is as expected.
In Checkout subscription, when payment is successfully it perfectly redirects to success page. For failed payment it also shows error messages after 3D authentication, but when I try to pay with another card or the same card after a failed attempt, it redirects me to Expired link page.
I checked in stripe demo checkout page (https://checkout.stripe.dev/preview) where it works fine but don't know what I'm missing.
What I understand from the stripe docs is that, for failed payment I should handle error and tell users/redirects to use a different payment method.
I have registered the following webhook events:
invoice.payment_action_required
charge.failed
customer.subscription.deleted
customer.subscription.created
checkout.session.completed
invoice.paid
When invoice.payment_action_required event is triggered, I have to manually confirm the payment for 3D secure authentication (it's required for subscription). I have written the following code to confirm the payment.
if (paymentIntent.status === 'requires_action') {
await stripe.paymentIntents.confirm(payment_intent);
}
Here is the code snippet to create a subscription session:
const session = await stripePrivate.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: planId,
quantity: 1
}
],
metadata: {
transactionId
},
subscription_data: {
metadata: {
transactionId
}
},
success_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=success&orderId=${orderId}`,
cancel_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=canceled&orderId=${orderId}`
});
const callbackUrl = session.url;
And below is the page, I'm redirected to if a subscription payment is failed (from 2nd attempt):
Here I'm adding some checkout URL for testing:
Test card 1: 4000008260003178 (insufficient balance)
Test card 2: 4000002500003155
https://checkout.stripe.com/pay/cs_test_a1SAsf7YCjXOZPNKf0K9AXNDHSm8lMLFwD80VZEajKxEAgpeD9GiZBH2wr#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1RTXGddpYeZy0zRfvuJrGWqtT3KiURrJFCjSDS9fK8OIhdmTPFBD0Mzx8#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1ZMzrim1XQWNVgHCceiSw9mjrtMMdTricwdGhzf7wdHYcEsSabFTxRGcv#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
Above URL will be expired within 24 hours. Please ask if you need
another active url for testing.
How to test?
Use the given test card 1 and complete 3D authentication
Now use test card 1 or 2 and try to subscribe again, you'll be redirected to Expired link page
How do I fix this issue? What am I missing here?
Checkout pages are single use. If you use one to create a subscription, it is then "consumed" and can't be used again.
It sounds like you're trying to deal with the case where a subscription is created, but a future invoice's payment fails due to the payment being in the requires_action status. This will happen if the card in question requires 3DS and must be confirmed on the client by the user. See the PaymentIntents flow: https://stripe.com/docs/payments/intents
You wouldn't redirect to the same Checkout session to action a card in the requires_action status, instead you'd either build your own UI or use the Customer portal: https://stripe.com/docs/billing/subscriptions/customer-portal.
I'm implementing the payment gateway API of a local company that provides a HTML fragment that renders a button in my page to handle the checkout and payment in a modal-like interface that is out of my control (without redirecting the user outside of my page).
The fragment is a script tag like this:
<form action="http://localhost:3000/api/checkout" method="POST">
<script
src="https://www.mercadopago.com.ar/integrations/v1/web-tokenize-checkout.js"
data-public-key="ENV_PUBLIC_KEY"
data-transaction-amount="100.00">
</script>
</form>
After the Card and payment info is entered by the user, the gateway makes a POST request to my API endpoint 'checkout' in Next.js (specified in the action attribute of the form) with some data to confirm the transaction as shown in the following.
/pages/api/checkout.js:
export default (req, res) => {
mercadopago.configurations.setAccessToken(configAccess_token);
var payment_data = {
transaction_amount: 100,
token: token,
description: 'Blue shirt',
installments: installments,
payment_method_id: payment_method_id,
issuer_id: issuer_id,
payer: {
email: 'john#yourdomain.com'
}
};
// Save and process the payment:
mercadopago.payment.save(payment_data).then((data) => {
Console.log(data.status);
}).catch( (error) => {
console.error(error)
});
};
I receive the payment info and I'm able to process the payment, however, the page keeps waiting for a promise-like response (stays loading indefinitely) after making the POST request. How could I handle this promise/response if I'm not explicitly making the POST request? I don't have access to the code that makes the POST request as it comes from the payment gateway itself.
I tried returning a response from my API endpoint with some data like this...
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({success: 'success!'});
... but it just renders the JSON data as plain text after redirecting me to my API URL: "http://localhost:3000/api/checkout".
The API documentation doesn't offer any info about this matter as far as I know after hours researching it.
My goal is to display some kind of notification of success (like a toast for example) or find any other way of letting the user know the transaction was completed without creating a generic '/success' page that anyone could visit at any time.
I have little experience in backend development so any help to put me in the right direction to handle this promise/response would be highly appreciated.
I have logic in my onMembersAdded function to load the user state and see if userData.accountNumber attribute exists. If it does not, a run an auth dialog to get the user's account number. If the attribute does exist, the welcome message should be displayed without a prompt.
When I test on local, this works fine. But when I test on Azure, I always end up in the !userData.accountNumber block. Through checking the console log, I can see that in the onMembersAdded function is showing {} for the userData object. But in auth dialog, even if I skip the prompt (which we allow the user to do), the accountNumber attribute is there in userState (if it had been entered previously).
The only thing I can figure is that somehow using BlobStorage for state, as I do on Azure, is somehow exhibiting different behavior than MemoryStorage which I am using for local testing. I thought it might be a timing issue, but I am awaiting the get user state call, and besides if I do enter an account number in the auth dialog, the console log immediately following the prompt shows the updated account number, no problem.
EDIT: From the comments below, it's apparent that the issue is the different way channels handle onMembersAdded. It seems in emulator both bot and user are added at the same time, but on webchat/directline, user isn't added until the first message is sent. So that is the issue I need a solution to.
Here is the code in the constructor defining the state variables and onMembersAdded function:
// Snippet from the constructor. UserState is passed in from index.js
// Create the property accessors
this.userDialogStateAccessor = userState.createProperty(USER_DIALOG_STATE_PROPERTY);
this.dialogState = conversationState.createProperty(DIALOG_STATE_PROPERTY);
// Create local objects
this.conversationState = conversationState;
this.userState = userState;
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let member of membersAdded) {
if (member.id === context.activity.recipient.id) {
this.appInsightsClient.trackEvent({name:'userAdded'});
// Get user state. If we don't have the account number, run an authentication dialog
// For initial release this is a simple prompt
const userData = await this.userDialogStateAccessor.get(context, {});
console.log('Members added flow');
console.log(userData);
if (!userData.accountNumber) {
console.log('In !userData.accountNumber block');
const dc = await this.dialogs.createContext(context);
await dc.beginDialog(AUTH_DIALOG);
await this.conversationState.saveChanges(context);
await this.userState.saveChanges(context);
} else {
console.log('In userData.accountNumber block');
var welcomeCard = CardHelper.GetHeroCard('',welcomeMessage,menuOptions);
await context.sendActivity(welcomeCard);
this.appInsightsClient.trackEvent({name:'conversationStart', properties:{accountNumber:userData.accountNumber}});
}
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
If you want your bot to receive a conversation update from Web Chat with the correct user ID before the user sends a message manually, you have two options:
Instead of connecting to Direct Line with a secret, connect with a token (recommended). Note that this will only work if you provide a user property in the body of your Generate Token request.
Have Web Chat send an initial activity to the bot automatically so the user doesn't have to. This would be in response to DIRECT_LINE/CONNECT_FULFILLED, and it could be an invisible event activity so to the user it still looks like the first activity in the conversation came from the bot.
If you go with option 1, your bot will receive one conversation update with both the bot and the user in membersAdded, and the from ID of the activity will be the user ID. This is ideal because it means you will be able to acess user state.
If you go with option 2, your bot will receive two conversation update activities. The first is the one you're receiving now, and the second is the one with the user ID that you need. The funny thing about that first conversation update is that the from ID is the conversation ID rather than the bot ID. I presume this was an attempt on Web Chat's part to get the bot to mistake it for the user being added, since Bot Framework bots typically recognize that conversation update by checking if the from ID is different from the member being added. Unfortunately this can result in two welcome messages being sent because it's harder to tell which conversation update to respond to.
Conversation updates have been historically unreliable in Web Chat, as evidenced by a series of GitHub issues. Since you may end up having to write channel-aware bot code anyway, you might consider having the bot respond to a backchannel event instead of a conversation update when it detects that the channel is Web Chat. This is similar to option 2 but you'd have your bot actually respond to that event rather than the conversation update that got sent because of the event.
Per Kyle's answer, I was able to resolve the issue. However, the documentation on initiating a chat session via tokens wasn't entirely clear, so I wanted to provide some guidance for others trying to solve this same issue.
First, you need to create an endpoint in your bot to generate the token. The reason I initiated the session from SECRET initially was because I didn't see a point to creating a token when the SECRET was exposed anyway to generate it. What wasn't made clear in the documentation was that you should create a separate endpoint so that the SECRET isn't in the browser code. You can/should further obfuscate the SECRET using environmental variables or key vault. Here is the code for the endpoint I set up (I'm passing in userId from browser, which you'll see in a minute).
server.post('/directline/token', async (req, res) => {
try {
var body = {User:{Id:req.body.userId}};
const response = await request({
url: 'https://directline.botframework.com/v3/directline/tokens/generate',
method: 'POST',
headers: { Authorization: `Bearer ${process.env.DIRECTLINE_SECRET}`},
json: body,
rejectUnauthorized: false
});
const token = response.token;
res.setHeader('Content-Type', 'text/plain');
res.writeHead(200);
res.write(token);
res.end();
} catch(err) {
console.log(err);
res.setHeader('Content-Type', 'text/plain');
res.writeHead(500);
res.write('Call to retrieve token from Direct Line failed');
res.end();
}
})
You could return JSON here, but I chose to return token only as text. Now to call the function, you'll need to hit this endpoint from the script wherever you are deploying the bot (this is assuming you are using botframework-webchat CDN). Here is the code I used for that.
const response = await fetch('https://YOURAPPSERVICE.azurewebsites.net/directline/token', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({userId:userID})
});
const token = await response.text();
Body of request must be stringified JSON. Fetch returns the response as a stream, so you need to convert it using .text() or .json() depending on how you are sending the response from your bot endpoint (I used .text()). You need to await both the fetch AND the response.text(). My whole script to deploy the webchat is within an async function. Just a note, if you need this to work in IE11 as I do, async/await won't work. I dealt with this by running the entire code through Babel once I was done and it seems to work fine.
I'm trying to create a bot that sends a direct message to a random slack user. The first step I am trying to accomplish is console.logging the list of users.
Here is what I have so far:
controller.hears('marco', 'direct_message', function(bot, message) {
bot.api.users.list({user: message.user}, function(err, list){
bot.reply(message, "polo");
console.log(bot.api.users.list);
})
});
When I direct message the bot marco, it replies polo and [Function] is logged. How am able to log some real data? I tried bot.api.users.list.members, but it logs as undefined. Thank you.
If you want to list ALL users, you are using a correct API call, just remove the params from it ({} instead of {user: message.user}).
Docs: users.list
If you want to get information about a specific user from your team, you should use the following API call: bot.api.users.info({ user: USER_ID }, function (err, response) { ... });
Docs: users.info
There's also a test section in the Slack docs ('Tester' tab) so that you can try out what the API calls will return for your actual users.
I am creating & sending the following payment via the Express checkout API V4:
return paypal.rest.payment.create(env, client, {
intent: 'authorize',
payer: {
"payment_method": "paypal"
},
transactions: [
{
amount: { total: '0.01', currency: 'GBP' }
}
]
});
and I'm returning the following object:
Which all seems to be on the right track. The problem is, there is no sign of this payment auth in the sandbox dashboard.
I've even tried using a live account, and sending a real penny, but there is no sign of the transaction in either the buyer or seller account.
If this payment is not being successfully created, why am I seeing the object returned with a state of "created"?
FYI: If I send a payment using intent: 'sale' it processes successfully and appears in the dashboard.
I had the same problem. API and documentation of PayPal is something awful...
The logic is this:
1. You have to make execute payment.
I tried to do it in different ways, but the easiest way ended up like this:
In the examples on the Paypal site show your complete code with onAuthorize: function(data, actions) , so this function should look like this:
onAuthorize: function(data, actions) {
return actions.payment.get().then(function(payment) {
console.log(payment);
var b = payment.payer;
var bb = b.payer_info;
// alert (bb.payer_id);
var newUrl = "http://YOURDOMAIN.COM/execute.php?paymentId="+payment.id+"&token=EC-"+payment.cart+"&PayerID="+bb.payer_id;
console.log(newUrl);
// go to the execute.php and send to paypal payment confirmation
window.location.replace(newUrl);
});
}
Once you got the object with a transaction you must still confirm it!
Go here https://github.com/paypal/PayPal-PHP-SDK/wiki/Installation download the PHP SDK, it will need to file execute.php to easily confirm the payment and it showed up in the admin panel PayPal. I downloaded the SDK for a direct link, without Composer.
Then in the newly created file execute.php connect this directly SDK without Composer.
// Use below for direct download installation
require __DIR__ . '/PayPal-PHP-SDK/autoload.php';
Then copy the contents of the file itself execute.php and replace it PayPal client ID and client secret. Full code of execute.php here http://pastebin.com/K750qcxE
I couldn't paste here all the code. Citation of code here is terribly implemented, as well as PayPal API :)
p.s. in the script I sent to return url, but I don't know why paypal did not redirect me to it, so I redirect using javascript when you get the transaction object.
sorry for my english