"HttpCode: 400, Errormessage: request body malformed" when calling api post - android

I am making an android application with kotlin.
I am facing the following error when calling the api.
{"status":400,"errorMessage":"request body malformed."}
I think the message part is the problem.
I gave the variables messages1 and messages2 values to messages, but to no avail. (RetrofitService.sendsms value was also changed to match the type)
please help me
Here is the api guide.
API Url & API Header
Request body
Response body
Next is the code I used.
<MainActivity.kt>
var messages1 = listOf(messages("0"+PhoneNum_to))
var messages2 = """{"to": "${"0"+PhoneNum_to}"}"""
timestamp = System.currentTimeMillis().toString()
smsservice.sendsms(
Content_Type = "application/json; charset=UTF-8",
timestamp = timestamp,
accesskey = "{accesskey}",
signature = "{signKey}",
type = "LMS",
contentType = "COMM",
from = "{Caller number}",
content = "{message content}",
messages = messages1
)?.enqueue(object : Callback<sendsms_Response> {
override fun onResponse(call: Call<sendsms_Response>, response: Response<sendsms_Response>) {
if(response.isSuccessful){
// true
} else{
// false
}
override fun onFailure(call: Call<sendsms_Response>, t: Throwable) {
// Exception
Log.d("NaverSMSApiResult", "onFailure 에러: " + t.message.toString());
}
})
<RetrofitService.kt>
interface RetrofitService {
// POST 예제
#FormUrlEncoded
#POST("/sms/v2/services/{Service_ID}/messages")
fun sendsms(
#Header("Content-Type") Content_Type: String,
#Header("x-ncp-apigw-timestamp") timestamp: String,
#Header("x-ncp-iam-access-key") accesskey: String,
#Header("x-ncp-apigw-signature-v2") signature: String,
#Field("type") type: String,
#Field("contentType") contentType: String,
#Field("from") from: String,
#Field("content") content: String,
#Field("messages") messages : List<messages>
): Call<sendsms_Response>
}
data class messages(
var to : String,
var subject : String,
var content : String,
) {
constructor(Number : String) : this(Number, "null", "null")
}
<sendsms_Response.kt>
// DTD Create
data class sendsms_Response(
#SerializedName("statusCode") var statusCode: String,
#SerializedName("statusName") var statusName: String,
#SerializedName("requestId") var requestId: String,
#SerializedName("requestTime") var requestTime: String,
)

The error is caused because your message data cannot be recognized as a JSON and has a problem in the body. Try to fix it in a correct JSON form and I think that will solve your problem.
also you can check out this link for more info:
https://www.ktmbytes.com/send-sms-from-android-app-using-nexmo-and-retrofit

Related

{ "status": "FAIL", "code": "400002", "errorMessage": "Signature for this request is not valid." }

Im trying to get "create order" api from binance pay api to make payments in my kotlin android application. I am following api documentation to build the payload and signature but unfortunately its not working.
Im getting this error : "errorMessage": "Signature for this request is not valid."
Anyone can help?
private fun createSignature(payload: String, secretKey: String): String {
val sha512HMAC = Mac.getInstance("HmacSHA512")
val secretKeySpec =
SecretKeySpec(secretKey.toByteArray(), "HmacSHA512")
sha512HMAC.init(secretKeySpec)
val digest = sha512HMAC.doFinal(payload.toByteArray())
return digest.toHex()
}
val timestamp = System.currentTimeMillis() + clockOffset
val nonceStr = generateNonce()
val body = Gson().toJson(
OrderRequest(
Env("APP"),
merchantTradeNb,
0.02,
"USDT",
Goods("01", "D000", "7876763A3C", "phone", "Good new phone")
)
)
val payload = timestamp.toString() + "\n" + nonceStr + "\n" + body + "\n"
val signature: String = createSignature(
payload.toByteArray(Charsets.UTF_8).toString(), secretKey
).uppercase()
val retrofitData = retrofitBuilder.binanceApi.createOrder(
"application/json", timestamp, nonceStr, certSn, signature, body
)
retrofitData.enqueue(object : Callback<OrderResponse> {
override fun onResponse(
call: Call<OrderResponse>,
response: Response<OrderResponse>,
) {
response.body()
}
override fun onFailure(call: Call<OrderResponse>, t: Throwable) {
}
})
}
interface BinancePayApi {
#POST("/binancepay/openapi/v2/order")
fun createOrder(
#Header("Content-type") contentType: String,
#Header("BinancePay-Timestamp") timestamp: Long,
#Header("BinancePay-Nonce") nonce: String,
#Header("BinancePay-Certificate-SN") apiKey: String,
#Header("BinancePay-Signature") signature: String,
#Body request: String
): Call<OrderResponse>
}
I guess the problem is in the signature as per the documentation you need to do this
String signature = hex(hmac("sha512", payload, secretKey)).toUpperCase()
So, to do this in kotlin you can do it as follows
fun hmac(algorithm: String, data: String, secretKey: String): ByteArray {
val secret = SecretKeySpec(secretKey.toByteArray(), algorithm)
val mac = Mac.getInstance(algorithm)
mac.init(secret)
return mac.doFinal(data.toByteArray())
}
Then you need to convert it to hex so you can create this method (I see that you are using this .toHex() but I don't know your implementation)
fun hex(bytes: ByteArray): String {
val hexChars = "0123456789ABCDEF".toCharArray()
val result = StringBuilder(bytes.size * 2)
for (b in bytes) {
result.append(hexChars[b.toInt().shr(4) and 0x0f])
result.append(hexChars[b.toInt() and 0x0f])
}
return result.toString()
}
Then the only thing you need to do is wrap it up, you can do it as follows :
val signature = hex(hmac("SHA-512", payload, secretKey)).toUpperCase()
I'm wondering if the problem you are having is that you are using HmacSHA512 instead of SHA-512.
I'm reading documentation you posted, and I read this example:
(https://developers.binance.com/docs/binance-pay/app-integration)
String payload = "certSn=317c110ebf7baf641e8f49ab6851331737dbe98541947c53b9dbba27f8c8414f" + "&" + "merchantId=98765987" + "&" + "noncestr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS" + "&" + "prepayId=98765987" + "&"+ "timeStamp=1618302830073";String signature = hex(hmac("sha512", payload, secretKey)).toUpperCase();
And in your createSignature method I don't see this logic.

How to make Retrofit deserialize response to null object when the returned type is not matching the expected?

Retrofit is not deserialising correctly. Because I don't have control over backend I need my code to adapt to it, I need Retrofit to deserialize response to null when it isn't the right type. Instead it creates the object but with null value members. So instead of returning null it returns DefaultResponseResult(success=null, action=null, res=null)
This is my method (I tried fixing it with ? at Call<DefaultResponseResult?>):
#PUT("/SOMETHING/fcmtoken/")
fun registerFCMToken(#Query("fcmToken") fcmToken: String, #Query("driverID") driverID: String, #Query("dtoken") dtoken: String): Call<DefaultResponseResult?>
and in client file:
fun <T> fetch(requestCall: Call<T?>, completion: ((Result<T, Exception>) -> Unit)) {
requestCall.enqueue(object: Callback<T?> {
override fun onResponse(call: Call<T?>, response: Response<T?>) {
val responseData = response.body()
var errorLog: ErrorLog? = response.errorBody()?.let { gson.fromJson(it.charStream(), ErrorLog::class.java) }
//FOR SOME REASON RESPONSE DATA IS NOT NULL BUT IT SHOULD BE
if (responseData == null) {
completion(Failure(APIError(internalError = "Unknown")))
return
}
//THIS PRINTS DefaultResponseResult(success=null, action=null, res=null) unfortunately
printLogs(responseData.toString())
completion(Success(responseData))
}
override fun onFailure(call: Call<T?>, t: Throwable) {
printLogs("Failure")
printLogs(call.request().toString())
printLogs(t.toString())
completion(Failure(APIError(internalError = "Failure")))
}
})
}
DefaultResponseResult:
data class DefaultResponseResult(
val success: String?,
val action: String?,
val res: String?
)
ErrorLog:
data class ErrorLog(
val error: ErrorLogBody
)
data class ErrorLogBody(
val authentication: String? = null
//...
)
Request returns raw response as:
{
"error": "SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry
'ecyNabqKQjSPx-zn6Oshpd:APA91bFS7fGpnmnO_CiYfkQoF0Oo52ODWGRt-M...' for
key 'PRIMARY'" }
And retrofit doesn't recognize it as error body because http code is 200 and that's something I can't change in the backend. I expect it to cast response.body() to null because data type is ErrorLog not DefaultResponseResult, instead it does this (from logcat console):
DefaultResponseResult(success=null, action=null, res=null)
How can I make it return null instead of DefaultResponseResult?

Android generic Gson.fromJson() conversion with coroutine and flow

I am making generic classes for hitting otp api.anybody can use otp section just have to pass request ,Response class and url and all will be done by this otp section.
Please note : this response class can be of different type (for eg: MobileOtpResponse,EmailOtpResponse)
below is the generic OtpClient which takes any request type and returns particular passed ResponseType (for example : Request class passed is OtpRequest ,ResponseType class passed is OtpResponse)
interface OtpClient {
#POST
suspend fun <Request : Any, ResponseType> sendOtp(#Url url: String,
#Body request:#JvmSuppressWildcards Any): #JvmSuppressWildcards ResponseType
}
OtpRequest
data class OtpRequest(#SerializedName("mobile_number") val mobileNumber: String,#SerializedName("app_version") val appVersion: String)
OtpResponse
data class OtpResponse(#SerializedName("status") val status: String = "",
#SerializedName("response") val response: OtpData? = null)
data class OtpData(
#SerializedName("otp_status") val otpStatus: Boolean = false,
#SerializedName("message") val message: String = "",
#SerializedName("error") val error: Int? = null,
#SerializedName("otp_length") val otpLength: Int? = null,
#SerializedName("retry_left") val retryLeft: Int? = null,)
Now i create Repo to call this api this simply use flow and when the data fetch it emits the data
class OtpRepoImpl<out Client : OtpClient>(val client: Client) :OtpRepo {
override fun <Request:Any, ResponseType> sentOtpApi(url: String, request: Request): Flow<ResponseType> {
return flow<ResponseType> {
// exectute API call and map to UI object
val otpResponse = client.sendOtp<Request, ResponseType>(url,request)
emit(otpResponse)
}.flowOn(Dispatchers.IO) // Use the IO thread for this Flow
}
}
this repo is used in viewmodel class
#ExperimentalCoroutinesApi
fun <A : Class<ResponseType>, Request : Any, ResponseType : Any> sendOtp(a: Class<ResponseType>, request: Request, response: ResponseType, url: String) {
viewModelScope.launch {
repo.sentOtpApi<Request, ResponseType>(url, request = request)
.onStart { _uiState.value = OtpState.Loading(true) }
.catch { cause ->
_uiState.value = OtpState.Loading(false)
getResponseFromError<Class<ResponseType>,ResponseType>(cause, response) {
// emit(it)
}
}
.collect {
_uiState.value = OtpState.Loading(false)
_uiState.value = OtpState.Success(it)
}
}
}
as you can see above this sendOtp method is called from the view class and inside this method we use repo.sentOtpApi and pass generic request response type.I get data in catch block coz api is send error otp data in 400 HttpException so i created another method getResponseFromError to get error response it should parse the errorBody response and call this lambda block.
private suspend fun <A : Class<*>, ResponseType : Any> getResponseFromError( cause: Throwable, rp: ResponseType, block: suspend (ResponseType) -> Unit) {
if (cause is HttpException) {
val response = cause.response()
if (response?.code() == 400) {
println("fetching error Response")
val errorResponse = response.errorBody()?.charStream()
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
} else {
println("someOther exception")
}
} else
_uiState.value = OtpState.Error(cause)
}
so here i am facing the problem inside above method
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
This finalErrorResponse is returning LinkedTreeMap instead of ResponseType (in this case its OtpResponse)
i have also tried using Class<*> type like this
val turnsType = object : TypeToken<A>() {}.type
val finalErrorResponse = Gson().fromJson<A>(errorResponse, turnsType)
but its not working.
calling of this sentOtp viewmodel func is like
var classType = OtpResponse::class.java
otpViewModel.sendOtp(a = classType, request = otpRequest, response = OtpResponse() , url =
"http://preprod-api.nykaa.com/user/otp/v2/send-wallet-otp")
[![value in finalErroResponse][1]][1]
[1]: https://i.stack.imgur.com/Holui.png
required: finalErroResponse should be of OtpResponse type because that was passed in sentOtp func
Please help :)

android-kotlin JsonObject JsonArray send request data via POST

I want to send data request via post to server I want to know How can I add data in array
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("detail")
val detail: List<String?>
)
Request
data class CityRequest(
#SerializedName("listCity")
val listCity: List<City?>
)
Response
data class CityResponse(
#SerializedName("code")
val code: String?,
#SerializedName("status")
val status: Boolean?,
#SerializedName("message")
val message: String?
)
API Server
#Headers("Content-Type: application/json")
#POST("city")
suspend fun sendCityContent(#Body listCity: CityRequest?):
Call<CityResponse?>
Connect Service
I don't know how I can add information to this section in question.
private suspend fun sendDataCity(city: List<city?>) {
val retrofit = clientCity
val sendDataToServer = retrofit?.create(CityService::class.java)
val call = sendDataToServer?.sendCityContent(CityRequest(city))
call?.enqueue(object : Callback<CityResponse?> {
override fun onResponse(
call: Call<CityResponse?>, response: Response<CityResponse?>) {
val getResponse = response.body()
Timber.tag("SALE_CITY: ").d("code: %s", getResponse?.code)
Timber.tag("SALE_CITY: ").d("status: %s", getResponse?.status)
Timber.tag("SALE_CITY: ").d("message: %s", getResponse?.message)
}
override fun onFailure(call: Call<CityResponse?>, t: Throwable?) {
t?.printStackTrace()
}
})
}
JSON Simple
{
"city": [
{
"cityId": 1,
"answer": [
"1"
]
},
{
"questionId": 2,
"answer": [
"2.1",
"2.2"
]
}
]}
What do I have to do next?
Can you have a sample add data in array for me?
Things I want
cityId = 1
detail = "1.1", "1.2"
cityId = 2
detail = "2.1", "2.2"
thank you
One issue i can see with your request is the key is different from what you are sending might be different check that. it should be city not listCity as given.
data class CityRequest(
#SerializedName("city")
val city: List<City?>
)
and your city class should have these keys answer which you have mentioned as details
data class City(
#SerializedName("cityId")
val cityId: Int?,
#SerializedName("answer")
val answer: List<String?>
)
I guess you are just sending with wrong keys that might be the reason the server is not accepting the request. make the above change it should work post if you get error.

how to parse data from json

i tried to parse json data, but it kind of weird because it not show the right data but if i tried to called json on browser it has the right data.
so this is how i parse the json data
doAsync {
val url = localhost.getMovie()
val request = okhttp3.Request.Builder().url(url).build()
val client = OkHttpClient()
uiThread {
client.newCall(request).enqueue(object : Callback, okhttp3.Callback {
override fun onResponse(call: okhttp3.Call?, response: okhttp3.Response?) {
val body = response?.body()?.string()
println(body)
uiThread {
val gson = GsonBuilder().create()
val movieFeed = gson.fromJson(body, Movie2Response::class.java)
Log.v("body", ""+body)
Log.v("feed", ""+movieFeed.data)
uiThread {
}
}
}
override fun onFailure(call: okhttp3.Call?, e: IOException) {
println("failed")
}
})
}
}
movie response
class Movie2Response (val data: MutableList<Movie2>)
movie
class Movie2 (
#SerializedName("id")
var movieId: String? = null,
#SerializedName("description")
var synopsis: String? = null,
#SerializedName("release_date")
var release: String? = null,
#SerializedName("poster")
var poster: String? = null,
#SerializedName("genre")
var genre: String? = null,
#SerializedName("title")
var title: String? = null
)
and this is what i got from the json data
V/body: {"data":[{"title":"Aquaman","description":""........
V/feed: [com.mqa.android.moviereview.model.Movie2#7509e04, com.mqa.android.moviereview.model.Movie2#890afed, com.mqa.android.moviereview.model.Movie2#9834e22, com.mqa.android.moviereview.model.Movie2#f02d0b3, com.mqa.android.moviereview.model.Movie2#d3b9670, com.mqa.android.moviereview.model.Movie2#4d55de9, com.mqa.android.moviereview.model.Movie2#cac2a6e, com.mqa.android.moviereview.model.Movie2#94fc50f, com.mqa.android.moviereview.model.Movie2#d9ba99c]
it shows right in body but in the array it show like that. please help what is wrong with it. because i want to show the title data to the spinner
Your code worked pretty well as the log results showed. The real problem is the log function Log.v("feed", ""+movieFeed.data). If you want to show pretty log, you should override the toString() method in Movie2 class by:
Open Movie2 and right click in the editor -> Generate -> then click toString() to override it.
For data class in Kotlin, you can just add data before class keyword.
Everything okey with you data. You just forgot adding default realisation to log this object.
class Movie2(/*your fields*/)
just add data before class. will be something like that
data class Movie2(/*your fields*/)
Kotlin doesn't know ho toString you Movie2. If you wanna default realisation use data class

Categories

Resources