Add Zalo payment method

This commit is contained in:
Nezumi
2023-03-23 21:26:31 +07:00
parent 01a900892e
commit c166919527
10 changed files with 467 additions and 2 deletions
@@ -1,9 +1,15 @@
package com.capstone.foodify.Activity;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
@@ -17,6 +23,8 @@ import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.widget.NestedScrollView;
@@ -31,8 +39,15 @@ import com.capstone.foodify.Model.Address;
import com.capstone.foodify.Model.DistrictWardResponse;
import com.capstone.foodify.Model.GoogleMap.GoogleMapResponse;
import com.capstone.foodify.R;
import com.capstone.foodify.ZaloPay.Api.CreateOrder;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.material.textfield.TextInputLayout;
import com.thecode.aestheticdialogs.AestheticDialog;
import com.thecode.aestheticdialogs.DialogStyle;
import com.thecode.aestheticdialogs.DialogType;
import com.thecode.aestheticdialogs.OnDialogClickListener;
import org.json.JSONObject;
import java.text.DecimalFormat;
import java.util.ArrayList;
@@ -41,6 +56,10 @@ import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import vn.zalopay.sdk.Environment;
import vn.zalopay.sdk.ZaloPayError;
import vn.zalopay.sdk.ZaloPaySDK;
import vn.zalopay.sdk.listeners.PayOrderListener;
public class OrderCheckOutActivity extends AppCompatActivity {
private static final Double LAT_SHOP = 16.0500622;
@@ -54,7 +73,7 @@ public class OrderCheckOutActivity extends AppCompatActivity {
EditText edt_address;
Spinner spn_list_address, spn_district, spn_ward;
ConstraintLayout manual_input_address_layout;
Button change_address_button, confirm_address_button;
Button change_address_button, confirm_address_button, btn_ZaloPay;
RadioButton list_address_input, auto_detect_location, manual_input_address, radio_button_selected, take_food_from_shop;
RadioGroup address_option;
ConstraintLayout progress_layout;
@@ -97,6 +116,7 @@ public class OrderCheckOutActivity extends AppCompatActivity {
progress_layout = findViewById(R.id.progress_layout);
txt_distance = findViewById(R.id.txt_distance);
txt_ship_cost = findViewById(R.id.txt_ship_cost);
btn_ZaloPay = findViewById(R.id.btnZaloPay);
if(getIntent() != null){
@@ -104,6 +124,13 @@ public class OrderCheckOutActivity extends AppCompatActivity {
txt_total.setText(Common.changeCurrencyUnit(total));
}
StrictMode.ThreadPolicy policy = new
StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
// ZaloPay SDK Init
ZaloPaySDK.init(2553, Environment.SANDBOX);
//Show list food in basket
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(linearLayoutManager);
@@ -170,6 +197,8 @@ public class OrderCheckOutActivity extends AppCompatActivity {
disableAllInputAddressOption();
getDistanceAndCalculateShipCost(finalAddress);
progress_layout.setVisibility(View.GONE);
}
}
});
@@ -181,6 +210,92 @@ public class OrderCheckOutActivity extends AppCompatActivity {
onBackPressed();
}
});
initZaloPaymentMethod();
}
private void initZaloPaymentMethod(){
// handle CreateOrder
btn_ZaloPay.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@SuppressLint("SetTextI18n")
@Override
public void onClick(View v) {
//Check address is null or not
if(finalAddress != null){
CreateOrder orderApi = new CreateOrder();
try {
JSONObject data = orderApi.createOrder(String.valueOf((int) total));
String code = data.getString("return_code");
if (code.equals("1")) {
//Success create order
String token = data.getString("zp_trans_token");
ZaloPaySDK.getInstance().payOrder(OrderCheckOutActivity.this, token, "demozpdk://app", new PayOrderListener() {
@Override
public void onPaymentSucceeded(final String transactionId, final String transToken, final String appTransID) {
runOnUiThread(new Runnable() {
@Override
public void run() {
new AestheticDialog.Builder(OrderCheckOutActivity.this, DialogStyle.FLAT, DialogType.SUCCESS)
.setTitle("Thành công!")
.setMessage("Bạn đã thanh toán thành công cho đơn hàng #" + transactionId)
.setCancelable(true)
.setOnClickListener(new OnDialogClickListener() {
@Override
public void onClick(@NonNull AestheticDialog.Builder builder) {
Common.LIST_BASKET_FOOD.clear();
startActivity(new Intent(OrderCheckOutActivity.this, MainActivity.class));
finish();
}
})
.show();
}
});
}
@Override
public void onPaymentCanceled(String zpTransToken, String appTransID) {
new AestheticDialog.Builder(OrderCheckOutActivity.this, DialogStyle.RAINBOW, DialogType.INFO)
.setTitle("Thông báo!")
.setMessage("Huỷ thanh toán thành công!")
.setCancelable(true)
.show();
}
@Override
public void onPaymentError(ZaloPayError zaloPayError, String zpTransToken, String appTransID) {
new AestheticDialog.Builder(OrderCheckOutActivity.this, DialogStyle.FLAT, DialogType.ERROR)
.setTitle("Thanh toán chưa thành công!")
.setMessage("Xin vui lòng thử lại!")
.setCancelable(true)
.setOnClickListener(new OnDialogClickListener() {
@Override
public void onClick(@NonNull AestheticDialog.Builder builder) {
Common.LIST_BASKET_FOOD.clear();
startActivity(new Intent(OrderCheckOutActivity.this, MainActivity.class));
finish();
}
})
.show();
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
Toast.makeText(OrderCheckOutActivity.this, "Bạn chưa chọn địa chỉ!", Toast.LENGTH_SHORT).show();
}
}
});
}
private void getDistanceAndCalculateShipCost(String address) {
@@ -510,4 +625,10 @@ public class OrderCheckOutActivity extends AppCompatActivity {
return view;
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
ZaloPaySDK.getInstance().onResult(intent);
}
}
@@ -0,0 +1,70 @@
package com.capstone.foodify.ZaloPay.Api;
import com.capstone.foodify.ZaloPay.Constant.AppInfo;
import com.capstone.foodify.ZaloPay.Helper.Helpers;
import org.json.JSONObject;
import java.util.Date;
import okhttp3.FormBody;
import okhttp3.RequestBody;
public class CreateOrder {
private class CreateOrderData {
String AppId;
String AppUser;
String AppTime;
String Amount;
String AppTransId;
String EmbedData;
String Items;
String BankCode;
String Description;
String Mac;
private CreateOrderData(String amount) throws Exception {
long appTime = new Date().getTime();
AppId = String.valueOf(AppInfo.APP_ID);
AppUser = "Android_Demo";
AppTime = String.valueOf(appTime);
Amount = amount;
AppTransId = Helpers.getAppTransId();
EmbedData = "{}";
Items = "[]";
BankCode = "zalopayapp";
Description = "Hoá đơn thanh toán cho đơn hàng #" + Helpers.getAppTransId();
String inputHMac = String.format("%s|%s|%s|%s|%s|%s|%s",
this.AppId,
this.AppTransId,
this.AppUser,
this.Amount,
this.AppTime,
this.EmbedData,
this.Items);
Mac = Helpers.getMac(AppInfo.MAC_KEY, inputHMac);
}
}
public JSONObject createOrder(String amount) throws Exception {
CreateOrderData input = new CreateOrderData(amount);
RequestBody formBody = new FormBody.Builder()
.add("app_id", input.AppId)
.add("app_user", input.AppUser)
.add("app_time", input.AppTime)
.add("amount", input.Amount)
.add("app_trans_id", input.AppTransId)
.add("embed_data", input.EmbedData)
.add("item", input.Items)
.add("bank_code", input.BankCode)
.add("description", input.Description)
.add("mac", input.Mac)
.build();
JSONObject data = HttpProvider.sendPost(AppInfo.URL_CREATE_ORDER, formBody);
return data;
}
}
@@ -0,0 +1,58 @@
package com.capstone.foodify.ZaloPay.Api;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.TlsVersion;
public class HttpProvider {
public static JSONObject sendPost(String URL, RequestBody formBody) {
JSONObject data = new JSONObject();
try {
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.callTimeout(5000, TimeUnit.MILLISECONDS)
.build();
Request request = new Request.Builder()
.url(URL)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.post(formBody)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
Log.println(Log.ERROR, "BAD_REQUEST", response.body().string());
data = null;
} else {
data = new JSONObject(response.body().string());
}
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return data;
}
}
@@ -0,0 +1,7 @@
package com.capstone.foodify.ZaloPay.Constant;
public class AppInfo {
public static final int APP_ID = 2553;
public static final String MAC_KEY = "PcY4iZIKFCIdgZvA6ueMcMHHUbRLYjPL";
public static final String URL_CREATE_ORDER = "https://sb-openapi.zalopay.vn/v2/create";
}
@@ -0,0 +1,93 @@
package com.capstone.foodify.ZaloPay.Helper.HMac;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.LinkedList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class HMacUtil {
public final static String HMACMD5 = "HmacMD5";
public final static String HMACSHA1 = "HmacSHA1";
public final static String HMACSHA256 = "HmacSHA256";
public final static String HMACSHA512 = "HmacSHA512";
public final static Charset UTF8CHARSET = StandardCharsets.UTF_8;
public final static LinkedList<String> HMACS = new LinkedList<String>(Arrays.asList("UnSupport", "HmacSHA256", "HmacMD5", "HmacSHA384", "HMacSHA1", "HmacSHA512"));
// @formatter:on
private static byte[] HMacEncode(final String algorithm, final String key, final String data) {
Mac macGenerator = null;
try {
macGenerator = Mac.getInstance(algorithm);
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
macGenerator.init(signingKey);
} catch (Exception ex) {
}
if (macGenerator == null) {
return null;
}
byte[] dataByte = null;
try {
dataByte = data.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
}
return macGenerator.doFinal(dataByte);
}
/**
* Calculating a message authentication code (MAC) involving a cryptographic
* hash function in combination with a secret cryptographic key.
*
* The result will be represented base64-encoded string.
*
* @param algorithm A cryptographic hash function (such as MD5 or SHA-1)
*
* @param key A secret cryptographic key
*
* @param data The message to be authenticated
*
* @return Base64-encoded HMAC String
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static String HMacBase64Encode(final String algorithm, final String key, final String data) {
byte[] hmacEncodeBytes = HMacEncode(algorithm, key, data);
if (hmacEncodeBytes == null) {
return null;
}
return Base64.getEncoder().encodeToString(hmacEncodeBytes);
}
/**
* Calculating a message authentication code (MAC) involving a cryptographic
* hash function in combination with a secret cryptographic key.
*
* The result will be represented hex string.
*
* @param algorithm A cryptographic hash function (such as MD5 or SHA-1)
*
* @param key A secret cryptographic key
*
* @param data The message to be authenticated
*
* @return Hex HMAC String
*/
public static String HMacHexStringEncode(final String algorithm, final String key, final String data) {
byte[] hmacEncodeBytes = HMacEncode(algorithm, key, data);
if (hmacEncodeBytes == null) {
return null;
}
return HexStringUtil.byteArrayToHexString(hmacEncodeBytes);
}
}
@@ -0,0 +1,69 @@
package com.capstone.foodify.ZaloPay.Helper.HMac;
import java.util.Locale;
public class HexStringUtil {
private static final byte[] HEX_CHAR_TABLE = {
(byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
// @formatter:on
/**
* Convert a byte array to a hexadecimal string
*
* @param raw
* A raw byte array
*
* @return Hexadecimal string
*/
public static String byteArrayToHexString(byte[] raw) {
byte[] hex = new byte[2 * raw.length];
int index = 0;
for (byte b : raw) {
int v = b & 0xFF;
hex[index++] = HEX_CHAR_TABLE[v >>> 4];
hex[index++] = HEX_CHAR_TABLE[v & 0xF];
}
return new String(hex);
}
/**
* Convert a hexadecimal string to a byte array
*
* @param hex
* A hexadecimal string
*
* @return The byte array
*/
public static byte[] hexStringToByteArray(String hex) {
String hexstandard = hex.toLowerCase(Locale.ENGLISH);
int sz = hexstandard.length() / 2;
byte[] bytesResult = new byte[sz];
int idx = 0;
for (int i = 0; i < sz; i++) {
bytesResult[i] = (byte) (hexstandard.charAt(idx));
++idx;
byte tmp = (byte) (hexstandard.charAt(idx));
++idx;
if (bytesResult[i] > HEX_CHAR_TABLE[9]) {
bytesResult[i] -= ((byte) ('a') - 10);
} else {
bytesResult[i] -= (byte) ('0');
}
if (tmp > HEX_CHAR_TABLE[9]) {
tmp -= ((byte) ('a') - 10);
} else {
tmp -= (byte) ('0');
}
bytesResult[i] = (byte) (bytesResult[i] * 16 + tmp);
}
return bytesResult;
}
}
@@ -0,0 +1,35 @@
package com.capstone.foodify.ZaloPay.Helper;
import android.annotation.SuppressLint;
import com.capstone.foodify.ZaloPay.Helper.HMac.HMacUtil;
import org.jetbrains.annotations.NotNull;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
public class Helpers {
private static int transIdDefault = 1;
@NotNull
@SuppressLint("DefaultLocale")
public static String getAppTransId() {
if (transIdDefault >= 100000) {
transIdDefault = 1;
}
transIdDefault += 1;
@SuppressLint("SimpleDateFormat") SimpleDateFormat formatDateTime = new SimpleDateFormat("yyMMdd_hhmmss");
String timeString = formatDateTime.format(new Date());
return String.format("%s%06d", timeString, transIdDefault);
}
@NotNull
public static String getMac(@NotNull String key, @NotNull String data) throws NoSuchAlgorithmException, InvalidKeyException {
return Objects.requireNonNull(HMacUtil.HMacHexStringEncode(HMacUtil.HMACSHA256, key, data));
}
}