I've been struggling to mock the 'INSTRUMENT_DECLINED' issue, which according to Paypal is an usual error after attempting to capture an order (https://developer.paypal.com/demo/checkout/#/pattern/server).
Correctly setting the header to mock the response will help me to simulate other errors from the docs https://developer.paypal.com/docs/business/test-and-go-live/simulation-tests/#orders.
I'm using Laravel back end and React front end.
I have already gotten the expected mocked response using axios and calling in the front-end:
return axios
.post(
`https://api-m.sandbox.paypal.com/v2/checkout/orders/${orderID}/capture`,
{},
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken,
"PayPal-Mock-Response":
'{"mock_application_codes":"INSTRUMENT_DECLINED"}',
},
}
)
.then((res) => {
console.log(res);
})
However, I don't want to expose accessToken into the front end, I'd prefer to do everything in the back-end with GuzzleHtttp.
I've already had a positive response - with 'status': 'COMPLETED' without adding PayPal-Mock-Response header like so:
$client = new \GuzzleHttp\Client();
$response = $client->request(
'POST',
'https://api-m.sandbox.paypal.com/v2/checkout/orders/' . $paypalOrderId . '/capture',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $access_token,
// 'PayPal-Mock-Response' => json_encode(["mock_application_codes" => "INSTRUMENT_DECLINED"]),
// 'PayPal-Mock-Response' => "['mock_application_codes':'INSTRUMENT_DECLINED]",
],
],
);
$data = json_decode($response->getBody(), true);
But when adding the PayPal-Mock-Response header like shown in the commented code from above, both attemps have returned this exception:
"message": "Client error: POST https://api-m.sandbox.paypal.com/v2/checkout/orders/9HG50866FU785784C/capture
resulted in a 404 Not Found response",
"exception": "GuzzleHttp\Exception\ClientException",
"file": "C:\xampp\htdocs\react\React-Laravel\vinos-gdl\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php",
This exception, I'm sure has to do to the way I'm passing the PayPal-Mock-Response header into the Guzzle http call but I can't find the way to do it correctly.
Any help is much appreciated.
UPDATE SOLVED**
Ok the way to go is sending the header like so:
'PayPal-Mock-Response' => json_encode(["mock_application_codes" => "INSTRUMENT_DECLINED"]),
I dunno if I had any typo but I finally got it. I also used INTERNAL_SERVER_ERROR to get a 500 response from server and built a catch for every situation:
} catch (ClientException $e) {
if ($e->hasResponse()) {
return response()->json([
'msg' => 'Client Exception',
'error' => json_decode($e->getResponse()->getBody()),
], 400);
}
return response()->json([
'msg' => 'Client Exception',
'request' => $e->getRequest(),
$e->hasResponse() ? $e->getResponse() : ""
]);
// return response()->json(['msg' => 'Server Error', 'error' => report($e)]);
} catch (BadResponseException $e) {
if ($e->hasResponse()) {
return response()->json([
'msg' => 'Uknown Exception',
'error' => json_decode($e->getResponse()->getBody()),
], 500);
}
return response()->json([
'msg' => 'Uknown Exception',
'request' => $e->getRequest(),
$e->hasResponse() ? $e->getResponse() : ""
]);
}
Thanks for the ones who commented.
"PayPal-Mock-Response":
'{"mock_application_codes":"INSTRUMENT_DECLINED"}',
...
// 'PayPal-Mock-Response' => "['mock_application_codes':'INSTRUMENT_DECLINED]",
There is a difference betwen {} (JSON object) and [] (JSON array).
What you have inside the brackets is not an array, so its syntax is invalid. It doesn't map to anything PayPal expects, hence the 404.
Related
Using Laravel 9, I'm running a pool of async http queries. However before fetching the body, I need to perform some verifications on the headers (e.g. Content-type and Content-Length)
Here is what I tried:
use GuzzleHttp\RequestOptions;
use Illuminate\Http\Client\Pool;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;
$responses = Http::pool(fn(Pool $pool) => [
$pool->withOptions([
RequestOptions::ON_HEADERS => function (ResponseInterface $response) {
if (($length = $response->getHeaderLine('Content-Length')) > 1048576) {
dump('Content-Length: ' . $length);
throw new \Exception('The file is too big');
}
},
])->get('<15MB_FILE_URL>'),
]);
$response = $responses[0];
if ($response instanceof Response) {
dump([
'ok' => $response->ok(),
'successful' => $response->successful(),
'failed' => $response->failed(),
'serverError' => $response->serverError(),
'clientError' => $response->clientError(),
'status' => $response->status(),
'reason' => $response->reason(),
'size' => strlen($response->body()),
]);
}
if ($response instanceof \Throwable) {
$this->warn('error: ' . $response->getMessage());
}
And it produces the following output
"Content-Length: 15882755"
array:8 [
"ok" => true
"successful" => true
"failed" => false
"serverError" => false
"clientError" => false
"status" => 200
"reason" => "OK"
"size" => 0
]
According to the Guzzle documentation:
If an exception is thrown by the callable, then the promise associated with the response will be rejected with a GuzzleHttp\Exception\RequestException that wraps the exception that was thrown.
I was expecting the response to be an Exception, the same way I end up with a GuzzleHttp\Exception\ConnectException when the server is not reachable, though the response seems to be errorless, event if the content is not downloaded.
Is there a better way to check the Content-Length and Content-Type (eventually after a redirection if needed) of a given url to avoid downloading the content whenever the type is not supported or the length is higher than expected in a request concurrency context.
Thanks for helping
I want to make concurrent Guzzle http requests in laravel to rest api i have users in 100k i want to perform billing for users.
Currently my guzzle http is doing synchronous calls to rest api which is taking 6 hours to complete 100k post requests and the requests does not have any call backs they are just post request with users msisdn and unique id in json format.
How to do concurrent 50 requests per second so that billing is performed quickly.
Following is part of my code which i use taken from https://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests
$requests = function ($total) {
$url = "url here";
$auth = base64_encode($username . ":" . $password);
for ($i = 0; $i < $total; $i++) {
$msgdata =[
'msisdn'=>$msisdn,
$subscription
=>$subscriptionInfo];
yield new Request('post', $url,
[
'headers' =>
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
'body' => json_encode($msgdata)
]);
}
$pool = new Pool($client, $requests(50), [
'concurrency' => 5,
'fulfilled' => function (Response $response, $index) {
// this is delivered each successful response
echo $response;
},
'rejected' => function (RequestException $reason, $index) {
// this is delivered each failed request
echo $reason;
},
]);
// Initiate the transfers and create a promise
$promise = $pool->promise();
// Force the pool of requests to complete.
$promise->wait();
i am getting response as
"status":401,"error":"Unauthorized"
But request params are not incorect idk why it is giving response as incorect
finally i found the solution to my problem, the problem was in request header and body parameters.
changed this
yield new Request('post', $url,
[
'headers' =>
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
'body' => json_encode($msgdata)
]);
to
yield new Request('post', $url,
[
'Content-Type' => 'application/json',
'Authorization' => $authorizaton
],
json_encode($msgdata)
);
OK I am at the end of my day and I am not thinking straight. So this is what I have...
a Laravel controller, send it a username, and it tells me if the username is available, and if it isnt, it gives me a 422 code
public function checkUsername(Request $request) {
Validator::make($request->all(), [
'name' => ['required', 'string', 'max:255', 'unique:users'],
])->validate();
return response()->json([
'valid' => true,
'data' => [
'message' => 'Username is available!'
]
], 200);
}
Example of valid response:
{"valid":true,"data":{"message":"Username is available!"}}%
The curl to test is:
curl -X POST -H "Content-Type: application/json" -d '{"name": "bossryan"}' http://127.0.0.1:8000/api/checkusername
Next: I have a frontend Vue using Vee-validate. It does a bunch of things, but I need to add this latest validation into the mix, so if the username is taken (I don't get the valid response from above, it needs to reply with "This username is already taken"
validateUsername(value) {
// if the field is empty
if (!value) {
return 'This field is required';
}
const regex = /^[a-zA-Z0-9_.+-]{4,20}$/i;
if (!regex.test(value)) {
return 'This must be a minimum of 4 characters';
}
return true;
},
This is the axios I created but it isnt working:
const isUnique = (value) => {
return axios.post('/api/checkusername', { email: value }).then((response) => {
// Notice that we return an object containing both a valid property and a data property.
return {
valid: response.data.valid,
data: {
message: response.data.message
}
};
});
};
I know I need to add in axios, but I am just having a heck of a time setting it up and my mind keeps rushing around. I am just looking for someone who can just help me plug in the axios request above //All is good, so I can finish this up.
THANKS FOR THE HELP COMMUNITY!
Vee-validate seems to want a resolved promise for async validation. Axios will reject the promise if the status is >= 400 so you need to handle that accordingly.
Assuming when validation fails that the response body matches the same { valid, data: { message } } format, you'd want something like the following
const isUnique = (name) =>
axios.post("/api/checkusername", { name })
.then(({ data }) => data)
.catch(err => ({ // resolve with error details
valid: err.response?.data?.valid ?? false,
data: {
// get the message from the response if it exists
message: err.response?.data?.data?.message ?? "Validation failed"
}
}));
export default {
methods: {
async validateUsername(value) {
// do your synchronous checks as per question
const check = await isUnique(value);
return check.valid || check.data.message;
}
}
}
This will provide a generic message "Validation failed" if the 422 response body doesn't match expectations.
I try to implement Paypal subscription service according to: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
This is my first try.
In sandbox business account I have created two test subscriptions: monthly and yearly and configured application with their id's.
This is the method:
public function createSubscription($planSlug, $name, $email) {
return $this->makeRequest(
'POST',
'v1/billing/subscriptions',
[],
[
'plan_id' => $this->plans[$planSlug],
'subscriber' => [
'name' => [
'given_name' => $name,
],
'email_address' => $email
],
'application_context'=> [
'brand_name' => config('app.name'),
'shipping_preference' => 'NO_SHIPPING',
'user_action' => 'SUBSCRIBE_NOW',
'return_url' => route('subscribe.approval', ['plan' => $planSlug]),
'cancel_url'=> route('subscribe.cancelled')
],
],
[],
$isJsonRequest = true
);
}
However, when I make a call to API, to create a test subscription, I get weird response that 'name' parameter is formed incorrectly:
php artisan tinker
>>> $paypal = resolve(App\Services\PaypalService::class);
=> App\Services\PaypalService {#3413}
>>> $paypal->createSubscription('monthly', 'Test', 'test#test.com');
GuzzleHttp\Exception\ClientException with message 'Client error: `POST https://api-
m.sandbox.paypal.com/v1/billing/subscriptions` resulted in a `400 Bad Request` response:
{"name":"INVALID_REQUEST","message":"Request is not well-formed, syntactically incorrect, or
violates schema.","debug_id (truncated...)
This is strange, because in Paypal API doc (see above), the 'name' param is described exactly like that!
Do I miss something or it is Paypal API is acting funky?
try this :
try {
// your code here
} catch(\Throwable $th) {
if ($th instanceof ClientException) {
$r = $th->getResponse();
$responseBodyAsString = json_decode($r->getBody()->getContents());
dd($responseBodyAsString);
}
}
I've faced this too before and it was not easy for me to figure out how to show an explicit error message.
'return_url' => route('subscribe.approval', ['plan' => $planSlug]),
'cancel_url'=> route('subscribe.cancelled')
the problem is in this two url, may be you have changed the APP_URL in the .env
APP_URL=http://127.0.0.1:8000
put app url that and try
I want to upload the image from Flutter. But I am getting this error:
The GET method is not supported for this route. Supported methods:
POST.
But I set my api route as POST method. And I also sending POST method request, but still I am getting this error.
But one more thing, it works on POSTMAN and INSOMNIA. There is no problem.
I use this header:
Content-Type: multipart/form-data
Authorization: ....
Please help me.
My route is:
Route::post('/avatar/update', 'Api\ProfileController#avatar_update')->name('api.avatar.update');
My controller:
public function avatar_update(Request $request){
$request->validate(array(
'avatar' => 'required|image',
));
try{
$image = Image::make($request->avatar)->fit(250, 250);
$photo_name = Auth::user()->username."-".strtotime(now()).'.'.$request->avatar->extension();
$path='images/avatars/'.$photo_name;
$image->save($path);
if(File::exists(Auth::user()->avatar)) {
File::delete(Auth::user()->avatar);
}
Auth::user()->update([
'avatar' => 'http://attendance.isadma.com/'.$path,
]);
return response()->json([
'status' => 1,
'message' => 'Picture updated.',
'image' => Auth::user()->avatar
], 200);
}
catch (\Exception $e){
return response()->json([
'status' => 0,
'message' => $e->getMessage()
], 500);
}
}
Flutter request code:
#override
Future<String> uploadProfilePic(File profilePic, String token) async {
var postUri = Uri.parse("$url/avatar/update");
print(profilePic.path);
print(postUri);
var request = http.MultipartRequest("POST", postUri);
request.headers['authorization'] = "Bearer $token";
request.headers['Content-Type'] = "multipart/form-data";
request.files.add(
await http.MultipartFile.fromPath(
'avatar',
profilePic.path,
contentType: MediaType('image', 'jpg'),
filename: basename(profilePic.path),
),
);
print(request.headers);
request.send().then((res) async {
print(res.headers);
print(res.statusCode);
print(await res.stream.bytesToString());
}).catchError((e) {
print(e);
});
}
make sure you are sending the csrf data (_token) in your post request