Why do I not get all Channel items visible in Android TV provider database? - android-tv

First of all, this is the /data/data/com.android.providers.tv/databases/tv.db
Then in my code I applied this code snippet to retrieve the channels:
TvInputManager tv = (TvInputManager)getApplicationContext().getSystemService(Context.TV_INPUT_SERVICE);
List<TvInputInfo> list = tv.getTvInputList();
ContentResolver cr = getContentResolver();
Iterator<TvInputInfo> it = list.iterator();
while(it.hasNext()) {
TvInputInfo aux = it.next();
Uri uri = TvContract.buildChannelsUriForInput(aux.getId());
Cursor cur = cr.query(uri, projection, null, null ,null);
cur.moveToFirst();
do {
val channel = Channel.fromCursor(channelCursor)
Log.d("Log", "New channel ${channel.id} : ${channel.displayName} : ${channel.packageName}"}
} while (channelCursor.moveToNext() && channelCursor.isLast.not())
}
Unfortunately, I get as input_id for TvInputInfo aux only
com.google.android.videos/.tv.usecase.tvinput.playback.TvInputService
thus my cursor returns only 1 Channel with _id = 4.
Despite, I did neither got the input_id's _id=22 for amazon nor _id=25 for netflix, as listed in the screenshot above, I would like to get all above shown 18 Channels.
How can I query all Channels, also those with an empty input_id?

You get restricted by TvContract.buildChannelsUriForInput(aux.getId()); what depends on input_id. When you take TvContractCompat.Channels.CONTENT_URI for Uri, you will get:
Uri uri = TvContractCompat.Channels.CONTENT_URI;
Cursor cur = getContentResolver().query(uri, projection, null, null ,null);
cur.moveToFirst();
do {
val channel = Channel.fromCursor(channelCursor)
Log.d("Log", "New channel ${channel.id} : ${channel.displayName} : ${channel.packageName}"}
} while (channelCursor.moveToNext())

Related

Cannot get Realm result for objects filtered by the latest (nsdate) value of a property of a collection property swift (the example is clearer)

I Have the following model
class Process: Object {
#objc dynamic var processID:Int = 1
let steps = List<Step>()
}
class Step: Object {
#objc private dynamic var stepCode: Int = 0
#objc dynamic var stepDateUTC: Date? = nil
var stepType: ProcessStepType {
get {
return ProcessStepType(rawValue: stepCode) ?? .created
}
set {
stepCode = newValue.rawValue
}
}
}
enum ProcessStepType: Int { // to review - real value
case created = 0
case scheduled = 1
case processing = 2
case paused = 3
case finished = 4
}
A process can start, processing , paused , resume (to be in step processing again), pause , resume again,etc. the current step is the one with the latest stepDateUTC
I am trying to get all Processes, having for last step ,a step of stepType processing "processing ", ie. where for the last stepDate, stepCode is 2 .
I came with the following predicate... which doesn't work. Any idea of the right perform to perform such query ?
my best trial is the one. Is it possible to get to this result via one realm query .
let processes = realm.objects(Process.self).filter(NSPredicate(format: "ANY steps.stepCode = 2 AND NOT (ANY steps.stepCode = 4)")
let ongoingprocesses = processes.filter(){$0.steps.sorted(byKeyPath: "stepDateUTC", ascending: false).first!.stepType == .processing}
what I hoped would work
NSPredicate(format: "steps[LAST].stepCode = \(TicketStepType.processing.rawValue)")
I understand [LAST] is not supported by realm (as per the cheatsheet). but is there anyway around I could achieve my goal through a realm query?
There are a few ways to approach this and it doesn't appear the date property is relevant because lists are stored in sequential order (as long as they are not altered), so the last element in the List was added last.
This first piece of code will filter for processes where the last element is 'processing'. I coded this long-handed so the flow is more understandable.
let results = realm.objects(Process.self).filter { p in
let lastIndex = p.steps.count - 1
let step = p.steps[lastIndex]
let type = step.stepType
if type == .processing {
return true
}
return false
}
Note that Realm objects are lazily loaded - which means thousands of objects have a low memory impact. By filtering using Swift, the objects are filtered in memory so the impact is more significant.
The second piece of code is what I would suggest as it makes filtering much simpler, but would require a slight change to the Process model.
class Process: Object {
#objc dynamic var processID:Int = 1
let stepHistory = List<Step>() //RENAMED: the history of the steps
#objc dynamic var name = ""
//ADDED: new property tracks current step
#objc dynamic var current_step = ProcessStepType.created.index
}
My thought here is that the Process model keeps a 'history' of steps that have occurred so far, and then what the current_step is.
I also modified the ProcessStepType enum to make it more filterable friendly.
enum ProcessStepType: Int { // to review - real value
case created = 0
case scheduled = 1
case processing = 2
case paused = 3
case finished = 4
//this is used when filtering
var index: Int {
switch self {
case .created:
return 0
case .scheduled:
return 1
case .processing:
return 2
case .paused:
return 3
case .finished:
return 4
}
}
}
Then to return all processes where the last step in the list is 'processing' here's the filter
let results2 = realm.objects(Process.self).filter("current_step == %#", ProcessStepType.processing.index)
The final thought is to add some code to the Process model so when a step is added to the list, the current_step var is also updated. Coding that is left to the OP.

Initial sync and duplicate objects with same primary key inside Realm Mobile Platform

I'm trying to turn on realm sync on device which already contains some data, that already persists on server. When new user connects to realm, it should merge local realm data with synced realm data. But this code is launched before initial sync happens. Since there is no data from server is received yet, app creates some records in synchronized realm. When sync finishes I see same data twice. Records I've just created and data fetched from server. With same primary key.
See code below for an example:
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:self.realmURL];
NSError *error = nil;
RLMRealm *newRealm = [RLMRealm realmWithConfiguration:config error:&error];
if(newRealm != nil && error == nil)
{
[newRealm beginWriteTransaction];
for(ModelFolder *folder in [ModelFolder allObjectsInRealm:curRealm])
{
ModelFolder *newFolder = [ModelFolder objectInRealm:newRealm forPrimaryKey:folder.uuid];
if(newFolder == nil)
[ModelFolder createInRealm:newRealm withValue:folder];
}
[newRealm commitWriteTransaction];
}
Is there a way to detect, that realm is completed initial sync?
UPD: Few more details.
ModelFolder contains #property RLMArray<ModelBookmark *><ModelBookmark> *bookmarks; And when I create Folder, that equals some folder that will be fetched in a few seconds they merged correctly. But. Bookmarks inside Folder object is not deduplicated and we get something like this:
RLMResults <0x802082d0> (
[0] ModelFolder {
uuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
bookmarks = RLMArray <0x806c78d0> (
[0] ModelBookmark {
uuid = C752FCEB-65CB-47C8-8CF4-6CA44C119ECC;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
folderUuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
longitude = 27.54834598813616;
latitude = 53.91333128839566;
mapZoom = 11.73785983313041;
category = 0;
visible = 1;
},
[1] ModelBookmark {
uuid = C752FCEB-65CB-47C8-8CF4-6CA44C119ECC;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
folderUuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
longitude = 27.54834598813616;
latitude = 53.91333128839566;
mapZoom = 11.73785983313041;
category = 0;
visible = 1;
}
);
tracks = RLMArray <0x806fb120> (
);
opened = 1;
}
)
Unfortunately merging of the ordered lists is not supported currently (until https://github.com/realm/realm-core/issues/1206 is implemented). For now you have to manually deduplicate list items, you can use the same workaround we use in RealmTasks app, see https://github.com/realm/RealmTasks/pull/180 for implementation details.

Game Maker Studio: DoSet :: Invalid comparison type

___________________________________________
############################################################################################
FATAL ERROR in
action number 4
of Create Event
for object eng_Global:
DoSet :: Invalid comparison type
at gml_Script_Data_Load (line 1) - ///Data_Load()
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_Data_Load (line 1)
called from - gml_Object_eng_Global_CreateEvent_4 (line 60) - Data_Load();
I get this error on a comment, not an actual if statement, I can't bypass this without commenting out Data_Load(), which is what loads the users' data.
I recently updated to version 1.4.1567, maybe that is a bug in this version.
I should state that "Connected" and "Guest" variables are both integers (boolean) and do not get set to string at any point in the code.
Here is the Data_Load() script:
///Data_Load()
if (Connected && !Guest) {
ini_open(User_Name+"_NSD_Temp.ini");
// Base Statistics
Level = ini_read_real("Statistics","Level",Level);
Exp_Total = ini_read_real("Statistics","Experience_Total",Exp_Total);
Exp = ini_read_real("Statistics","Experience",Exp);
Exp_Left = ini_read_real("Statistics","Experience_Left",Exp_Left);
Exp_Max = ceil(Level*5);
Gold = ini_read_real("Statistics","Gold",Gold);
Gold_Total = ini_read_real("Statistics","Gold_Total",Gold_Total);
Karma = ini_read_real("Statistics","Karma",Karma);
Karma_Total = ini_read_real("Statistics","Karma_Total",Karma_Total);
Highscore = ini_read_real("Statistics","Highscore",Highscore);
Weapons_Inv_Length = ini_read_real("Statistics","Weapons_Inv_Length",Weapons_Inv_Length);
Stones_Inv_Length = ini_read_real("Statistics","Stones_Inv_Length",Stones_Inv_Length);
Stone_Slots_Owned = ini_read_real("Statistics","Stones_Slots_Owned",Stones_Slots_Owned);
// Game
Ninja_Name = ini_read_string("Game","Ninja_Name",Ninja_Name);
Ninja_Level = ini_read_real("Game","Ninja_Level",Ninja_Level);
Ninja_Health = ini_read_real("Game","Ninja_Health",Ninja_Health);
Ninja_Health_Max = ini_read_real("Game","Ninja_Health_Max",Ninja_Health_Max);
Ninja_Health_Regen_Upgrade = ini_read_real("Game","Ninja_Health_Regen_Upgrade",Ninja_Health_Regen_Upgrade);
Ninja_Health_Regen = Ninja_Health_Base*(Ninja_Health_Regen_Upgrade)/room_speed;
Ninja_Weapon = ini_read_real("Game","Ninja_Weapon",Ninja_Weapon);
Ninja_Colour = ini_read_real("Game","Ninja_Colour",Ninja_Colour);
Ninja_Power = ini_read_real("Game","Ninja_Power",Ninja_Power);
Ninja_Max_Speed = ini_read_real("Game","Ninja_Max_Speed",Ninja_Max_Speed);
Ninja_Attack_Speed = ini_read_real("Game","Ninja_Attack_Speed",Ninja_Attack_Speed);
// Weapons Inventory
for (i=0; i<Weapons_Inv_Length; i++) {
Weapons_Inv[i,0] = i;
Weapons_Inv[i,1] = ini_read_real("Weapons Inventory","Inv_Slot_"+string(i),0);
Weapons[Weapons_Inv[i,1],5] = ini_read_real("Weapons Inventory","Inv_Slot_"+string(i)+"_Owned",Weapons[Weapons_Inv[i,1],5]);
}
// Stones Inventory
for (i=0; i<Stones_Inv_Length; i++) {
Stones_Inv[i,0] = i;
Stones_Inv[i,1] = ini_read_real("Stones Inventory","Inv_Slot_"+string(i),0);
Stones[Stones_Inv[i,1],5] = ini_read_real("Stones Inventory","Inv_Slot_"+string(i)+"_Owned",Stones[Stones_Inv[i,1],5]);
}
// Equipped Stones
for (i=0; i<Stone_Slots_Owned; i++) {
Stone_Slots[i,0] = i;
Stone_Slots[i,1] = ini_read_real("Stones Equipped","Slot_"+string(i),Stone_Slots[i,1]);
}
// Costume Colours
for (i=0; i<array_height_2d(Colours); i++) {
Colours[i,5] = ini_read_real("Costume Colours",Colours[i,1],Colours[i,5]);
}
// Stats
Stat_Clouds_Clicked = ini_read_real("Stats","Clouds_Clicked",Stat_Clouds_Clicked);
Stat_Stars_Clicked = ini_read_real("Stats","Stars_Clicked",Stat_Stars_Clicked);
// Options
SoundFX = ini_read_real("Options","SoundFX",SoundFX);
// Version
Save_Version = ini_read_string("Version","Current",Save_Version);
// Resets
ForceResets = ini_read_string("External","Force_Resets",Force_Resets);
ini_close();
if (ForceResets != Force_Resets) {
Data_Erase();
}
Data_Submit();
} // If Connected & Not Guest
GM's compiler is always weird about those line errors. It often doesn't count blank lines as actual lines.
If you adjust for that issue, the real line of code that is throwing the error is this:
if (ForceResets != Force_Resets) {
Maybe it doesn't like that you're basically asking "If something is not equal to itself", which hardly makes sense. That statement will always evaluate to false, so you should probably remove it.
Seeing as you don't declare var on any of these variables, then you're manipulating the variables on the instance that called this script. If there's somehow a ForceResets script variable, and a ForceResets variable on the calling instance, then this whole thing might be a naming issue. I'm also making this assuming because you called:
ForceResets = ini_read_string("External","Force_Resets",Force_Resets);
Where that third parameter isn't declared anywhere in this script.
All in all, I'd say that you need to clean this script up a little.
Pro Tip: Use for(var i = 0; ... instead of for(i = 0 99% of the time. Otherwise you're leaving this instance with an i variable that it will never use.

Writing to General Journal, Financial Dimensions not working

I'm writing to the general journal in x++. I am able to post there currently but the dimensions on the line item are not showing up. The code runs just will have 701100- - - - - and the rest not populated on the line item. i'm not sure why... I've tried several different things... such as below.
ledgerDimensions = ["701100","701100", "MIDWHS", "ACCT", "000001", "AIR", "019-000100"];
journalTrans.parmLedgerDimension(AxdDimensionUtil::getLedgerAccountId(ledgerDimensions));
offsetDimensions = ["701100","701100", "MIDWHS", "ACCT", "000001", "AIR", "019-000100"];
journalTrans.parmOffsetLedgerDimension(AxdDimensionUtil::getLedgerAccountId(offsetDimensions));
journaltrans.save()
and also have tried
// dimensionAttribute = DimensionAttribute::findByName("Location");
// dimensionAttributeValue = //DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute, "MINOT");
// dimStorage = DimensionAttributeValueSetStorage::find(0);
// dimStorage.addItem(dimensionAttributeValue);
// journalTrans.parmOffsetDefaultDimension(dimStorage.save());
//journalTrans.save()
both are just adding the account number and nothing else.. don't know why!
I used the service for our import interface for general journal.
Below my code, try it this way, it's easy to create the dimensions with this service.
I copied the code from our system and commented it, if you need any further explaination, ask me.
Declarations:
LedgerGeneralJournal ledgerGeneralJournal;
LedgerGeneralJournalService ledgerGeneralJournalService;
AfStronglyTypedDataContainerList journalHeaderCollection;
LedgerGeneralJournal_LedgerJournalTable journalHeader;
AfStronglyTypedDataContainerList journalLineCollection;
AifEntityKeyList journalHeaderCollectionKeyList;
int journalLineCounter; // to create more than one line
int entityKeyCount;
int journalHeaderCounter; // to create more than one journal header if needed
Add parm methods for your counter variables if needed:
public int parmJournalLineCounter(int _journalLineCounter = journalLineCounter)
{
journalLineCounter = _journalLineCounter;
return journalLineCounter;
}
public int parmJournalHeaderCounter(int _journalHeaderCounter = journalHeaderCounter)
{
journalHeaderCounter = _journalHeaderCounter;
return journalHeaderCounter;
}
initializations:
Header
ledgerGeneralJournalService = LedgerGeneralJournalService::construct();
ledgerGeneralJournal = new LedgerGeneralJournal();
journalHeaderCollection = ledgerGeneralJournal.createLedgerJournalTable();
this.parmJournalLineCounter(0); // note to add a parm method for your counter variables
this.parmJournalHeaderCounter(_newInstance ? 1 : this.parmJournalHeaderCounter() + 1); // i added a parameter if more headers are needed
journalHeader = journalHeaderCollection.insertNew(this.parmJournalHeaderCounter());
journalHeader.parmJournalName(contract.parmJournalNameId()); // init your own journal ID
Here my method to create the lines with dimensions.
Note that i only use CostCenter here, if you need more, add them as done with CostCenter (i commented the lines)
protected void writeLine(
str _oldCompany,
TransDate _transDate,
Voucher _voucher,
str _mainAccountNum,
AmountMST _amount,
LedgerJournalTransTxt _transTxt,
CurrencyCode _currencyCode,
str _offsetAccountNum,
str costCenter
)
{
LedgerGeneralJournal_LedgerJournalTrans journalLine;
AifMultiTypeAccount journalLineLedgerDimensionMain;
AifDimensionAttributeValue journalLineDim1Main;
AfStronglyTypedDataContainerList journalLineDimensionCollectionMain;
AifMultiTypeAccount journalLineLedgerDimensionOffset;
str lineMainAccount;
str lineFullAccount;
str lineMainDimensionName = 'CostCenter';
str lineMainDimensionValue;
str lineOffsetAccount;
str lineOffsetFullAccount;
this.parmJournalLineCounter(this.parmJournalLineCounter()+1);
journalLine = this.parmJournalLineCollection().insertNew(this.parmJournalLineCounter());
journalLine.parmLineNum(this.parmJournalLineCounter());
journalLine.parmCompany(CompanyInfo::findByPTROldCompany(_oldCompany).company());
journalLine.parmOffsetCompany(journalLine.parmCompany());
journalLine.parmTransDate(_transDate);
journalLine.parmVoucher(_voucher);
journalLine.parmAccountType(LedgerJournalACType::Ledger);
lineMainAccount = _mainAccountNum;
journalLine.parmAmountCurCredit(_amount > 0 ? 0 : _amount);
journalLine.parmAmountCurDebit(_amount > 0 ? _amount : 0);
journalLine.parmTxt(_transTxt);
journalLine.parmCurrencyCode(_currencyCode);
journalLine.parmOffsetAccountType(LedgerJournalACType::Ledger);
lineOffsetAccount = _offsetAccountNum;
// Create Main Account Dimensions
journalLineLedgerDimensionMain = journalLine.createLedgerDimension();
journalLineLedgerDimensionMain.parmAccount(lineMainAccount);
lineFullAccount = strFmt("%1-%2-", lineMainAccount, costCenter ? lineMainDimensionValue : ''); // if you need more dimensions, add them here first
journalLineLedgerDimensionMain.parmDisplayValue(lineFullAccount);
// and then add the values here like costcenter:
if (costCenter)
{
lineMainDimensionValue = costCenter;
journalLineDimensionCollectionMain = journalLineLedgerDimensionMain.createValues();
journalLineDim1Main = new AifDimensionAttributeValue();
journalLineDim1Main.parmName(lineMainDimensionName);
journalLineDim1Main.parmValue(lineMainDimensionValue);
journalLineDimensionCollectionMain.add(journalLineDim1Main);
journalLineLedgerDimensionMain.parmValues(journalLineDimensionCollectionMain);
}
journalLine.parmLedgerDimension(journalLineLedgerDimensionMain);
// Create Offset Account Dimensions
// same procedure with offset dimensions if needed
if (_offsetAccountNum)
{
journalLineLedgerDimensionOffset = journalLine.createOffsetLedgerDimension();
journalLineLedgerDimensionOffset.parmAccount(lineOffsetAccount);
lineOffsetFullAccount = strFmt("%1--", lineOffsetAccount);
journalLineLedgerDimensionOffset.parmDisplayValue(lineOffsetFullAccount);
journalLine.parmOffsetLedgerDimension(journalLineLedgerDimensionOffset);
}
}
// Create Lines
journalLineCollection = journalHeader.createLedgerJournalTrans();
Finally to write the journal:
public void finalizeLedgerJournal()
{
int keyCount;
List journalIdList;
journalHeader.parmLedgerJournalTrans(journalLineCollection);
ledgerGeneralJournal.parmLedgerJournalTable(journalHeaderCollection);
journalHeaderCollectionKeyList = LedgerGeneralJournalService.create(ledgerGeneralJournal);
// if you need the journalId for further processing:
this.parmEntityKeyCount(journalHeaderCollectionKeyList.getEntityKeyCount());
if (entityKeyCount > 0)
{
for (keyCount = 1;keyCount <= entityKeyCount;keyCount++)
{
if (!contract.parmJournalIdList())
{
contract.parmJournalIdList(new List(Types::String));
}
journalIdList = contract.parmJournalIdList();
journalIdList.addEnd(LedgerJournalTable::findByRecId(journalHeaderCollectionKeyList.getEntityKey(keyCount).parmRecId()).JournalNum);
contract.parmJournalId(LedgerJournalTable::findByRecId(journalHeaderCollectionKeyList.getEntityKey(keyCount).parmRecId()).JournalNum);
}
}
}

Parse Credit Card input from Magnetic Stripe

Does anyone know how to parse a credit card string input from a Magnetic Card Swiper?
I tried a JavaScript parser but never got it to work. This is what the input looks like.
%BNNNNNNNNNNNNNNNN^DOE/JOHN
^1210201901000101000100061000000?;NNNNNNNNNNNNNNNN=12102019010106111001?
The N's are the credit card number.
See the Magnetic Stripe Card entry # Wikipedia:
Track one, Format B:
Start sentinel — one character (generally '%')
Format code="B" — one character (alpha only)
Primary account number (PAN) — up to 19 characters. Usually, but not
always, matches the credit card number
printed on the front of the card.
Field Separator — one character (generally '^')
Name — two to 26 characters
Field Separator — one character (generally '^')
Expiration date — four characters in the form YYMM.
Service code — three characters
Discretionary data — may include Pin Verification Key Indicator (PVKI,
1 character), PIN Verification Value
(PVV, 4 characters), Card Verification
Value or Card Verification Code (CVV
or CVK, 3 characters)
End sentinel — one character (generally '?')
Longitudinal redundancy check (LRC) — one character (Most reader devices
do not return this value when the card
is swiped to the presentation layer,
and use it only to verify the input
internally to the reader.)
I hope the data is fake, otherwise Anyone could get the:
Name
Expiration Date
CVV
And I'm not sure but I think the credit card number (or # of possibilities) can be computed using the LRC.
I did you one better: I made a video showing how to do exactly this with ASP.Net/c#:
http://www.markhagan.me/Samples/CreditCardSwipeMagneticStripProcessing
Here is the section of code that you probably care about:
protected void CardReader_OTC(object sender, EventArgs e)
{
bool CaretPresent = false;
bool EqualPresent = false;
CaretPresent = CardReader.Text.Contains("^");
EqualPresent = CardReader.Text.Contains("=");
if (CaretPresent)
{
string[] CardData = CardReader.Text.Split('^');
//B1234123412341234^CardUser/John^030510100000019301000000877000000?
PersonName.Text = FormatName(CardData[1]);
CardNumber.Text = FormatCardNumber(CardData[0]);
CardExpiration.Text = CardData[2].Substring(2, 2) + "/" + CardData[2].Substring(0, 2);
}
else if (EqualPresent)
{
string[] CardData = CardReader.Text.Split('=');
//1234123412341234=0305101193010877?
CardNumber.Text = FormatCardNumber(CardData[0]);
CardExpiration.Text = CardData[1].Substring(2, 2) + "/" + CardData[1].Substring(0, 2);
}
}
The complete code is on that website I linked above.
From what I can remember:
That is a two-track magnetic strip data - first track starts with % and ends with ?, the second track starts with ; and ends with ?. These are Start/End markers.
The first track is alphanumeric, the second track is numeric, and there is a third track which is numeric also (if my memory serves correct).
The data between the start/end markers can be variable depending on the recording density of the magnetic strip. The higher the density, the more it can be recorded on one track.
Using a regex to get at the data may not be a reliable method to pick out the information required.
And not all credit cards have exactly two tracks, some uses three tracks.
Generally for a card-not present transaction (i.e. MOTO transactions) you will need cc#, expiry and possibly the CVV (aka CVC2 etc). You can obtain the first 2 from a card-swipe as this in the track data. CVV is printed on the card.
Name on card doesn't matter so much. Unless your acquirer and the cardholder are using address verification, but you can find that between ^^, it may have white space padding which you can remove.
The part you want is track2 NNNNNNNNNNNNNNNN=1210 where NNNNN=card number PAN, and 1210 = Expiry date.
Even if track1 is empty (which sometimes it is as it's not used in processing), you will still get the ;?, so you could use the index of the second ; as start of the string and = as the end of the cc# string. With the 4 characters after the = as the expiry.
I would advise getting the card holder to sign something in record of the transaction otherwise they could dispute the card and do a charge-back.
And not all credit cards have exactly two tracks, some uses three tracks.
Only track2 is used for processing and has a standardized format.
Debit cards can't generally be processed (unless they have a visa-debit card or something).
P.S. you shouldn't store cc data in plain text, so try and keep everything in mem or strong encryption.
Try this :
https://github.com/pdamer/CardReader/blob/master/CardReader.js
Or this:
http://blog.cnizz.com/2008/10/16/javascript-snippet-for-handling-credit-card-readers/
I think that what u need
here is my code:
1st the listener to get the data.... this data needs validation which i am looking for help on. A good swipe works fine, but a bad swipe will cause an error in the parser.
$('#cc-dialog-form').keypress(function(e)
{
var charCode = e.which;
//ie? evt = e || window.event;
track_start = '%';
finished = false;
timeout = 100;
track_start_code = track_start.charCodeAt(0);
//console.log('Track_start_code: ' + track_start_code);
//console.log('keycode ' + e.keycode);
//console.log('charcode ' + charCode);
//console.log('track_start_code ' + track_start_code);
if (charCode == track_start_code)
{
collect_track_data = true;
$('#offline_cc_entry').hide();
$('#cc_online').hide();
$('#Manual_CC_DATA').hide();
$('#cc_loading_image').show();
}
if (collect_track_data)
{
if (charCode == $.ui.keyCode.ENTER)
{
//all done
//console.log( card_data);
collect_track_data = false;
$('#cc_loading_image').hide();
$('#Manual_CC_DATA').show();
//console.log("Track Data: " + card_data);
process_swipe_cc_payment(card_data);
card_data = '';
}
else
{
card_data = card_data + String.fromCharCode(charCode);
console.log(card_data);
if (e.preventDefault) e.preventDefault();
e.returnValue=false;
return false;
}
}
else
{
//i am guessing this will be regular input?
if (charCode == $.ui.keyCode.ENTER)
{
process_keyed_or_offline_CC_payment();
}
}
//console.log("which: " + e.which);
//console.log("keyCode: " + e.keyCode);
//track and collect data here?
});
And here is the parser.... note I put it all in one function so I can destroy all the variables so they are not lingering in a browser.
parse_data = true;
if (parse_data)
{
var parsed_card_data = {};
parsed_card_data['card_data'] = card_data;
var tracks = card_data.split("?");
//console.log ("tracks");
//console.log (tracks);
parsed_card_data['track1'] = tracks[0];
parsed_card_data['track2'] = tracks[1];
//if there is a third track we might find it under tracks[2]
//splitting the card data OPTION 1
var track1_parsed = tracks[0].split("^");
//console.log (track1_parsed);
//track1 data....
var card_number_track1 = track1_parsed[0].substring(2);
parsed_card_data['card_number_track1'] = card_number_track1;
var details2_1 = tracks[1].split(";");
details2_1 = details2_1[1].split("=");
var exp_date_track_1 = details2_1[1];
exp_date_track_1 = exp_date_track_1.substring(0, exp_date_track_1.length - 1);
exp_date_track_1 = exp_date_track_1.substring(2, 4) + "/" + exp_date_track_1.substring(0,2);
parsed_card_data['exp_track1'] = exp_date_track_1;
//now check if track one matches track 2...
track2_parsed = tracks[1].split("=");
card_number_track_2 = track2_parsed[0].substring(1);
parsed_card_data['card_number_track_2'] = card_number_track_2;
exp_date_track_2 = track2_parsed[1].substring(0,4);
exp_date_track_2 = exp_date_track_2.substring(2, 4) + "/" + exp_date_track_2.substring(0,2);
parsed_card_data['exp_date_track_2'] = exp_date_track_2;
var primary_account_number = card_number_track1.substring(0,1);
if(card_number_track1 == card_number_track_2 && exp_date_track_1 == exp_date_track_2)
{
//now make a security feature showing the last 4 digits only....
parsed_card_data['secure_card_number'] = "xxxx " + card_number_track1.substring(card_number_track1.length-4, card_number_track1.length);
if(card_number_track1.length == 15)
{
parsed_card_data['card_type'] = "American Express";
}
else if(primary_account_number == 4)
{
parsed_card_data['card_type'] = "Visa";
}
else if(primary_account_number == 5)
{
parsed_card_data['card_type'] = "Master Card";
}
else if(primary_account_number == 6)
{
parsed_card_data['card_type'] = "Discover";
}
else
{
parsed_card_data['card_type'] = false;
}
var names_1 = track1_parsed[1].split("/");
parsed_card_data['first_name'] = names_1[1].trim();
parsed_card_data['last_name'] = names_1[0].trim();
//console.log("return Data");
//console.log(return_data);
}
else
{
parsed_card_data = false;
}
//zero out the variables...
tracks = '';
track1_parsed = '';
card_number_track1 = '';
details2_1 = '';
exp_date_track_1 = '';
track2_parsed = '';
card_number_track_2 = '';
exp_date_track_2 = '';
primary_account_number = '';
}
if(parsed_card_data)
{
//console.log(parsed_card_data);
$('#card_type').val(parsed_card_data['card_type']);
$('#credit_card_number').val(parsed_card_data['secure_card_number']);
$('#expiration').val(parsed_card_data['exp']);
$('#card_holder').val(parsed_card_data['first_name']+ " " + parsed_card_data['last_name']);
//parsed_card_data['track1'] is basically what we want???
$('#CC_SWIPE_INSTRUCTIONS').hide();
$('#CC_DATA').hide();
$('#cc_loading_image').show();
var post_string = {};
post_string['ajax_request'] = 'CREDIT_CARD_PAYMENT';
post_string['amount'] = $('#cc_input').val();
post_string['card_data'] = parsed_card_data;
post_string['pos_sales_invoice_id'] = pos_sales_invoice_id;
post_string['pos_payment_gateway_id'] = $('#pos_payment_gateway_id').val();
post_string['line'] = 'online';
post_string['swipe'] = 'swipe';
card_data = '';
parsed_card_data = {};
var url = 'ajax_requests.php';
$.ajax({
type: 'POST',
url: url,
data: post_string,
async: true,
success: function(response)
{
$('#cc_loading_image').hide();
console.log(response);
$('#CC_RESPONSE').show();
$('#CC_RESPONSE').html(response);
//here we would update the payment table - currently we will just refresh
post_string = '';
}
});
post_string = '';
}
else
{
//error
alert("Read Error");
$( "#cc-dialog-form" ).dialog( "close" );
}

Resources