I have encountered a strange behaviour on the Cygnus module. I’m using Context Broker version 0.28 and Cygnus version 0.13. Let’s suppose that I have loaded on the CB (Context Broker) some entities similars to this one:
(curl 172.21.0.33:1026/v1/updateContext -s -S
--header 'Fiware-Service: cb33_1003_06'
--header 'Fiware-ServicePath: /Lugar'
--header 'Content-Type: application/json'
--header 'Accept: application/json' -d #- | python -mjson.tool) <<EOF
{
"contextElements": [
{
"type": "Acceso_Wifi",
"isPattern": "false",
"id": "AP_1.3",
"attributes": [
{
"name": "temperature",
"type": "float",
"value": "25"
},
{
"name": "pressure",
"type": "integer",
"value": "1"
},
{
"name": "position",
"type": "coords",
"value": "13.322326, -1.983824",
"metadatas": [
{
"name": "location",
"type": "string",
"value": "WGS84"
}
]
}
]
}
],
"updateAction": "APPEND"
}
EOF
And now I subscribe the Cygnus:
(curl 172.21.0.33:1026/v1/subscribeContext -s -S
--header 'Fiware-Service: cb33_1003_06'
--header 'Fiware-ServicePath: /Lugar'
--header 'Content-Type: application/json'
--header 'Accept: application/json' -d #- | python -mjson.tool) <<EOF
{
"entities": [
{
"type": "Acceso_Wifi",
"isPattern": "true",
"id": "AP_.*"
}
],
"attributes": [
"temperature",
"pressure"
],
"reference": "http://172.21.0.33:5050/notify",
"duration": "P1M",
"notifyConditions": [
{
"type": "ONCHANGE",
"condValues": []
}
],
"throttling": "PT1S"
}
EOF
The CB will send one notification to the Cygnus for each matching entity loaded previously on the CB database. Let’s assume that there are 3 entities that meet the subscription’s criteria. In that case, one collection will be created on the Cygnus for each one of these entities (I’m using the default dm-by-entity data model). The problem is that the collection's name are not well formed. The Fiware-ServicePath is concatenated once per each entity, on each collection name:
cygnus2_/Lugar__Lugar__Lugar_AP_1.1_Acceso_Wifi
cygnus2_/Lugar__Lugar__Lugar_AP_1.2_Acceso_Wifi
cygnus2_/Lugar__Lugar__Lugar_AP_1.3_Acceso_Wifi
If we update one of these entities, a new collection will be created with a correct name:
cygnus2_/Lugar_AP_1.1_Acceso_Wifi
cygnus2_/Lugar__Lugar__Lugar_AP_1.1_Acceso_Wifi
cygnus2_/Lugar__Lugar__Lugar_AP_1.2_Acceso_Wifi
cygnus2_/Lugar__Lugar__Lugar_AP_1.3_Acceso_Wifi
On this example there were just 3 entities, but as the amount of matching entities increases, the longer will be the collection's name. And when it arrives to 50 characters, a warning will be raised and no collection will be created.
org.apache.flume.source.http.HTTPBadRequestException: 'fiware-servicePath' header length greater than 50)
at com.telefonica.iot.cygnus.handlers.OrionRestHandler.getEvents(OrionRestHandler.java:209)
at org.apache.flume.source.http.HTTPSource$FlumeHTTPServlet.doPost(HTTPSource.java:184)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:814)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:401)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:945)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
¿It’s that behaviour due to an incorrect configuration, or it’s Cygnus’s problem?
Here is the OrionMongoSink's configuration:
# OrionMongoSink configuration
# sink class, must not be changed
cygnusagent.sinks.mongo-sink.type = com.telefonica.iot.cygnus.sinks.OrionMongoSink
# channel name from where to read notification events
cygnusagent.sinks.mongo-sink.channel = mongo-channel
# true if the grouping feature is enabled for this sink, false otherwise
cygnusagent.sinks.mongo-sink.enable_grouping = false
# true if lower case is wanted to forced in all the element names, false otherwise
cygnusagent.sinks.mongo-sink.enable_lowercase = false
# FQDN/IP:port where the MongoDB server runs (standalone case) or comma-separated list of FQDN/IP:port pairs where the MongoDB replica set members run
cygnusagent.sinks.mongo-sink.mongo_hosts = mongodb1.spider.net:27017,mongodb2.spider.net:27017,mongodb3.spider.net:27017
###cygnusagent.sinks.mongo-sink.mongo_hosts = 172.21.0.25:27017,172.21.0.26:27017,172.21.0.28:27017
# a valid user in the MongoDB server (or empty if authentication is not enabled in MongoDB)
cygnusagent.sinks.mongo-sink.mongo_username =
# password for the user above (or empty if authentication is not enabled in MongoDB)
cygnusagent.sinks.mongo-sink.mongo_password =
# prefix for the MongoDB databases
cygnusagent.sinks.mongo-sink.db_prefix = cygnus2_
# prefix pro the MongoDB collections
cygnusagent.sinks.mongo-sink.collection_prefix = cygnus2_
# true is collection names are based on a hash, false for human redable collections
cygnusagent.sinks.mongo-sink.should_hash = false
# Must be dm-by-service-path or dm-by-entity
cygnusagent.sinks.mongo-sink.data_model = dm-by-entity
# how the attributes are stored, either per row either per column (row, column)
cygnusagent.sinks.mongo-sink.attr_persistence = column
# number of notifications to be included within a processing batch
cygnusagent.sinks.mongo-sink.batch_size = 1
# timeout for batch accumulation
cygnusagent.sinks.mongo-sink.batch_timeout = 30
# number of retries upon persistence error
cygnusagent.sinks.mongo-sink.batch_ttl = 10
# Collections will be removed if older than the value specified in seconds. Set to 0 if not wanting this policy.
cygnusagent.sinks.mongo-sink.data_expiration = 0
# The oldest data (according to insertion time) will be removed if the size of the data collection gets bigger than the value specified in bytes. Minimum value (different than 0) is 4096 bytes.
cygnusagent.sinks.mongo-sink.collection_size = 0
# The oldest data (according to insertion time) will be removed if the number of documents in the data collections goes beyond the specified value. Set to 0 if not wanting this policy
cygnusagent.sinks.mongo-sink.max_documents = 0
And another agent's configuration:
# source configuration
# channel name where to write the notification events
#####cygnusagent.sources.http-source.channels = hdfs-channel mysql-channel postgresql-channel ckan-channel mongo-channel sth-channel kafka-channel
cygnusagent.sources.http-source.channels = mongo-channel
# source class, must not be changed
cygnusagent.sources.http-source.type = org.apache.flume.source.http.HTTPSource
# listening port the Flume source will use for receiving incoming notifications
cygnusagent.sources.http-source.port = 5050
# Flume handler that will parse the notifications, must not be changed
cygnusagent.sources.http-source.handler = com.telefonica.iot.cygnus.handlers.OrionRestHandler
# URL target
cygnusagent.sources.http-source.handler.notification_target = /notify
# Default service (service semantic depends on the persistence sink)
cygnusagent.sources.http-source.handler.default_service = def_serv
# Default service path (service path semantic depends on the persistence sink)
cygnusagent.sources.http-source.handler.default_service_path = def_serv_path
# Number of channel re-injection retries before a Flume event is definitely discarded (-1 means infinite retries)
cygnusagent.sources.http-source.handler.events_ttl = -1
# Source interceptors, do not change
cygnusagent.sources.http-source.interceptors = ts gi
# TimestampInterceptor, do not change
cygnusagent.sources.http-source.interceptors.ts.type = timestamp
# GroupinInterceptor, do not change
cygnusagent.sources.http-source.interceptors.gi.type = com.telefonica.iot.cygnus.interceptors.GroupingInterceptor$Builder
# Grouping rules for the GroupingInterceptor, put the right absolute path to the file if necessary
# See the doc/design/interceptors document for more details
#####cygnusagent.sources.http-source.interceptors.gi.grouping_rules_conf_file = /usr/cygnus/conf/grouping_rules.conf
Channel's conf:
# mongo-channel configuration
# channel type (must not be changed)
cygnusagent.channels.mongo-channel.type = memory
# capacity of the channel
cygnusagent.channels.mongo-channel.capacity = 10000
# amount of bytes that can be sent per transaction
cygnusagent.channels.mongo-channel.transactionCapacity = 100
After talking with an Orion expert, this seems to be a problem with the initial notification, i.e. when the subscription is created and a first notification is sent; in that case, a multi-valued fiware-servicePath is sent. Nevertheless, after that, next notifications should contain a single Context Element and, thus, a single-valued fiware-servicePath header.
This needs a fix in the code (either to ignore the first notification, which is useless btw, either by supporting multi-valued service path headers). In the meantime, a workaround would be to make the subscriptions with Cygnus stopped, and once made all the subscriptions, start Cygnus.
EDIT 1:
I've create this issue at Cygnus Github: https://github.com/telefonicaid/fiware-cygnus/issues/923
Related
this is the JSON I am working with -
{
"content": {
"macOS": {
"releases": [
{
"version": "3.21",
"updateItems": [
{
"id": 1,
"title": "Automatic detection for inactivity is here",
"message": "We've finally added a feature long requested - Orby now detects when you've been inactive on your computer. You can modify the maximum allowable inactive time through settings, or just turn it off if you don't need it",
"image": "https://static.image.png"
},
{
"id": 2,
"title": "In case you missed it... We have an iOS app 📱 🙌",
"message": "It's far from perfect, but it's come a long way since we first pushed out version 1.0. We don't that many users on it so far, but I'm hoping that it's useful. Please send any feedback and feature requests my way",
"image": "https://static.image.png"
}
]
}
]
},
"iOS": {
"releases": [
{
"version": "1.31",
"updateItems": [
{
"image": "https://static.image.png",
"id": 1,
"link": "orbit://com.orbit:/settings",
"message": "Strict mode offers a fantastic new way to keep your focus and get more done. To enable it, go to settings and toggle it on. Now when you want to run a timer, put your device face down on a surface. The timer will stop if you pick it up.",
"title": "Strict mode is here 🙌 ⚠️ ⚠️ ⚠️"
}
]
}
]
}
}
}
I want to translate all the title values and message values (I use shell translate).
In my attempt, I have looped through the releases, gotten the indices, then looped through the updateItems and gotten their indices, then translated the title and message based off both these indices, and then I've attempted to assign these new values to replace the existing title and message.
I've noticed that this results in all the title values being the same, and all the message values being the same.
I'm clearly using jq the wrong way, but am unsure how to correct.
Please help.
curl ${URL} | jq >englishContent.json
LANGUAGES=(
# "en"
# "de"
# "fr"
# "es"
"it"
# "ja"
# "ko"
# "nl"
# "pt-BR"
# "ru"
# "zh-Hans"
)
for language in $LANGUAGES; do
# Create new json payload from the downloaded english content json
result=$(jq '{ "language": "'$language'", "response": . }' englishContent.json)
# Get the total number of release items
macOS_releases_length=$(jq -r ".response.content.macOS.releases | length" <(echo "$result"))
# Iterate over releases items and then use index to substitute values into nested arrays
macOS_releases_length=$(expr "$macOS_releases_length" - 1)
for macOS_release_index in $(seq 0 $macOS_releases_length); do
update_items_length=$(jq ".response.content.macOS.releases[$macOS_release_index].updateItems | length" <(echo "$result"))
update_items_length=$(expr "$update_items_length" - 1)
for update_item_index in $(seq 0 $update_items_length); do
title=$(jq ".response.content.macOS.releases[$macOS_release_index].updateItems[$update_item_index].title" <(echo "$result"))
translated_title=$(trans -brief -no-warn :$language $title | xargs)
message=$(jq ".response.content.macOS.releases[$macOS_release_index].updateItems[$update_item_index].message" <(echo "$result"))
translated_message=$(trans -brief -no-warn :$language $message | xargs)
result=$(jq --arg release_index "$(echo "$macOS_release_index" | jq 'tonumber')" --arg item_index "$("$update_item_index" | jq 'tonumber')" --arg new_title $translated_title '.response.content.macOS.releases['$release_index'].updateItems['$item_index'].title |= $new_title' <(echo "$result"))
result=$(jq --arg release_index "$(echo "$macOS_release_index" | jq 'tonumber')" --arg item_index "$("$update_item_index" | jq 'tonumber')" --arg new_message $translated_message '.response.content.macOS.releases['$release_index'].updateItems['$item_index'].message |= $new_message' <(echo "$result"))
done
done
echo $result
done
I am currently trying to prepare a JSON body for an API call, which should look something like this
curl -XPOST -H 'Authorization: Bearer ***API*KEY***' -H 'Content-Type: application/json' http://127.0.0.1:9000/api/alert -d '{
"title": "Other alert",
"description": "alert description",
"type": "external",
"source": "instance1",
"sourceRef": "alert-ref",
"severity": 3,
"tlp": 3,
"artifacts": [
{ "dataType": "ip", "data": "127.0.0.1", "message": "localhost" },
{ "dataType": "domain", "data": "thehive-project.org", "tags": ["home", "TheHive"] },
],
"caseTemplate": "external-alert"
}'
The problem however is that my json body which I create with powershell has weird characters and don't see where the problem is. This is my JSON Body
{
"tlp": 1,
"source": "Test",
"title": "Test Alert1",
"artifacts": "{\r\n \"dataType\": \"ip\",\r\n \"data\": \"127.0.0.1\"\r\n}",
"type": "external",
"sourceRef": "1",
"description": "Test",
"severity": 1
}
I create this JSON body as follows. I have already tried CustomObjects and Hashtables, but always get the same output
$body = #{
title = "$title"
description = "$Alert_Description"
type ="external"
source ="$Source"
sourceRef ="$SourceRef"
severity = $Severity
tlp = $tlp
$artifacts = [PSCustomObject]#{
dataType=ip
data=127.0.0.1}
}| ConvertTo-Json
$JsonBody = $body | ConvertTo-Json
Can somebody give me a hint? I have no clue anymore
First, fix your hashtable so that it creates the intended JSON representation, and only apply ConvertTo-Json once:
$body = [ordered] #{
title = $title
description = $Alert_Description
type = 'external'
source = $Source
sourceRef = $SourceRef
severity = $Severity
tlp = $tlp
artifacts = , [pscustomobject] #{
dataType = 'ip'
data = '127.0.0.1'
}
} | ConvertTo-Json
Variables don't need enclosing in "..." unless you explicitly want to convert their values to strings.
Literal textual information such as ip does require quoting ('...', i.e. a verbatim string literal, is best).
The artifacts property is an array in your sample JSON, so the unary form of ,, the array constructor operator is used to wrap the [pscustomobject] instance in a single-element array.
If you have multiple objects, place , between them; if the array was constructed previously and stored in a variable named, say, $arr, use artifacts = $arr.
Use of [ordered], i.e. the creation of an ordered hashtable isn't strictly necessary, but makes it easier to compare the resulting JSON representation to the input hashtable.
Generally keep in mind that explicit use of a -Depth argument with ConvertTo-Json is situationally required in order to ensure that properties aren't truncated - see this post for more information. However, with the hashtable in your question this happens not to be a problem.
Second, since you're sending the json to an external program, curl (note that in Windows PowerShell you'd have to use curl.exe in order to bypass the built-in curl alias for Invoke-WebRequest, an additional escaping step is - unfortunately - required up to at least v7.1 - though that may change in v7.2: The " instances embedded in the value of $body must manually be escaped as \", due to a long-standing bug in PowerShell:
curl -XPOST -H 'Authorization: Bearer ***API*KEY***' -H 'Content-Type: application/json' `
http://127.0.0.1:9000/api/alert -d ($body -replace '([\\]*)"', '$1$1\"')
Note: With your particular sample JSON, -replace '"', '\"' would do.
See this answer for more information.
Slap a "-Depth 50" on your ConvertTo-Json, that should solve your issue.
Like here: ConvertTo-Json flattens arrays over 3 levels deep
I am trying to setup my three subscriptions using curl and im making sure the values are correct. Trying to do a request and get a response but my curl is not functioning corectly.
https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create
My intention:
3 Plans to choose from: Elite (149 PHP/MONTH), Premium (349 PHP/MONTH), Luxury (549 PHP/MONTH)
1 month is 30 days as paypal said
id like to set it to auto renew monthly until the customer chooses to cancel it.
only one will be active at a time, if a user chooses another of the three while one is running, they will automatically be stopped getting billed and will be charged with the new one chosen. (eg: currently elite149, the subscription and renewal will change to 549luxury once chosen).
curl -v -X POST https://api.sandbox.paypal.com/v1/billing/plans \
-H "Content-Type: application/json" \
-H "Authorization: Basic account_clientid:account_secretcode" \
-H "PayPal-Request-Id: EPL-25012019-001" \
-d '{
"product_id": "MWC-2019EPL",
"name": "My White Card Subscription Plans",
"description": "MyWhiteCard Membership Levels",
"status": "ACTIVE",
"billing_cycles": [
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 1,
"total_cycles": 999,
"pricing_scheme": {
"fixed_price": {
"value": "149",
"currency_code": "PHP"
}
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 2,
"total_cycles": 999,
"pricing_scheme": {
"fixed_price": {
"value": "349",
"currency_code": "PHP"
}
}
},
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 3,
"total_cycles": 999,
"pricing_scheme": {
"fixed_price": {
"value": "549",
"currency_code": "PHP"
}
}
}
],
"payment_preferences": {
"auto_bill_outstanding": true,
"setup_fee": {
"value": "0",
"currency_code": "PHP"
},
"setup_fee_failure_action": "CONTINUE",
"payment_failure_threshold": 3
},
"taxes": {
"percentage": "10",
"inclusive": false
}
}'
I just took out my account's client_id:secret
Live credentials are being used
My questions and concerns:
In the access token, do I need to put the "access_token$production$" and then the code given?
Can I manually create the Paypal request id and the product id?
Will the billing cycles be all on the same command or do I have to trigger this three times?
Is the setup fee the charge if a customer subscribes? I first assumed that is the case and set it to 0.
My intention is to have the monthly subscription (30 Days) auto renew until the user unsubscribes. I set my example to 12 but will "total_cycles": NEVER be the correct input?
I am not sure how the tax part works, why am I the one that gets to modify it?
Additional Concerns:
The document does not show the live equivalent of the link https://api.sandbox.paypal.com/v1/billing/plans is it just simply https://api.live.paypal.com/v1/billing/plans?
I tried to add the above code using git bash and curl but as I run it it shows 1008{"error":"invalid_client","error_description":"Client credentials are missing"}
Do I try to run the curl code in Git bash and not change my directory? I just start git and run the curl here:
Any help will be appreciated. I just making sure everything is what needs to be because these three subscriptions will go to a live website. I have to be certain only one subscription runs at a time.
UPDATE: I tried making it to curl -v -X POST https://api.production.paypal.com/v1/billing/plans and this one is not functionning either.
UPDATE: Can anyone show an example of a working curl sample request?
UPDATE: at the -H "Authorization: Basic account_clientid:account_secretcode", just to be clear I took out my id code there since I cant just show it in public. An example that I placed there is
-H "Authorization: Basic JAKRc85nJy2eMLq3aIV:01PvLC934xMAwLHqU4JqA89as4N"
UPDATE I tried to run this curl in git after reading the answers so far and somehow I still get and error. I made sure that the api is in api.paypal.com and the client id and secret id is the live version.
curl -v -X POST https://api.paypal.com/v1/billing/plans \
-H "Content-Type: application/json" \
-H "Authorization: Basic AR7nnwwotKOt4YdcGHZc0P2RVsRT67_Gf2hyrKyDl3ZgCKsikeKbXdQ9Fj-_21v4RulkXsgAASe7_VKv:EKwsdDo1ehtOOOSZCGMu1C9903qr4cQOOZI2rgFYhvugh2SO1V04q9MWY9SXwa352zBt1mGglLuWgR4D" \
-H "PayPal-Request-Id: MWC-2501E-001" \
-d '{
"product_id": "ELITE-2501149",
"name": "Elite Membership",
"description": "Elite Membership Monthly Plan",
"status": "ACTIVE",
"billing_cycles": [
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 1,
"total_cycles": 999,
"pricing_scheme": {
"fixed_price": {
"value": "149",
"currency_code": "PHP"
}
}
}
],
"payment_preferences": {
"auto_bill_outstanding": true,
"setup_fee": {
"value": "0",
"currency_code": "PHP"
},
"setup_fee_failure_action": "CONTINUE",
"payment_failure_threshold": 3
},
"taxes": {
"percentage": "12",
"inclusive": false
}
}'
I get this error:
100 921 100 159 100 762 99 478 0:00:01 0:00:01 --:--:-- 578{"name":"INTERNAL_SERVICE_ERROR","debug_id":"1975a4fe9232","links":[{"href":"https://developer.paypal.com/docs/api/overview/#error","rel":"information_link"}]}
UPDATE
Read all the links and followed the steps, I think this is the only issue left
{"name":"NOT_AUTHORIZED","message":"Authorization failed due to insufficient permissions.","debug_id":"484a9d7460069","details":[{"issue":"PERMISSION_DENIED","description":"You do not have permission to access or perform operations on this resource"}],"links":[{"href":"https://developer.paypal.com/docs/api/v1/billing/subscriptions#NOT_AUTHORIZED","rel":"information_link","method":"GET"}]}
I checked the Paypal help center and it seems that I need someone from Paypal itself to authorize my REST.
Q1 - In the access token, do I need to put the "access_token$production$" and then the code given?
In your example, you are using Basic authentication scheme. You can use:
-H "Authorization: Bearer Access-Token" \
But to answer your question, no you don't need to specifically include the access_token text string.
Q2 - Can I manually create the Paypal request id and the product id?
Yes, request id is used so you can retry your API calls.
https://developer.paypal.com/docs/api/reference/api-requests/#http-request-headers
HOWEVER product_id should come from https://developer.paypal.com/docs/api/catalog-products/v1/#products_create
so you will need to do it first. See my update at the bottom part of this answer.
Q3 - Will the billing cycles be all on the same command or do I have to trigger this three times?
You will need to call each plan configuration to create three subscription plans. Basically, in the API doc example, it shows you it has a billing cycle created for a Trial period and the regular plan it self. It also says:
https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create
An array of billing cycles for trial and regular billing. A plan can
have multiple billing cycles but only one regular billing cycle.
You also mentioned:
only one will be active at a time, if a user chooses another of the
three while one is running, they will automatically be stopped getting
billed and will be charged with the new one chosen. (eg: currently
elite149, the subscription and renewal will change to 549luxury once
chosen).
You'll need to do this programmatically on your end.
If a user unsubscribes to a plan, then you will need to cancel his subscription by calling:
https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_cancel
If you plan to change subscription, then first you need to cancel existing, then subscribe the user to the new subscription plan using: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
Q4 - Is the setup fee the charge if a customer subscribes? I first assumed that is the case and set it to 0.
The setup fee is just an add-on fee you can charge your customers. You will need to manually declare this or make it optional by making setup fee value 0.
https://developer.paypal.com/docs/api/subscriptions/v1/#definition-payment_preferences
Q5 - My intention is to have the monthly subscription (30 Days) auto renew until the user unsubscribes. I set my example to 12 but will "total_cycles": NEVER be the correct input?
The maximum value is 999. It only accepts integer.
https://developer.paypal.com/docs/api/subscriptions/v1/#definition-billing_cycle
Q6 - I am not sure how the tax part works, why am I the one that gets to modify it?
Because tax is dependent on what region or country you are in. You are using PHP or Philippine Peso as currency so it could mean that you are in the Philippines. You will need to setup your tax percentage as 12% as that is what is used for taxing goods and services
Source: https://www.full-suite.com/blog/whats-difference-vat-percentage-tax/
Paypal tax object definition: https://developer.paypal.com/docs/api/subscriptions/v1/#definition-taxes
Q7 - The document does not show the live equivalent of the link https://api.sandbox.paypal.com/v1/billing/plans is it just simply https://api.live.paypal.com/v1/billing/plans?
You can find the addresses here:
https://developer.paypal.com/docs/api/overview/#get-an-access-token
It says:
Sandbox: https://api.sandbox.paypal.com
Live: https://api.paypal.com
Q8 - I tried to add the above code using git bash and curl but as I run it it shows 1008{"error":"invalid_client","error_description":"Client credentials are missing"}
It could mean either mean that you are passing invalid credentials. Make sure that you are sending them via the headers using
Bearer <Access-Token>
or
Basic <client_id>:<secret>
And verify that your string input represents the actual values.
Also, make sure you are using Sandbox credentials as it has a different set of credentials from the production or live
https://developer.paypal.com/docs/classic/lifecycle/sb-create-accounts/#create-a-sandbox-account
Q9 - Do I try to run the curl code in Git bash and not change my directory? I just start git and run the curl here:
Do not misinterpret Git Bash as the command line interface, it is simply a versioning tool for your projects. However the Git Package for windows has built-in components that should allow you to run CURL. Since Curl is installed in the Bin directory, you should be able to run it on any directory.
You can run the curl command using different tools ideally using a scripting or programming language like PHP, Phyton, Java or even Node which has better support for curl and should allow you to write and test your program easier in a neater way.
UPDATE
I've investigated on this further. I thought I'd share it with you because it seems you haven't read the whole API document yet.
You are creating Subscription plans however you'll need to Create the products first. (I have updated my answer to Question #2)
First - Create the Product
This will create the product_id you need to create your subscription plans.
https://developer.paypal.com/docs/api/catalog-products/v1/#products_create
To go back and check the product you created an get its product id, you can use this api call:
https://developer.paypal.com/docs/api/catalog-products/v1/#products_list
Then - Create The Subscription Plan
After you have created your products, you can then create multiple subscription plans for it.
https://developer.paypal.com/docs/api/subscriptions/v1/#plans_create
Moving forward, if you want to subscribe / unsubscribe users you'll need to programatically do it as per my answer to Question #3.
About your problem with invalid credentials
Try getting the access token first. To do that follow instructions here:
https://developer.paypal.com/docs/api/get-an-access-token-curl/
curl -v https://api.sandbox.paypal.com/v1/oauth2/token \
-H "Accept: application/json" \
-H "Accept-Language: en_US" \
-u "client_id:secret" \
-d "grant_type=client_credentials"
Then get the token returned and use that to make your api calls.
It will return something like this:
{
"scope": "scope",
"access_token": "Access-Token",
"token_type": "Bearer",
"app_id": "APP-80W284485P519543T",
"expires_in": 31349,
"nonce": "nonce"
}
Here is a modified version of your code that I used to test, notice it uses Bearer token instead basic client_id:secret
curl -v -X POST https://api.sandbox.paypal.com/v1/billing/plans \
-H "Content-Type: application/json" \
-H "Authorization: Bearer Access-Token-goeshere" \
-H "PayPal-Request-Id: MWC-helper" \
-d '{
"product_id": "PROD-ELITETEST", //<---- this product id should be taken from the real product id when you created your product through product_create api.
"name": "Elite Membership",
"description": "Elite Membership Monthly Plan",
"status": "ACTIVE",
"billing_cycles": [
{
"frequency": {
"interval_unit": "MONTH",
"interval_count": 1
},
"tenure_type": "REGULAR",
"sequence": 1,
"total_cycles": 999,
"pricing_scheme": {
"fixed_price": {
"value": "149",
"currency_code": "PHP"
}
}
}
],
"payment_preferences": {
"auto_bill_outstanding": true,
"setup_fee": {
"value": "0",
"currency_code": "PHP"
},
"setup_fee_failure_action": "CONTINUE",
"payment_failure_threshold": 3
},
"taxes": {
"percentage": "12",
"inclusive": false
}
}'
It worked for me!
We are using a server software offering called FreezerPro (https://www.freezerpro.com/product-tour) with an API that can be called programmatically. There are simple methods like freezers that work with curl calls like this:
freezers -- Retrive a list of freezers
Returned objects: Freezers
Required parameters: None
Optional query parameters: None
Optional control parameters: None
curl -g --insecure 'https://username:password#demo-usa.freezerpro.com/api?method=freezers' | jq . | head -n 12
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8697 0 8697 0 0 15980 0 --:--:-- --:--:-- --:--:-- 15987
{
"Freezers": [
{
"rfid_tag": "355AB1CBC00000700000075A",
"barcode_tag": "7000001882",
"boxes": 0,
"subdivisions": 1,
"access": 0,
"description": "[1000000000]",
"name": "[1000000000]",
"id": 1882
},
Then there is a search_samples method that searches for any fields in samples given a query. E.g.:
search_samples -- search for samples:
Returned objects: Samples
Required parameters: None
Optional query parameters:
query = <filter text> optional search string to filter the results.
Optional control parameters:
start = <staring record>
limit = <limit number of records to retrieve>
sort = <sort_field>
dir = <ASC / DESC>
curl -g --insecure 'https://username:password#demo-usa.freezerpro.com/api?method=search_samples&query=111222333' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 347 0 347 0 0 977 0 --:--:-- --:--:-- --:--:-- 977
{
"Samples": [
{
"created_at": "06/11/2018",
"owner_id": 45,
"owner": "<span ext:qtip=\"username\">username</span>",
"description": "test",
"sample_id": 53087,
"id": 53087,
"loc_id": 54018,
"type": "cfDNA",
"scount": 1,
"name": "123456AB",
"location": "ER111→Level 1→Level 2→test001 (1)",
"icon": "images/box40/i53.png"
}
],
"Total": 1
}
So far so good. The problem comes when trying to run the advanced_search query, which takes an array of hashes in the query section. Given the sample above, which has a udf called patient_id with value 111222333, and advanced_search query for udf patient_id value=111222333 should return something, but it just gives a blank result:
Example command:
curl -g --insecure 'https://username:password#demo-usa.freezerpro.com/api?method=advanced_search&subject_type=Sample&query=[{type="udf",field="patient_id",value=111222333}]'
I am using:
curl --version
curl 7.35.0 (x86_64-pc-linux-gnu) libcurl/7.35.0 OpenSSL/1.0.1f zlib/1.2.8 libidn/1.28 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smtp smtps telnet tftp
Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP
Is this something to do with the way curl works on interpreting/passing the query section of the URL?
Any ideas about how to construct the query? Is this a curl specific issue?
EDIT: tried curl urlencode, it complains about the query not being setup:
curl -g -G --insecure 'https://username:password#demo-usa.freezerpro.com/api' --data-urlencode 'method=advanced_search' --data-urlencode 'query=[{type="udf",field="patient_id",value=111222333}]'
{"error":true,"message":"Query or search conditions must be specified","success":false}
You must URL-encode values of your URL parameters. e.g.
curl -g --insecure 'https://username:password#demo-usa.freezerpro.com/api?method=advanced_search&subject_type=Sample&query=%5B%7Btype%3D%22udf%22%2Cfield%3D%22patient_id%22%2Cvalue%3D111222333%7D%5D'
Also please run curl with -v parameter to make it verbose, so we could at least know what HTTP status is returned.
I've found a solution using the --data flag together with the -k flag:
curl -k --header "Content-Type: application/json" --request GET --data '{"username":"user", "password":"password", "method":"advanced_search", "query":[{"type":"udf","field":"patient_id","op":"eq","value":"111222333"}], "udfs":["patient_id","other"]}' https://demo-usa.freezerpro.com/api | jq .
I'd like to filter the JSON output of ad-hoc ansible commands - e.g. grab the long list of "facts" for multiple hosts, and show only one that could be several levels deep, such as ansible_lsb.description, so I can quickly compare what versions of software they're running, check accurate times or timezones, whatever.
This works:
ansible myserver -m setup -a 'filter=ansible_lsb'
myserver | SUCCESS => {
"ansible_facts": {
"ansible_lsb": {
"codename": "wheezy",
"description": "Debian GNU/Linux 7.11 (wheezy)",
"id": "Debian",
"major_release": "7",
"release": "7.11"
}
},
"changed": false
}
However, as the setup module docs state, "the filter option filters only the first level subkey below ansible_facts", so this fails:
ansible myserver -m setup -a 'filter=ansible_lsb.description'
myserver | SUCCESS => {
"ansible_facts": {},
"changed": false
}
(though for reference, you can use dot notation in other places such as a task's when conditional)
Is there a way to filter the JSON keys before the output is displayed?
Standard setup module can apply filter only on "top-level" facts.
To achieve what you want, you can make an action plugin with setup name to apply custom filters.
Working example ./action_plugins/setup.py:
from ansible.plugins.action import ActionBase
class ActionModule(ActionBase):
def run(self, tmp=None, task_vars=None):
def lookup(obj, path):
return reduce(dict.get, path.split('.'), obj)
result = super(ActionModule, self).run(tmp, task_vars)
myfilter = self._task.args.get('myfilter', None)
module_args = self._task.args.copy()
if myfilter:
module_args.pop('myfilter')
module_return = self._execute_module(module_name='setup', module_args=module_args, task_vars=task_vars, tmp=tmp)
if not module_return.get('failed') and myfilter:
return {"changed":False, myfilter:lookup(module_return['ansible_facts'], myfilter)}
else:
return module_return
It calls original setup module stripping myfilter parameter, then filters result with simple reduce implementation if task is not failed and myfilter is set. Lookup function is very simple, so it will not work with lists, only with objects.
Result:
$ ansible myserver -m setup -a "myfilter=ansible_lsb.description"
myserver | SUCCESS => {
"ansible_lsb.description": "Ubuntu 12.04.4 LTS",
"changed": false
}