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
+2
View File
@@ -51,6 +51,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-auth:20.4.1' implementation 'com.google.android.gms:play-services-auth:20.4.1'
implementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1' implementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
implementation 'com.google.android.gms:play-services-maps:18.1.0' implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation files('libs/zpdk-release-v3.1.aar')
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
@@ -83,4 +84,5 @@ dependencies {
//Firebase //Firebase
implementation platform('com.google.firebase:firebase-bom:31.2.3') implementation platform('com.google.firebase:firebase-bom:31.2.3')
implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation("com.squareup.okhttp3:okhttp:4.10.0")
} }
Binary file not shown.
+11 -1
View File
@@ -30,7 +30,17 @@
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".Activity.OrderCheckOutActivity" android:name=".Activity.OrderCheckOutActivity"
android:exported="false" /> android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="demozpdk"
android:host="app" />
</intent-filter>
</activity>
<activity <activity
android:name=".Activity.FavoriteFoodActivity" android:name=".Activity.FavoriteFoodActivity"
android:exported="false" /> android:exported="false" />
@@ -1,9 +1,15 @@
package com.capstone.foodify.Activity; package com.capstone.foodify.Activity;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
@@ -17,6 +23,8 @@ import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.widget.NestedScrollView; 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.DistrictWardResponse;
import com.capstone.foodify.Model.GoogleMap.GoogleMapResponse; import com.capstone.foodify.Model.GoogleMap.GoogleMapResponse;
import com.capstone.foodify.R; import com.capstone.foodify.R;
import com.capstone.foodify.ZaloPay.Api.CreateOrder;
import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLng;
import com.google.android.material.textfield.TextInputLayout; 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.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -41,6 +56,10 @@ import java.util.List;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; 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 { public class OrderCheckOutActivity extends AppCompatActivity {
private static final Double LAT_SHOP = 16.0500622; private static final Double LAT_SHOP = 16.0500622;
@@ -54,7 +73,7 @@ public class OrderCheckOutActivity extends AppCompatActivity {
EditText edt_address; EditText edt_address;
Spinner spn_list_address, spn_district, spn_ward; Spinner spn_list_address, spn_district, spn_ward;
ConstraintLayout manual_input_address_layout; 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; RadioButton list_address_input, auto_detect_location, manual_input_address, radio_button_selected, take_food_from_shop;
RadioGroup address_option; RadioGroup address_option;
ConstraintLayout progress_layout; ConstraintLayout progress_layout;
@@ -97,6 +116,7 @@ public class OrderCheckOutActivity extends AppCompatActivity {
progress_layout = findViewById(R.id.progress_layout); progress_layout = findViewById(R.id.progress_layout);
txt_distance = findViewById(R.id.txt_distance); txt_distance = findViewById(R.id.txt_distance);
txt_ship_cost = findViewById(R.id.txt_ship_cost); txt_ship_cost = findViewById(R.id.txt_ship_cost);
btn_ZaloPay = findViewById(R.id.btnZaloPay);
if(getIntent() != null){ if(getIntent() != null){
@@ -104,6 +124,13 @@ public class OrderCheckOutActivity extends AppCompatActivity {
txt_total.setText(Common.changeCurrencyUnit(total)); 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 //Show list food in basket
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false);
recyclerView.setLayoutManager(linearLayoutManager); recyclerView.setLayoutManager(linearLayoutManager);
@@ -170,6 +197,8 @@ public class OrderCheckOutActivity extends AppCompatActivity {
disableAllInputAddressOption(); disableAllInputAddressOption();
getDistanceAndCalculateShipCost(finalAddress); getDistanceAndCalculateShipCost(finalAddress);
progress_layout.setVisibility(View.GONE);
} }
} }
}); });
@@ -181,6 +210,92 @@ public class OrderCheckOutActivity extends AppCompatActivity {
onBackPressed(); 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) { private void getDistanceAndCalculateShipCost(String address) {
@@ -510,4 +625,10 @@ public class OrderCheckOutActivity extends AppCompatActivity {
return view; 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));
}
}