I successfully created a simple chatbot using Google Dialogflow and a webhook. The bot asks a user to enter their age.
If their age is between 18 and 60, the webhook sends back a
"followupEventInput" response and calls the "ask_gender" event. The
response also sets the "age" parameter.
If the age is not within that range, it will call event "reenter_age"
that sits on another intent and asks the user to re-enter their age.
No parameters are set here.
This all works 100% on Facebook and the Dialogflow test window, no problems at all.
I then enabled Google Assistant on exactly the same project using the same intents, same responses, etc.
The user is asked to enter their age. I then enter an age outside of the expected range and the "reenter_age" event is triggered. The Google Assistant then asks the user to re-enter their age. All good, no problems at all. Works the same as above, using the same code, etc.
I then enter the correct age and my webhook sends a "followupEventInput" response and calls the "ask_gender" event. The response also sets the "age" parameter. Exactly the same as in the first point above. But, unfortunately I keep getting a "Webhook error (206)" response, with Code 14. Here, the Google Assistant is supposed to ask for the person's gender (a question that it gets from the intent linked to the called event, not from my webhook), but that does not happen (works perfect in Facebook and the Dialogflow test window on the right).
When I remove the parameter value from my response, the Google Assistant will proceed to ask parameter prompts, because the parameter value never gets set, which is correct, this is supposed to happen.
I do believe this might be a bug, but I will really appreciated your input in case you stumbled upon this as well.
This gets shown in the Google Assistant simulator Window: "Sorry, COMPANY_NAME Cover isn't responding right now. Please try again soon." and just below that "xxx has left the conversation".
Funny thing is, in the Audio tab the text the assistant was supposed to ask is displayed: "Thank you.
Are you male or female?"
Below is the debug log from the Google Assistant simulator test page:
"response": "Sorry, COMPANY_NAME Cover isn't responding right now. Please try again soon.",
"expectUserResponse": false,
"conversationToken": "xxx...",
"audioResponse": "//xxx...",
"ssmlMarkList": [],
"debugInfo": {
"assistantToAgentDebug": {
"curlCommand": "curl -v 'https://api.api.ai/api/integrations/google?token=xxxxx' -H 'Content-Type: application/json;charset=UTF-8' -H 'Google-Actions-API-Version: 2' -H 'Authorization: eyJhbGciOiJSUzI1NiIsImtpZCI6IjI4Yjc0MWU4ZGU5ODRhNDcxNTlmMTllNmQ3NzgzZTlkNGZhODEwZGIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiJhZ2VudC1jb3Zlci10ZXN0LW5xbXB3aCIsIm5iZiI6MTU4ODA3MTg5MSwiaWF0IjoxNTg4MDcyMTkxLCJleHAiOjE1ODgwNzIzMTEsImp0aSI6ImE0MWUyMzgzMWMzYTQ3MDY0MTViYTM1ODQxNGJlZmRlNTM4NzYzYzEifQ.aHxw-af5z3jFgIR0biT-MLGSfHsxtKn2-b966pmqMiAGlhD5xe4OUnAiyXedgR4I65AMAEdiYDHODI-H_WgnPyHh9j23Dfqfn3RJD-Z_vvCl_woEFQB4DXp1uFOZEOsJVf-n3tBWTmkCJvERGuDBdbisvwIIHvcjg3NTDeUPNDvuDvGbU915TA7qWp0vtTNr923zu5qoZC3x9lVYNqK5eXXZgDBd0DUjLkfnmEmX_6cRhvTvFptLjFD54TOw920kjlfCGslu2cy4GzMSsbOYiLwDc6Nmt7SW7ekTRPY4FwMXP7YE0-fj5ELDDGV-7zh3jydJusqx_Yi1UvkDr1EWwg' -A Google-ActionsOnGoogle/1.0 -X POST -d '{\"user\":{\"locale\":\"en-US\",\"lastSeen\":\"2020-04-28T11:09:46Z\",\"userVerificationStatus\":\"VERIFIED\"},\"conversation\":{\"conversationId\":\"ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc\",\"type\":\"ACTIVE\",\"conversationToken\":\"[\\\"expecting_age\\\"]\"},\"inputs\":[{\"intent\":\"actions.intent.TEXT\",\"rawInputs\":[{\"inputType\":\"KEYBOARD\",\"query\":\"40\"}],\"arguments\":[{\"name\":\"text\",\"rawText\":\"40\",\"textValue\":\"40\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.ACCOUNT_LINKING\"}]},\"isInSandbox\":true,\"requestType\":\"SIMULATOR\"}'",
"assistantToAgentJson": "{\"user\":{\"locale\":\"en-US\",\"lastSeen\":\"2020-04-28T11:09:46Z\",\"userVerificationStatus\":\"VERIFIED\"},\"conversation\":{\"conversationId\":\"ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc\",\"type\":\"ACTIVE\",\"conversationToken\":\"[\\\"expecting_age\\\"]\"},\"inputs\":[{\"intent\":\"actions.intent.TEXT\",\"rawInputs\":[{\"inputType\":\"KEYBOARD\",\"query\":\"40\"}],\"arguments\":[{\"name\":\"text\",\"rawText\":\"40\",\"textValue\":\"40\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.SCREEN_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.ACCOUNT_LINKING\"}]},\"isInSandbox\":true,\"requestType\":\"SIMULATOR\"}",
"delegatedRequest": {
"delegatedRequest": ""
}
},
"agentToAssistantDebug": {
"agentToAssistantJson": "{\n \"conversationToken\": \"[\\\"expecting_gender\\\",\\\"expecting_age\\\"]\",\n \"expectUserResponse\": true,\n \"expectedInputs\": [{\n \"inputPrompt\": {\n \"richInitialPrompt\": {\n \"items\": [{\n \"simpleResponse\": {\n \"textToSpeech\": \"Thank you.\"\n }\n }, {\n \"simpleResponse\": {\n \"textToSpeech\": \"Are you male or female?\"\n }\n }]\n }\n },\n \"possibleIntents\": [{\n \"intent\": \"assistant.intent.action.TEXT\"\n }, {\n \"intent\": \"956e9d14-6369-4b69-a1dd-443f37ebff64\"\n }, {\n \"intent\": \"e4abebaf-e693-4989-bfa9-5fc3e329a5ce\"\n }],\n \"speechBiasingHints\": [\"$entity-gender\", \"$sys.number-integer\"]\n }],\n \"responseMetadata\": {\n \"status\": {\n \"code\": 14,\n \"message\": \"Webhook error (206)\"\n },\n \"queryMatchInfo\": {\n \"queryMatched\": true,\n \"intent\": \"956e9d14-6369-4b69-a1dd-443f37ebff64\",\n \"parameterNames\": [\"age\"]\n },\n \"delegatedRequest\": {\n \"delegatedRequest\": \"{\\n \\\"responseId\\\": \\\"77a4105c-11c4-41fc-b8b4-f85753f056c9-8e1f57f1\\\",\\n \\\"queryResult\\\": {\\n \\\"queryText\\\": \\\"ask_gender\\\",\\n \\\"parameters\\\": {\\n \\\"age\\\": {\\n \\\"amount\\\": 40.0,\\n \\\"unit\\\": \\\"year\\\"\\n }\\n },\\n \\\"allRequiredParamsPresent\\\": true,\\n \\\"fulfillmentText\\\": \\\"Are you male or female?\\\",\\n \\\"fulfillmentMessages\\\": [{\\n \\\"platform\\\": \\\"ACTIONS_ON_GOOGLE\\\",\\n \\\"simpleResponses\\\": {\\n \\\"simpleResponses\\\": [{\\n \\\"textToSpeech\\\": \\\"Thank you.\\\"\\n }]\\n }\\n }, {\\n \\\"platform\\\": \\\"ACTIONS_ON_GOOGLE\\\",\\n \\\"simpleResponses\\\": {\\n \\\"simpleResponses\\\": [{\\n \\\"textToSpeech\\\": \\\"Are you male or female?\\\"\\n }]\\n }\\n }, {\\n \\\"text\\\": {\\n \\\"text\\\": [\\\"Thank you.\\\"]\\n }\\n }, {\\n \\\"text\\\": {\\n \\\"text\\\": [\\\"Are you male or female?\\\"]\\n }\\n }],\\n \\\"outputContexts\\\": [{\\n \\\"name\\\": \\\"projects/agent-cover-test-nqmpwh/agent/sessions/ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc/contexts/expecting_gender\\\",\\n \\\"lifespanCount\\\": 20,\\n \\\"parameters\\\": {\\n \\\"age\\\": {\\n \\\"amount\\\": 40.0,\\n \\\"unit\\\": \\\"year\\\"\\n },\\n \\\"age.original\\\": \\\"40\\\"\\n }\\n }, {\\n \\\"name\\\": \\\"projects/agent-cover-test-nqmpwh/agent/sessions/ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc/contexts/ask_gender\\\",\\n \\\"parameters\\\": {\\n \\\"age\\\": {\\n \\\"amount\\\": 40.0,\\n \\\"unit\\\": \\\"year\\\"\\n },\\n \\\"age.original\\\": \\\"40\\\"\\n }\\n }, {\\n \\\"name\\\": \\\"projects/agent-cover-test-nqmpwh/agent/sessions/ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc/contexts/expecting_age\\\",\\n \\\"lifespanCount\\\": 17,\\n \\\"parameters\\\": {\\n \\\"age\\\": {\\n \\\"amount\\\": 40.0,\\n \\\"unit\\\": \\\"year\\\"\\n },\\n \\\"age.original\\\": \\\"40\\\"\\n }\\n }, {\\n \\\"name\\\": \\\"projects/agent-cover-test-nqmpwh/agent/sessions/ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc/contexts/__system_counters__\\\",\\n \\\"parameters\\\": {\\n \\\"no-input\\\": 0.0,\\n \\\"no-match\\\": 0.0,\\n \\\"age\\\": {\\n \\\"amount\\\": 40.0,\\n \\\"unit\\\": \\\"year\\\"\\n },\\n \\\"age.original\\\": \\\"40\\\"\\n }\\n }],\\n \\\"intent\\\": {\\n \\\"name\\\": \\\"projects/agent-cover-test-nqmpwh/agent/intents/956e9d14-6369-4b69-a1dd-443f37ebff64\\\",\\n \\\"displayName\\\": \\\"cover.funeral.get.quote.age\\\"\\n },\\n \\\"intentDetectionConfidence\\\": 1.0,\\n \\\"languageCode\\\": \\\"en\\\"\\n },\\n \\\"originalDetectIntentRequest\\\": {\\n \\\"source\\\": \\\"google\\\",\\n \\\"version\\\": \\\"2\\\",\\n \\\"payload\\\": {\\n \\\"user\\\": {\\n \\\"locale\\\": \\\"en-US\\\",\\n \\\"lastSeen\\\": \\\"2020-04-28T11:09:46Z\\\",\\n \\\"userVerificationStatus\\\": \\\"VERIFIED\\\"\\n },\\n \\\"conversation\\\": {\\n \\\"conversationId\\\": \\\"ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc\\\",\\n \\\"type\\\": \\\"ACTIVE\\\",\\n \\\"conversationToken\\\": \\\"[\\\\\\\"expecting_age\\\\\\\"]\\\"\\n },\\n \\\"inputs\\\": [{\\n \\\"intent\\\": \\\"actions.intent.TEXT\\\",\\n \\\"rawInputs\\\": [{\\n \\\"inputType\\\": \\\"KEYBOARD\\\",\\n \\\"query\\\": \\\"40\\\"\\n }],\\n \\\"arguments\\\": [{\\n \\\"name\\\": \\\"text\\\",\\n \\\"rawText\\\": \\\"40\\\",\\n \\\"textValue\\\": \\\"40\\\"\\n }]\\n }],\\n \\\"surface\\\": {\\n \\\"capabilities\\\": [{\\n \\\"name\\\": \\\"actions.capability.SCREEN_OUTPUT\\\"\\n }, {\\n \\\"name\\\": \\\"actions.capability.WEB_BROWSER\\\"\\n }, {\\n \\\"name\\\": \\\"actions.capability.AUDIO_OUTPUT\\\"\\n }, {\\n \\\"name\\\": \\\"actions.capability.MEDIA_RESPONSE_AUDIO\\\"\\n }, {\\n \\\"name\\\": \\\"actions.capability.ACCOUNT_LINKING\\\"\\n }]\\n },\\n \\\"isInSandbox\\\": true,\\n \\\"requestType\\\": \\\"SIMULATOR\\\"\\n }\\n },\\n \\\"session\\\": \\\"projects/agent-cover-test-nqmpwh/agent/sessions/ABwppHGl1ac0akIrzrAht8MzMsi-Um7CE4a_6YBczuuJQrgypBqq3BHGkoRcnYXeuZm33-MKMAiPXgOQs_LacUF5yBgtpLc\\\"\\n}\"\n },\n \"delegatedResponse\": {\n \"delegatedResponse\": \"{\\\"followupEventInput\\\": {\\\"name\\\": \\\"ask_gender\\\", \\\"languageCode\\\": \\\"en-US\\\", \\\"parameters\\\": {\\\"age\\\": {\\\"amount\\\": 40.0, \\\"unit\\\": \\\"year\\\"}, \\\"age.original\\\": \\\"40\\\"}}}\"\n }\n }\n}",
"delegatedResponse": {
"delegatedResponse": ""
}
},
"sharedDebugInfoList": [
{
"name": "ResponseValidation",
"debugInfo": "",
"subDebugEntryList": [
{
"name": "MalformedResponse",
"debugInfo": "Webhook error (206).",
"subDebugEntryList": []
}
]
}
],
"conversationBuilderExecutionEventsList": []
},
"visualResponse": {
"visualElementsList": [
{
"displayText": {
"content": "Sorry, COMPANY_NAME Cover isn't responding right now. Please try again soon."
}
}
],
"suggestionsList": [],
"agentLogoUrl": ""
},
"clientError": 0,
"is3pResponse": true,
"clientOperationList": [
{
"operationType": 7,
"exitIndicatorPayLoad": {
"status": 1
}
}
],
"projectName": "",
"renderedHtml": ""
}```
I found a workaround. If you send back a" fulfillmentMessages" response (not the followUpIntent reponse), Google Assistant will continue with the next intent, and keep all contexts and parameters in tact. This is most definitely not ideal but it is a workaround. You can put whatever text you want in there and it continues.
Related
There are two collections based on user score. The first consist of user score and the list of 500 careers are placed in other collection. I want to show careers based on the user score. Suppose user score is 80 so all the careers that comes under 80 score can be shown to user.
There can be one or more careers having same score.
The report collection is :
{
"score": "80",
"details" : "hello",
}
the career collection is :
{
"number": "1",
"intro": "intro of 6",
"education": "education of 6",
"training": "training of 6",
"career_name" : "Computer Designer",
"career_img" :"marine.jpg",
"abilities": "Loreupm ipsun CAS",
"salary_global":"$90000",
"work_styles":"Loerum ipsum 6",
"scope": "scope of 6",
"image": "https://t4.ftcdn.net/jpg/02/98/58/41/360_F_298584167_WLdKSUF4ZpQxLe4dX1div4tvC41Nd9N0.jpg"
}
I want to display the careers based on the user score.
The collection should be :
{
"score": "80",
"details" : "hello",
"career": {
{
"number": "1",
"intro": "intro of 6",
"education": "education of 6",
"training": "training of 6",
"career_name" : "Computer Designer",
"career_img" :"marine.jpg",
"abilities": "Loreupm ipsun CAS",
"salary_global":"$90000",
"work_styles":"Loerum ipsum 6",
"scope": "scope of 6"}
{
"number": "2",
"intro": "intro of 6",
"education": "education of 6",
"training": "training of 6",
"career_name" : "Computer Designer",
"career_img" :"marine.jpg",
"abilities": "Loreupm ipsun CAS",
"salary_global":"$90000",
"work_styles":"Loerum ipsum 6",
"scope": "scope of 6",}
}
}
How can I do this using mongodb.
I tried:
let advices = await db.aggregate('report', [
{$match :{code: user.score}},
{
$lookup: {
from: "career",
localField: "career",
foreignField: "_id",
as: "career"
}
},
{ $unwind: {path: "$career", "preserveNullAndEmptyArrays": true},
}
]);
This not working. please can you access. There can be one or more careers based on score.
for example: computer science can be shown to user having 120 score , 80 score and 150 score.
This is react code:
{advices.map(advice => (
<>
<Container maxwidth="sm" component="main">
<Grid container spacing={3} direction="row" justifyContent="center" flexGrow: 1 }} key={advice.id}>
<Fragment>
<Grid item xs={12} md={6} sm={6} alignItems="center" justifyContent="center">
{advice.code}
</Grid>
<Grid item xs={12} sm={12} md={12} sx={{flexGrow:1}}>
<div>
**{ advice.career && advice.career.map(carer => (
<li key={carer.number}>{carer.career_name}</li>
))}**
</div>
</Grid>
I am new to web development and am about half way through a full-stack web development course. How would I go about calling the value of the data stored with the source: "Rotten Tomatoes"?
I have tried Ratings[1].Value and it does not seem to work.
var movieObject = JSON.parse(body);
console.log('Rotten Tomatoes Rating: ', movieObject.Ratings[1].Value);
Body Content:
{
"Title": "Avatar",
"Year": "2009",
"Rated": "PG-13",
"Released": "18 Dec 2009",
"Runtime": "162 min",
"Genre": "Action, Adventure, Fantasy",
"Director": "James Cameron",
"Writer": "James Cameron",
"Actors": "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
"Plot": "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
"Language": "English, Spanish",
"Country": "UK, USA",
"Awards": "Won 3 Oscars. Another 85 wins & 128 nominations.",
"Poster": "https://images-na.ssl-images-amazon.com/images/M/MV5BMTYwOTEwNjAzMl5BMl5BanBnXkFtZTcwODc5MTUwMw##._V1_SX300.jpg",
"Ratings": [
{
"Source": "Internet Movie Database",
"Value": "7.8/10"
},
{
"Source": "Rotten Tomatoes",
"Value": "83%"
},
{
"Source": "Metacritic",
"Value": "83/100"
}
],
"Metascore": "83",
"imdbRating": "7.8",
"imdbVotes": "967,488",
"imdbID": "tt0499549",
"Type": "movie",
"DVD": "22 Apr 2010",
"BoxOffice": "$749,700,000",
"Production": "20th Century Fox",
"Website": "http://www.avatarmovie.com/",
"Response": "True"
}
Your call is correct, so it must be something in the setup. Best guess is that you're calling JSON.parse() on an object that has already been converted to an object.
We're based in the EU. When we sell our digital products to private persons or companies without a VAT number, we have to charge them VAT (Value Added Tax). This is what I'm trying:
import stripe
stripe.api_key = 'sk_test_xxx'
stripe.api_version = '2015-10-16'
product = stripe.Product.create(
id='product',
name='Product',
shippable=False
)
sku = stripe.SKU.create(
product='product',
price=100,
currency='eur',
inventory={'type': 'infinite'}
)
customer = stripe.Customer.create(
email='customer#example.org',
description="Customer"
)
order = stripe.Order.create(
customer=customer.id,
currency='eur',
items=[
{
'type': 'sku',
'quantity': 5,
'parent': sku.id,
'amount': 500
},
{
'type': 'tax',
'description': "20% VAT",
'amount': 100
}
]
)
The Order creation call gives me:
stripe.error.InvalidRequestError: Request req_xxx: Items of type tax are not supported at order creation.
When I replace the last order creation call without the tax:
order = stripe.Order.create(
customer=customer.id,
currency='eur',
items=[
{
'type': 'sku',
'quantity': 5,
'parent': sku.id,
'amount': 500
}
]
)
I'm getting back these order['items']:
[
{
"amount": 500,
"currency": "eur",
"description": "Product",
"object": "order_item",
"parent": "sku_xxx",
"quantity": 5,
"type": "sku"
},
{
"amount": 0,
"currency": "eur",
"description": "Taxes (included)",
"object": "order_item",
"parent": null,
"quantity": null,
"type": "tax"
},
{
"amount": 0,
"currency": "eur",
"description": "Free shipping",
"object": "order_item",
"parent": "ship_free-shipping",
"quantity": null,
"type": "shipping"
}
]
However, an order does not allow updating the items field after the order has been created.
What's the correct and semantic way to add VAT to the order items?
I contacted Stripe support and this should now be possible in a private beta. You can ask Stripe to join the taxes beta.
After joining, you can access documentation here: https://stripe.com/docs/relay#shipping-and-taxes and here: https://stripe.com/docs/relay/dynamic-shipping-taxes#order-creation-event.
There will be an option in your Stripe dashboard (Relay settings) to specify a "dynamic" taxes webhook where Stripe sends an Order to, and your server should then respond with a Order Item containing a tax entry. The webhook is hit immediately after creating an Order.
I have an object stored in arangodb which has additional inner objects, my current use case requires that I update just one of the elements.
Store Object
{
"status": "Active",
"physicalCode": "99999",
"postalCode": "999999",
"tradingCurrency": "USD",
"taxRate": "14",
"priceVatInclusive": "No",
"type": "eCommerce",
"name": "John and Sons inc",
"description": "John and Sons inc",
"createdDate": "2015-05-25T11:04:14+0200",
"modifiedDate": "2015-05-25T11:04:14+0200",
"physicalAddress": "Corner moon and space 9 station",
"postalAddress": "PO Box 44757553",
"physicalCountry": "Mars Sector 9",
"postalCountry": "Mars Sector 9",
"createdBy": "john.doe",
"modifiedBy": "john.doe",
"users": [
{
"id": "577458630580",
"username": "john.doe"
}
],
"products": [
{
"sellingPrice": "95.00",
"inStock": "10",
"name": "School Shirt Green",
"code": "SKITO2939999995",
"warehouseId": "723468998682"
},
{
"sellingPrice": "95.00",
"inStock": "5",
"name": "School Shirt Red",
"code": "SKITO245454949495",
"warehouseId": "723468998682"
},
{
"sellingPrice": "95.00",
"inStock": "10",
"discount": "5%",
"name": "School Shirt Blue",
"code": "SKITO293949495",
"warehouseId": "723468998682"
}
]
}
I want to change just one of the products stock value
{
"sellingPrice": "95.00",
"inStock": "10",
"discount": "5%",
"name": "School Shirt Blue",
"code": "SKITO293949495",
"warehouseId": "723468998682"
}
Like update store product stock less 1 where store id = x, something to this effect
FOR store IN stores
FILTER store._key == "837108415472"
FOR product IN store.products
FILTER product.code == "SKITO293949495"
UPDATE product WITH { inStock: (product.inStock - 1) } IN store.products
Apart from the above possibly it makes sense to store product as a separate document in collection store_products. I believe in NOSQL that is the best approach to reduce document size.
Found answer
here arangodb-aql-update-single-object-in-embedded-array and there
arangodb-aql-update-for-internal-field-of-object
I however believe it is best to maintain separate documents and rather use joins when retrieving. Updates easily
I am using stripe as my payment provider and storing encrypted credit card id in my db returned from stripe.
My question is that from GUI customer can add same card again. I see stripe do not prevent to add same card multiple time for same customer. Since stripe always generates different encrypted card id for same card so I can't use it to validate if same card is being added again.
How can stop customer to add same card again again.
Looks like I got that . I can use fingerprint returned in json response. I saw stripe dashboard and found that fingerprint is always same for same card which I was adding again again.
Here is json request and response to confirm
Request
{
"source": {
"number": "378282246310005",
"cvc": "123",
"address_line2": "4th Floor",
"address_line1": "140 2nd Street",
"address_country": "USA",
"name": "VIAY KUMAR",
"address_state": "CA",
"exp_month": 12,
"exp_year": 2015,
"address_zip": "94105",
"address_city": "San Francisco",
"object": "card"
}
}
Response
{
"id": "card_166H9rC8Y8JrMFgBh9GVsmNG",
"object": "card",
"status": null,
"exp_month": 12,
"exp_year": 2015,
"last4": "0005",
"country": "US",
"type": null,
"name": "VIAY KUMAR",
"customer": "cus_6IrxhfwXNyD1Uw",
"recipient": null,
"address_line1": "140 2nd Street",
"address_line2": "4th Floor",
"address_zip": "94105",
"address_city": "San Francisco",
"address_state": "CA",
"address_country": "USA",
"address_zip_check": "pass",
"address_line1_check": "pass",
"cvc_check": "pass",
"fingerprint": "TwjSA2KqPDhSMUvQ",
"brand": "American Express",
"funding": "credit"
}
added same card again and got different card id but same finger print :-)
Request
{
"source": {
"number": "378282246310005",
"cvc": "123",
"address_line2": "4th Floor",
"address_line1": "140 2nd Street",
"address_country": "USA",
"name": "VIAY KUMAR",
"address_state": "CA",
"exp_month": 12,
"exp_year": 2015,
"address_zip": "94105",
"address_city": "San Francisco",
"object": "card"
}
}
Response
{
"id": "card_166HKVC8Y8JrMFgBfvbHPgk2",
"object": "card",
"status": null,
"exp_month": 12,
"exp_year": 2015,
"last4": "0005",
"country": "US",
"type": null,
"name": "VIAY KUMAR",
"customer": "cus_6IrxhfwXNyD1Uw",
"recipient": null,
"address_line1": "140 2nd Street",
"address_line2": "4th Floor",
"address_zip": "94105",
"address_city": "San Francisco",
"address_state": "CA",
"address_country": "USA",
"address_zip_check": "pass",
"address_line1_check": "pass",
"cvc_check": "pass",
"fingerprint": "TwjSA2KqPDhSMUvQ",
"brand": "American Express",
"funding": "credit"
}
Thanks to Vijay for the above answer.
I've written the following Ruby code in my Rails app to check for this.
Replace the relevant #user.stripe_customer_id variable with your own.
# Retrieve the customer we're adding this token to
customer = Stripe::Customer.retrieve(#user.stripe_customer_id)
# Retrieve the token
token = Stripe::Token.retrieve(params[:stripeToken])
# The fingerprint of the card is stored in `card.fingerprint`
card_fingerprint = token.card.fingerprint
# Check if the card fingerprint submitted matches one of the customer's current sources
fingerprint_already_exists = customer.sources.any? {|source| source[:fingerprint] == card_fingerprint}
if fingerprint_already_exists
# You can do whatever you want here. I've personally set a flash message to let the user know this card is already on their account
flash[:warning] = "That card already exists on your account."
redirect_to user_path(#user) and return
end
# Continue adding the source as normal
customer.sources.create(source: params[:stripeToken])
# Anything else you want to do...
flash[:success] = "Your account has been updated."
redirect_to user_path(#user) and return
The fingerprint generated by Stripe is unique to each card. As taken from the docs:
fingerprint string
Uniquely identifies this particular card number. You can use this attribute to check whether two customers who’ve signed up with you are using the same card number, for example.
Assuming you already have a customer object at this point (either a new one or one to update), this will let you add a card and make it default, without duplicates:
// $customer = \Stripe\Customer::retrieve( $clicker->customer );
/**
* Add the card, but only if it does not exist. Make it default in any case.
**/
$token = \Stripe\Token::retrieve( $_POST['stripeToken'] );
$card = null;
/** Does this customer already have this card? If so, find it. **/
foreach ( $customer->sources['data'] as $source ) {
if ( $source['fingerprint'] == $token['card']->fingerprint ) {
$card = $source;
}
}
/** If not, add the new card to the customer **/
if ( is_null( $card ) ) {
$card = $customer->sources->create( array( 'source' => $_POST['stripeToken'] ) );
}
$customer->default_source = $card->id;
$customer->save();
You'd enclose this in the usual try/catch as exemplified by the docs.
That being said, from the point of view of the user, it will be better, if possible, to show them a list of their cards (last4 + expire date) and let them pick the one they'd like to charge – instead of asking for their info needlessly.