The Walgreens HTML Checkout PhotoPrints API gives the ability for customers of third party mobile photo applications to print photos from their mobile devices and pick them up at their local Walgreens store.
Below are the technical steps you will need to go through in order to get your integration configured and branded correctly:
The external services listed below make up the complete list of services that are required in order to complete all phases of the PhotoPrints experience. Please note that all of the service endpoints documented below are brand new for this version of the PhotoPrints API.
This endpoint can be used to fetch the credentials needed to upload images to the Walgreens Storage server. Take note, when uploading to our storage, the files will not be accessible once uploaded and you should save image data if planning to use later in your checkout flow for previewing the images.
Sandbox: https://services-qa.walgreens.com/api/photo/creds/v3
Production: https://services.walgreens.com/api/photo/creds/v3
Request format | JSON |
Response format | JSON |
Authentication? | Yes |
Rate limited? | Yes |
Requests Per Minute | 300 |
Name | Optionality | Description | Example |
---|---|---|---|
apiKey | required | Your API Key. | "AbCdEfGhIjKlMnOpQrStUvWxYz" |
affId | required | Your AffiliateID. | "AAAAAAAAAA" |
platform | required | The software platform. | "ios" || "android" |
transaction | required | A static value describing the reason for fetching the credentials. | "photocheckoutv2" |
appVer | optional | The build version of your application. | "1.0" |
devInf | optional | The device manufacturer and version. | "iPhone,13.0" |
curl --request POST \
--url https://services-qa.walgreens.com/api/photo/creds/v3 \
--header 'Content-Type: application/json' \
--data '{ \
"apiKey":"YOUR_API_KEY", \
"affId":"YOUR_AFFILIATE_ID", \
"platform":"ios", \
"transaction":"photocheckoutv2", \
"devInf":"DEVICE,##.#" \
"appVer":"#.#", \
}' \
{
"uploadLimit": 200,
"template": "default",
"landingUrl": "https://m5.walgreens.com/photoprints/",
"cloud": [{
"sasKeyToken": "https://pstrgqp01.blob.core.windows.net/qpcontainerin?sig=
BLAHBLAHBLAH&se=YYYY-MM-DDT22%3A53%3A57Z&sv=YYYY-MM-DD&sp=w&sr=c"
}]
}
Before an image can be uploaded, the value for the "sasKeyToken" (looks like a url) must be converted correctly into the "Upload_URL" . This is completed by doing the following:
SWIFT:
let blobContainer:String = sasToken.components(separatedBy: "?").first!
let signature:String = sasToken.components(separatedBy: "?").last!
let imageName:String = "Image-\(affId)-\(UUID().uuidString).jpg"
let Upload_URL:String = "\(blobContainer)/\(imageName)?\(signature)"
KOTLIN:
var blobContainer:String = sasToken.split("?").toTypedArray().first()
var signature:String = sasToken.split("?").toTypedArray().last()
var imageName:String = Image-".plus(affId).plus("-").plus(UUID.randomUUID().toString()).plus(".jpg")
var Upload_URL:String = blobContainer.plus("/").plus(imageName).plus("?").plus(signature)
JAVASCRIPT:
function generateUUID()
{
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)
{
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
var blobContainer = sasToken.split("?").shift();
var signature = sasToken.split("?").pop();
var imageName = "Image-"+affId+"-"+generateUUID()+".jpg";
var Upload_URL = blobContainer+"/"+imageName+"?"+signature;
https://pstrgqp01.blob.core.windows.net/qpcontainerin/
Image-YOUR_AFFILIATE_ID-a12b3cd-e4f5-6789-ghi0-1234567j89kl.jpg?
sig=BLAHBLAHBLAH&se=YYYY-MM-DDT22%3A53%3A57Z&sv=YYYY-MM-DD&sp=w&sr=c
To upload an image, make a PUT request to the generated "UPLOAD_URL" with the custom HTTP headers. Place the image data to be uploaded in the HTTP body. If multiple images are to be printed, repeat the process above for every single image.
The image url is generated using the process above.
Request format | Binary Data |
Response format | XML |
Authentication? | Yes |
Rate limited? | No |
Name | Optionality | Description | Example |
---|---|---|---|
Content-Type | required | The type of image format for the file. We convert the file to JPEG on our side, however we prefer you do this on your side. | "img/jpeg" |
Content-Length | required | The count of the bytes of data in the image file. | "1073741824" |
x-ms-client-request-id | required | A Universally Unique Identifier (UUID) for each request. | "a12b3cd-e4f5-6789-ghi0-1234567" |
x-ms-blob-type | required | A static value describing the request upload type. | "BlockBlob" |
The binary data of the image file.
curl -X PUT \
'https://pstrgqp01.blob.core.windows.net/qpcontainerin/Image-YOUR_AFFILIATE_ID-a12b3cd-e4f5-6789-ghi0-1234567j89kl.jpg?sig=BLAHBLAHBLAH&se=YYYY-MM-DDT22%3A53%3A57Z&sv=YYYY-MM-DD&sp=w&sr=c' \
-H 'Content-Length: 1073741824' \
-H 'Content-Type: image/jpeg' \
-H 'x-ms-blob-type: BlockBlob' \
-H 'x-ms-client-request-id: a12b3cd-e4f5-6789-ghi0-1234567' --upload-file image.jpeg
HTTP STATUS CODE: 201
You are absolutely able to use your own url for images uploaded to another server then the Walgreens server. Please ensure the url is accessible for at least 36 hours (due to delays at store level) with a simple HTTP Get Request and that as that is how our image downloader will get the file to the store.
The landing URL can be obtained from the findURL web service, this url allows the customer to do the Photo Print Checkout.
Sandbox: https://services-qa.walgreens.com/api/util/v3.0/mweb5url
Production: https://services.walgreens.com/api/util/v3.0/mweb5url
curl --request POST \
--url https://services-qa.walgreens.com/api/photo/creds/v3 \
--header 'Content-Type: application/json' \
--data '{ \
"apiKey": "YOUR API KEY",
"affId": "YOUR AFFILIATE ID",
"productGroupId": "PRODUCT GROUP ID",
"publisherId": "YOUR PUBLISHER ID",
"channelInfo": "CHANNEL INFO: web or ''",
"callBackLink": "IF CHANNEL INFO IS web: YOUR CALLBACK URL",
"expiryTime": "IMAGE EXPIRATION TIME",
"images": [
"URL1",
"URL2",
"URL3",
"etc..."
],
"lat": "CUSTOMER LATITUDE",
"lng": "CUSTOMER LONGITUDE",
"customer": {
"firstName": "CUSTOMER FIRST NAME",
"lastName": "CUSTOMER LAST NAME",
"email": "CUSTOMER EMAIL ADDRESS",
"phone": "CUSTOMER PHONE NUMBER"
},
"transaction": "photoCheckoutv2",
"act": "mweb5UrlV2",
"view": "mweb5UrlV2JSON",
"devinf": "THE DEVICE INFO",
"appver": "THE APP VERSION",
"affNotes": "TRACKING ID / NOTES"
}' \
{
"landingUrl": "CHECKOUT_LANDING_URL",
"template": "TEMPLATE_FOR_CUSTOM_TEMPLATES",
"uploadLimit": "UPLOAD_LIMIT_FOR_YOUR_ACCOUNT",
"token": "ACCESS_TOKEN_FOR_CHECKOUT",
"err": "ERROR_CODE_NUMBER"
}
Once a LandingURL has been obtained, the URL should be loaded in a WebView/Browser Window.
To properly open the URL you must append the value of "token" to queryString of the "landingUrl".
{GET:landingUrl+"&token="+token}
Once the user has loaded the checkout page there are some button interactions that must be handled by the callback URL passed in generating the checkout:
Obviously we don't want any errors to happen, but sometimes they do and as a result we respond the following error codes. The table below helps explain why each of the error codes could occur:
Code | Type | Message |
---|---|---|
143 | ERROR | If there is any exception in web service. |
500 | ERROR | If the Affiliate ID is invalid. |
501 | ERROR | If the operation is not supported. |
502 | ERROR | If there is an error in getting Affiliate Info. |
503 | ERROR | If the request is not made in secure mode. |
504 | ERROR | If the session is not secure. |
505 | ERROR | If the view is not found. |
506 | ERROR | If the service action is not found |
507 | ERROR | If there is no mapping in droplet. |
508 | ERROR | If the required parameter is missing. |
509 | ERROR | If the view parameter is missing. |
510 | ERROR | If there is an exception. |
511 | ERROR | If there is no mapping in droplet. |
512 | ERROR | If the required view parameter is missing. |
513 | ERROR | If the Token is invalid. |
524 | ERROR | If there is an exception in the findURLV2 service. |
525 | ERROR | If Image URL list is invalid. |
527 | ERROR | For affiliate notes exceeding more than 240 characters. |
654 | ERROR | Missing Product ID. |
659 | ERROR | If there is no match for the vendor. |
691 | ERROR | If internal services are down while validating Single Use Coupons. |
2003 | ERROR | When the credentials are invalid then it will give this Exception. |
2009 | ERROR | Service Unavailable Exception. |
2010 | ERROR | For any Exception in Service. |
6002 | ERROR | Order already placed. |
6003 | ERROR | Order Cancelled Error code. |
6004 | ERROR | Repeated order cancelled. |
6006 | ERROR | If multiimgqty attribute is empty for Creative Products. |
6007 | ERROR | If Creative Product Indicator is empty for Creative Orders. |
6008 | ERROR | MIQ and CPI are invalid. |
Code | Type | Message |
---|---|---|
111 | ERROR | For any Exception with the service. |
112 | ERROR | Your affiliateID is setup incorrectly. |
632 | ERROR | If required parameters are missing. |
Code | Type | Message |
---|---|---|
659 | ERROR | AffiliateID setup is invalid. |
991 | ERROR | Product Details Not Found. |
9111 | ERROR | Exception when fetching Price List. |
9112 | ERROR | Exception in Price List Service. |
Code | Type | Message |
---|---|---|
656 | ERROR | Coupon Code is empty. |
661 | ERROR | Coupon code is invalid. |
682 | ERROR | If coupon is already used. |
683 | ERROR | If coupon or promotion is expired. |
684 | ERROR | If coupon code is not found. |
685 | ERROR | Web service System Error while coupon service validation. |
686 | ERROR | If store Number is empty. |
687 | ERROR | If Coupon redemption date is invalid. |
688 | ERROR | If coupon RFN number is empty. |
689 | ERROR | If invalid amount or null amount. |
690 | ERROR | If Barcode is null or empty. |
1006 | ERROR | If the input parameters are invalid. |
9100 | ERROR | If the Product details are missing. |
9102 | ERROR | If there is an internal exception occurred. |
9103 | ERROR | If there is a JSON exception. |
9107 | ERROR | If the quantity is missing. |
Code | Type | Message |
---|---|---|
651 | ERROR | Invalid Search. |
652 | ERROR | No Stores Available. |
653 | ERROR | No Search Parameter Given. |
851 | ERROR | One or many of the Mandatory fields are empty. |
1601 | ERROR | If the ProductID is missing. |
1602 | ERROR | If the Quantity is missing. |
9100 | ERROR | If the Product details are missing. |
9104 | ERROR | If there is an Internal Exception |
9105 | ERROR | If there is a JSON exception. |
9106 | ERROR | If there is an Internal Exception. |
Code | Type | Message |
---|---|---|
527 | ERROR | If the Affiliate Notes at maximum length. |
630 | ERROR | If the phone number is invalid. |
631 | ERROR | If the promise time is invalid. |
632 | ERROR | If any of the mandatory fields is null. |
633 | ERROR | Exception in service. |
634 | ERROR | If the order is not successful. |
640 | ERROR | If the coupon code is invalid. |
660 | ERROR | If the quantity is invalid. |
664 | ERROR | If Number of Images is greater then 200. |
665 | ERROR | If Number of Prints Per Image is > 10. |
851 | ERROR | One or many of the Mandatory fields are empty. |
1016 | ERROR | If Invalid Image URLs are passed. |
9104 | ERROR | If the image quantity is not with the range. |