OAuth 2 Endpoint: Additional Fields? - rest

Is is acceptable to include additional fields in an OAuth token endpoint response?
For example, I am returning UserId, Username and CompanyId below:
{
"access_token": "pHd4Wz1EF...",
"token_type": "bearer",
"expires_in": 86399,
"UserId": "7e7fbc39-8abd-41e1-b165-9d18b635b7a7",
"Username": "user#somewhere.com",
"CompanyId": "874f380a-76eb-49b1-81b5-a42100f7e4d0",
}
This essentially means that additional requests won't be necessary in order to get this information.

You can do that - but a more standardized solution would be to use "OpenID Connect" for that purpose (see http://openid.net/connect/).

Related

Oauth2 Flutter Authorization Parameters

I have a (possibly?) niche question about Oauth2 in Dart and Flutter (I'm using the standard Oauth2 package: https://pub.dev/packages/oauth2). Specifically, I need some values that are passed back as part of the Authorization flow, but they are not the standard values, so it's not returned as part of the client.
Specifically, as part of my flow, I run:
final returnValue = await authClient.authenticate(
authorizationUrl: authorizationUrl,
redirectUri: redirectUri!,
);
No problem there. Then I run:
client = await grant
.handleAuthorizationResponse(Uri.parse(returnValue).queryParameters);
Again, works properly, returns a Client that I can use. However, following this function down, handleAuthorizationResponse calls _handleAuthorizationResponse. The function _handleAuthorizationResponse ends with:
var response =
await _httpClient!.post(tokenEndpoint, headers: headers, body: body);
// print(response.headers);
// print(response.body);
var credentials = handleAccessTokenResponse(
response, tokenEndpoint, startTime, _scopes, _delimiter,
getParameters: _getParameters);
return Client(credentials,
identifier: identifier,
secret: secret,
basicAuth: _basicAuth,
httpClient: _httpClient,
onCredentialsRefreshed: _onCredentialsRefreshed);
This is where my issue is. That response has some fields in the body that I need. They're not standard, so they're not passed back as part of the Client. Without rewriting my own versions of these functions, is there another way I can get access to these values?
In answer to a question, this uses a SMART on FHIR launch (it's a launch framework that's basically just an oauth2 wrapper) - it's standard in healthcare. The return json from the accessToken would look something like this:
{
"need_patient_banner": true,
"smart_style_url": "https://smart.argo.run/smart-style.json",
"patient": "87a339d0-8cae-418e-89c7-8651e6aab3c6",
"token_type": "Bearer",
"scope": "launch/patient patient/Observation.rs patient/Patient.rs",
"expires_in": 3600,
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjp0cnVlLCJzbWFydF9zdHlsZV91cmwiOiJodHRwczovL3NtYXJ0LmFyZ28ucnVuLy9zbWFydC1zdHlsZS5qc29uIiwicGF0aWVudCI6Ijg3YTMzOWQwLThjYWUtNDE4ZS04OWM3LTg2NTFlNmFhYjNjNiIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJzY29wZSI6ImxhdW5jaC9wYXRpZW50IHBhdGllbnQvT2JzZXJ2YXRpb24ucnMgcGF0aWVudC9QYXRpZW50LnJzIiwiY2xpZW50X2lkIjoiZGVtb19hcHBfd2hhdGV2ZXIiLCJleHBpcmVzX2luIjozNjAwLCJpYXQiOjE2MzM1MzIwMTQsImV4cCI6MTYzMzUzNTYxNH0.PzNw23IZGtBfgpBtbIczthV2hGwanG_eyvthVS8mrG4",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7Im5lZWRfcGF0aWVudF9iYW5uZXIiOnRydWUsInNtYXJ0X3N0eWxlX3VybCI6Imh0dHBzOi8vc21hcnQuYXJnby5ydW4vL3NtYXJ0LXN0eWxlLmpzb24iLCJwYXRpZW50IjoiODdhMzM5ZDAtOGNhZS00MThlLTg5YzctODY1MWU2YWFiM2M2In0sImNsaWVudF9pZCI6ImRlbW9fYXBwX3doYXRldmVyIiwic2NvcGUiOiJsYXVuY2gvcGF0aWVudCBwYXRpZW50L09ic2VydmF0aW9uLnJzIHBhdGllbnQvUGF0aWVudC5ycyBvZmZsaW5lX2FjY2VzcyIsImlhdCI6MTYzMzUzMzg1OSwiZXhwIjoxNjY1MDY5ODU5fQ.Q41QwZCEQlZ16M7YwvYuVbUP03mRFJoqRxL8SS8_ImM"
}
So it has the typical values of an accessToken (expires_in, token_type, etc), but it also has things like 'patient'. Those are the values that I need.
In my example, the final Credentials (client.credentials looks like this):
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46b2lkOmZoaXIiLCJjbGllbnRfaWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJlcGljLmVjaSI6InVybjplcGljOk9wZW4uRXBpYy1jdXJyZW50IiwiZXBpYy5tZXRhZGF0YSI6IjVYWnFRU0lrSk9PNi1XRkhpVXBiMlg2ak5MQzJ1aDFQdWtaSHVWSHkzcTdJMTBBT1BfYXR5V0tEb19LMlRFRF9ic21TNk9UTmZiVGtISnY1dnFvM1RtTVduRlk2RDBPSlE2WkRhd1NJWkk4WDh0Xy1XT1pYWEs1WjFrcTNBNm9mIiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE2NTQ2NDEzMDIsImlhdCI6MTY1NDYzNzcwMiwiaXNzIjoidXJuOm9pZDpmaGlyIiwianRpIjoiMDg1ZTdhNTYtY2I0OS00Zjg3LWFiYmEtZDg0M2ZmODI2YmQ2IiwibmJmIjoxNjU0NjM3NzAyLCJzdWIiOiJlYjRHaWE3RnlpanRQbVhrcnRqUnBQdzMifQ.rAKweImVE86oF3ciZDGhDysrYY9-XV6fBbyzqkQiJxHg-V-zImW414m3X5wKcP9B0J1MMdJCwg5DTpcbd0iU-N3SXRVXxBO2BqTcMAGLr-jlepnqBfu1Esg0nAI9jVasSWhz6tXFcLWOoCocg1hLcMfY875xnszwztJiJieDhumKZSStcsQM4KR9lUQZdJ3-U6IXV7wn3kaD4GQBSPZ0OkUe2d8zdCpjcbGCO-wWNdfe_sQDd7k7MbBJ1ryFRtd45GSzhKFa3Cch8kWTo3bGPlzzFuvhX_kbX1WtqTXaeB2G-o49lT4RJldnZi62L51VtS69_M15EsQtmMRHg6WMEA",
"refreshToken": null,
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRvVzlqTVVTTi81L0wzaXdhUUdkVG1ORHVodnAvSmNBWlZIL2NPSjZPckU9IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJleHAiOjE2NTQ2MzgwMDIsImZoaXJVc2VyIjoiaHR0cHM6Ly9maGlyLmVwaWMuY29tL2ludGVyY29ubmVjdC1maGlyLW9hdXRoL2FwaS9GSElSL1I0L1BhdGllbnQvZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIiwiaWF0IjoxNjU0NjM3NzAyLCJpc3MiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyIiwic3ViIjoiZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIn0.hh33_q4f3tnioB7Iq6jY07-m5i_OsaqUt_kg_ZnPMGPKK8AnYVk3Tps2XTdUzUIHizFRWlGmAT_E0F283LBmVPTrbtD_X6EwqmUbTBrWj94RyvE-k3ofoEo-CwbSJZXu8MrQTb3DzpRKTGo7D1sI5E4UqnKQhPWFmhwCjMXpbdRy6bddb14fdWZzjS_Ffq4OsNRIalnePR8z1zNtSy14_RCiSh8o2elkj3p1AOmSXeD9-nZ91Z646lt4C5oP9gwN7OhmBovQRuDYaql1tz1aHOhilIsBZc1jMxEZJ65cekmFy6HZ4rME23xg-EQHu7XhKWOpOjovbMPwapSlC-eUcA",
"tokenEndpoint": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
"scopes": [
"patient/Patient.read",
"patient/Questionnaire.read",
"patient/QuestionnaireResponse.Read",
"fhirUser",
"launch/patient",
"openid",
"profile"
],
"expiration": 1654641291566
}
I've tried decoding the accessToken and the idToken and neither of them have fields like "patient". Howevever, if after I get the response and print the body, it looks like this:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46b2lkOmZoaXIiLCJjbGllbnRfaWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJlcGljLmVjaSI6InVybjplcGljOk9wZW4uRXBpYy1jdXJyZW50IiwiZXBpYy5tZXRhZGF0YSI6IkJJR1dLcDZPbU1ZcmJkT2YzMkFvX0djRmtiUG1RbEtZcHdEN1d4S0VLNEJXQTZvSUxXVUdRd05SUWdtMUdsd00xTzE0YUVZNi1GTmd6Si1jMGRiQ0puODRIUEU4SDEzNmp6UzNCZGFDUVBMSXFyYXpZenF3Uldyd1MwX3NWU1k5IiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE2NTQ2NDc2MDgsImlhdCI6MTY1NDY0NDAwOCwiaXNzIjoidXJuOm9pZDpmaGlyIiwianRpIjoiMTA1OTFmOTMtYjUxMi00MmNlLTk1YjgtYjY2NTEwODM0MzNkIiwibmJmIjoxNjU0NjQ0MDA4LCJzdWIiOiJlYjRHaWE3RnlpanRQbVhrcnRqUnBQdzMifQ.AJOd9g8YAJp91n0qY3Hg9F2sNpo26VMYKpNKR5y7CIV8zrADh2whv2WRm8gi-cIeS6XUR6UzXyzXVJ9Ips5FgFdIZ4yQI_HXxH9r8aeF6VS6jT-ZQygtzWnVYeyJvu-1b3YpbgdCd3KTrnWLwhU3vqUmil2L8gJzWG473ihXDz-7ezsJBBl9R-c5Ap_L6WF6Ox8lHH6mgwbZHeKr0U0aYne-QLM7mylsPC5BC_WlUOwMnEJ73DKjF2E0X6wMCP7jMieJxhpkTIDRwKQbuGwLjtneS-Efu69NHGsxSP_m3aN652rdh9-b5WyIsT-DqjPHxHTtbxGQI-WthHOhnLaDkQ",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/Patient.read patient/Questionnaire.read patient/QuestionnaireResponse.Read fhirUser launch/patient openid profile",
"__epic.dstu2.patient": "TnOZ.elPXC6zcBNFMcFA7A5KZbYxo2.4T-LylRk4GoW4B",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRvVzlqTVVTTi81L0wzaXdhUUdkVG1ORHVodnAvSmNBWlZIL2NPSjZPckU9IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJleHAiOjE2NTQ2NDQzMDgsImZoaXJVc2VyIjoiaHR0cHM6Ly9maGlyLmVwaWMuY29tL2ludGVyY29ubmVjdC1maGlyLW9hdXRoL2FwaS9GSElSL1I0L1BhdGllbnQvZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIiwiaWF0IjoxNjU0NjQ0MDA4LCJpc3MiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyIiwic3ViIjoiZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIn0.wxlvguGhAZdWJiSpX1-jzANXk0hFhLeIPFS5BlnIJLLZg8ibvpzLutQr2Z7Rg_d07_amI4gGbNigso9gvPbN5e1jjDGZkU2QYUbcLZbwkTcxXfVWOsyAADOZZrqx0J1yrGIeA4V4EfqQ4xBym_e8CeEjGP9L4ouRBKK6AHR5N5Mmdo_I4_RoPr-mCR2e2Q_of7tYFuhcl8mHaT6brbn-ZoEuAMgAQztF-7SBpDSvRB1C4HzV6mk-Hql0jNhZ0WefZe_ve0gB3exdWDjCLClpRRjt_MRaFTYGPqiZuyJF-dEFEqNar1Y5BRjQmUdJbDWj8ecfWaldigXNVAvNthbs4g",
"patient": "erXuFYUfucBZaryVksYEcMg3"
}
So you can see why I need access to that information. Any idea, apart from extending the class and overriding the functions I could get access to it?
access_token contains all the information that is in the response.
{
"need_patient_banner": true,
"smart_style_url": "https://smart.argo.run//smart-style.json",
"patient": "87a339d0-8cae-418e-89c7-8651e6aab3c6",
"token_type": "bearer",
"scope": "launch/patient patient/Observation.rs patient/Patient.rs",
"client_id": "demo_app_whatever",
"expires_in": 3600,
"iat": 1633532014,
"exp": 1633535614
}
Access token is not meant to be read by the client, but ID token is for that. Defining the right scopes in authentication request will also return ID token.
Client has a credentials property, which holds all the tokens.
id_token's sub field seems to match patient. There is also fhirUser user endpoint, which probably gives you more information about the patient.
{
"aud": "3fa5caf9-8a98-4828-92de-59e66bcb2064",
"exp": 1654644308,
"fhirUser": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/erXuFYUfucBZaryVksYEcMg3",
"iat": 1654644008,
"iss": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2",
"sub": "erXuFYUfucBZaryVksYEcMg3"
}
So for anyone running into the same problem, so far I've found the answer is that you can't do it from the client side. This is an issue with the response returned from the server. If you don't have access to the server, you're SOL and will have to use a workaround like creating a proxy to send and accept all of your queries.
Specifically for my problem above, it was that the server did not include the following header
Access-Control-Expose-Headers:Location
Here are some other places to read about CORS that I found at least somewhat helpful:
Set cookies for cross origin requests
How to solve flutter web api cors error only with dart code?
https://github.com/flutterchina/dio/issues/1027
https://appvesto.medium.com/how-to-add-cors-to-the-dart-server-9d55a2835397

clientId missing from resource_access field in jwt token when using impersonation

I'm using Keycloak 14.0.0 and enabled the feature preview of token_exchange in order to do impersonation. After configuring my user in Keycloak to take on the impersonation role on the client "realm-management" (as according to the [documentation][1]), the actual request to do the token exchange fails as the token is not valid.
After some debugging it turns out that the jwt token is indeed malformed:
...
"session_state": "a03aeg0e-b5ce-4a50-9038-c339e50338c4",
"acr": "1",
"allowed-origins": [
"http://0.0.0.0:9180"
],
"scope": "openid identity_provider email admin profile company",
"permissions": [
"consented-readonly",
"readonly",
"trackingdisabled"
],
"resource_access": {
".roles": [
"impersonation"
]
},
"email_verified": false,
"idp": "myidp",
...
In the above, please notice the ".roles". I assume this is incorrect. It should be something like:
"resource_access": {
"myclient": {
"roles": [
"impersonation"
]
}
How can this be fixed?
[1]: https://www.keycloak.org/docs/latest/securing_apps/index.html#impersonation
It turns out that the configuration of a mapper was incorrect. In this case it was the "client roles" mapper (client scopes -> roles -> mapper -> client roles in keycloak ui) which, in my keycloak setup, had the value of:
resource_access..roles
This is incorrect as it should contain a clientId placeholder as shown below:
resource_access.${client_id}.roles
after this change the accessToken includes the actual client resulting in a valid json in the accessToken

Keycloak RestAPI: Assign client role to group

I have trying this:
url="https://{localhost}/auth/admin/realms/{realm_name}/groups/{group_id}/role-mappings"
payload=[{"id":"role_id","name":"Role_name","composite":"false","clientRole":"true","containerId":"client_id"}]
But, it doesnt work, i think it may be related to the wrong payload
Any ideas pls
We can use the python-keycloak lib: https://pypi.org/project/python-keycloak/
there is a function: assign_group_client_roles
The format is correct but you make sure the client has available payloaded role.
This is example for, "Hr" group added "RomoteApp" client the "tile limit" role.
In Keycloak UI, # Client > RemoteApp > Roles
Role name
time limit
Groups > hr
Client Roles: RemoteApp
Available Roles: time limit
Assigned Roles: not yet include "time limit"
POST commend by Postman - I am not yet can attache image due to low level of stan overflow
http://127.0.0.1:8080/auth/admin/realms/Test-realm/groups/9efef6bf-7edb-496e-bc0d-a8d4f5f4e3a9/role-mappings/clients/60a19179-f244-42f8-9950-04f2119a4e5b?client=RemoteApp&id=60a19179-f244-42f8-9950-04f2119a4e5b&realm=Test-realm
in body of postman with row JSON option ( Content-Type : application/json )
[
{
"id": "e17f9f7f-62dc-4998-b058-49b845ff5cef",
"name": "time limit",
"description": "time limit",
"composite": false,
"clientRole": true,
"containerId": "60a19179-f244-42f8-9950-04f2119a4e5b"
}
]
group list
{
"id": "9efef6bf-7edb-496e-bc0d-a8d4f5f4e3a9",
"name": "hr",
"path": "/hr",
"subGroups": []
}
client list
{
"id": "60a19179-f244-42f8-9950-04f2119a4e5b",
"clientId": "RemoteApp",
...
}
also don't remember the access token called by token endpoint w/ grant_type is password
http://127.0.0.1:8080/auth/realms/Test-realm/protocol/openid-connect/token

Not able to create user using keycloak api

Getting token is successful:
Post: http://localhost:8180/auth/realms/public-library/protocol/openid-connect/token
Content type:application/x-www-form-urlencoded
Body:x-www-form-urlencoded
client_id:spring-boot-app
username:pooja.kumawat
password:poojakumawat#23
grant_type:password
Output:Token
Creating a user is getting an error:
Post : http://localhost:8180/auth/public-library/users
Authorization:Bearer+Token
Content type:Application/Json
Body:Raw
{
"username": "name",
"enabled": true,
"emailVerified": false,
"firstName": "first",
"lastName": "last",
"credentials": [
{
"type": "password",
"value": "newPas1*",
"temporary": false
}
]
}
Error is:
{
"error": "RESTEASY003210: Could not find resource for full path: http://localhost:8180/auth/public-library/users"
}
It looks like you are using the wrong endpoint, change it to
http://localhost:8180/auth/admin/realms/public-library/users
And keep in mind, the user who is performing the POST request need to have at least the manage-users realm-management role assigned. Otherwise the response will be 403 Forbidden
What you need to do is go to the realm you are using in keycloak. Then go to the users section and select the user you are you using for the request. Under role Mappings, select realm management and assign this user the role realm admin and manage users role. After that, you will be able to make requests

Paypal REST API: How to retrieve payment ID after user has approved the payment.

By following the guide on https://developer.paypal.com/webapps/developer/docs/integration/web/accept-paypal-payment/ , I have successfully created a payment and redirect the user to approve it.
The created payment is something look like bellow, and I save it in user's session for further reference.
{
"id": "PAY-6RV70583SB702805EKEYSZ6Y",
"create_time": "2013-03-01T22:34:35Z",
"update_time": "2013-03-01T22:34:36Z",
"state": "created",
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"transactions": [
{
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"description": "This is the payment transaction description."
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RV70583SB702805EKEYSZ6Y",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=EC-60U79048BN7719609",
"rel": "approval_url",
"method": "REDIRECT"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-6RV70583SB702805EKEYSZ6Y/execute",
"rel": "execute",
"method": "POST"
}
]
}
After user approved the payment, Paypal will redirect the user to the return_url. For example, http://<return_url>?token=EC-60U79048BN7719609&PayerID=7E7MGXCWTTKK2.
In order to execute the payment, a POST request has to made to https://api.sandbox.paypal.com/v1/payments/payment/{payment_id}/execute/.
Question
The only two pieces of information provided from Paypal in the URL is token and PayerID. How can I find the corresponding payment_id?
Possible Solution
The token is part of the approval_url, parse the URL and store the token -> payment relationship can solve the problem. But I'm looking for a better solution that doesn't require parsing.
I think the paypal documentation isn't clear about this. But you can do something simple to resolve your problem passing de PaymentID through a parameter in your return url.
Like this:
return_url = 'http://www.yourdomain.com/paypal/success/?paymentID=PAY-1234567'
When the Paypal redirect to your site, then, it will return the paymentID together with the other parameters.
You would have to remember the Payment ID on your side (typically attached with your user session - shopping cart or order or as a session cookie) before redirecting the user to PayPal approval url. Once the is redirected back to your return Url along with the PayerID - you would need to extract the PaymentID from your user session and execute the Payment.
The Payment Id can be obtained in PHP by using the following method after the first API request has returned a successful response:
$payment->getId();
The online code sample (http://paypal.github.io/PayPal-PHP-SDK/sample/doc/payments/CreatePaymentUsingPayPal.html) shows how to send the request, however it does not include the getId() method.
To find this out I had to look in the downloaded SDK files at the file sample\payments\CreatePayment.php which has the following example code showing the use of this method:
ResultPrinter::printResult('Create Payment Using Credit Card', 'Payment', $payment->getId(), $request, $payment);
I found this link to be extremely helpful, in case anyone wants to check it out: https://github.com/paypal/PayPal-NET-SDK/issues/79
Since the v1/payments API has been deprecated for some time now, the best solution is to use the current v2/checkout/orders API for all new integrations. The order ID is returned in the URL.
However, redirects are an old integration method, for old websites. It is preferred to not use any redirects, at all, and keep your site loaded at ALL times. Instead use this approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
paymentid:
After you created the payment, in return json, you can get paymentid.
like this: "id":"PAY-01K00482KX842131HKORKVKY"
payerid:
you can use API:GET /v1/payments/payment/{paymentId} to get payer id after user approved the payment,and you will find payerid in return json,like this:
{
"id":"PAY-01K00482KX842131HKORKVKY",
"create_time":"2014-06-19T09:17:31Z",
"update_time":"2014-06-19T09:17:31Z",
"state":"created",
"intent":"sale",
"payer":{
"payment_method":"paypal",
"payer_info":{
"email":"buyer#samsung.com",
"first_name":"buyer",
"last_name":"samsung",
"payer_id":"H2DKRTTYWW8HS",
"shipping_address":{ "line1":"Lushan Road Num.188", "line2":"JianYe",
"city":"Tucson",
"state":"AZ",
"postal_code":"85715",
"country_code":"US",
"recipientName":"buyer samsung"}}},
"transactions":[{
"amount":{
"total":"12.00",
"currency":"USD",
"details"{"subtotal":"12.00"}},
"description":"creating a payment"}],
"links":[
{"href":"xxxxxxx","rel":"self","method":"GET"},
{"href":"xxxxxxx","rel":"approval_url","method":"REDIRECT"},
{"href":"xxxxxxx","rel":"execute","method":"POST"}]}