Notify users of Alipay+ availability
When a user travels abroad, the Mobile Payment Provider (MPP) can proactively notify the user of Alipay+ availability if you (the MPP) detect that the user is abroad and Alipay+ is available in the user's region.
This topic introduces how to check Alipay+ availability and send notifications when Alipay+ is available.
Workflow
The following figure illustrates the workflow of the periodic task which is to check whether the user is abroad in a location where Alipay+ is available and send notifications to users of Alipay+ availability.
Figure 1: Notify users of Alipay+ availability
- The MPP app starts the periodic task by calling the startMonitor API. See Sample code for more information. (Step 1)
- The MPP app checks whether the number of attempts to check Alipay+ availability exceeds the daily limit. The MPP can set the maximum limit for a natural day. (Step 2)
- (Optional) If the attempt is within the maximum number of attempts, the MPP app can obtain the user's location based on its own processing logic or calls the getCurrentRegion API from Alipay+ Client SDK to obtain the user's location. (Steps 3-5)
- If the attempt is within the maximum number of attempts, the MPP app calls the isAlipayplusSupportedRegion API to check whether Alipay+ is available in the user's current location. (Steps 6-7)
- The registered listener in the startMonitor API notifies the MPP app when the user is abroad and in a region where the Alipay+ is available. Then, the MPP app decides the notification method, such as in-app notification or SMS message, and the frequency for notifying the user of Alipay+ availability. (Steps 8-9)
Sample code
Android sample code
copy
public interface IAlipayPlusRegionListener {
void didEnterSupportedAlipayPlusRegion();
}
copy
public class AlipayPlusRegionHelper {
private static volatile AlipayPlusRegionHelper instance;
private final long crossBorderDetectedRefreshTime = 2 * 60 * 60 * 1000;
private final int maxDetectedCount = 10;
private Handler crossingBorderDetectHandler;
private IAlipayPlusRegionListener listener;
private boolean hasStartMonitor = false;
public final String KEY_CURRENT_DETECTED_COUNT = "currentDetectedCount";
public final String KEY_LAST_DETECTED_TIME = "lastDetectedTime";
private final String MCC_STORAGE_NAME = "mccAlipayPlus";
public static AlipayPlusRegionHelper getInstance() {
if (instance == null) {
synchronized (AlipayPlusRegionHelper.class) {
if (instance == null) {
instance = new AlipayPlusRegionHelper();
}
}
}
return instance;
}
/**
* start monitoring whether A+ is available and then notifying
*
* @param listener IAlipayPlusRegionListener
*/
public synchronized void startMonitor(Context context, IAlipayPlusRegionListener listener) {
if (hasStartMonitor) {
return;
}
hasStartMonitor = true;
this.listener = listener;
if (crossingBorderDetectHandler == null) {
HandlerThread thread = new HandlerThread("AlipayPlus-CrossingBorderDetected-Thread");
thread.start();
crossingBorderDetectHandler = new Handler(thread.getLooper());
}
//delay 6s ensuring getting latest regionRule
scheduleNextDetectedTask(context, 6 * 1000);
}
/**
* stop monitoring whether A+ is available and then notifying
*/
public void stopMonitor() {
hasStartMonitor = false;
listener = null;
if (crossingBorderDetectHandler != null) {
crossingBorderDetectHandler.removeCallbacksAndMessages(null);
crossingBorderDetectHandler = null;
}
}
private void scheduleNextDetectedTask(Context context, long delayTimeMs) {
if (context == null) {
return;
}
crossingBorderDetectHandler.postDelayed(() -> {
long lastRefreshTime = getLong(context, KEY_LAST_DETECTED_TIME, 0);
long currentTime = System.currentTimeMillis();
boolean isSameDay = isSameDate(lastRefreshTime, currentTime);
if (!isSameDay) {
putInt(context, KEY_CURRENT_DETECTED_COUNT, 0);
}
int currentMaxCount = getInt(context, KEY_CURRENT_DETECTED_COUNT, 0);
if (currentMaxCount < maxDetectedCount) {
currentMaxCount++;
putInt(context, KEY_CURRENT_DETECTED_COUNT, currentMaxCount);
putLong(context, KEY_LAST_DETECTED_TIME, System.currentTimeMillis());
String region = AlipayPlusClient.getInstance().getCurrentRegion(context);
boolean isAvailable = AlipayPlusClient.getInstance().isAlipayPlusSupportedRegion(context, region);
if (isAvailable) {
//notify bizPart crossing border and A+ supported
if (listener != null) {
//back to main thread
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> listener.didEnterSupportedAlipayPlusRegion());
}
}
}
//prepare next task
scheduleNextDetectedTask(context, crossBorderDetectedRefreshTime);
}, delayTimeMs);
}
private boolean isSameDate(long time1, long time2) {
boolean result = false;
try {
Calendar calendar1 = Calendar.getInstance();
Calendar calendar2 = Calendar.getInstance();
@SuppressLint("SimpleDateFormat") SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dataStr1 = df.format(time1);
String dataStr2 = df.format(time2);
java.util.Date date1 = df.parse(dataStr1);
java.util.Date date2 = df.parse(dataStr2);
if (date1 != null && date2 != null) {
calendar1.setTime(date1);
calendar2.setTime(date2);
result = (calendar1.get(Calendar.ERA) == calendar2.get(Calendar.ERA)) && (calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)) && (calendar1.get(Calendar.DAY_OF_YEAR) == calendar2.get(Calendar.DAY_OF_YEAR));
}
} catch (Exception ignored) {
}
return result;
}
private void putLong(Context context, String key, long value) {
context.getSharedPreferences(MCC_STORAGE_NAME, Context.MODE_PRIVATE).edit().putLong(key, value).apply();
}
private Long getLong(Context context, String key, long defaultValue) {
return context.getSharedPreferences(MCC_STORAGE_NAME, Context.MODE_PRIVATE).getLong(key, defaultValue);
}
private void putInt(Context context, String key, int value) {
context.getSharedPreferences(MCC_STORAGE_NAME, Context.MODE_PRIVATE).edit().putInt(key, value).apply();
}
private int getInt(Context context, String key, int defaultValue) {
return context.getSharedPreferences(MCC_STORAGE_NAME, Context.MODE_PRIVATE).getInt(key, defaultValue);
}
}
iOS sample code
copy
@protocol IAlipayPlusRegionListenerProtocol <NSObject>
- (void)didEnterSupportedAlipayPlusRegion;
@end
@interface AlipayPlusRegionHelper : NSObject
+ (instancetype)shared;
/// start monitoring whether A+ is available and then notifying
/// - Parameter listenter: IAlipayPlusRegionListenerProtocol
- (void)startMonitor:(id<IAlipayPlusRegionListenerProtocol>)listenter;
/// stop monitoring whether A+ is available
- (void)stopMonitor;
@end
copy
#import "AlipayPlusRegionHelper.h"
#import <MPPAlipayPlusClient/MPPAlipayPlusClient.h>
@interface AlipayPlusRegionHelper ()
@property (nonatomic, strong) NSTimer *detectedTimer;
@property (nonatomic, strong, nullable) id<IAlipayPlusRegionListenerProtocol> listenter;
@end
@implementation AlipayPlusRegionHelper
NSString * const maxDetectedCount = @"maxDetectedCount";
NSString * const lastDetectedCount = @"lastDetectedCount";
NSInteger const MAX_DETECTED_COUNT = 10;
NSInteger const DETECTED_REFRESH_TIME = 2 * 60 * 60;
+ (instancetype)shared {
static AlipayPlusRegionHelper *helper = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
helper = [AlipayPlusRegionHelper new];
});
return helper;
}
#pragma mark start monitoring whether A+ is available and then notifying
- (void)startMonitor:(id<IAlipayPlusRegionListenerProtocol>)listenter {
if (listenter == nil || _detectedTimer.isValid) {
return;
}
_listenter = listenter;
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf scheduledDetectedTask];
weakSelf.detectedTimer = [NSTimer timerWithTimeInterval:DETECTED_REFRESH_TIME target:self selector:@selector(scheduledDetectedTask) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:weakSelf.detectedTimer forMode:NSRunLoopCommonModes];
});
}
#pragma mark stop monitoring whether A+ is available
- (void)stopMonitor {
[_detectedTimer invalidate];
_detectedTimer = nil;
_listenter = nil;
}
- (void)scheduledDetectedTask {
NSDate *lastDate = [[NSUserDefaults standardUserDefaults] objectForKey:lastDetectedCount];
bool isSameDay = [self isSameDay:lastDate date2:[NSDate date]];
if (!isSameDay) {
[[NSUserDefaults standardUserDefaults] setInteger:0 forKey:maxDetectedCount];
}
NSInteger currentCount = [[NSUserDefaults standardUserDefaults] integerForKey:maxDetectedCount];
if (currentCount < MAX_DETECTED_COUNT) {
currentCount++;
[[NSUserDefaults standardUserDefaults] setInteger:currentCount forKey:maxDetectedCount];
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:lastDetectedCount];
NSString *region = [MPPAlipayPlusClient.shared getCurrentRegion];
bool isAvailable = [MPPAlipayPlusClient.shared isAlipayPlusSupportedRegion:region];
if (isAvailable && self.listenter != nil) {
if ([self.listenter respondsToSelector:@selector(didEnterSupportedAlipayPlusRegion)]) {
[self.listenter didEnterSupportedAlipayPlusRegion];
}
}
}
}
- (BOOL)isAlipayPlusAvailable:(NSString *)currentValue localValue:(NSArray *)localValue supportedAlipayPlusValue:(NSArray *)supportedAlipayPlusValue{
if (currentValue.length == 0 || localValue.count == 0 || supportedAlipayPlusValue.count == 0) {
return FALSE;
}
for (NSString *item in localValue) {
if ([currentValue.uppercaseString isEqualToString:item.uppercaseString]) {
return FALSE;
}
}
for (NSString *item in supportedAlipayPlusValue) {
if ([currentValue.uppercaseString isEqualToString:item.uppercaseString]) {
return TRUE;
}
}
return FALSE;
}
- (BOOL)isSameDay:(NSDate*)date1 date2:(NSDate*)date2 {
if (date1 == nil || date2 == nil) {
return false;
}
NSCalendar* calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents* comp1 = [calendar components:unitFlags fromDate:date1];
NSDateComponents* comp2 = [calendar components:unitFlags fromDate:date2];
return [comp1 day] == [comp2 day] &&
[comp1 month] == [comp2 month] &&
[comp1 year] == [comp2 year];
}
@end