I'm using QBMS to process credit card payments. Then, I'm using QBXML and the QB Web Connector to add these payments into QB. I have QBMS payment processing working fine, and the ReceivePaymentAddRq to QB correctly adds the transactions data in QB. My problem is that I can't get QB to return the CreditCardTxnInfo in the ReceivePaymentAddRs markup. My QBXML request is in the following form:
<ReceivePaymentAddRq><ReceivePaymentAdd>
<CustomerRef><ListID>8000074A-1369078671</ListID></CustomerRef>
<TxnDate>2013-04-27</TxnDate>
<TotalAmount>21.05</TotalAmount>
<PaymentMethodRef><FullName>Visa</FullName></PaymentMethodRef>
<Memo>Some note about the payment.</Memo>
<DepositToAccountRef><FullName>Undeposited Funds</FullName></DepositToAccountRef>
<CreditCardTxnInfo><CreditCardTxnInputInfo>
<CreditCardNumber>xxxxxxxxxxxx6224</CreditCardNumber>
<ExpirationMonth>7</ExpirationMonth><ExpirationYear>2015</ExpirationYear>
<NameOnCard>John G Smith</NameOnCard>
<CreditCardAddress>7 Walnut Lane</CreditCardAddress>
<CreditCardPostalCode>11714</CreditCardPostalCode>
<CreditCardTxnType>Charge</CreditCardTxnType>
</CreditCardTxnInputInfo>
<CreditCardTxnResultInfo><ResultCode>0</ResultCode>
<ResultMessage>Status OK</ResultMessage>
<CreditCardTransID>ME0147410371</CreditCardTransID>
<MerchantAccountNumber>6241710108583287</MerchantAccountNumber>
<AuthorizationCode>08368C</AuthorizationCode>
<ReconBatchID>240141438 1R19595257770038186280AKTO03</ReconBatchID>
<PaymentGroupingCode>5</PaymentGroupingCode>
<PaymentStatus>Completed</PaymentStatus>
<TxnAuthorizationTime>2013-04-28T02:49:10</TxnAuthorizationTime>
<TxnAuthorizationStamp>1367117354</TxnAuthorizationStamp>
<ClientTransID>qc947863</ClientTransID>
</CreditCardTxnResultInfo>
</CreditCardTxnInfo>
<IsAutoApply>true</IsAutoApply>
</ReceivePaymentAdd>
<IncludeRetElement>CustomerRef</IncludeRetElement>
<IncludeRetElement>PaymentMethodRef</IncludeRetElement>
<IncludeRetElement>TotalAmount</IncludeRetElement>
<IncludeRetElement>CreditCardTxnInfo</IncludeRetElement>
</ReceivePaymentAddRq>
I expected the response to include the credit card transaction information (CC number, CC Trans ID, etc.). I need this info so that I can match up responses to the requests in my web service. Unfortunately, the response I'm getting looks like this:
<ReceivePaymentAddRs statusCode="0" statusSeverity="Info" statusMessage="Status OK">
<ReceivePaymentRet>
<CustomerRef><ListID>8020014A-1367478579</ListID><FullName>Smith, John</FullName></CustomerRef>
<TotalAmount>51.05</TotalAmount>
<PaymentMethodRef><ListID>80000004-1232402081</ListID><FullName>Visa</FullName></PaymentMethodRef>
</ReceivePaymentRet>
</ReceivePaymentAddRs>
Where's the CreditCardTxnInfo?
Did you authorize your application to "allow access to sensitive data"?
For QBWC, add <PersonalDataPref>pdpRequired</PersonalDataPref> to the QWC file.
Reference: https://member.developer.intuit.com/qbSDK-Current/doc/html/QBWC%20Developers%20Guide/04_TeachingQBWC_AboutYourWebService.6.2.html
For QBSDK users, you can use the AuthPreferences object to require access.
Reference: https://member.developer.intuit.com/qbSDK-Current/doc/html/QBSDK%20Programmers%20Guide/04_SupportingUserAuth.6.3.html
C# Sample:
RequestProcessor2 rp = new RequestProcessor2();
AuthPreferences auth = (AuthPreferences)rp.AuthPreferences;
auth.PutPersonalDataPref(QBXMLRPPersonalDataPrefType.pdpRequired);
rp.OpenConnection2("", "Your App Name", QBXMLRPConnectionType.localQBD);
Related
I am integrating a Smart on FHIR app that will be launched from within an EHR. When the user clicks a button to launch the app, we set a GUID and the current Patient ID to a database record on our FHIR server. The assumption being that given the 'Launch' scope, the OAuth server will call the appropriate API to retrieve the Patient ID given that the GUID is included in the url params.
The call to auth looks like this:
_clientID = {the unique client ID registered to our auth server}
_redirectURL = {redirect back to auth for eventual token request}
launch={the GUID value generated at start of the session and paired with the Patient ID}
_scopes = "launch patient/*.* openid profile"
state = {some opaque value}
aud = {the base URL for our FHIR server}
string url = $"{authorizeURL}?response_type=code&client_id={_clientID}&" +
$"redirect_uri={_redirectURL}&" +
$"launch={launch}&" +
$"scope={HttpUtility.UrlEncode(_scopes)}&" +
$"state={state}&" +
$"aud=https://xxx-smart.xxxxxxxxx.com";
All of this works and I end up with a json response that includes the id_token, access_token, expires_in, token_type('Bearer'). But, no 'patient'.
My assumption was that the OAuth server would call the scope 'launch/patient' on our FHIR server but no such call is being made. In fact, I created a few endpoints just for the purpose of logging and NONE of them are being called.
Here is an example of one of my FHIR Server test/log endpoints (I created few with 1 to 4 parameters):
[AllowAnonymous]
[HttpGet("{functionName}/{id}")]
public string GetPatientData3(string functionName, string id)
{
TelemetryClient telemetry = new();
telemetry.TrackEvent($"FHIR SVR GetPatientData3 {functionName} {id}");
string configJson = "0009998888";
return configJson;
}
How do I set this 'patient' context properly?
How does the OAuth server retrieve this context so I can have that patient ID appear in the json response from the ~/token call?
Further Notes:
The contents of the openid-configuration:
{"token_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported":
["client_secret_post","private_key_jwt","client_secret_basic"],
"jwks_uri":
"https://login.microsoftonline.com/xxx/discovery/v2.0/keys",
"response_modes_supported": ["query","fragment","form_post"],
"subject_types_supported": ["pairwise"],
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported":["code","id_token","code
id_token","id_token token"],
"scopes_supported":["openid","profile","email","offline_access"],
"issuer": "https://login.microsoftonline.com/xxx/v2.0",
"request_uri_parameter_supported":false,
"userinfo_endpoint":"https://graph.microsoft.com/oidc/userinfo",
"authorization_endpoint":
"https://aadproxy.azurewebsites.net/xxx/oauth2/v2.0/authorize",
"device_authorization_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/devicecode",
"http_logout_supported":true,
"frontchannel_logout_supported":true,
"end_session_endpoint":
"https://login.microsoftonline.com/xxx/oauth2/v2.0/logout",
"claims_supported":
["sub","iss","cloud_instance_name","cloud_instance_host_name",
"cloud_graph_host_name","msgraph_host","aud","exp","iat",
"auth_time","acr","nonce","preferred_username",
"name","tid","ver","at_hash","c_hash","email"],
"kerberos_endpoint":
"https://login.microsoftonline.com/xxx/kerberos",
"tenant_region_scope":"NA",
"cloud_instance_name":"microsoftonline.com",
"cloud_graph_host_name":"graph.windows.net",
"msgraph_host":"graph.microsoft.com",
"rbac_url":"https://pas.windows.net"}
So, I notice that the 'patient/.' and the 'launch' scope among a whole host of others that I have are not supported according to my openid config. The only supported ones are "openid","profile","email", "offline_access".
In Azure AD, 'App Registration' > 'Expose an API' I have a list of at least 15 scopes entered there. In 'API' permissions they are all listed there as well.
One other thing to note, AzureAD does not handle scopes with a forward slash. So, launch/patient has to be entered as launch-patient. We also had to implement a proxy server to capture the ~/oauth2/v2.0/authorize request and modify the scope parameter entries to reflect this before passing on the request to the actual server.
I guess the pertinent question now is: How do the scopes that I have entered manually get supported?
I'm trying use a service account with google's api to work with classroom data with the goal of synchronizing our web service for schools with the google classroom data.
I have delegated domain wide authority to the service account and have activated the Google Classroom API. I have downloaded the json Key file used below.
I have added https://www.googleapis.com/auth/classroom.courses to the scope of the service account.
My test code in app/models/g_service.rb:
class GService
require 'google/apis/classroom_v1'
def get_course
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: File.open('/Users/jose/Downloads/skt1-301603-4a655caa8963.json'),
scope: [ Google::Apis::ClassroomV1::AUTH_CLASSROOM_COURSES ]
)
authorizer.fetch_access_token!
service = Google::Apis::ClassroomV1::ClassroomService.new
service.authorization = authorizer
puts "\n service\n #{service.inspect}"
response = service.get_course( '99999' )
puts "\n response \n#{response.inspect}"
end
end
The results in the console are:
>> GService.new.get_course
service
#<Google::Apis::ClassroomV1::ClassroomService:0x007fe1cff98338 #root_url="https://classroom.googleapis.com/", #base_path="", #upload_path="upload/", #batch_path="batch", #client_options=#<struct Google::Apis::ClientOptions application_name="unknown", application_version="0.0.0", proxy_url=nil, open_timeout_sec=nil, read_timeout_sec=nil, send_timeout_sec=nil, log_http_requests=false, transparent_gzip_decompression=true>, #request_options=#<struct Google::Apis::RequestOptions authorization=#<Google::Auth::ServiceAccountCredentials:0x0xxxxxxx #project_id="sssssssss", #authorization_uri=nil, #token_credential_uri=#<Addressable::URI:0x000000000 URI:https://www.googleapis.com/oauth2/v4/token>, #client_id=nil, #client_secret=nil, #code=nil, #expires_at=2021-01-13 20:56:46 -0800, #issued_at=2021-01-13 19:56:47 -0800, #issuer="xxxxxxx.iam.gserviceaccount.com", #password=nil, #principal=nil, #redirect_uri=nil, #scope=["https://www.googleapis.com/auth/classroom.courses"], #state=nil, #username=nil, #access_type=:offline, #expiry=60, #audience="https://www.googleapis.com/oauth2/v4/token", #signing_key=#<OpenSSL::PKey::RSA:0xxxxxxxxx>, #extension_parameters={}, #additional_parameters={}, #connection_info=nil, #grant_type=nil, #refresh_token=nil, #access_token="-------------------------------------------------------------------------------------->, retries=0, header=nil, normalize_unicode=false, skip_serialization=false, skip_deserialization=false, api_format_version=nil, use_opencensus=true>>
Google::Apis::ClientError: forbidden: The caller does not have permission
It appears everything is working fine until the service.get_course('99999') call.
I've tested this call using the https://developers.google.com/classroom/reference/rest/v1/courses/get online tool and it works fine.
I've poured over the documentation but have been unable to resolve this.
Can anybody please let me know what I am missing?
I'm running rails 3.2 and ruby 2.1
Considering the error you're getting, I think you are not impersonating any account.
The purpose of domain-wide delegation is that the service account can impersonate a regular account in your domain, but in order to do that, you have to specify which account you want to impersonate. Otherwise, you are calling the service account by itself, and it doesn't matter that you've enabled domain-wide delegation for it.
In the Ruby library, you can specify that using the :sub parameter, as shown in the section Preparing to make an authorized API call at the library docs:
authorizer.sub = "<email-address-to-impersonate>"
Note:
Make sure the account you impersonate has access to this course, otherwise you'll get the same error.
Related:
Delegating domain-wide authority to the service account
Google API Server-to-Server Communication not working (Ruby implementation)
Our project lets users upload videos to OUR public youtube channel when they are signed into our session. We do not want to require additional OAuth2 verification, and instead follow the follow of Google API v2.
My code (php) and error are below, but my general question is: can my server make the insert-video POST using my API key/secrets without requiring the user to authenticate.
This question - Google Calendar API v3 hardcoded credentials - is very similar. Howevr if CURLing Google for the access_token is the only answer, i'll be disappointed. Thanks regardless.
Google API V3 Settings:
Client ID: xxxx.apps.googleusercontent.com
Email address: xxxx#developer.gserviceaccount.com
Client secret: xxxx
Redirect URIs: http://fbtabs.imagendigital.co/unilever/sedal/surprise/galeria
JavaScript origins: https://fbtabs.imagendigital.co
API key:
AIzaSyBgS-jMJQUPIiAyX20xC-encFWTPsR7qxQ
Referers:
Any referer allowed
Activated on: Jul 4, 2013 1:21 PM
Activated by: xxxx#gmail.com – you
Code:
<?php
require_once BASE_CD . 'app/src/Idframework/Tools.php';
require_once BASE_CD . 'app/vendor/google-api-php-client/src/Google_Client.php';
require_once BASE_CD . 'app/vendor/google-api-php-client/src/contrib/Google_YouTubeService.php';
$client = new Google_Client();
$client->setApplicationName('Google+ PHP Starter Application');
$client->setClientId('xxx.apps.googleusercontent.com');
$client->setClientSecret('xxxx');
$client->setRedirectUri('http://fbtabs.imagendigital.co/unilever/sedal/surprise/galeria');
$client->setDeveloperKey('xxxx');
$youtube = new Google_YoutubeService($client);
$snippet = new Google_VideoSnippet();
$status = new Google_VideoStatus();
$video = new Google_Video();
if (isset($_POST['tip_desc'])) $snippet->setDescription($_POST['tip_desc']);
$snippet->setTitle($_POST['tip_name']);
$snippet->setTags(array("sedal","hair","pello","cabello"));
$status->privacyStatus = "public";
$video->setSnippet($snippet);
$video->setStatus($status);
$filename = $_FILES['videoInp']['tmp_name'];
$mime = \Idframework\Tools::get_mime($filename);
try {
$part = "status";
$obj = $youtube->videos->insert($part, $video,
array("data"=>file_get_contents($filename),
"mimeType" => $mime));
} catch(Google_ServiceException $e) {
print "Caught Google service Exception ".$e->getCode(). " message is ".$e->getMessage(). " <br>";
print "Stack trace is ".$e->getTraceAsString();
}
Error:
Notice: Undefined index: content-type in C:\DATA\IDInteractive\SedalSurprise\app\vendor\google-api-php-client\src\service\Google_MediaFileUpload.php on line 99
Caught Google service Exception 401 message is Error calling POST https://www.googleapis.com/upload/youtube/v3/videos?part=player&uploadType=multipart&key=AIzaSyBgS-jMJQUPIiAyX20xC-encFWTPsR7qxQ: (401) Login Required
Stack trace is #0 C:\DATA\IDInteractive\SedalSurprise\app\vendor\google-api-php-client\src\io\Google_REST.php(36): Google_REST::decodeHttpResponse(Object(Google_HttpRequest)) #1 C:\DATA\IDInteractive\SedalSurprise\app\vendor\google-api-php-client\src\service\Google_ServiceResource.php(186): Google_REST::execute(Object(Google_HttpRequest)) #2 C:\DATA\IDInteractive\SedalSurprise\app\vendor\google-api-php-client\src\contrib\Google_YouTubeService.php(789): Google_ServiceResource->__call('insert', Array) #3 C:\DATA\IDInteractive\SedalSurprise\youtuber.php(56): Google_VideosServiceResource->insert('player', Object(Google_Video), Array) #4 {main}
First off, letting arbitrary users upload videos into a "master" YouTube channel is not recommended, for the reasons outlined in this blog post.
That being said, if you're determined to do it, then you need to include an access token associated with your channel in each upload request. The appropriate way to get a fresh access token (they expire after an hour) is to take a refresh token that you've previously generated and stored somewhere and make an HTTP request to the appropriate URL to get back an access token, as explained in the OAuth 2 documentation. There's no way around that—I'm not sure whether there's a native method in the Google APIs PHP client library that will automate the process for you, but that's what needs to happen under the hood.
The information in Google Calendar API v3 hardcoded credentials does seem relevant.
I'm trying to use OAuth2 for Server to Server Applications in conjunction with Google's Content API for Shopping using the google-api-client gem and Ruby on Rails 3.2.5. Also, I have already set up my merchant account as prescribed in the Content API documentation.
This was the best way I found to be able to:
create/update products in the background
have created products fall under my company's Google Products 'umbrella'
not require every user to authenticate/authorize when their token expires
Using lines 1 - 23 from this sample as a starting point, I've begun to write the following module for use in background jobs:
require 'httparty'
require 'google/api_client'
module GoogleProducts
GOOGLE_CONFIG = YAML.load_file(File.join(Rails.root, "config", "google.yml"))[Rails.env]
CLIENT_ID = "XXXXXXXXXXXX#developer.gserviceaccount.com"
MERCHANT_ID = "XXXXXXX"
SCOPE = "https://www.googleapis.com/auth/structuredcontent"
KEY_FILE_PATH = File.join(Rails.root, "config", "my-privatekey.p12")
KEY_FILE_PASS = "XXXXXXXXXX"
def self.add_item(item_id)
self.fetch_token
xml = self.gen_item_xml(item_id)
headers = {"Content-type" => "application/atom+xml", "Content-Length" => xml.length.to_s}
url = "https://content.googleapis.com/content/v1/#{MERCHANT_ID}/items/products/generic?access_token=#{$gp_token}"
response = HTTParty.post(url, :body => xml, :headers => headers).parsed_response
end
def self.gen_item_xml(item_id)
#building product xml
end
private
def self.fetch_token
api_client = Google::APIClient.new(:authorization => :oauth2)
key = Google::APIClient::PKCS12.load_key(KEY_FILE_PATH, KEY_FILE_PASS)
asserter = Google::APIClient::JWTAsserter.new(CLIENT_ID, SCOPE, key)
begin
api_client.authorization = asserter.authorize
#todo - store in something other than a global
$gp_token = api_client.authorization.access_token
rescue Signet::AuthorizationError => e
puts e.message
ensure
return $gp_token
end
end
end
Everything seemingly works fine - the authentication, the handling of the auth token - until I attempt to actually add an item, which I get the following when I do:
<errors xmlns='http://schemas.google.com/g/2005'>
<error>
<domain>GData</domain>
<code>ServiceForbiddenException</code>
<internalReason>Could not find authenticated customer</internalReason>
</error>
</errors>
Any ideas?
After much anguish and mental toil, I've finally solved my issue!
Since I am using OAuth 2 Server to Server authentication the suggestion hjblok gave didn't apply (thanks for giving it a shot, though!).
I simply added the email address that was associated with my Service Account key from the Google API Console (e.g. XXXXXXXXXXXX#developer.gserviceaccount.com) to my Google Merchant account (Settings > Users on the merchant admin page), and it worked.
If there's any clarification needed, please feel free to comment!
The Google Content API documentation says you need to set it up in the Settings page of the Google Merchant Center:
https://developers.google.com/shopping-content/getting-started/usingapi-products
EDIT rewrote the answer after diving into the Google's API documentation
Did you already try to use Google's OAuth 2.0 playground? I was able to successfully access https://content.googleapis.com/content/v1/#{MERCHANT_ID}/items/products/generic.
In "Step 1" I've chosen the "Content API for Shopping" and then authorized the API with my account.
Then in "Step 2" I've "exchanged authorization code for tokens", which results in a "refresh token" and an "access token".
Then in "Step 3" I've invoked a GET request to https://content.googleapis.com/content/v1/1234567/items/products/generic. Because 1234567 is not a valid MERCHANT_ID it returns an Error. But the Error Messages contains a MERCHANT_ID which actually belongs to your account.
I repeated "Step 3" but now with the correct MERCHANT_ID. Which returns a HTTP/1.1 200 OK with the requested items in the body.
Furthermore I'm not sure, but doesn't Google API expect an Authorization header to be present with the access_token ($gp_token)? Within the OAuth 2.0 playground this Authorization header is used to sent the access_token.
I also found the Structured Content API demo page (https://google-content-api-tools.appspot.com/demo/demo.html), which is more specific to the Content API for Shopping.
I've looked all over for some documentation on this, but haven't found it. Some posts reference a user-agent string:
http://groups.google.com/group/feedburner-services/browse_thread/thread/7aee14cf6a2432e7/49464335d2228e25?lnk=gst&q=aweber#49464335d2228e25
I had assumed there would be an API or something. More generally, how does ANY rss feed reader/aggregator (like Bloglines, etc) report subscriber numbers to Feedburner?
I'm working on developing a new app that would need this functionality.
Thanks for your help!
Brian
As you discovered in your link, you put the subscriber count in your user-agent, then you contact the Feedburner Support Group and tell them what format you will be using.
The consensus format is something
User-agent: Service Name (http://example.com/service/info/; ### subscribers ; [optional feed identifier] )
The optional feed identifier is typically used if you run several different services, and fetch the feed separately for each one; e.g. if you have a mail service and a web-based reader service, with different subscribers, then you might either use:
User-agent: SO Agg/1.3 (http://example.com/SOAgg ; 5000 subscribers ; feed-id=mail-134 )
on request for the mailer, and
User-agent: SO Agg/1.3 (http://example.com/SOAgg ; 2000 subscribers ; feed-id=web-134 )
on the request for the website; or use
User-agent: SO Agg/1.3 (http://example.com/SOAgg ; 7000 subscribers ; )
if your system makes only one request for both services...
You will usually need to specify what IP addresses are authorised to request the feed with that user-agent, as well.
Many major aggregators report user stats by including them as part of the useragent string. Examples:
Bloglines reporting description in blog comment
Google Reader: Tips for Publishers
PostRank: Reporting Subscription Counts
There's no standard for this at this time.
To the best of my knowledge, folks will contact major feed analytics vendors like Feedburner directly, to make sure their useragent-based reporting is being counted.