Can't POST to issueRefund endpoint
Hi everyone,
I have the following code in python:
from base64 import b64encode
from urllib.parse import urlparse
import json
import sys
import time
import requests
from Crypto.PublicKey import ECC
from Crypto.Signature import eddsa
from Crypto.Hash import SHA256
from requests.exceptions import HTTPError
class EBayRefund:
__config: dict = {}
def __init__(self):
with open('config.json') as user_file:
json_file = user_file.read()
self.__config = json.loads(json_file)
def __get_access_token(self, ebay_refresh_token: str, oauth_token: str, scope: str):
"""Returns an eBay API access token, required to make further API calls."""
headers = {
"Authorization": "Basic " + oauth_token,
"Content-Type": "application/x-www-form-urlencoded",
}
data = {
"grant_type": "refresh_token",
"refresh_token": ebay_refresh_token,
"scope": scope,
}
try:
response = requests.post(
"https://api.ebay.com/identity/v1/oauth2/token",
headers=headers,
data=data,
timeout=10,
)
response.raise_for_status()
result = response.json()
return result["access_token"]
except (HTTPError, KeyError) as error:
sys.exit(f"Unable to fetch access token: {error}")
def __get_content_digest(self, content: str) -> str:
"""
Generate a digest of the provided content.
A content digest is needed when using one of the few POST requests requiring a digital
signature.
"""
hasher = SHA256.new()
hasher.update(bytes(content, encoding="utf-8"))
digest = b64encode(hasher.digest()).decode()
return digest
def __get_digital_signature(self, ebay_private_key: str, ebay_public_key_jwe: str, request_url: str, signature_params: str, digest: str = "") -> str:
"""
Generate the digital signature using the details provided. The signature is created
using ED25519.
To add support for POST requests, pass in a content digest and add a "Content-Digest"
entry to params, with a value of sha-256:digest:
"""
url = urlparse(request_url)
params = (
f'"x-ebay-signature-key": {ebay_public_key_jwe}\n'
f'"@method": POST\n'
f'"@path": {url.path}\n'
f'"@authority": {url.netloc}\n'
f'"@signature-params": {signature_params}\n'
f'"content-digest": sha-256=:{digest}:'
).encode()
print(params)
try:
private_key = ECC.import_key(f"""-----BEGIN PRIVATE KEY-----\n{ebay_private_key}\n-----END PRIVATE KEY-----""")
signer = eddsa.new(private_key, mode="rfc8032")
signature = signer.sign(params)
return b64encode(signature).decode()
except ValueError as error:
sys.exit(f"Error creating digital signature: {error}")
def __send_signed_api_request(self, ebay_private_key: str, ebay_public_key_jwe: str, access_token: str) -> None:
"""
Sends a request to the eBay API with a digital signature attached.
The API response text is printed before exiting.
"""
order_id = "XX-XXXXX-XXXXX"
request_url: str = f"https://api.ebay.com/sell/fulfillment/v1/order/{order_id}/issue_refund"
signature_input = f'("content-digest" "x-ebay-signature-key" "@method" "@path" "@authority");created={int(time.time())}'
content = {
"orderLevelRefundAmount":
{
"value": "17.52",
"currency": "GBP"
},
"reasonForRefund": "BUYER_RETURN"
}
content = json.dumps(content)
content_digest = self.__get_content_digest(content = content)
signature = self.__get_digital_signature(
ebay_private_key=ebay_private_key,
ebay_public_key_jwe=ebay_public_key_jwe,
request_url=request_url,
signature_params=signature_input,
digest=content_digest
)
headers = {
"Authorization": "Bearer " + access_token,
"Signature-Input": f'sig1={signature_input}',
"Signature": f"sig1=:{signature}:",
"x-ebay-signature-key": ebay_public_key_jwe,
"x-ebay-enforce-signature": "true",
"content-digest": f"sha-256=:{content_digest}:"
}
print(json.dumps(headers, indent=4))
try:
response = requests.post(request_url, headers = headers, data = content, timeout = 10)
result = response.json()
print(json.dumps(result, indent = 4))
sys.exit()
except HTTPError as error:
sys.exit(f"Unable to send request: {error}")
def start(self):
"""Load credentials and read runtime arguments."""
scope = "https://api.ebay.com/oauth/api_scope/sell.fulfillment"
access_token = self.__get_access_token(
ebay_refresh_token = self.__config["refreshToken"],
oauth_token = self.__config["credentials"],
scope = scope,
)
self.__send_signed_api_request(
ebay_private_key = self.__config["privateKey"],
ebay_public_key_jwe = self.__config["jweKey"],
access_token = access_token,
)
if __name__ == "__main__":
EBR = EBayRefund()
EBR.start()
which attempts to issue a refund but instead returns:
{
"errors": [
{
"errorId": 215122,
"domain": "ACCESS",
"category": "REQUEST",
"message": "Signature validation failed",
"longMessage": "Signature validation failed to fulfill the request."
}
]
}
This script was originally made for making calls to https://apiz.ebay.com/sell/finances/v1/transaction?limit=20&offset=0 as an example and it still works fine when I test it but POSTing to the issueRefund won't work.
To be honest there aren't many resources for the E-Bay API's usage of the digital signature header - and even less for the POST calls.
Thanks
tyler-mcg
Posted 3 years ago·Last reply 3 years ago
3 comments
tyler-mcg
OP3 years agoOK, I'll be answering my own question and hope it helps others
After asking for help here I finally managed to get it working. Here's how:
my new signature base looks like this now:
note the order in which the properties are arranged
and the headers:
Note: Because I'm calling the /post-order/v2/return/{return_id}/issue_refund API endpoint I had to replace Bearer with TOKEN inside the Authorization header.
Big thanks to Ulrich Hergerb from GitHub
afridigems
·3 years ago@tyler-mcg
I am passing through the same issue. I had a script which was utilizing "/post-order/v2/return/{return_id}/issue_refund", but when eBay made the Digital Signature mandatory, I tried to implement it on the above API, but all in vain.
I tried to refund with https://api.ebay.com/sell/fulfillment/v1/order/{order_id}/issue_refund, however, that also not successful. When I call the https://api.ebay.com/sell/fulfillment/v1/order/{order_id}/issue_refund API, it results with the captioned error.
{"errors":[{"errorId":34916,"domain":"API_FULFILLMENT","category":"BUSINESS","message":"A post-transaction case exists on this order, seller refund can’t be triggered."}]}
Here is my code highlights:
I am coding in PHP/Laravel.
I am using OAuth Token.
Here is my "refunder.config.json" contents. { "digestAlgorithm": "sha-256", "algorithm": "ED25519", "privateKeyStr": "-----BEGIN PRIVATE KEY-----\nMC4CA MY PRIVATE KEY gUE/\n-----END PRIVATE KEY-----", "jwe": "My JWE Key", "signatureParams": [ "content-digest", "x-ebay-signature-key", "@method", "@path", "@authority" ] }
Here is the code.
` $signature = new Signature(public_path("refunder-config.json"));
The financial call is successful, which means the Digital Signature is working because that API also needs Digital Signature.
Can you help me in this regard.
Your help will be highly appreciated.
tyler-mcg
OP3 years agoHi @afridigems
The hint is in the message - you can't refund an order that has been returned. As far as I know the fulfillment API is used for refunding orders that have not been shipped yet.
To refund an order with an open case (i.e. the buyer has returned it) you have to use the post-order API